mstro-app 0.4.39 → 0.4.43
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/bin/commands/login.js +17 -7
- package/bin/commands/logout.js +14 -6
- package/bin/commands/status.js +9 -3
- package/bin/commands/whoami.js +10 -4
- package/bin/mstro.js +11 -1
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.js +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
- package/dist/server/cli/headless/index.d.ts +1 -0
- package/dist/server/cli/headless/index.d.ts.map +1 -1
- package/dist/server/cli/headless/index.js +2 -0
- package/dist/server/cli/headless/index.js.map +1 -1
- package/dist/server/cli/headless/resilient-runner.d.ts +47 -0
- package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -0
- package/dist/server/cli/headless/resilient-runner.js +234 -0
- package/dist/server/cli/headless/resilient-runner.js.map +1 -0
- package/dist/server/cli/headless/retry-strategies.d.ts +44 -0
- package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -0
- package/dist/server/cli/headless/retry-strategies.js +262 -0
- package/dist/server/cli/headless/retry-strategies.js.map +1 -0
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +5 -0
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.d.ts +2 -0
- package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.js +31 -4
- package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +1 -30
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +16 -3
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/prompt-builders.d.ts.map +1 -1
- package/dist/server/cli/prompt-builders.js +31 -13
- package/dist/server/cli/prompt-builders.js.map +1 -1
- package/dist/server/index.js +1 -9
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-cli.js +5 -4
- package/dist/server/mcp/bouncer-cli.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +1 -1
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +14 -8
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-patterns.js +1 -1
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +19 -9
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +6 -1
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +158 -76
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts +1 -0
- package/dist/server/services/plan/front-matter.d.ts.map +1 -1
- package/dist/server/services/plan/front-matter.js +6 -0
- package/dist/server/services/plan/front-matter.js.map +1 -1
- package/dist/server/services/plan/issue-classification.d.ts +11 -0
- package/dist/server/services/plan/issue-classification.d.ts.map +1 -0
- package/dist/server/services/plan/issue-classification.js +20 -0
- package/dist/server/services/plan/issue-classification.js.map +1 -0
- package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.js +7 -4
- package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +0 -5
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
- package/dist/server/services/plan/issue-retry.js +12 -241
- package/dist/server/services/plan/issue-retry.js.map +1 -1
- package/dist/server/services/plan/parser-core.d.ts.map +1 -1
- package/dist/server/services/plan/parser-core.js +1 -0
- package/dist/server/services/plan/parser-core.js.map +1 -1
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +9 -6
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +1 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.js +11 -4
- package/dist/server/services/platform-credentials.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +7 -1
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/websocket/handler-context.d.ts +2 -0
- package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +18 -7
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.js +6 -6
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-fix-agent.js +90 -42
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +48 -7
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-persistence.d.ts +22 -0
- package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-persistence.js +48 -1
- package/dist/server/services/websocket/quality-persistence.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +74 -32
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-tools.js +18 -18
- package/dist/server/services/websocket/quality-tools.js.map +1 -1
- package/dist/server/services/websocket/skill-handlers.d.ts +3 -1
- package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-handlers.js +52 -41
- package/dist/server/services/websocket/skill-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-watcher.d.ts +17 -0
- package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -0
- package/dist/server/services/websocket/skill-watcher.js +85 -0
- package/dist/server/services/websocket/skill-watcher.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +2 -268
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +0 -4
- package/dist/server/services/websocket/types.js.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/claude-invoker-stream.ts +1 -0
- package/server/cli/headless/index.ts +2 -0
- package/server/cli/headless/resilient-runner.ts +354 -0
- package/server/cli/headless/retry-strategies.ts +330 -0
- package/server/cli/headless/stall-assessor.ts +5 -0
- package/server/cli/headless/tool-watchdog.ts +40 -4
- package/server/cli/improvisation-retry.ts +1 -32
- package/server/cli/improvisation-session-manager.ts +17 -3
- package/server/cli/prompt-builders.ts +33 -12
- package/server/index.ts +1 -9
- package/server/mcp/bouncer-cli.ts +5 -4
- package/server/mcp/bouncer-haiku.ts +1 -1
- package/server/mcp/bouncer-integration.ts +15 -8
- package/server/mcp/security-patterns.ts +1 -1
- package/server/services/plan/agents/code-review.md +109 -0
- package/server/services/plan/agents/commit-message.md +26 -0
- package/server/services/plan/agents/fix-quality.md +24 -0
- package/server/services/plan/agents/pr-description.md +28 -0
- package/server/services/plan/composer.ts +20 -9
- package/server/services/plan/executor.ts +160 -76
- package/server/services/plan/front-matter.ts +7 -0
- package/server/services/plan/issue-classification.ts +21 -0
- package/server/services/plan/issue-prompt-builder.ts +8 -4
- package/server/services/plan/issue-retry.ts +15 -330
- package/server/services/plan/parser-core.ts +1 -0
- package/server/services/plan/review-gate.ts +9 -6
- package/server/services/plan/types.ts +3 -0
- package/server/services/platform-credentials.ts +10 -4
- package/server/services/terminal/pty-manager.ts +7 -1
- package/server/services/websocket/handler-context.ts +2 -0
- package/server/services/websocket/handler.ts +18 -8
- package/server/services/websocket/plan-execution-handlers.ts +7 -7
- package/server/services/websocket/quality-fix-agent.ts +86 -44
- package/server/services/websocket/quality-handlers.ts +48 -7
- package/server/services/websocket/quality-persistence.ts +75 -1
- package/server/services/websocket/quality-review-agent.ts +70 -31
- package/server/services/websocket/quality-tools.ts +16 -14
- package/server/services/websocket/skill-handlers.ts +50 -40
- package/server/services/websocket/skill-watcher.ts +79 -0
- package/server/services/websocket/types.ts +0 -311
- package/dist/server/services/deploy/ai-broker.d.ts +0 -63
- package/dist/server/services/deploy/ai-broker.d.ts.map +0 -1
- package/dist/server/services/deploy/ai-broker.js +0 -360
- package/dist/server/services/deploy/ai-broker.js.map +0 -1
- package/dist/server/services/deploy/board-execution-handler.d.ts +0 -114
- package/dist/server/services/deploy/board-execution-handler.d.ts.map +0 -1
- package/dist/server/services/deploy/board-execution-handler.js +0 -621
- package/dist/server/services/deploy/board-execution-handler.js.map +0 -1
- package/dist/server/services/deploy/credentials.d.ts +0 -35
- package/dist/server/services/deploy/credentials.d.ts.map +0 -1
- package/dist/server/services/deploy/credentials.js +0 -177
- package/dist/server/services/deploy/credentials.js.map +0 -1
- package/dist/server/services/deploy/deploy-ai-service.d.ts +0 -107
- package/dist/server/services/deploy/deploy-ai-service.d.ts.map +0 -1
- package/dist/server/services/deploy/deploy-ai-service.js +0 -294
- package/dist/server/services/deploy/deploy-ai-service.js.map +0 -1
- package/dist/server/services/deploy/headless-session-handler.d.ts +0 -94
- package/dist/server/services/deploy/headless-session-handler.d.ts.map +0 -1
- package/dist/server/services/deploy/headless-session-handler.js +0 -266
- package/dist/server/services/deploy/headless-session-handler.js.map +0 -1
- package/dist/server/services/websocket/deploy-handlers.d.ts +0 -14
- package/dist/server/services/websocket/deploy-handlers.d.ts.map +0 -1
- package/dist/server/services/websocket/deploy-handlers.js +0 -409
- package/dist/server/services/websocket/deploy-handlers.js.map +0 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.d.ts +0 -11
- package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +0 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.js +0 -176
- package/dist/server/services/websocket/handlers/deploy-handlers.js.map +0 -1
- package/server/cli/headless/RESEARCH.md +0 -627
- package/server/services/deploy/ai-broker.ts +0 -512
- package/server/services/deploy/board-execution-handler.ts +0 -847
- package/server/services/deploy/credentials.ts +0 -200
- package/server/services/deploy/deploy-ai-service.ts +0 -401
- package/server/services/deploy/headless-session-handler.ts +0 -414
- package/server/services/websocket/deploy-handlers.ts +0 -544
- package/server/services/websocket/handlers/deploy-handlers.ts +0 -228
|
@@ -98,10 +98,10 @@ export const DEFAULT_TOOL_TIMEOUT_PROFILES: Record<string, ToolTimeoutProfile> =
|
|
|
98
98
|
useHaikuTiebreaker: true,
|
|
99
99
|
},
|
|
100
100
|
Write: {
|
|
101
|
-
coldStartMs:
|
|
102
|
-
floorMs:
|
|
103
|
-
ceilingMs:
|
|
104
|
-
useAdaptive:
|
|
101
|
+
coldStartMs: 300_000, // 5 min — large docs stream slowly through stdio; model generates content inline
|
|
102
|
+
floorMs: 120_000, // 2 min minimum — prevents premature kills on big writes
|
|
103
|
+
ceilingMs: 600_000, // 10 min hard cap
|
|
104
|
+
useAdaptive: false, // bimodal: 1-line config vs 50KB research doc defeats EMA
|
|
105
105
|
useHaikuTiebreaker: true,
|
|
106
106
|
},
|
|
107
107
|
};
|
|
@@ -244,6 +244,22 @@ export class ToolWatchdog {
|
|
|
244
244
|
|
|
245
245
|
const elapsedMs = Date.now() - watch.startTime;
|
|
246
246
|
|
|
247
|
+
// Activity-gated auto-extension: if data is actively streaming, extend without
|
|
248
|
+
// consuming the one-shot Haiku tiebreaker. Respects ceiling to prevent runaway.
|
|
249
|
+
const tokenSilenceMs = this.getTokenSilenceMs?.();
|
|
250
|
+
if (tokenSilenceMs !== undefined && tokenSilenceMs < 60_000) {
|
|
251
|
+
const remainingToCeiling = profile.ceilingMs - elapsedMs;
|
|
252
|
+
if (remainingToCeiling > 0) {
|
|
253
|
+
const extensionMs = Math.min(5 * 60_000, remainingToCeiling);
|
|
254
|
+
if (this.verbose) {
|
|
255
|
+
hlog(`[WATCHDOG] ${toolName} (${toolId}) hit timeout after ${Math.round(elapsedMs / 1000)}s, but stream active ${Math.round(tokenSilenceMs / 1000)}s ago — auto-extending ${Math.round(extensionMs / 1000)}s`);
|
|
256
|
+
}
|
|
257
|
+
this.scheduleActivityGatedTimeout(watch, toolId, toolName, toolInput, profile, extensionMs, onTimeout);
|
|
258
|
+
watch.timeoutMs = elapsedMs + extensionMs;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
247
263
|
if (!profile.useHaikuTiebreaker || !this.onTiebreaker || watch.tiebreakerAttempted) {
|
|
248
264
|
if (this.verbose) {
|
|
249
265
|
hlog(`[WATCHDOG] ${toolName} (${toolId}) timed out after ${Math.round(elapsedMs / 1000)}s, killing`);
|
|
@@ -294,6 +310,26 @@ export class ToolWatchdog {
|
|
|
294
310
|
return false;
|
|
295
311
|
}
|
|
296
312
|
|
|
313
|
+
/** Schedule an activity-gated timeout that re-enters the full timeout handler */
|
|
314
|
+
private scheduleActivityGatedTimeout(
|
|
315
|
+
watch: ActiveWatch,
|
|
316
|
+
toolId: string,
|
|
317
|
+
toolName: string,
|
|
318
|
+
toolInput: Record<string, unknown>,
|
|
319
|
+
profile: ToolTimeoutProfile,
|
|
320
|
+
extensionMs: number,
|
|
321
|
+
onTimeout: () => void,
|
|
322
|
+
): void {
|
|
323
|
+
watch.timer = setTimeout(async () => {
|
|
324
|
+
const w = this.activeWatches.get(toolId);
|
|
325
|
+
if (!w) return;
|
|
326
|
+
const extended = await this.handleTimeoutWithTiebreaker(toolId, toolName, toolInput, profile, onTimeout);
|
|
327
|
+
if (!extended) {
|
|
328
|
+
onTimeout();
|
|
329
|
+
}
|
|
330
|
+
}, extensionMs);
|
|
331
|
+
}
|
|
332
|
+
|
|
297
333
|
/** Schedule a post-extension timeout that kills without another tiebreaker */
|
|
298
334
|
private scheduleExtensionTimeout(
|
|
299
335
|
watch: ActiveWatch,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { AnalyticsEvents, trackEvent } from '../services/analytics.js';
|
|
10
10
|
import { hlog } from './headless/headless-logger.js';
|
|
11
11
|
import { HeadlessRunner } from './headless/index.js';
|
|
12
|
+
import { extractFinalTextBlock, isResponseAbandoned } from './headless/retry-strategies.js';
|
|
12
13
|
import { assessBestResult, assessContextLoss, assessPrematureCompletion, type ContextLossContext } from './headless/stall-assessor.js';
|
|
13
14
|
import type { ExecutionCheckpoint } from './headless/types.js';
|
|
14
15
|
import type { FileAttachment, HeadlessRunResult, ImprovisationOptions, MovementRecord, RetryLoopState, SessionHistory } from './improvisation-types.js';
|
|
@@ -455,38 +456,6 @@ function isPrematureCompletionCandidate(
|
|
|
455
456
|
return result.stopReason === 'max_tokens' || result.stopReason === 'end_turn';
|
|
456
457
|
}
|
|
457
458
|
|
|
458
|
-
/**
|
|
459
|
-
* Fast heuristic: detect response abandonment without a Haiku call.
|
|
460
|
-
* When thinking is significantly longer than the response and the response
|
|
461
|
-
* contains no tool calls, Claude likely planned work it never executed.
|
|
462
|
-
* This pattern occurs after context compaction or heavy parallel tool results.
|
|
463
|
-
*/
|
|
464
|
-
function isResponseAbandoned(result: HeadlessRunResult): boolean {
|
|
465
|
-
const thinkingLen = result.thinkingOutput?.length ?? 0;
|
|
466
|
-
const responseLen = result.assistantResponse?.length ?? 0;
|
|
467
|
-
const toolCallsInResponse = result.toolUseHistory?.filter(t => t.result !== undefined).length ?? 0;
|
|
468
|
-
|
|
469
|
-
if (thinkingLen < 500 || responseLen > 1000) return false;
|
|
470
|
-
if (toolCallsInResponse > 0 && responseLen > 200) return false;
|
|
471
|
-
|
|
472
|
-
return thinkingLen >= responseLen * 3;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Extract the final text block from a concatenated response.
|
|
477
|
-
* The assistantResponse concatenates all text deltas including interleaved
|
|
478
|
-
* progress messages between tool calls. The final paragraph (after the last
|
|
479
|
-
* double-newline break) is the actual conclusion — earlier fragments are
|
|
480
|
-
* progress updates that were already acted on via tool calls.
|
|
481
|
-
*/
|
|
482
|
-
function extractFinalTextBlock(response: string, maxLen: number): string {
|
|
483
|
-
const lastBreak = response.lastIndexOf('\n\n');
|
|
484
|
-
if (lastBreak !== -1 && response.length - lastBreak > 20) {
|
|
485
|
-
return response.slice(lastBreak + 2).slice(-maxLen);
|
|
486
|
-
}
|
|
487
|
-
return response.slice(-maxLen);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
459
|
/** Use Haiku to assess whether an end_turn response is genuinely complete */
|
|
491
460
|
async function assessEndTurnCompletion(result: HeadlessRunResult, verbose: boolean): Promise<boolean> {
|
|
492
461
|
if (!result.assistantResponse) return false;
|
|
@@ -201,7 +201,7 @@ export class ImprovisationSessionManager extends EventEmitter {
|
|
|
201
201
|
|
|
202
202
|
let result = await this.runRetryLoop(state, sequenceNumber, promptWithAttachments, imageAttachments, options?.workingDir);
|
|
203
203
|
|
|
204
|
-
if (this._cancelled) {
|
|
204
|
+
if (this._cancelled || this._cancelCompleteEmitted) {
|
|
205
205
|
return this.handleCancelledExecution(result, displayPrompt, sequenceNumber, _execStart);
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -218,7 +218,9 @@ export class ImprovisationSessionManager extends EventEmitter {
|
|
|
218
218
|
this._executionStartTimestamp = undefined;
|
|
219
219
|
this.executionEventLog = [];
|
|
220
220
|
|
|
221
|
-
this.
|
|
221
|
+
if (!this._cancelCompleteEmitted) {
|
|
222
|
+
this.emitMovementComplete(movement, result, _execStart, sequenceNumber);
|
|
223
|
+
}
|
|
222
224
|
this.maybeAutoContinue(result, userPrompt);
|
|
223
225
|
|
|
224
226
|
return movement;
|
|
@@ -271,9 +273,15 @@ export class ImprovisationSessionManager extends EventEmitter {
|
|
|
271
273
|
let result: HeadlessRunResult | undefined;
|
|
272
274
|
const callbacks = this.buildRetryCallbacks();
|
|
273
275
|
|
|
276
|
+
const RETRY_BACKOFF_MS = [1000, 5000, 30000];
|
|
274
277
|
// eslint-disable-next-line no-constant-condition
|
|
275
278
|
while (true) {
|
|
276
279
|
if (this._cancelled) break;
|
|
280
|
+
if (state.retryNumber > 0) {
|
|
281
|
+
const delay = RETRY_BACKOFF_MS[Math.min(state.retryNumber - 1, RETRY_BACKOFF_MS.length - 1)];
|
|
282
|
+
await new Promise(r => setTimeout(r, delay));
|
|
283
|
+
if (this._cancelled) break;
|
|
284
|
+
}
|
|
277
285
|
const iteration = await this.executeRetryIteration(state, callbacks, sequenceNumber, imageAttachments, workingDirOverride);
|
|
278
286
|
result = iteration.result;
|
|
279
287
|
if (this._cancelled) break;
|
|
@@ -580,6 +588,8 @@ export class ImprovisationSessionManager extends EventEmitter {
|
|
|
580
588
|
this.currentRunner = null;
|
|
581
589
|
}
|
|
582
590
|
|
|
591
|
+
this.destroyQueueTimer();
|
|
592
|
+
|
|
583
593
|
if (this._isExecuting && !this._cancelCompleteEmitted) {
|
|
584
594
|
this._cancelCompleteEmitted = true;
|
|
585
595
|
const execStart = this._executionStartTimestamp || Date.now();
|
|
@@ -609,11 +619,15 @@ export class ImprovisationSessionManager extends EventEmitter {
|
|
|
609
619
|
this.flushOutputQueue();
|
|
610
620
|
}
|
|
611
621
|
|
|
612
|
-
|
|
622
|
+
private destroyQueueTimer(): void {
|
|
613
623
|
if (this.queueTimer) {
|
|
614
624
|
clearInterval(this.queueTimer);
|
|
615
625
|
this.queueTimer = null;
|
|
616
626
|
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
destroy(): void {
|
|
630
|
+
this.destroyQueueTimer();
|
|
617
631
|
this.flushOutputQueue();
|
|
618
632
|
}
|
|
619
633
|
|
|
@@ -22,13 +22,27 @@ export function summarizeToolInput(input: Record<string, unknown>): string {
|
|
|
22
22
|
return JSON.stringify(input).slice(0, 100);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const NETWORK_TOOLS = new Set(['WebFetch', 'WebSearch']);
|
|
26
|
+
|
|
25
27
|
/** Format a list of timed-out tools for retry prompts */
|
|
26
28
|
export function formatTimedOutTools(tools: Array<{ toolName: string; input: Record<string, unknown>; timeoutMs: number }>): string[] {
|
|
27
29
|
const lines: string[] = [];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const networkTools = tools.filter(t => NETWORK_TOOLS.has(t.toolName));
|
|
31
|
+
const localTools = tools.filter(t => !NETWORK_TOOLS.has(t.toolName));
|
|
32
|
+
|
|
33
|
+
if (networkTools.length > 0) {
|
|
34
|
+
lines.push('### Network resources that timed out (DO NOT retry these URLs):');
|
|
35
|
+
for (const t of networkTools) {
|
|
36
|
+
const inputSummary = summarizeToolInput(t.input);
|
|
37
|
+
lines.push(`- **${t.toolName}**(${inputSummary}) — timed out after ${Math.round(t.timeoutMs / 1000)}s`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (localTools.length > 0) {
|
|
41
|
+
lines.push('### Tools that previously timed out (OK to retry with same or smaller content):');
|
|
42
|
+
for (const t of localTools) {
|
|
43
|
+
const inputSummary = summarizeToolInput(t.input);
|
|
44
|
+
lines.push(`- **${t.toolName}**(${inputSummary}) — timed out after ${Math.round(t.timeoutMs / 1000)}s`);
|
|
45
|
+
}
|
|
32
46
|
}
|
|
33
47
|
return lines;
|
|
34
48
|
}
|
|
@@ -211,17 +225,24 @@ export function buildResumeRetryPrompt(
|
|
|
211
225
|
`Your previous ${checkpoint.hungTool.toolName} call timed out after ${Math.round(checkpoint.hungTool.timeoutMs / 1000)}s${checkpoint.hungTool.url ? ` fetching: ${checkpoint.hungTool.url}` : ''}.`
|
|
212
226
|
);
|
|
213
227
|
|
|
214
|
-
if (allTimedOut && allTimedOut.length >
|
|
228
|
+
if (allTimedOut && allTimedOut.length > 0) {
|
|
229
|
+
const networkTools = allTimedOut.filter(t => NETWORK_TOOLS.has(t.toolName));
|
|
230
|
+
const localTools = allTimedOut.filter(t => !NETWORK_TOOLS.has(t.toolName));
|
|
215
231
|
parts.push('');
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
232
|
+
if (networkTools.length > 0) {
|
|
233
|
+
parts.push('Network resources that timed out (DO NOT retry these URLs):');
|
|
234
|
+
for (const t of networkTools) {
|
|
235
|
+
parts.push(`- ${t.toolName}(${summarizeToolInput(t.input)})`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (localTools.length > 0) {
|
|
239
|
+
parts.push('Tools that previously timed out (OK to retry):');
|
|
240
|
+
for (const t of localTools) {
|
|
241
|
+
parts.push(`- ${t.toolName}(${summarizeToolInput(t.input)})`);
|
|
242
|
+
}
|
|
220
243
|
}
|
|
221
|
-
} else {
|
|
222
|
-
parts.push('This URL/resource is unreachable. DO NOT retry the same URL or query.');
|
|
223
244
|
}
|
|
224
|
-
parts.push('Continue your task — find
|
|
245
|
+
parts.push('Continue your task — find alternative sources for network failures, or proceed with the results you already have.');
|
|
225
246
|
|
|
226
247
|
return parts.join('\n');
|
|
227
248
|
}
|
package/server/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
import { createPlatformRelayContext, ensureClaudeSettings, setTerminalTitle, wrapWebSocket } from './server-setup.js'
|
|
28
28
|
import { AnalyticsEvents, initAnalytics, shutdownAnalytics, trackEvent } from './services/analytics.js'
|
|
29
29
|
import { AuthService } from './services/auth.js'
|
|
30
|
-
import { createAiBrokerRoutes, setDeployHealthUpdateListener, setDeployUsageReportListener } from './services/deploy/ai-broker.js'
|
|
31
30
|
import { FileService } from './services/files.js'
|
|
32
31
|
import { InstanceRegistry, type MstroInstance } from './services/instances.js'
|
|
33
32
|
import { PlatformConnection } from './services/platform.js'
|
|
@@ -82,7 +81,7 @@ app.use('*', cors({
|
|
|
82
81
|
app.use('*', logger())
|
|
83
82
|
|
|
84
83
|
const authMiddleware = async (c: Context, next: Next) => {
|
|
85
|
-
const publicPaths = ['/health', '/api/config'
|
|
84
|
+
const publicPaths = ['/health', '/api/config']
|
|
86
85
|
if (publicPaths.some(path => c.req.path.startsWith(path))) {
|
|
87
86
|
return next()
|
|
88
87
|
}
|
|
@@ -105,7 +104,6 @@ app.route('/api/shutdown', createShutdownRoute(instanceRegistry))
|
|
|
105
104
|
app.route('/api/improvise', createImproviseRoutes(WORKING_DIR))
|
|
106
105
|
app.route('/api/files', createFileRoutes(fileService))
|
|
107
106
|
app.route('/api/notifications', createNotificationRoutes(WORKING_DIR))
|
|
108
|
-
app.route('/api/deploy/ai', createAiBrokerRoutes())
|
|
109
107
|
|
|
110
108
|
app.post('/api/reload-pty', async (c) => {
|
|
111
109
|
const success = await reloadPty()
|
|
@@ -195,12 +193,6 @@ async function startServer() {
|
|
|
195
193
|
wsHandler.setUsageReporter((report) => {
|
|
196
194
|
platformConnection.send({ type: 'reportUsage', data: report })
|
|
197
195
|
})
|
|
198
|
-
setDeployUsageReportListener((report) => {
|
|
199
|
-
platformConnection.send({ type: 'deployUsageReport', data: report })
|
|
200
|
-
})
|
|
201
|
-
setDeployHealthUpdateListener((update) => {
|
|
202
|
-
platformConnection.send({ type: 'deployAiHealthUpdate', data: update })
|
|
203
|
-
})
|
|
204
196
|
},
|
|
205
197
|
onDisconnected: () => {
|
|
206
198
|
if (platformRelayContext) {
|
|
@@ -29,14 +29,14 @@ function buildOperation(toolName: string, toolInput: Record<string, unknown>): s
|
|
|
29
29
|
|
|
30
30
|
async function evaluate(rawInput: string): Promise<{ decision: string; reason: string }> {
|
|
31
31
|
if (!rawInput.trim()) {
|
|
32
|
-
return { decision: '
|
|
32
|
+
return { decision: 'deny', reason: 'Empty input — cannot evaluate safety' };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
let parsed: { tool_name?: string; toolName?: string; input?: Record<string, unknown>; toolInput?: Record<string, unknown> };
|
|
36
36
|
try {
|
|
37
37
|
parsed = JSON.parse(rawInput);
|
|
38
38
|
} catch {
|
|
39
|
-
return { decision: '
|
|
39
|
+
return { decision: 'deny', reason: 'Invalid JSON input — cannot evaluate safety' };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const toolName = parsed.tool_name || parsed.toolName || 'unknown';
|
|
@@ -68,6 +68,7 @@ async function main(): Promise<void> {
|
|
|
68
68
|
console.log(JSON.stringify(result));
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
main().catch(() => {
|
|
72
|
-
console.
|
|
71
|
+
main().catch((err) => {
|
|
72
|
+
console.error('[Bouncer] Fatal error:', err);
|
|
73
|
+
console.log(JSON.stringify({ decision: 'deny', reason: 'Bouncer crash — denying for safety' }));
|
|
73
74
|
});
|
|
@@ -95,7 +95,7 @@ export async function analyzeWithHaiku(
|
|
|
95
95
|
return new Promise((resolve, reject) => {
|
|
96
96
|
const userRequest = request.context?.userRequest;
|
|
97
97
|
const userContextBlock = userRequest
|
|
98
|
-
? `\nUSER'S ORIGINAL REQUEST (what the user actually asked Claude to do):\n
|
|
98
|
+
? `\nUSER'S ORIGINAL REQUEST (what the user actually asked Claude to do):\n<user_request>\n${userRequest}\n</user_request>\n`
|
|
99
99
|
: '';
|
|
100
100
|
|
|
101
101
|
const prompt = loadSkillPrompt('check-injection', {
|
|
@@ -80,11 +80,17 @@ interface CachedDecision {
|
|
|
80
80
|
|
|
81
81
|
const decisionCache = new Map<string, CachedDecision>();
|
|
82
82
|
|
|
83
|
-
function
|
|
84
|
-
const
|
|
83
|
+
function buildCacheKey(operation: string, context?: BouncerReviewRequest['context']): string {
|
|
84
|
+
const sessionId = context?.sessionId ?? '_';
|
|
85
|
+
return `${sessionId}:${operation}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getCachedDecision(operation: string, context?: BouncerReviewRequest['context']): BouncerDecision | null {
|
|
89
|
+
const key = buildCacheKey(operation, context);
|
|
90
|
+
const entry = decisionCache.get(key);
|
|
85
91
|
if (!entry) return null;
|
|
86
92
|
if (Date.now() > entry.expiresAt) {
|
|
87
|
-
decisionCache.delete(
|
|
93
|
+
decisionCache.delete(key);
|
|
88
94
|
return null;
|
|
89
95
|
}
|
|
90
96
|
return entry.decision;
|
|
@@ -95,13 +101,14 @@ export function clearDecisionCache(): void {
|
|
|
95
101
|
decisionCache.clear();
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
function cacheDecision(operation: string, decision: BouncerDecision): void {
|
|
104
|
+
function cacheDecision(operation: string, context: BouncerReviewRequest['context'] | undefined, decision: BouncerDecision): void {
|
|
99
105
|
if (decision.confidence < 50) return;
|
|
100
106
|
if (decisionCache.size >= CACHE_MAX_SIZE) {
|
|
101
107
|
const firstKey = decisionCache.keys().next().value;
|
|
102
108
|
if (firstKey !== undefined) decisionCache.delete(firstKey);
|
|
103
109
|
}
|
|
104
|
-
|
|
110
|
+
const key = buildCacheKey(operation, context);
|
|
111
|
+
decisionCache.set(key, { decision, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
// ── Decision Finalization ─────────────────────────────────────
|
|
@@ -134,7 +141,7 @@ function finalizeDecision(
|
|
|
134
141
|
});
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
if (!opts?.skipCache) cacheDecision(operation, decision);
|
|
144
|
+
if (!opts?.skipCache) cacheDecision(operation, context, decision);
|
|
138
145
|
return decision;
|
|
139
146
|
}
|
|
140
147
|
|
|
@@ -216,8 +223,8 @@ export async function reviewOperation(request: BouncerReviewRequest): Promise<Bo
|
|
|
216
223
|
const fin = (d: BouncerDecision, layer: string, opts?: Parameters<typeof finalizeDecision>[6]) =>
|
|
217
224
|
finalizeDecision(operation, d, layer, startTime, request.context, logBouncerDecision, opts);
|
|
218
225
|
|
|
219
|
-
// Check cache first
|
|
220
|
-
const cached = getCachedDecision(operation);
|
|
226
|
+
// Check cache first (keyed on operation + session to prevent cross-context bypass)
|
|
227
|
+
const cached = getCachedDecision(operation, request.context);
|
|
221
228
|
if (cached) {
|
|
222
229
|
console.error(`[Bouncer] ⚡ Cache hit: ${cached.decision} (${cached.confidence}%)`);
|
|
223
230
|
return cached;
|
|
@@ -153,7 +153,7 @@ export const SAFE_OPERATIONS: SecurityPattern[] = [
|
|
|
153
153
|
{ pattern: /^Bash:\s*git\s+(commit|push|tag|remote|rebase|merge|cherry-pick|reset|revert)($|\s)/i },
|
|
154
154
|
{ pattern: /^Bash:\s*git\s+(worktree|submodule|config|clean|gc)($|\s)/i },
|
|
155
155
|
{ pattern: /^Bash:\s*(uname|hostname|whoami|id|groups|uptime|df|du|free|top|ps|lsof|stat|file|readlink)($|\s)/i },
|
|
156
|
-
{ pattern: /^Bash:\s*(
|
|
156
|
+
{ pattern: /^Bash:\s*(touch|basename|dirname|realpath|mktemp|xargs|tee|tr|cut|paste|comm|diff|patch)($|\s)/i },
|
|
157
157
|
{ pattern: /^Bash:\s*(tar|gzip|gunzip|zip|unzip|bzip2)\s/i },
|
|
158
158
|
{ pattern: /^Bash:\s*(ruby|python3?|php|java|javac|scala|kotlinc|swift|rustc|gcc|g\+\+|clang)\s/i },
|
|
159
159
|
{ pattern: /^Bash:\s*(pip|pip3|gem|bundle|composer|maven|gradle|sbt|cargo|rustup)\s/i },
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: "Senior staff engineer code review — surfaces architectural violations, SOLID issues, security vulnerabilities, bugs, and performance problems with structured evidence. Use when performing a comprehensive AI code review of a directory."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
type: review
|
|
6
|
+
allowed-tools: Read, Grep, Glob, Bash
|
|
7
|
+
context: fork
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a senior staff engineer performing a rigorous, honest code review. Your job is to surface the most impactful quality bottlenecks — the issues a principal engineer would flag in a code review. Be critical and objective. Do NOT inflate scores.
|
|
11
|
+
|
|
12
|
+
IMPORTANT: Your current working directory is "{{dirPath}}". Only review files within this directory.
|
|
13
|
+
{{cliFindingsSection}}
|
|
14
|
+
## Review Process
|
|
15
|
+
|
|
16
|
+
1. **Discover**: Use Glob to find source files (e.g. "**/*.{ts,tsx,js,py,rs,go,java,rb,php}"). Understand the project structure.
|
|
17
|
+
2. **Read**: Read the most important files — entry points, core modules, handlers, services. Prioritize files with recent git changes (`git diff --name-only HEAD~5` via Bash if available).
|
|
18
|
+
3. **Analyze**: Look for real, actionable issues across ALL of these categories:
|
|
19
|
+
|
|
20
|
+
### Architecture
|
|
21
|
+
- What is the current architecture (monolith, microservices, layered, etc.)?
|
|
22
|
+
- Are there architectural violations? (e.g., presentation layer directly accessing data layer, circular dependencies between modules)
|
|
23
|
+
- Is there proper separation of concerns?
|
|
24
|
+
- Are there god objects or god modules that do too much?
|
|
25
|
+
|
|
26
|
+
### SOLID / OOP Principles
|
|
27
|
+
- **SRP**: Classes/modules with multiple unrelated responsibilities
|
|
28
|
+
- **OCP**: Code that requires modification instead of extension for new features
|
|
29
|
+
- **LSP**: Subtypes that don't properly substitute for their base types
|
|
30
|
+
- **ISP**: Interfaces/contracts that force implementations to depend on methods they don't use
|
|
31
|
+
- **DIP**: High-level modules directly depending on low-level modules instead of abstractions
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
- Injection vulnerabilities (SQL, XSS, command), hardcoded secrets/credentials, auth bypasses, insecure crypto, path traversal, SSRF, unsafe deserialization
|
|
35
|
+
|
|
36
|
+
### Bugs & Logic
|
|
37
|
+
- Null/undefined errors, race conditions, logic errors, unhandled edge cases, off-by-one errors, resource leaks, incorrect error handling, incorrect algorithms
|
|
38
|
+
|
|
39
|
+
### Performance
|
|
40
|
+
- N+1 queries, unnecessary re-renders, missing memoization, blocking I/O in hot paths, unbounded data structures, missing pagination
|
|
41
|
+
|
|
42
|
+
## CRITICAL — Structured Evidence Requirement
|
|
43
|
+
|
|
44
|
+
For EACH finding, you MUST provide structured evidence that grounds the finding in actual code. This is required to prevent false positives.
|
|
45
|
+
|
|
46
|
+
For each finding, use this reasoning process:
|
|
47
|
+
|
|
48
|
+
1. **PREMISE**: State the observable fact from the code. Quote the exact code you see.
|
|
49
|
+
2. **CONTEXT**: What is the surrounding code doing? Are there guards, fixes, or patterns elsewhere that might handle this?
|
|
50
|
+
3. **COUNTER-CHECK**: Actively look for evidence that CONTRADICTS your finding. Check for:
|
|
51
|
+
- Guards or validation earlier in the call chain
|
|
52
|
+
- Error handling wrapping the code
|
|
53
|
+
- Configuration that changes behavior (e.g., NODE_ENV checks)
|
|
54
|
+
- Comments explaining intentional design choices
|
|
55
|
+
4. **CONCLUSION**: Only report the finding if you could not find contradicting evidence.
|
|
56
|
+
|
|
57
|
+
### Common False Positive Patterns to AVOID
|
|
58
|
+
|
|
59
|
+
- Claiming a function uses API X when it actually uses API Y (e.g., claiming Math.random() when code uses crypto.randomInt()) — ALWAYS quote the actual function call
|
|
60
|
+
- Claiming a header/value is leaked when code already deletes/filters it — READ the full function
|
|
61
|
+
- Claiming there's no guard when a condition check exists nearby — READ surrounding lines
|
|
62
|
+
- Claiming N fields/methods when the actual count differs — COUNT explicitly
|
|
63
|
+
- Claiming a resource leaks when cleanup exists in a different handler — SEARCH for the cleanup code
|
|
64
|
+
|
|
65
|
+
## Rules
|
|
66
|
+
|
|
67
|
+
- Only report findings you are >90% confident about after completing the counter-check step.
|
|
68
|
+
- Focus on architecture, SOLID violations, bugs, and security over style nits.
|
|
69
|
+
- Each finding MUST reference a specific file and line number. Do not report vague or file-level issues.
|
|
70
|
+
- Each finding MUST include an "evidence" field with the exact code snippet (1-5 lines) proving the issue exists.
|
|
71
|
+
- Limit to the 25 most important findings, ranked by severity.
|
|
72
|
+
- Do NOT modify any files. This is a read-only review.
|
|
73
|
+
- Be HONEST about the overall quality. A codebase with serious issues should score low.
|
|
74
|
+
|
|
75
|
+
## Scoring Guidelines
|
|
76
|
+
|
|
77
|
+
After your analysis, provide an honest overall quality score (0-100) and letter grade:
|
|
78
|
+
- **A (90-100)**: Excellent — clean architecture, minimal issues, well-tested, follows best practices
|
|
79
|
+
- **B (80-89)**: Good — solid code with minor issues, mostly well-structured
|
|
80
|
+
- **C (70-79)**: Adequate — functional but has notable quality issues that should be addressed
|
|
81
|
+
- **D (60-69)**: Below average — significant issues in architecture, testing, or code quality
|
|
82
|
+
- **F (0-59)**: Poor — serious problems: security vulnerabilities, broken architecture, major bugs, or unmaintainable code
|
|
83
|
+
|
|
84
|
+
Consider ALL findings (both CLI tool findings and your own) when determining the score. The score should reflect the overall state of the codebase honestly. A project with 50+ linting errors, formatting issues, complex functions, AND architectural problems should NOT score above 70.
|
|
85
|
+
|
|
86
|
+
## Output
|
|
87
|
+
|
|
88
|
+
After your analysis, output EXACTLY one JSON code block with your findings. No other text after the JSON block.
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"score": 72,
|
|
93
|
+
"grade": "C",
|
|
94
|
+
"scoreRationale": "Brief explanation of why this score was given, referencing key issues",
|
|
95
|
+
"findings": [
|
|
96
|
+
{
|
|
97
|
+
"severity": "critical|high|medium|low",
|
|
98
|
+
"category": "architecture|oop|security|bugs|performance|logic",
|
|
99
|
+
"file": "relative/path/to/file.ts",
|
|
100
|
+
"line": 42,
|
|
101
|
+
"title": "Short title describing the issue",
|
|
102
|
+
"description": "What the problem is and why it matters.",
|
|
103
|
+
"suggestion": "How to fix it.",
|
|
104
|
+
"evidence": "const token = Math.random().toString(36) // exact code from file proving the issue"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"summary": "Brief 1-2 sentence summary of overall code quality."
|
|
108
|
+
}
|
|
109
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-message
|
|
3
|
+
description: "Generate a conventional git commit message from staged changes. Use when committing code and wanting an AI-generated commit message."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are generating a git commit message for the following staged changes.
|
|
9
|
+
|
|
10
|
+
RECENT COMMIT MESSAGES (for style reference):
|
|
11
|
+
{{recentCommits}}
|
|
12
|
+
|
|
13
|
+
STAGED FILES:
|
|
14
|
+
{{stagedFiles}}
|
|
15
|
+
|
|
16
|
+
DIFF OF STAGED CHANGES:
|
|
17
|
+
{{diff}}
|
|
18
|
+
|
|
19
|
+
Generate a commit message following these rules:
|
|
20
|
+
1. First line: imperative mood, max 72 characters (e.g., "Add user authentication", "Fix memory leak in parser")
|
|
21
|
+
2. If the changes are complex, add a blank line then bullet points explaining the key changes
|
|
22
|
+
3. Focus on the "why" not just the "what"
|
|
23
|
+
4. Match the style of recent commits if possible
|
|
24
|
+
5. No emojis unless the repo already uses them
|
|
25
|
+
|
|
26
|
+
Respond with ONLY the commit message, nothing else.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fix-quality
|
|
3
|
+
description: "Fix code quality issues found by linters, complexity analyzers, or AI code review. Systematically works through findings from most to least severe. Use when fixing quality scan results."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
type: review
|
|
6
|
+
allowed-tools: Read, Edit, Write, Grep, Glob, Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a code quality fix agent. Fix the following quality issues in the codebase.
|
|
10
|
+
|
|
11
|
+
## Issues to Fix ({{issueCount}} total, showing top {{showCount}})
|
|
12
|
+
|
|
13
|
+
{{issueList}}
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- Fix each issue by editing the relevant file at the specified location.
|
|
18
|
+
- For complexity issues: refactor into smaller functions. For long files: split or extract modules. For long functions: break into smaller functions.
|
|
19
|
+
- For security issues: apply the suggested fix or use secure coding best practices.
|
|
20
|
+
- For bugs: fix the root cause, not just the symptom.
|
|
21
|
+
- For linting/formatting: apply the standard for the project.
|
|
22
|
+
- Do NOT introduce new issues. Make minimal, focused changes.
|
|
23
|
+
- After fixing, verify the changes compile/pass linting if tools are available.
|
|
24
|
+
- Work through the issues systematically from most to least severe.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pr-description
|
|
3
|
+
description: "Generate a pull request title and description from branch commits and diff. Use when creating a PR and wanting an AI-generated title and body."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are generating a pull request title and description for the following changes.
|
|
9
|
+
|
|
10
|
+
COMMITS ({{baseBranch}}..HEAD):
|
|
11
|
+
{{commits}}
|
|
12
|
+
|
|
13
|
+
FILES CHANGED:
|
|
14
|
+
{{filesChanged}}
|
|
15
|
+
|
|
16
|
+
DIFF:
|
|
17
|
+
{{diff}}
|
|
18
|
+
|
|
19
|
+
Generate a pull request title and description following these rules:
|
|
20
|
+
1. TITLE: First line must be the PR title — imperative mood, under 70 characters
|
|
21
|
+
2. Leave a blank line after the title
|
|
22
|
+
3. BODY: Write a concise description in markdown with:
|
|
23
|
+
- A "## Summary" section with 1-3 bullet points explaining what changed and why
|
|
24
|
+
- Optionally a "## Details" section if the changes are complex
|
|
25
|
+
4. Focus on the "why" not just the "what"
|
|
26
|
+
5. No emojis
|
|
27
|
+
|
|
28
|
+
Respond with ONLY the title and description, nothing else.
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import type { ToolUseEvent } from '../../cli/headless/index.js';
|
|
15
|
+
import { ResilientRunner } from '../../cli/headless/resilient-runner.js';
|
|
16
16
|
import type { HandlerContext } from '../websocket/handler-context.js';
|
|
17
17
|
import type { WSContext } from '../websocket/types.js';
|
|
18
18
|
import { defaultPmDir, getNextId, parseBoardDirectory, parsePlanDirectory, resolvePmDir } from './parser.js';
|
|
@@ -196,6 +196,7 @@ created: "YYYY-MM-DD"
|
|
|
196
196
|
blocked_by: [] # Use backlog-relative paths: backlog/IS-NNN.md
|
|
197
197
|
blocks: [] # Use backlog-relative paths: backlog/IS-NNN.md
|
|
198
198
|
review_gate: auto
|
|
199
|
+
output_type: auto # code = modify source files, document = produce written artifact, auto = infer
|
|
199
200
|
output_file: null
|
|
200
201
|
---
|
|
201
202
|
|
|
@@ -228,6 +229,14 @@ Implementation guidance.
|
|
|
228
229
|
- If an issue requires work across multiple subsystems, split it into one issue per subsystem with blocked_by edges between them
|
|
229
230
|
- Research/investigation issues should be separate from implementation issues
|
|
230
231
|
|
|
232
|
+
## output_type rules (critical — determines how the AI executes and reviews each issue)
|
|
233
|
+
|
|
234
|
+
- Set \`output_type: document\` for research, design, analysis, writing, planning, learning, or educational issues — anything that produces a written artifact rather than code changes
|
|
235
|
+
- Set \`output_type: code\` for issues that MUST modify source code files (implementation, bug fixes, refactoring)
|
|
236
|
+
- Set \`output_type: auto\` when unsure — the system will infer from "Files to Modify" (if the section lists real source paths it's treated as code, otherwise as document)
|
|
237
|
+
- When output_type is \`document\`, "Files to Modify" entries are treated as references, not files to edit. The AI produces a document artifact and is reviewed on document quality.
|
|
238
|
+
- When output_type is \`code\`, "Files to Modify" lists actual source files the AI must edit. The review gate verifies source files were changed.
|
|
239
|
+
|
|
231
240
|
## Epic creation rules
|
|
232
241
|
|
|
233
242
|
- Create an EP-*.md file in ${cc.backlogPath} with type: epic and a children: [] field in front matter
|
|
@@ -246,12 +255,13 @@ User request: ${userPrompt}`;
|
|
|
246
255
|
data: { message: 'Starting project planning...' },
|
|
247
256
|
});
|
|
248
257
|
|
|
249
|
-
const runner = new
|
|
258
|
+
const runner = new ResilientRunner({
|
|
250
259
|
workingDir: executionDir || workingDir,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
prompt: enrichedPrompt,
|
|
261
|
+
policy: 'STANDARD',
|
|
262
|
+
stallWarningMs: 300_000,
|
|
263
|
+
stallKillMs: 900_000,
|
|
264
|
+
stallHardCapMs: 1_800_000,
|
|
255
265
|
verbose: true,
|
|
256
266
|
outputCallback: (text: string) => {
|
|
257
267
|
ctx.send(ws, {
|
|
@@ -271,6 +281,8 @@ User request: ${userPrompt}`;
|
|
|
271
281
|
}
|
|
272
282
|
};
|
|
273
283
|
})(),
|
|
284
|
+
logLabel: 'pm-compose',
|
|
285
|
+
logDir: cc.effectiveBoardId ? join(pmDir, 'boards', cc.effectiveBoardId, 'logs') : undefined,
|
|
274
286
|
});
|
|
275
287
|
|
|
276
288
|
ctx.broadcastToAll({
|
|
@@ -278,8 +290,7 @@ User request: ${userPrompt}`;
|
|
|
278
290
|
data: { message: 'Claude is planning your project...' },
|
|
279
291
|
});
|
|
280
292
|
|
|
281
|
-
const
|
|
282
|
-
const result = await runWithFileLogger('pm-compose', () => runner.run(), boardLogDir);
|
|
293
|
+
const result = await runner.run();
|
|
283
294
|
|
|
284
295
|
ctx.broadcastToAll({
|
|
285
296
|
type: 'planPromptProgress',
|