@bradygaster/squad-cli 0.9.2-insider.5 → 0.9.3-insider.1
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/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +27 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/loop.d.ts +51 -0
- package/dist/cli/commands/loop.d.ts.map +1 -0
- package/dist/cli/commands/loop.js +352 -0
- package/dist/cli/commands/loop.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/budget-check.d.ts +29 -0
- package/dist/cli/commands/watch/capabilities/budget-check.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/budget-check.js +38 -0
- package/dist/cli/commands/watch/capabilities/budget-check.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts +52 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.js +152 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/cleanup.d.ts +18 -0
- package/dist/cli/commands/watch/capabilities/cleanup.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/cleanup.js +135 -0
- package/dist/cli/commands/watch/capabilities/cleanup.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/execute.d.ts +15 -3
- package/dist/cli/commands/watch/capabilities/execute.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/execute.js +108 -51
- package/dist/cli/commands/watch/capabilities/execute.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.d.ts +20 -0
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.js +144 -0
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/health-check.d.ts +29 -0
- package/dist/cli/commands/watch/capabilities/health-check.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/health-check.js +139 -0
- package/dist/cli/commands/watch/capabilities/health-check.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.d.ts +48 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.js +115 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/index.js +4 -0
- package/dist/cli/commands/watch/capabilities/index.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/lockfile.d.ts +30 -0
- package/dist/cli/commands/watch/capabilities/lockfile.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/lockfile.js +100 -0
- package/dist/cli/commands/watch/capabilities/lockfile.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts +30 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.js +103 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/post-failure.d.ts +19 -0
- package/dist/cli/commands/watch/capabilities/post-failure.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/post-failure.js +58 -0
- package/dist/cli/commands/watch/capabilities/post-failure.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/priority.d.ts +59 -0
- package/dist/cli/commands/watch/capabilities/priority.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/priority.js +101 -0
- package/dist/cli/commands/watch/capabilities/priority.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.d.ts +67 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.js +187 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/self-pull.d.ts +4 -1
- package/dist/cli/commands/watch/capabilities/self-pull.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/self-pull.js +53 -5
- package/dist/cli/commands/watch/capabilities/self-pull.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts +23 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.js +87 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts +29 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.js +114 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.js.map +1 -0
- package/dist/cli/commands/watch/config.d.ts +18 -0
- package/dist/cli/commands/watch/config.d.ts.map +1 -1
- package/dist/cli/commands/watch/config.js +20 -1
- package/dist/cli/commands/watch/config.js.map +1 -1
- package/dist/cli/commands/watch/health.d.ts +41 -0
- package/dist/cli/commands/watch/health.d.ts.map +1 -0
- package/dist/cli/commands/watch/health.js +141 -0
- package/dist/cli/commands/watch/health.js.map +1 -0
- package/dist/cli/commands/watch/index.d.ts +9 -2
- package/dist/cli/commands/watch/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/index.js +115 -8
- package/dist/cli/commands/watch/index.js.map +1 -1
- package/dist/cli/commands/watch/types.d.ts +2 -0
- package/dist/cli/commands/watch/types.d.ts.map +1 -1
- package/dist/cli/commands/watch/verbose.d.ts +12 -0
- package/dist/cli/commands/watch/verbose.d.ts.map +1 -0
- package/dist/cli/commands/watch/verbose.js +28 -0
- package/dist/cli/commands/watch/verbose.js.map +1 -0
- package/dist/cli/core/detect-squad-dir.d.ts.map +1 -1
- package/dist/cli/core/detect-squad-dir.js +9 -12
- package/dist/cli/core/detect-squad-dir.js.map +1 -1
- package/dist/cli/core/nap.d.ts.map +1 -1
- package/dist/cli/core/nap.js +9 -4
- package/dist/cli/core/nap.js.map +1 -1
- package/dist/cli/core/upgrade.d.ts +18 -0
- package/dist/cli/core/upgrade.d.ts.map +1 -1
- package/dist/cli/core/upgrade.js +119 -0
- package/dist/cli/core/upgrade.js.map +1 -1
- package/dist/cli-entry.js +208 -12
- package/dist/cli-entry.js.map +1 -1
- package/package.json +5 -1
- package/templates/ceremonies.md +28 -0
- package/templates/issue-lifecycle.md +2 -1
- package/templates/loop.md +46 -0
- package/templates/ralph-triage.js +3 -1
- package/templates/scribe-charter.md +20 -1
- package/templates/squad.agent.md.template +9 -7
- package/templates/workflows/squad-triage.yml +4 -2
- package/templates/workflows/sync-squad-labels.yml +3 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Circuit Breaker capability — model-level fallback with cooldown.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 `Get-CircuitBreakerState` / `Update-CircuitBreakerOn*`.
|
|
5
|
+
* Tracks model failures, auto-fallback through a configurable chain, and
|
|
6
|
+
* cooldown timer. State persisted to `.squad/ralph-circuit-breaker.json`.
|
|
7
|
+
*
|
|
8
|
+
* This is a **utility module** consumed by the main watch loop, not a
|
|
9
|
+
* phase-based capability (it gates every round, not a specific phase).
|
|
10
|
+
*
|
|
11
|
+
* Config (via squad.config.ts → watch.circuitBreaker):
|
|
12
|
+
* preferredModel – default model to use (default: "claude-sonnet-4")
|
|
13
|
+
* fallbackChain – ordered list of fallback models
|
|
14
|
+
* cooldownMinutes – minutes before half-open probe (default: 10)
|
|
15
|
+
* requiredSuccessesToClose – successes in half-open before closing (default: 2)
|
|
16
|
+
*/
|
|
17
|
+
export type CircuitState = 'closed' | 'open' | 'half-open';
|
|
18
|
+
export interface ModelCircuitBreakerState {
|
|
19
|
+
state: CircuitState;
|
|
20
|
+
preferredModel: string;
|
|
21
|
+
currentModel: string;
|
|
22
|
+
fallbackChain: string[];
|
|
23
|
+
lastRateLimitHit: string | null;
|
|
24
|
+
cooldownMinutes: number;
|
|
25
|
+
consecutiveSuccesses: number;
|
|
26
|
+
requiredSuccessesToClose: number;
|
|
27
|
+
totalFallbacks: number;
|
|
28
|
+
totalRecoveries: number;
|
|
29
|
+
}
|
|
30
|
+
export interface CircuitBreakerConfig {
|
|
31
|
+
preferredModel?: string;
|
|
32
|
+
fallbackChain?: string[];
|
|
33
|
+
cooldownMinutes?: number;
|
|
34
|
+
requiredSuccessesToClose?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare class ModelCircuitBreaker {
|
|
37
|
+
private statePath;
|
|
38
|
+
constructor(squadDir: string, config?: CircuitBreakerConfig);
|
|
39
|
+
load(): ModelCircuitBreakerState;
|
|
40
|
+
save(state: ModelCircuitBreakerState): void;
|
|
41
|
+
/** Get the model to use for the current round. */
|
|
42
|
+
getCurrentModel(): string;
|
|
43
|
+
/** Call after a successful round. */
|
|
44
|
+
onSuccess(): void;
|
|
45
|
+
/** Call when a rate limit or model error is detected. */
|
|
46
|
+
onRateLimit(): void;
|
|
47
|
+
/** Reset to defaults (used by post-failure remediation). */
|
|
48
|
+
reset(): void;
|
|
49
|
+
/** Get full state for diagnostics. */
|
|
50
|
+
getState(): ModelCircuitBreakerState;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,YAAY,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAeD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,SAAS,CAAS;gBAEd,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB;IAkB3D,IAAI,IAAI,wBAAwB;IAuBhC,IAAI,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI;IAU3C,kDAAkD;IAClD,eAAe,IAAI,MAAM;IAmBzB,qCAAqC;IACrC,SAAS,IAAI,IAAI;IAcjB,yDAAyD;IACzD,WAAW,IAAI,IAAI;IAanB,4DAA4D;IAC5D,KAAK,IAAI,IAAI;IAIb,sCAAsC;IACtC,QAAQ,IAAI,wBAAwB;CAGrC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Circuit Breaker capability — model-level fallback with cooldown.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 `Get-CircuitBreakerState` / `Update-CircuitBreakerOn*`.
|
|
5
|
+
* Tracks model failures, auto-fallback through a configurable chain, and
|
|
6
|
+
* cooldown timer. State persisted to `.squad/ralph-circuit-breaker.json`.
|
|
7
|
+
*
|
|
8
|
+
* This is a **utility module** consumed by the main watch loop, not a
|
|
9
|
+
* phase-based capability (it gates every round, not a specific phase).
|
|
10
|
+
*
|
|
11
|
+
* Config (via squad.config.ts → watch.circuitBreaker):
|
|
12
|
+
* preferredModel – default model to use (default: "claude-sonnet-4")
|
|
13
|
+
* fallbackChain – ordered list of fallback models
|
|
14
|
+
* cooldownMinutes – minutes before half-open probe (default: 10)
|
|
15
|
+
* requiredSuccessesToClose – successes in half-open before closing (default: 2)
|
|
16
|
+
*/
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
const DEFAULT_STATE = {
|
|
20
|
+
state: 'closed',
|
|
21
|
+
preferredModel: 'claude-sonnet-4',
|
|
22
|
+
currentModel: 'claude-sonnet-4',
|
|
23
|
+
fallbackChain: ['gpt-4.1', 'claude-haiku-4.5'],
|
|
24
|
+
lastRateLimitHit: null,
|
|
25
|
+
cooldownMinutes: 10,
|
|
26
|
+
consecutiveSuccesses: 0,
|
|
27
|
+
requiredSuccessesToClose: 2,
|
|
28
|
+
totalFallbacks: 0,
|
|
29
|
+
totalRecoveries: 0,
|
|
30
|
+
};
|
|
31
|
+
export class ModelCircuitBreaker {
|
|
32
|
+
statePath;
|
|
33
|
+
constructor(squadDir, config) {
|
|
34
|
+
this.statePath = path.join(squadDir, 'ralph-circuit-breaker.json');
|
|
35
|
+
// Ensure defaults incorporate any user config
|
|
36
|
+
if (config) {
|
|
37
|
+
const state = this.load();
|
|
38
|
+
let dirty = false;
|
|
39
|
+
if (config.preferredModel && state.preferredModel !== config.preferredModel) {
|
|
40
|
+
state.preferredModel = config.preferredModel;
|
|
41
|
+
if (state.state === 'closed')
|
|
42
|
+
state.currentModel = config.preferredModel;
|
|
43
|
+
dirty = true;
|
|
44
|
+
}
|
|
45
|
+
if (config.fallbackChain) {
|
|
46
|
+
state.fallbackChain = config.fallbackChain;
|
|
47
|
+
dirty = true;
|
|
48
|
+
}
|
|
49
|
+
if (config.cooldownMinutes) {
|
|
50
|
+
state.cooldownMinutes = config.cooldownMinutes;
|
|
51
|
+
dirty = true;
|
|
52
|
+
}
|
|
53
|
+
if (config.requiredSuccessesToClose) {
|
|
54
|
+
state.requiredSuccessesToClose = config.requiredSuccessesToClose;
|
|
55
|
+
dirty = true;
|
|
56
|
+
}
|
|
57
|
+
if (dirty)
|
|
58
|
+
this.save(state);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
load() {
|
|
62
|
+
try {
|
|
63
|
+
if (!fs.existsSync(this.statePath))
|
|
64
|
+
return { ...DEFAULT_STATE };
|
|
65
|
+
const raw = fs.readFileSync(this.statePath, 'utf-8');
|
|
66
|
+
if (!raw)
|
|
67
|
+
return { ...DEFAULT_STATE };
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
// Handle legacy nested schema (model_fallback wrapper)
|
|
70
|
+
const legacy = parsed;
|
|
71
|
+
if (!parsed.preferredModel && legacy['model_fallback']) {
|
|
72
|
+
const mf = legacy['model_fallback'];
|
|
73
|
+
return {
|
|
74
|
+
...DEFAULT_STATE,
|
|
75
|
+
preferredModel: mf['preferred'] ?? DEFAULT_STATE.preferredModel,
|
|
76
|
+
currentModel: mf['preferred'] ?? DEFAULT_STATE.currentModel,
|
|
77
|
+
fallbackChain: mf['fallback_chain'] ?? DEFAULT_STATE.fallbackChain,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { ...DEFAULT_STATE, ...parsed };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return { ...DEFAULT_STATE };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
save(state) {
|
|
87
|
+
try {
|
|
88
|
+
const dir = path.dirname(this.statePath);
|
|
89
|
+
if (!fs.existsSync(dir)) {
|
|
90
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
93
|
+
}
|
|
94
|
+
catch { /* best-effort */ }
|
|
95
|
+
}
|
|
96
|
+
/** Get the model to use for the current round. */
|
|
97
|
+
getCurrentModel() {
|
|
98
|
+
const state = this.load();
|
|
99
|
+
if (state.state === 'closed')
|
|
100
|
+
return state.preferredModel;
|
|
101
|
+
if (state.state === 'open') {
|
|
102
|
+
if (state.lastRateLimitHit) {
|
|
103
|
+
const elapsed = Date.now() - new Date(state.lastRateLimitHit).getTime();
|
|
104
|
+
if (elapsed >= state.cooldownMinutes * 60_000) {
|
|
105
|
+
state.state = 'half-open';
|
|
106
|
+
state.currentModel = state.preferredModel;
|
|
107
|
+
this.save(state);
|
|
108
|
+
return state.preferredModel;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return state.currentModel;
|
|
112
|
+
}
|
|
113
|
+
// half-open — probe preferred
|
|
114
|
+
return state.preferredModel;
|
|
115
|
+
}
|
|
116
|
+
/** Call after a successful round. */
|
|
117
|
+
onSuccess() {
|
|
118
|
+
const state = this.load();
|
|
119
|
+
if (state.state === 'half-open') {
|
|
120
|
+
state.consecutiveSuccesses++;
|
|
121
|
+
if (state.consecutiveSuccesses >= state.requiredSuccessesToClose) {
|
|
122
|
+
state.state = 'closed';
|
|
123
|
+
state.currentModel = state.preferredModel;
|
|
124
|
+
state.consecutiveSuccesses = 0;
|
|
125
|
+
state.totalRecoveries++;
|
|
126
|
+
}
|
|
127
|
+
this.save(state);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Call when a rate limit or model error is detected. */
|
|
131
|
+
onRateLimit() {
|
|
132
|
+
const state = this.load();
|
|
133
|
+
state.state = 'open';
|
|
134
|
+
state.lastRateLimitHit = new Date().toISOString();
|
|
135
|
+
state.consecutiveSuccesses = 0;
|
|
136
|
+
state.totalFallbacks++;
|
|
137
|
+
// Pick first fallback
|
|
138
|
+
if (state.fallbackChain.length > 0) {
|
|
139
|
+
state.currentModel = state.fallbackChain[0];
|
|
140
|
+
}
|
|
141
|
+
this.save(state);
|
|
142
|
+
}
|
|
143
|
+
/** Reset to defaults (used by post-failure remediation). */
|
|
144
|
+
reset() {
|
|
145
|
+
this.save({ ...DEFAULT_STATE });
|
|
146
|
+
}
|
|
147
|
+
/** Get full state for diagnostics. */
|
|
148
|
+
getState() {
|
|
149
|
+
return this.load();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup capability — housekeeping for stale temp and log files.
|
|
3
|
+
*
|
|
4
|
+
* Runs in the 'housekeeping' phase. Clears the scratch directory, prunes
|
|
5
|
+
* old orchestration-log and session-log entries, and warns about stale
|
|
6
|
+
* decision inbox files.
|
|
7
|
+
*/
|
|
8
|
+
import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js';
|
|
9
|
+
export declare class CleanupCapability implements WatchCapability {
|
|
10
|
+
readonly name = "cleanup";
|
|
11
|
+
readonly description = "Remove stale scratch files, prune old logs, warn about stale inbox";
|
|
12
|
+
readonly configShape: "object";
|
|
13
|
+
readonly requires: string[];
|
|
14
|
+
readonly phase: "housekeeping";
|
|
15
|
+
preflight(context: WatchContext): Promise<PreflightResult>;
|
|
16
|
+
execute(context: WatchContext): Promise<CapabilityResult>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAyDpG,qBAAa,iBAAkB,YAAW,eAAe;IACvD,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,wEAAwE;IAC5F,QAAQ,CAAC,WAAW,EAAG,QAAQ,CAAU;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAM;IACjC,QAAQ,CAAC,KAAK,EAAG,cAAc,CAAU;IAEnC,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;IAQ1D,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAoEhE"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup capability — housekeeping for stale temp and log files.
|
|
3
|
+
*
|
|
4
|
+
* Runs in the 'housekeeping' phase. Clears the scratch directory, prunes
|
|
5
|
+
* old orchestration-log and session-log entries, and warns about stale
|
|
6
|
+
* decision inbox files.
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { rmSync } from 'node:fs';
|
|
10
|
+
import { FSStorageProvider } from '@bradygaster/squad-sdk';
|
|
11
|
+
const storage = new FSStorageProvider();
|
|
12
|
+
/** Default: files older than this many days are pruned. */
|
|
13
|
+
const DEFAULT_MAX_AGE_DAYS = 30;
|
|
14
|
+
/** Default: run cleanup every N rounds (not every round). */
|
|
15
|
+
const DEFAULT_EVERY_N_ROUNDS = 10;
|
|
16
|
+
/** Stale inbox warning threshold (days). */
|
|
17
|
+
const STALE_INBOX_DAYS = 7;
|
|
18
|
+
function parseConfig(raw) {
|
|
19
|
+
return {
|
|
20
|
+
everyNRounds: typeof raw.everyNRounds === 'number' && Number.isFinite(raw.everyNRounds) && raw.everyNRounds > 0
|
|
21
|
+
? raw.everyNRounds
|
|
22
|
+
: DEFAULT_EVERY_N_ROUNDS,
|
|
23
|
+
maxAgeDays: typeof raw.maxAgeDays === 'number' && Number.isFinite(raw.maxAgeDays) && raw.maxAgeDays > 0
|
|
24
|
+
? raw.maxAgeDays
|
|
25
|
+
: DEFAULT_MAX_AGE_DAYS,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function isOlderThan(filename, days) {
|
|
29
|
+
// Filenames may start with ISO timestamp: 2026-04-01T12-00-00Z-agent.md
|
|
30
|
+
const match = filename.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
31
|
+
if (!match)
|
|
32
|
+
return false;
|
|
33
|
+
const fileDate = new Date(match[1]);
|
|
34
|
+
if (isNaN(fileDate.getTime()))
|
|
35
|
+
return false;
|
|
36
|
+
const cutoffMs = Date.now() - (days * 86400000);
|
|
37
|
+
return fileDate.getTime() < cutoffMs;
|
|
38
|
+
}
|
|
39
|
+
function safeList(dir) {
|
|
40
|
+
try {
|
|
41
|
+
if (!storage.existsSync(dir))
|
|
42
|
+
return [];
|
|
43
|
+
return storage.listSync?.(dir) ?? [];
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function safeDelete(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
rmSync(filePath, { recursive: true, force: true });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class CleanupCapability {
|
|
59
|
+
name = 'cleanup';
|
|
60
|
+
description = 'Remove stale scratch files, prune old logs, warn about stale inbox';
|
|
61
|
+
configShape = 'object';
|
|
62
|
+
requires = [];
|
|
63
|
+
phase = 'housekeeping';
|
|
64
|
+
async preflight(context) {
|
|
65
|
+
const squadDir = path.join(context.teamRoot, '.squad');
|
|
66
|
+
if (!storage.existsSync(squadDir)) {
|
|
67
|
+
return { ok: false, reason: '.squad/ directory not found' };
|
|
68
|
+
}
|
|
69
|
+
return { ok: true };
|
|
70
|
+
}
|
|
71
|
+
async execute(context) {
|
|
72
|
+
const config = parseConfig(context.config);
|
|
73
|
+
const everyN = config.everyNRounds ?? DEFAULT_EVERY_N_ROUNDS;
|
|
74
|
+
// Only run on every Nth round (or round 1 for immediate feedback)
|
|
75
|
+
if (context.round > 1 && context.round % everyN !== 0) {
|
|
76
|
+
return { success: true, summary: `cleanup: skipped (runs every ${everyN} rounds)` };
|
|
77
|
+
}
|
|
78
|
+
const squadDir = path.join(context.teamRoot, '.squad');
|
|
79
|
+
const maxAge = config.maxAgeDays ?? DEFAULT_MAX_AGE_DAYS;
|
|
80
|
+
const actions = [];
|
|
81
|
+
// 1. Clear .squad/.scratch/ — everything in here is ephemeral
|
|
82
|
+
const scratchDir = path.join(squadDir, '.scratch');
|
|
83
|
+
const scratchFiles = safeList(scratchDir);
|
|
84
|
+
let scratchDeleted = 0;
|
|
85
|
+
for (const f of scratchFiles) {
|
|
86
|
+
if (safeDelete(path.join(scratchDir, f)))
|
|
87
|
+
scratchDeleted++;
|
|
88
|
+
}
|
|
89
|
+
if (scratchDeleted > 0) {
|
|
90
|
+
actions.push(`scratch: ${scratchDeleted} files cleared`);
|
|
91
|
+
}
|
|
92
|
+
// 2. Prune old orchestration-log entries (older than maxAge days)
|
|
93
|
+
const orchDir = path.join(squadDir, 'orchestration-log');
|
|
94
|
+
const orchFiles = safeList(orchDir);
|
|
95
|
+
let orchPruned = 0;
|
|
96
|
+
for (const f of orchFiles) {
|
|
97
|
+
if (isOlderThan(f, maxAge)) {
|
|
98
|
+
if (safeDelete(path.join(orchDir, f)))
|
|
99
|
+
orchPruned++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (orchPruned > 0) {
|
|
103
|
+
actions.push(`orchestration-log: ${orchPruned} entries pruned (>${maxAge}d)`);
|
|
104
|
+
}
|
|
105
|
+
// 3. Prune old session logs
|
|
106
|
+
const logDir = path.join(squadDir, 'log');
|
|
107
|
+
const logFiles = safeList(logDir);
|
|
108
|
+
let logPruned = 0;
|
|
109
|
+
for (const f of logFiles) {
|
|
110
|
+
if (isOlderThan(f, maxAge)) {
|
|
111
|
+
if (safeDelete(path.join(logDir, f)))
|
|
112
|
+
logPruned++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (logPruned > 0) {
|
|
116
|
+
actions.push(`log: ${logPruned} entries pruned (>${maxAge}d)`);
|
|
117
|
+
}
|
|
118
|
+
// 4. Warn about stale decision inbox files
|
|
119
|
+
const inboxDir = path.join(squadDir, 'decisions', 'inbox');
|
|
120
|
+
const inboxFiles = safeList(inboxDir).filter(f => f.endsWith('.md'));
|
|
121
|
+
const staleInbox = inboxFiles.filter(f => isOlderThan(f, STALE_INBOX_DAYS));
|
|
122
|
+
if (staleInbox.length > 0) {
|
|
123
|
+
actions.push(`⚠ ${staleInbox.length} stale inbox files (>${STALE_INBOX_DAYS}d)`);
|
|
124
|
+
}
|
|
125
|
+
if (actions.length === 0) {
|
|
126
|
+
return { success: true, summary: 'cleanup: nothing to do' };
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
summary: `cleanup: ${actions.join('; ')}`,
|
|
131
|
+
data: { scratchDeleted, orchPruned, logPruned, staleInboxCount: staleInbox.length },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/cleanup.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,12 +15,24 @@ export interface ExecutableWorkItem {
|
|
|
15
15
|
login: string;
|
|
16
16
|
}>;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
19
|
-
export declare function
|
|
18
|
+
/** Classify an issue as read-heavy or write-heavy by title keywords. */
|
|
19
|
+
export declare function classifyIssue(title: string): 'read' | 'write';
|
|
20
|
+
/** Find issues eligible for autonomous execution.
|
|
21
|
+
*
|
|
22
|
+
* Pre-filters to keep only clearly actionable items:
|
|
23
|
+
* - must have a squad/squad:* label
|
|
24
|
+
* - must not be assigned to a human (agent decides once it reads ralph-instructions.md)
|
|
25
|
+
* - must not carry a blocking status label
|
|
26
|
+
*
|
|
27
|
+
* Matches the PS1 ralph-watch pre-filter design.
|
|
28
|
+
*/
|
|
29
|
+
export declare function findExecutableIssues(_roster: Array<{
|
|
20
30
|
name: string;
|
|
21
31
|
label: string;
|
|
22
32
|
expertise: string[];
|
|
23
|
-
}>,
|
|
33
|
+
}>, _capabilities: MachineCapabilities | null, issues: ExecutableWorkItem[]): ExecutableWorkItem[];
|
|
34
|
+
/** Build the rich agent prompt matching PS1 ralph-watch design. */
|
|
35
|
+
export declare function buildAgentPrompt(issues: ExecutableWorkItem[], teamRoot: string): string;
|
|
24
36
|
export declare class ExecuteCapability implements WatchCapability {
|
|
25
37
|
readonly name = "execute";
|
|
26
38
|
readonly description = "Spawn Copilot sessions to work on eligible issues";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAGrF,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChC,SAAS,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrC;AAoBD,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAM7D;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,EACpE,aAAa,EAAE,mBAAmB,GAAG,IAAI,EACzC,MAAM,EAAE,kBAAkB,EAAE,GAC3B,kBAAkB,EAAE,CAItB;AAaD,mEAAmE;AACnE,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,QAAQ,EAAE,MAAM,GACf,MAAM,CA8BR;AA6BD,qBAAa,iBAAkB,YAAW,eAAe;IACvD,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,uDAAuD;IAC3E,QAAQ,CAAC,WAAW,EAAG,SAAS,CAAU;IAC1C,QAAQ,CAAC,QAAQ,WAAU;IAC3B,QAAQ,CAAC,KAAK,EAAG,cAAc,CAAU;IAEnC,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;IAQ3D,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA4ChE"}
|
|
@@ -2,18 +2,33 @@
|
|
|
2
2
|
* Execute capability — spawns Copilot sessions for eligible issues.
|
|
3
3
|
*/
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createVerboseLogger } from '../verbose.js';
|
|
8
|
+
/** Check whether an issue carries a squad or squad:* label. */
|
|
9
|
+
function hasSquadLabel(issue) {
|
|
10
|
+
return issue.labels.some(l => l.name === 'squad' || l.name.startsWith('squad:'));
|
|
11
|
+
}
|
|
12
|
+
/** Keywords that indicate read-heavy / analysis work. */
|
|
13
|
+
const READ_KEYWORDS = [
|
|
14
|
+
'research', 'review', 'analyze', 'investigate', 'audit',
|
|
15
|
+
'check', 'scan', 'assess', 'evaluate', 'fact-check',
|
|
16
|
+
'document', 'report',
|
|
17
|
+
];
|
|
18
|
+
/** Keywords that indicate write-heavy / implementation work. */
|
|
19
|
+
const WRITE_KEYWORDS = [
|
|
20
|
+
'fix', 'implement', 'create', 'build', 'refactor',
|
|
21
|
+
'add', 'update', 'migrate', 'deploy', 'feature',
|
|
22
|
+
];
|
|
23
|
+
/** Classify an issue as read-heavy or write-heavy by title keywords. */
|
|
24
|
+
export function classifyIssue(title) {
|
|
25
|
+
const lower = title.toLowerCase();
|
|
26
|
+
const isRead = READ_KEYWORDS.some(k => lower.includes(k));
|
|
27
|
+
const isWrite = WRITE_KEYWORDS.some(k => lower.includes(k));
|
|
28
|
+
if (isRead && !isWrite)
|
|
29
|
+
return 'read';
|
|
30
|
+
return 'write'; // default to write (safer — gets full agent session)
|
|
31
|
+
}
|
|
17
32
|
/** Build agent command for a prompt. */
|
|
18
33
|
function buildAgentCommand(prompt, context) {
|
|
19
34
|
if (context.agentCmd) {
|
|
@@ -28,32 +43,71 @@ function buildAgentCommand(prompt, context) {
|
|
|
28
43
|
}
|
|
29
44
|
return { cmd: 'gh', args };
|
|
30
45
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!labels.some(l => memberLabels.has(l)))
|
|
37
|
-
return false;
|
|
38
|
-
if (issue.assignees && issue.assignees.length > 0)
|
|
39
|
-
return false;
|
|
40
|
-
if (labels.some(l => BLOCKED_LABELS.has(l)))
|
|
41
|
-
return false;
|
|
42
|
-
return true;
|
|
43
|
-
});
|
|
46
|
+
/** Labels that indicate an issue should not be auto-executed. */
|
|
47
|
+
const BLOCKING_LABELS = ['status:blocked', 'status:wontfix', 'status:on-hold', 'blocked'];
|
|
48
|
+
/** Check whether an issue has a blocking status label. */
|
|
49
|
+
function hasBlockingLabel(issue) {
|
|
50
|
+
return issue.labels.some(l => BLOCKING_LABELS.includes(l.name));
|
|
44
51
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/** Check whether an issue is already assigned to a human. */
|
|
53
|
+
function isAssigned(issue) {
|
|
54
|
+
return issue.assignees.length > 0;
|
|
55
|
+
}
|
|
56
|
+
/** Find issues eligible for autonomous execution.
|
|
57
|
+
*
|
|
58
|
+
* Pre-filters to keep only clearly actionable items:
|
|
59
|
+
* - must have a squad/squad:* label
|
|
60
|
+
* - must not be assigned to a human (agent decides once it reads ralph-instructions.md)
|
|
61
|
+
* - must not carry a blocking status label
|
|
62
|
+
*
|
|
63
|
+
* Matches the PS1 ralph-watch pre-filter design.
|
|
64
|
+
*/
|
|
65
|
+
export function findExecutableIssues(_roster, _capabilities, issues) {
|
|
66
|
+
return issues.filter(issue => hasSquadLabel(issue) && !isAssigned(issue) && !hasBlockingLabel(issue));
|
|
67
|
+
}
|
|
68
|
+
/** Format issue list for the agent prompt. */
|
|
69
|
+
function formatIssueList(issues) {
|
|
70
|
+
return issues.map(i => {
|
|
71
|
+
const labels = i.labels.map(l => l.name).join(', ');
|
|
72
|
+
const assigned = i.assignees?.length
|
|
73
|
+
? `assigned: ${i.assignees.map(a => a.login).join(',')}`
|
|
74
|
+
: 'unassigned';
|
|
75
|
+
return `- #${i.number}: ${i.title} [${labels}] ${assigned}`;
|
|
76
|
+
}).join('\n');
|
|
77
|
+
}
|
|
78
|
+
/** Build the rich agent prompt matching PS1 ralph-watch design. */
|
|
79
|
+
export function buildAgentPrompt(issues, teamRoot) {
|
|
80
|
+
const issueList = formatIssueList(issues);
|
|
81
|
+
const hasInstructions = existsSync(path.join(teamRoot, '.squad', 'ralph-instructions.md'));
|
|
82
|
+
if (hasInstructions) {
|
|
83
|
+
return [
|
|
84
|
+
'Ralph, Go! Read .squad/ralph-instructions.md for your full instructions. Follow ALL sections there. MAXIMIZE PARALLELISM — spawn agents for ALL actionable issues simultaneously.',
|
|
85
|
+
'',
|
|
86
|
+
'Here are the current open squad issues:',
|
|
87
|
+
issueList,
|
|
88
|
+
'',
|
|
89
|
+
'Task: Read the issues, follow your instructions in .squad/ralph-instructions.md, and work on what\'s actionable.',
|
|
90
|
+
'WHY: Keep the squad pipeline moving — no idle work.',
|
|
91
|
+
'Success: Issues get branches, PRs, and progress.',
|
|
92
|
+
'Escalation: If blocked, comment on the issue and move to next.',
|
|
93
|
+
].join('\n');
|
|
54
94
|
}
|
|
55
|
-
|
|
56
|
-
|
|
95
|
+
// Fallback when ralph-instructions.md does not exist
|
|
96
|
+
return [
|
|
97
|
+
'You are Ralph, the autonomous work monitor. Review the open squad issues below and work on every actionable one. Skip issues that are blocked, waiting on external input, or already assigned.',
|
|
98
|
+
'',
|
|
99
|
+
'Here are the current open squad issues:',
|
|
100
|
+
issueList,
|
|
101
|
+
'',
|
|
102
|
+
'Task: Triage the list, pick up unblocked/unassigned issues, create branches and PRs.',
|
|
103
|
+
'WHY: Keep the squad pipeline moving — no idle work.',
|
|
104
|
+
'Success: Issues get branches, PRs, and progress.',
|
|
105
|
+
'Escalation: If blocked, comment on the issue and move to next.',
|
|
106
|
+
].join('\n');
|
|
107
|
+
}
|
|
108
|
+
/** Spawn a single agent session for all eligible issues. */
|
|
109
|
+
async function executeAll(issues, context, timeoutMs) {
|
|
110
|
+
const prompt = buildAgentPrompt(issues, context.teamRoot);
|
|
57
111
|
const { cmd, args } = buildAgentCommand(prompt, context);
|
|
58
112
|
return new Promise((resolve) => {
|
|
59
113
|
const _cp = execFile(cmd, args, { cwd: context.teamRoot, timeout: timeoutMs, maxBuffer: 50 * 1024 * 1024 }, (err) => {
|
|
@@ -82,9 +136,10 @@ export class ExecuteCapability {
|
|
|
82
136
|
});
|
|
83
137
|
}
|
|
84
138
|
async execute(context) {
|
|
139
|
+
const vlog = createVerboseLogger(context.verbose ?? false);
|
|
85
140
|
try {
|
|
86
|
-
const maxConcurrent = context.config['maxConcurrent'] ?? 1;
|
|
87
141
|
const timeout = (context.config['timeout'] ?? 30) * 60_000;
|
|
142
|
+
vlog.log(`Execute: agentCmd=${context.agentCmd ?? 'gh copilot'}, timeout=${timeout / 60_000}m`);
|
|
88
143
|
// Fetch open issues with squad label
|
|
89
144
|
const sdkItems = await context.adapter.listWorkItems({ tags: ['squad'], state: 'open', limit: 50 });
|
|
90
145
|
const issues = sdkItems.map(wi => ({
|
|
@@ -93,22 +148,24 @@ export class ExecuteCapability {
|
|
|
93
148
|
labels: wi.tags.map(t => ({ name: t })),
|
|
94
149
|
assignees: wi.assignedTo ? [{ login: wi.assignedTo }] : [],
|
|
95
150
|
}));
|
|
96
|
-
//
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
151
|
+
// Minimal filter: must have squad or squad:* label (agent decides the rest)
|
|
152
|
+
const eligible = findExecutableIssues(context.roster, null, issues);
|
|
153
|
+
vlog.log(`Execute: ${issues.length} total issues, ${eligible.length} eligible`);
|
|
154
|
+
for (const issue of eligible.slice(0, 5)) {
|
|
155
|
+
const labels = issue.labels.map(l => l.name).join(', ');
|
|
156
|
+
vlog.log(` → #${issue.number}: "${issue.title}" [${labels}]`);
|
|
157
|
+
}
|
|
158
|
+
if (eligible.length === 0) {
|
|
159
|
+
return { success: true, summary: 'no squad-labeled issues found' };
|
|
104
160
|
}
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const failed = results.length - succeeded;
|
|
161
|
+
// Single agent invocation with all issues — agent reads ralph-instructions.md
|
|
162
|
+
const result = await executeAll(eligible, context, timeout);
|
|
108
163
|
return {
|
|
109
|
-
success:
|
|
110
|
-
summary:
|
|
111
|
-
|
|
164
|
+
success: result.success,
|
|
165
|
+
summary: result.success
|
|
166
|
+
? `agent dispatched with ${eligible.length} issues`
|
|
167
|
+
: `agent failed: ${result.error}`,
|
|
168
|
+
data: { dispatched: eligible.length, success: result.success },
|
|
112
169
|
};
|
|
113
170
|
}
|
|
114
171
|
catch (e) {
|