@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.
Files changed (110) hide show
  1. package/dist/cli/commands/doctor.d.ts +2 -0
  2. package/dist/cli/commands/doctor.d.ts.map +1 -1
  3. package/dist/cli/commands/doctor.js +27 -5
  4. package/dist/cli/commands/doctor.js.map +1 -1
  5. package/dist/cli/commands/loop.d.ts +51 -0
  6. package/dist/cli/commands/loop.d.ts.map +1 -0
  7. package/dist/cli/commands/loop.js +352 -0
  8. package/dist/cli/commands/loop.js.map +1 -0
  9. package/dist/cli/commands/watch/capabilities/budget-check.d.ts +29 -0
  10. package/dist/cli/commands/watch/capabilities/budget-check.d.ts.map +1 -0
  11. package/dist/cli/commands/watch/capabilities/budget-check.js +38 -0
  12. package/dist/cli/commands/watch/capabilities/budget-check.js.map +1 -0
  13. package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts +52 -0
  14. package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts.map +1 -0
  15. package/dist/cli/commands/watch/capabilities/circuit-breaker.js +152 -0
  16. package/dist/cli/commands/watch/capabilities/circuit-breaker.js.map +1 -0
  17. package/dist/cli/commands/watch/capabilities/cleanup.d.ts +18 -0
  18. package/dist/cli/commands/watch/capabilities/cleanup.d.ts.map +1 -0
  19. package/dist/cli/commands/watch/capabilities/cleanup.js +135 -0
  20. package/dist/cli/commands/watch/capabilities/cleanup.js.map +1 -0
  21. package/dist/cli/commands/watch/capabilities/execute.d.ts +15 -3
  22. package/dist/cli/commands/watch/capabilities/execute.d.ts.map +1 -1
  23. package/dist/cli/commands/watch/capabilities/execute.js +108 -51
  24. package/dist/cli/commands/watch/capabilities/execute.js.map +1 -1
  25. package/dist/cli/commands/watch/capabilities/fleet-dispatch.d.ts +20 -0
  26. package/dist/cli/commands/watch/capabilities/fleet-dispatch.d.ts.map +1 -0
  27. package/dist/cli/commands/watch/capabilities/fleet-dispatch.js +144 -0
  28. package/dist/cli/commands/watch/capabilities/fleet-dispatch.js.map +1 -0
  29. package/dist/cli/commands/watch/capabilities/health-check.d.ts +29 -0
  30. package/dist/cli/commands/watch/capabilities/health-check.d.ts.map +1 -0
  31. package/dist/cli/commands/watch/capabilities/health-check.js +139 -0
  32. package/dist/cli/commands/watch/capabilities/health-check.js.map +1 -0
  33. package/dist/cli/commands/watch/capabilities/heartbeat.d.ts +48 -0
  34. package/dist/cli/commands/watch/capabilities/heartbeat.d.ts.map +1 -0
  35. package/dist/cli/commands/watch/capabilities/heartbeat.js +115 -0
  36. package/dist/cli/commands/watch/capabilities/heartbeat.js.map +1 -0
  37. package/dist/cli/commands/watch/capabilities/index.d.ts.map +1 -1
  38. package/dist/cli/commands/watch/capabilities/index.js +4 -0
  39. package/dist/cli/commands/watch/capabilities/index.js.map +1 -1
  40. package/dist/cli/commands/watch/capabilities/lockfile.d.ts +30 -0
  41. package/dist/cli/commands/watch/capabilities/lockfile.d.ts.map +1 -0
  42. package/dist/cli/commands/watch/capabilities/lockfile.js +100 -0
  43. package/dist/cli/commands/watch/capabilities/lockfile.js.map +1 -0
  44. package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts +30 -0
  45. package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts.map +1 -0
  46. package/dist/cli/commands/watch/capabilities/machine-capabilities.js +103 -0
  47. package/dist/cli/commands/watch/capabilities/machine-capabilities.js.map +1 -0
  48. package/dist/cli/commands/watch/capabilities/post-failure.d.ts +19 -0
  49. package/dist/cli/commands/watch/capabilities/post-failure.d.ts.map +1 -0
  50. package/dist/cli/commands/watch/capabilities/post-failure.js +58 -0
  51. package/dist/cli/commands/watch/capabilities/post-failure.js.map +1 -0
  52. package/dist/cli/commands/watch/capabilities/priority.d.ts +59 -0
  53. package/dist/cli/commands/watch/capabilities/priority.d.ts.map +1 -0
  54. package/dist/cli/commands/watch/capabilities/priority.js +101 -0
  55. package/dist/cli/commands/watch/capabilities/priority.js.map +1 -0
  56. package/dist/cli/commands/watch/capabilities/rate-pool.d.ts +67 -0
  57. package/dist/cli/commands/watch/capabilities/rate-pool.d.ts.map +1 -0
  58. package/dist/cli/commands/watch/capabilities/rate-pool.js +187 -0
  59. package/dist/cli/commands/watch/capabilities/rate-pool.js.map +1 -0
  60. package/dist/cli/commands/watch/capabilities/self-pull.d.ts +4 -1
  61. package/dist/cli/commands/watch/capabilities/self-pull.d.ts.map +1 -1
  62. package/dist/cli/commands/watch/capabilities/self-pull.js +53 -5
  63. package/dist/cli/commands/watch/capabilities/self-pull.js.map +1 -1
  64. package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts +23 -0
  65. package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts.map +1 -0
  66. package/dist/cli/commands/watch/capabilities/stale-reclaim.js +87 -0
  67. package/dist/cli/commands/watch/capabilities/stale-reclaim.js.map +1 -0
  68. package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts +29 -0
  69. package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts.map +1 -0
  70. package/dist/cli/commands/watch/capabilities/webhook-alerts.js +114 -0
  71. package/dist/cli/commands/watch/capabilities/webhook-alerts.js.map +1 -0
  72. package/dist/cli/commands/watch/config.d.ts +18 -0
  73. package/dist/cli/commands/watch/config.d.ts.map +1 -1
  74. package/dist/cli/commands/watch/config.js +20 -1
  75. package/dist/cli/commands/watch/config.js.map +1 -1
  76. package/dist/cli/commands/watch/health.d.ts +41 -0
  77. package/dist/cli/commands/watch/health.d.ts.map +1 -0
  78. package/dist/cli/commands/watch/health.js +141 -0
  79. package/dist/cli/commands/watch/health.js.map +1 -0
  80. package/dist/cli/commands/watch/index.d.ts +9 -2
  81. package/dist/cli/commands/watch/index.d.ts.map +1 -1
  82. package/dist/cli/commands/watch/index.js +115 -8
  83. package/dist/cli/commands/watch/index.js.map +1 -1
  84. package/dist/cli/commands/watch/types.d.ts +2 -0
  85. package/dist/cli/commands/watch/types.d.ts.map +1 -1
  86. package/dist/cli/commands/watch/verbose.d.ts +12 -0
  87. package/dist/cli/commands/watch/verbose.d.ts.map +1 -0
  88. package/dist/cli/commands/watch/verbose.js +28 -0
  89. package/dist/cli/commands/watch/verbose.js.map +1 -0
  90. package/dist/cli/core/detect-squad-dir.d.ts.map +1 -1
  91. package/dist/cli/core/detect-squad-dir.js +9 -12
  92. package/dist/cli/core/detect-squad-dir.js.map +1 -1
  93. package/dist/cli/core/nap.d.ts.map +1 -1
  94. package/dist/cli/core/nap.js +9 -4
  95. package/dist/cli/core/nap.js.map +1 -1
  96. package/dist/cli/core/upgrade.d.ts +18 -0
  97. package/dist/cli/core/upgrade.d.ts.map +1 -1
  98. package/dist/cli/core/upgrade.js +119 -0
  99. package/dist/cli/core/upgrade.js.map +1 -1
  100. package/dist/cli-entry.js +208 -12
  101. package/dist/cli-entry.js.map +1 -1
  102. package/package.json +5 -1
  103. package/templates/ceremonies.md +28 -0
  104. package/templates/issue-lifecycle.md +2 -1
  105. package/templates/loop.md +46 -0
  106. package/templates/ralph-triage.js +3 -1
  107. package/templates/scribe-charter.md +20 -1
  108. package/templates/squad.agent.md.template +9 -7
  109. package/templates/workflows/squad-triage.yml +4 -2
  110. 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
- /** Find issues eligible for autonomous execution. */
19
- export declare function findExecutableIssues(roster: Array<{
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
- }>, capabilities: MachineCapabilities | null, issues: ExecutableWorkItem[]): ExecutableWorkItem[];
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;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAErF,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;AAiCD,qDAAqD;AACrD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,EACnE,YAAY,EAAE,mBAAmB,GAAG,IAAI,EACxC,MAAM,EAAE,kBAAkB,EAAE,GAC3B,kBAAkB,EAAE,CAStB;AAsCD,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;CAyChE"}
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
- /** Labels that block autonomous execution. */
6
- const BLOCKED_LABELS = new Set([
7
- 'status:blocked',
8
- 'status:waiting-external',
9
- 'status:postponed',
10
- 'status:scheduled',
11
- 'status:needs-action',
12
- 'status:needs-decision',
13
- 'status:needs-review',
14
- 'pending-user',
15
- 'do-not-merge',
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
- /** Find issues eligible for autonomous execution. */
32
- export function findExecutableIssues(roster, capabilities, issues) {
33
- const memberLabels = new Set(roster.map(m => m.label));
34
- return issues.filter(issue => {
35
- const labels = issue.labels.map(l => l.name);
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
- /** Spawn agent for a single issue. */
46
- async function executeOne(issue, context, timeoutMs) {
47
- // Claim the issue
48
- try {
49
- await context.adapter.addTag(issue.number, 'status:in-progress');
50
- }
51
- catch { /* best-effort */ }
52
- try {
53
- await context.adapter.addComment(issue.number, '🤖 Ralph: starting autonomous work on this issue.');
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
- catch { /* best-effort */ }
56
- const prompt = `Work on issue #${issue.number}: ${issue.title}. Read the issue body for full details.`;
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
- // Filter by capabilities
97
- const { filterByCapabilities, loadCapabilities } = await import('@bradygaster/squad-sdk/ralph/capabilities');
98
- const capabilities = await loadCapabilities(context.teamRoot);
99
- const { handled } = filterByCapabilities(issues, capabilities);
100
- const executable = findExecutableIssues(context.roster, capabilities, handled);
101
- const batch = executable.slice(0, maxConcurrent);
102
- if (batch.length === 0) {
103
- return { success: true, summary: 'no executable issues' };
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
- const results = await Promise.all(batch.map(issue => executeOne(issue, context, timeout)));
106
- const succeeded = results.filter(r => r.success).length;
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: true,
110
- summary: `${succeeded} executed, ${failed} failed`,
111
- data: { executed: succeeded, failed },
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) {