@gempack/squad-mcp 0.8.2 → 0.10.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +7 -4
- package/CHANGELOG.md +92 -0
- package/README.md +41 -35
- package/agents/senior-debugger.md +85 -0
- package/commands/debug.md +22 -0
- package/commands/stats.md +22 -0
- package/dist/config/ownership-matrix.d.ts +1 -1
- package/dist/config/ownership-matrix.js +16 -0
- package/dist/config/ownership-matrix.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/agent-loader.js +1 -0
- package/dist/resources/agent-loader.js.map +1 -1
- package/dist/runs/aggregate.d.ts +166 -0
- package/dist/runs/aggregate.js +378 -0
- package/dist/runs/aggregate.js.map +1 -0
- package/dist/runs/store.d.ts +328 -0
- package/dist/runs/store.js +406 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/tools/list-runs.d.ts +52 -0
- package/dist/tools/list-runs.js +142 -0
- package/dist/tools/list-runs.js.map +1 -0
- package/dist/tools/record-run.d.ts +202 -0
- package/dist/tools/record-run.js +124 -0
- package/dist/tools/record-run.js.map +1 -0
- package/dist/tools/registry.js +15 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/util/path-safety.d.ts +36 -0
- package/dist/util/path-safety.js +76 -0
- package/dist/util/path-safety.js.map +1 -1
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +70 -7
- package/skills/debug/SKILL.md +345 -0
- package/skills/question/SKILL.md +73 -1
- package/skills/squad/SKILL.md +83 -0
- package/skills/stats/SKILL.md +189 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { AGENT_NAMES_TUPLE } from "../config/ownership-matrix.js";
|
|
5
|
+
import { SquadError } from "../errors.js";
|
|
6
|
+
import { ensureRelativeInsideRoot } from "../util/path-safety.js";
|
|
7
|
+
import { withFileLock } from "../util/file-lock.js";
|
|
8
|
+
import { logger } from "../observability/logger.js";
|
|
9
|
+
import { SafeString } from "../tools/_shared/schemas.js";
|
|
10
|
+
/**
|
|
11
|
+
* SQUAD RUNS STORE — telemetry journal for skill invocations. As of v0.10.0
|
|
12
|
+
* the legitimate writers are the squad skill (`/squad:implement` and
|
|
13
|
+
* `/squad:review`, invocations `implement | review | task`) and the debug
|
|
14
|
+
* skill (`/squad:debug`, invocation `debug`). Each writer follows the same
|
|
15
|
+
* two-phase contract: one row at start (`in_flight`) and one at end
|
|
16
|
+
* (`completed | aborted`), paired by id. Mirrored line-for-line after
|
|
17
|
+
* `src/learning/store.ts` — same lock + quarantine + mtime cache + atomic-
|
|
18
|
+
* append-under-PIPE_BUF discipline.
|
|
19
|
+
*
|
|
20
|
+
* Plan v4 (cycle 2 advisory consensus) explicit decisions:
|
|
21
|
+
*
|
|
22
|
+
* - NO multi-row partial fallback. If `JSON.stringify(record)` exceeds
|
|
23
|
+
* MAX_RECORD_BYTES the store rejects with `RECORD_TOO_LARGE` rather
|
|
24
|
+
* than splitting into continuation rows. Five advisors converged on
|
|
25
|
+
* "splitting erodes the one-row-per-record JSONL invariant and
|
|
26
|
+
* reopens parsing ambiguities"; rejection puts the burden on the
|
|
27
|
+
* caller to cap their `mode_warning.message` (already capped 512B)
|
|
28
|
+
* or shorten their inputs.
|
|
29
|
+
*
|
|
30
|
+
* - File mode 0o600 (user-only), directory mode 0o700. The journal
|
|
31
|
+
* contains commit refs and prompt-length signals that can leak
|
|
32
|
+
* business context (branch names like `feat/acme-acquisition`); on
|
|
33
|
+
* shared workstations world-readable 0o644 would expose them to
|
|
34
|
+
* co-tenants.
|
|
35
|
+
*
|
|
36
|
+
* - Single-writer contract: the squad skill (`skills/squad/SKILL.md`)
|
|
37
|
+
* AND the debug skill (`skills/debug/SKILL.md`) are the only legitimate
|
|
38
|
+
* callers of `appendRun`. `apply_consolidation_rules` and other server-
|
|
39
|
+
* side code MUST NOT emit terminal rows; doing so breaks the two-phase
|
|
40
|
+
* `(in_flight, completed)` pair-by-id invariant.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Hard cap per JSONL entry so a single line fits in POSIX PIPE_BUF
|
|
44
|
+
* (4096 bytes) and `fs.appendFile` remains atomic w.r.t. concurrent
|
|
45
|
+
* appenders. Length includes serialised JSON + trailing newline.
|
|
46
|
+
*
|
|
47
|
+
* Realistic finalization row with 9 agents + capped mode_warning.message
|
|
48
|
+
* lands around 1.5-2 KB — well under the limit. Oversize is a hard error,
|
|
49
|
+
* not a soft truncation (see RECORD_TOO_LARGE in errors.ts).
|
|
50
|
+
*/
|
|
51
|
+
export const MAX_RECORD_BYTES = 4_000;
|
|
52
|
+
/**
|
|
53
|
+
* Default location for the JSONL file, relative to workspace_root.
|
|
54
|
+
* Defaults are gitignored at the v0.9.0 release — the journal contains
|
|
55
|
+
* local-only operational telemetry; users opting into team-wide sharing
|
|
56
|
+
* remove `.squad/runs.jsonl` from their `.gitignore` deliberately.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_RUNS_PATH = ".squad/runs.jsonl";
|
|
59
|
+
/**
|
|
60
|
+
* Severity tally compacted into a single sortable number. The cycle-1
|
|
61
|
+
* design carried `{ Blocker, Major, Minor, Suggestion }` per agent
|
|
62
|
+
* (~30 bytes / agent of JSON overhead); cycle 2 architects + dev flagged
|
|
63
|
+
* this as PIPE_BUF-budget waste on 9-agent runs. Collapsed to one number
|
|
64
|
+
* with positional digits: B*1000 + M*100 + m*10 + s. Inverse decode in
|
|
65
|
+
* aggregate.ts. Safe up to 9 of each severity per agent (more than that
|
|
66
|
+
* is itself a signal something went sideways).
|
|
67
|
+
*/
|
|
68
|
+
function severityScore(counts) {
|
|
69
|
+
return counts.Blocker * 1000 + counts.Major * 100 + counts.Minor * 10 + counts.Suggestion;
|
|
70
|
+
}
|
|
71
|
+
/** Inverse of `severityScore`. Used by aggregate.ts. */
|
|
72
|
+
export function decodeSeverityScore(n) {
|
|
73
|
+
return {
|
|
74
|
+
Blocker: Math.floor(n / 1000),
|
|
75
|
+
Major: Math.floor((n % 1000) / 100),
|
|
76
|
+
Minor: Math.floor((n % 100) / 10),
|
|
77
|
+
Suggestion: n % 10,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Public re-export so callers can build records without re-implementing the encoding. */
|
|
81
|
+
export { severityScore };
|
|
82
|
+
/**
|
|
83
|
+
* Strip C0 (`\x00`-`\x1F` except `\t`), C1 (`\x7F`-`\x9F`), and ESC (`\x1B`)
|
|
84
|
+
* from a string before it lands on disk. This is the WRITER-side mirror of
|
|
85
|
+
* the renderer's `aggregate.stripControlChars` (security #5).
|
|
86
|
+
*
|
|
87
|
+
* Defense in depth: render-time sanitisation protects users running
|
|
88
|
+
* `/squad:stats`; writer-side sanitisation also protects users running
|
|
89
|
+
* `cat .squad/runs.jsonl` or any other tool that bypasses the aggregator.
|
|
90
|
+
* The regex is intentionally duplicated (3 lines, no runtime cost) rather
|
|
91
|
+
* than importing from `aggregate.ts` because that would invert the natural
|
|
92
|
+
* dep direction (store is foundational; aggregate consumes store).
|
|
93
|
+
*/
|
|
94
|
+
function stripWriterControlChars(s) {
|
|
95
|
+
return s.replace(/[\x00-\x08\x0A-\x1F\x7F-\x9F]/g, "");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Canonical tuple of accepted journal invocations. Single source of truth.
|
|
99
|
+
*
|
|
100
|
+
* Why a tuple, not just a Zod enum: the same set is consumed by FIVE call
|
|
101
|
+
* sites — this store's `InvocationEnum`, the tool boundary at
|
|
102
|
+
* `src/tools/record-run.ts`, the filter schema at `src/tools/list-runs.ts`,
|
|
103
|
+
* the Record literal in the aggregate output type, and the `invocation_counts`
|
|
104
|
+
* initialiser in `src/runs/aggregate.ts`. Exporting one tuple makes "add a
|
|
105
|
+
* new invocation" a single-line change instead of five-sites-must-stay-in-
|
|
106
|
+
* sync. Pattern parallels `AGENT_NAMES_TUPLE` in `src/config/ownership-matrix.ts`.
|
|
107
|
+
*
|
|
108
|
+
* `as const` (readonly tuple) is what Zod's `z.enum` requires.
|
|
109
|
+
*/
|
|
110
|
+
export const INVOCATION_VALUES = [
|
|
111
|
+
"implement",
|
|
112
|
+
"review",
|
|
113
|
+
"task",
|
|
114
|
+
"question",
|
|
115
|
+
"brainstorm",
|
|
116
|
+
"debug",
|
|
117
|
+
];
|
|
118
|
+
const InvocationEnum = z.enum(INVOCATION_VALUES);
|
|
119
|
+
const ModeEnum = z.enum(["quick", "normal", "deep"]);
|
|
120
|
+
const ModeSourceEnum = z.enum(["user", "auto"]);
|
|
121
|
+
const StatusEnum = z.enum(["in_flight", "completed", "aborted"]);
|
|
122
|
+
const WorkTypeEnum = z.enum([
|
|
123
|
+
"Feature",
|
|
124
|
+
"Bug Fix",
|
|
125
|
+
"Refactor",
|
|
126
|
+
"Performance",
|
|
127
|
+
"Security",
|
|
128
|
+
"Business Rule",
|
|
129
|
+
]);
|
|
130
|
+
const VerdictEnum = z.enum(["APPROVED", "CHANGES_REQUIRED", "REJECTED"]);
|
|
131
|
+
const ModelEnum = z.enum(["haiku", "sonnet", "opus", "inherit"]);
|
|
132
|
+
const GitRefSchema = z
|
|
133
|
+
.object({
|
|
134
|
+
kind: z.enum(["head", "diff_base", "pr_head"]),
|
|
135
|
+
value: SafeString(200),
|
|
136
|
+
})
|
|
137
|
+
.nullable();
|
|
138
|
+
/**
|
|
139
|
+
* Per-agent dispatch metrics captured by the squad skill orchestrator.
|
|
140
|
+
*
|
|
141
|
+
* - `batch_duration_ms` (renamed from `duration_ms` in v0.9.0): wall-clock
|
|
142
|
+
* from this agent's Task() dispatch to its result. Note that advisors in
|
|
143
|
+
* a parallel batch overlap; this is "round-trip latency for this dispatch"
|
|
144
|
+
* not "exclusive time spent on this agent's work". Reflected in the
|
|
145
|
+
* /squad:stats output label.
|
|
146
|
+
*
|
|
147
|
+
* - `prompt_chars` / `response_chars` (renamed from input/output_chars in
|
|
148
|
+
* v0.9.0): orchestrator-visible character counts of the dispatch prompt
|
|
149
|
+
* and the agent's returned string. EXCLUDES the agent's own internal
|
|
150
|
+
* tool_use roundtrips (file reads, sub-dispatches like code-explorer).
|
|
151
|
+
* For agents that read heavily, the recorded chars are a substantial
|
|
152
|
+
* underestimate — documented in `est_tokens_method` and rendered in the
|
|
153
|
+
* stats panel disclaimer.
|
|
154
|
+
*
|
|
155
|
+
* - `severity_score`: encoded findings tally (see severityScore()).
|
|
156
|
+
*/
|
|
157
|
+
const AgentMetricsSchema = z.object({
|
|
158
|
+
name: z.enum(AGENT_NAMES_TUPLE),
|
|
159
|
+
model: ModelEnum,
|
|
160
|
+
score: z.number().int().min(0).max(100).nullable(),
|
|
161
|
+
severity_score: z.number().int().min(0).max(9999).nullable(),
|
|
162
|
+
batch_duration_ms: z.number().int().nonnegative().finite(),
|
|
163
|
+
prompt_chars: z.number().int().nonnegative().finite(),
|
|
164
|
+
response_chars: z.number().int().nonnegative().finite(),
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* RunRecord schema_version 1. PUBLIC STABLE CONTRACT from v0.9.0 — readers
|
|
168
|
+
* (the `list_runs` MCP tool, the `/squad:stats` skill) key on
|
|
169
|
+
* `schema_version` and quarantine unknown versions rather than failing.
|
|
170
|
+
*
|
|
171
|
+
* Discriminated by `status`:
|
|
172
|
+
* - `in_flight` rows carry only the Phase-1-known fields (skill knows what
|
|
173
|
+
* it's about to do; verdict/scores are still pending).
|
|
174
|
+
* - `completed | aborted` rows carry full metrics + verdict.
|
|
175
|
+
*
|
|
176
|
+
* For ergonomics under Zod we keep finalisation fields optional on the base
|
|
177
|
+
* schema rather than splitting into two schemas; the writer validates the
|
|
178
|
+
* appropriate subset at the call site (`appendRun` vs `finalizeRun`).
|
|
179
|
+
*/
|
|
180
|
+
const runRecordSchema = z.object({
|
|
181
|
+
schema_version: z.literal(1),
|
|
182
|
+
id: SafeString(40),
|
|
183
|
+
status: StatusEnum,
|
|
184
|
+
started_at: SafeString(40),
|
|
185
|
+
completed_at: SafeString(40).optional(),
|
|
186
|
+
duration_ms: z.number().int().nonnegative().finite().optional(),
|
|
187
|
+
invocation: InvocationEnum,
|
|
188
|
+
mode: ModeEnum,
|
|
189
|
+
mode_source: ModeSourceEnum,
|
|
190
|
+
work_type: WorkTypeEnum.optional(),
|
|
191
|
+
git_ref: GitRefSchema,
|
|
192
|
+
files_count: z.number().int().nonnegative().finite(),
|
|
193
|
+
agents: z.array(AgentMetricsSchema).max(20),
|
|
194
|
+
verdict: VerdictEnum.nullable().optional(),
|
|
195
|
+
weighted_score: z.number().min(0).max(100).nullable().optional(),
|
|
196
|
+
est_tokens_method: z.literal("chars-div-3.5"),
|
|
197
|
+
mode_warning: z
|
|
198
|
+
.object({
|
|
199
|
+
code: SafeString(64),
|
|
200
|
+
message: SafeString(512),
|
|
201
|
+
})
|
|
202
|
+
.nullable()
|
|
203
|
+
.optional(),
|
|
204
|
+
});
|
|
205
|
+
export { runRecordSchema, WorkTypeEnum };
|
|
206
|
+
const cache = new Map();
|
|
207
|
+
/** Test-only: clear the per-process cache. Production code MUST NOT call this. */
|
|
208
|
+
export function __resetRunsStoreCacheForTests() {
|
|
209
|
+
cache.clear();
|
|
210
|
+
}
|
|
211
|
+
function resolveRunsFile(workspaceRoot, configuredPath) {
|
|
212
|
+
const rel = configuredPath ?? DEFAULT_RUNS_PATH;
|
|
213
|
+
if (configuredPath !== undefined) {
|
|
214
|
+
ensureRelativeInsideRoot(workspaceRoot, rel, "runs.path");
|
|
215
|
+
}
|
|
216
|
+
return path.resolve(workspaceRoot, rel);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Generate a fresh run id. Date.now() base36 prefix + 6 chars from
|
|
220
|
+
* [a-z0-9] (36^6 = 2.18B unique values per millisecond — collision
|
|
221
|
+
* chance is effectively zero across realistic concurrent writers in
|
|
222
|
+
* the same ms).
|
|
223
|
+
*/
|
|
224
|
+
export function generateRunId() {
|
|
225
|
+
const ts = Date.now().toString(36);
|
|
226
|
+
let suffix = "";
|
|
227
|
+
const ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
228
|
+
for (let i = 0; i < 6; i++) {
|
|
229
|
+
suffix += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
|
|
230
|
+
}
|
|
231
|
+
return `${ts}-${suffix}`;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Read all run records from the JSONL file. Returns `[]` if the file does
|
|
235
|
+
* not exist (fresh repo, first run). Corrupt lines are quarantined to a
|
|
236
|
+
* timestamped sibling file and logged once; the surviving entries return
|
|
237
|
+
* in append order.
|
|
238
|
+
*
|
|
239
|
+
* Unknown `schema_version` rows are quarantined too — readers must NEVER
|
|
240
|
+
* silently include rows they don't understand. The quarantine file is
|
|
241
|
+
* `.squad/runs.jsonl.corrupt-<ts>.jsonl` alongside the source.
|
|
242
|
+
*/
|
|
243
|
+
export async function readRuns(workspaceRoot, options = {}) {
|
|
244
|
+
const filePath = resolveRunsFile(workspaceRoot, options.configuredPath);
|
|
245
|
+
const absRoot = path.resolve(workspaceRoot);
|
|
246
|
+
let stat;
|
|
247
|
+
try {
|
|
248
|
+
stat = await fs.stat(filePath);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
if (!stat.isFile())
|
|
254
|
+
return [];
|
|
255
|
+
const cached = cache.get(absRoot);
|
|
256
|
+
if (cached &&
|
|
257
|
+
cached.filePath === filePath &&
|
|
258
|
+
cached.mtimeMs === stat.mtimeMs &&
|
|
259
|
+
cached.size === stat.size) {
|
|
260
|
+
return cached.entries;
|
|
261
|
+
}
|
|
262
|
+
let raw;
|
|
263
|
+
try {
|
|
264
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
throw new SquadError("CONFIG_READ_FAILED", `failed to read runs file ${filePath}: ${err.message}`, { source: filePath });
|
|
268
|
+
}
|
|
269
|
+
const lines = raw.split(/\r?\n/);
|
|
270
|
+
const entries = [];
|
|
271
|
+
const corruptLines = [];
|
|
272
|
+
let skippedUnknownVersion = 0;
|
|
273
|
+
let lineNo = 0;
|
|
274
|
+
for (const line of lines) {
|
|
275
|
+
lineNo++;
|
|
276
|
+
const trimmed = line.trim();
|
|
277
|
+
if (trimmed === "")
|
|
278
|
+
continue;
|
|
279
|
+
let parsed;
|
|
280
|
+
try {
|
|
281
|
+
parsed = JSON.parse(trimmed);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
corruptLines.push({
|
|
285
|
+
line: lineNo,
|
|
286
|
+
raw: trimmed,
|
|
287
|
+
reason: `invalid JSON: ${err.message}`,
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
// Schema_version gate: skip+log instead of throwing. A future v2 writer
|
|
292
|
+
// would otherwise brick v1 readers; this lets a heterogeneous-version
|
|
293
|
+
// journal be partially read by older clients (architect A-6 + dev #11).
|
|
294
|
+
if (typeof parsed === "object" &&
|
|
295
|
+
parsed !== null &&
|
|
296
|
+
"schema_version" in parsed &&
|
|
297
|
+
parsed.schema_version !== 1) {
|
|
298
|
+
skippedUnknownVersion++;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const validated = runRecordSchema.safeParse(parsed);
|
|
302
|
+
if (!validated.success) {
|
|
303
|
+
corruptLines.push({
|
|
304
|
+
line: lineNo,
|
|
305
|
+
raw: trimmed,
|
|
306
|
+
reason: `schema violation: ${validated.error.message}`,
|
|
307
|
+
});
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
entries.push(validated.data);
|
|
311
|
+
}
|
|
312
|
+
if (skippedUnknownVersion > 0) {
|
|
313
|
+
logger.warn("runs: skipped rows with unknown schema_version", {
|
|
314
|
+
details: { file: filePath, count: skippedUnknownVersion },
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (corruptLines.length > 0) {
|
|
318
|
+
const quarantinePath = `${filePath}.corrupt-${Date.now()}.jsonl`;
|
|
319
|
+
try {
|
|
320
|
+
const body = corruptLines.map((c) => `# line ${c.line}: ${c.reason}\n${c.raw}\n`).join("");
|
|
321
|
+
// Write quarantine with same restricted mode as the source.
|
|
322
|
+
await fs.writeFile(quarantinePath, body, { encoding: "utf8", mode: 0o600 });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Diagnostic, not load-bearing.
|
|
326
|
+
}
|
|
327
|
+
logger.warn("runs: corrupt lines quarantined", {
|
|
328
|
+
details: {
|
|
329
|
+
file: filePath,
|
|
330
|
+
quarantine: quarantinePath,
|
|
331
|
+
count: corruptLines.length,
|
|
332
|
+
lines: corruptLines.map((c) => c.line),
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
cache.set(absRoot, { mtimeMs: stat.mtimeMs, size: stat.size, filePath, entries });
|
|
337
|
+
return entries;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Append one RunRecord. Validates against Zod, then enforces
|
|
341
|
+
* MAX_RECORD_BYTES (post-serialisation) before acquiring the file lock.
|
|
342
|
+
* Oversize records throw `RECORD_TOO_LARGE` — no silent split, no soft
|
|
343
|
+
* truncation. The caller (the squad skill) is responsible for keeping
|
|
344
|
+
* `mode_warning.message` capped and the agent list short enough that
|
|
345
|
+
* realistic records stay well under the cap.
|
|
346
|
+
*/
|
|
347
|
+
export async function appendRun(workspaceRoot, record, options = {}) {
|
|
348
|
+
const validated = runRecordSchema.safeParse(record);
|
|
349
|
+
if (!validated.success) {
|
|
350
|
+
throw new SquadError("INVALID_INPUT", `run record schema violation: ${validated.error.message}`, { issues: validated.error.issues.length });
|
|
351
|
+
}
|
|
352
|
+
// Defense in depth: strip control chars from `mode_warning.message` at the
|
|
353
|
+
// writer too. The aggregator's `stripControlChars` already runs at render
|
|
354
|
+
// (security #5 in v0.9.0), but direct file inspection (`cat`, `less`) sees
|
|
355
|
+
// raw bytes. Sanitising here keeps the recorded data terminal-safe for any
|
|
356
|
+
// future viewer that bypasses the aggregator. We strip only this one field
|
|
357
|
+
// because it's the documented free-form leak surface — other fields are
|
|
358
|
+
// schema-bounded to safer shapes (enums, ISO strings, numbers, sha refs).
|
|
359
|
+
const sanitized = validated.data.mode_warning != null
|
|
360
|
+
? {
|
|
361
|
+
...validated.data,
|
|
362
|
+
mode_warning: {
|
|
363
|
+
...validated.data.mode_warning,
|
|
364
|
+
message: stripWriterControlChars(validated.data.mode_warning.message),
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
: validated.data;
|
|
368
|
+
const line = JSON.stringify(sanitized) + "\n";
|
|
369
|
+
const byteLen = Buffer.byteLength(line, "utf8");
|
|
370
|
+
if (byteLen > MAX_RECORD_BYTES) {
|
|
371
|
+
throw new SquadError("RECORD_TOO_LARGE", `run record exceeds MAX_RECORD_BYTES (${byteLen} > ${MAX_RECORD_BYTES}); ` +
|
|
372
|
+
`cap mode_warning.message or shorten inputs`, { byteLen, max: MAX_RECORD_BYTES, id: validated.data.id });
|
|
373
|
+
}
|
|
374
|
+
const filePath = resolveRunsFile(workspaceRoot, options.configuredPath);
|
|
375
|
+
const dir = path.dirname(filePath);
|
|
376
|
+
// Directory mode 0o700 — user-only. Subsequent runs inherit the existing
|
|
377
|
+
// mode if the dir already exists (mkdir recursive is idempotent on mode).
|
|
378
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
379
|
+
// Cross-process lock around the append. The lock file lives in the same
|
|
380
|
+
// directory; file-lock.ts cleans it up in a finally.
|
|
381
|
+
await withFileLock(filePath, async () => {
|
|
382
|
+
// Create the journal with explicit mode 0o600 on first write. fs.open
|
|
383
|
+
// honours mode only when O_CREAT applies (i.e. the file is being
|
|
384
|
+
// created); subsequent appends ride the existing mode.
|
|
385
|
+
const fh = await fs.open(filePath, "a", 0o600);
|
|
386
|
+
try {
|
|
387
|
+
await fh.writeFile(line, "utf8");
|
|
388
|
+
}
|
|
389
|
+
finally {
|
|
390
|
+
await fh.close();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
// Invalidate cache so the next readRuns picks up the append.
|
|
394
|
+
const absRoot = path.resolve(workspaceRoot);
|
|
395
|
+
cache.delete(absRoot);
|
|
396
|
+
logger.info("run recorded", {
|
|
397
|
+
details: {
|
|
398
|
+
file: filePath,
|
|
399
|
+
id: validated.data.id,
|
|
400
|
+
status: validated.data.status,
|
|
401
|
+
invocation: validated.data.invocation,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
return { filePath, record: validated.data };
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/runs/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ToolDef } from "./registry.js";
|
|
3
|
+
import { type AgentName } from "../config/ownership-matrix.js";
|
|
4
|
+
declare const schema: z.ZodObject<{
|
|
5
|
+
workspace_root: z.ZodEffects<z.ZodString, string, string>;
|
|
6
|
+
/** ISO 8601 lower bound on `started_at`. */
|
|
7
|
+
since: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
8
|
+
/** Cap output to the most recent N folded runs. */
|
|
9
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
/** Restrict to runs that included this agent in any role. */
|
|
11
|
+
agent: z.ZodOptional<z.ZodEnum<[AgentName, ...AgentName[]]>>;
|
|
12
|
+
/** Restrict to a single terminal verdict. */
|
|
13
|
+
verdict: z.ZodOptional<z.ZodEnum<["APPROVED", "CHANGES_REQUIRED", "REJECTED"]>>;
|
|
14
|
+
/** Restrict to a single dispatch mode. */
|
|
15
|
+
mode: z.ZodOptional<z.ZodEnum<["quick", "normal", "deep"]>>;
|
|
16
|
+
/** Restrict to a single invocation kind. */
|
|
17
|
+
invocation: z.ZodOptional<z.ZodEnum<["implement", "review", "task", "question", "brainstorm", "debug"]>>;
|
|
18
|
+
/** Restrict to a single work type. */
|
|
19
|
+
work_type: z.ZodOptional<z.ZodEnum<["Feature", "Bug Fix", "Refactor", "Performance", "Security", "Business Rule"]>>;
|
|
20
|
+
/**
|
|
21
|
+
* If true, return aggregate views (outcomes + health + trend) instead of
|
|
22
|
+
* the folded raw rows. The /squad:stats skill uses this; CLI inspections
|
|
23
|
+
* default to the raw list.
|
|
24
|
+
*/
|
|
25
|
+
aggregate: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
/** Days of trend sparkline data to compute when aggregate=true. Default 14. */
|
|
27
|
+
trend_days: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
workspace_root: string;
|
|
30
|
+
work_type?: "Feature" | "Bug Fix" | "Refactor" | "Performance" | "Security" | "Business Rule" | undefined;
|
|
31
|
+
agent?: AgentName | undefined;
|
|
32
|
+
mode?: "quick" | "normal" | "deep" | undefined;
|
|
33
|
+
limit?: number | undefined;
|
|
34
|
+
invocation?: "debug" | "review" | "task" | "implement" | "question" | "brainstorm" | undefined;
|
|
35
|
+
verdict?: "APPROVED" | "CHANGES_REQUIRED" | "REJECTED" | undefined;
|
|
36
|
+
since?: string | undefined;
|
|
37
|
+
aggregate?: boolean | undefined;
|
|
38
|
+
trend_days?: number | undefined;
|
|
39
|
+
}, {
|
|
40
|
+
workspace_root: string;
|
|
41
|
+
work_type?: "Feature" | "Bug Fix" | "Refactor" | "Performance" | "Security" | "Business Rule" | undefined;
|
|
42
|
+
agent?: AgentName | undefined;
|
|
43
|
+
mode?: "quick" | "normal" | "deep" | undefined;
|
|
44
|
+
limit?: number | undefined;
|
|
45
|
+
invocation?: "debug" | "review" | "task" | "implement" | "question" | "brainstorm" | undefined;
|
|
46
|
+
verdict?: "APPROVED" | "CHANGES_REQUIRED" | "REJECTED" | undefined;
|
|
47
|
+
since?: string | undefined;
|
|
48
|
+
aggregate?: boolean | undefined;
|
|
49
|
+
trend_days?: number | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
export declare const listRunsToolDef: ToolDef<typeof schema>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SafeString } from "./_shared/schemas.js";
|
|
3
|
+
import { AGENT_NAMES_TUPLE } from "../config/ownership-matrix.js";
|
|
4
|
+
import { readRuns, INVOCATION_VALUES } from "../runs/store.js";
|
|
5
|
+
import { foldById, applyFilters, aggregateOutcomes, aggregateHealth, trendByDay, getEstTokens, } from "../runs/aggregate.js";
|
|
6
|
+
/**
|
|
7
|
+
* Read tool: load runs from `.squad/runs.jsonl`, fold the two-phase
|
|
8
|
+
* (in_flight, terminal) pair by id, apply filters, and either return the
|
|
9
|
+
* folded raw list or a precomputed aggregate bundle.
|
|
10
|
+
*
|
|
11
|
+
* Plan v4 architect A-3: collapse the original `list_runs + aggregate_runs`
|
|
12
|
+
* pair into one tool with an `aggregate: boolean` flag. The skill calls
|
|
13
|
+
* the aggregate form for `/squad:stats`; humans (or other tools) call the
|
|
14
|
+
* non-aggregate form to inspect raw rows.
|
|
15
|
+
*
|
|
16
|
+
* No writes here. The single-writer contract belongs to `record_run`.
|
|
17
|
+
*/
|
|
18
|
+
const InvocationEnum = z.enum(INVOCATION_VALUES);
|
|
19
|
+
const ModeEnum = z.enum(["quick", "normal", "deep"]);
|
|
20
|
+
const VerdictEnum = z.enum(["APPROVED", "CHANGES_REQUIRED", "REJECTED"]);
|
|
21
|
+
const WorkTypeEnum = z.enum([
|
|
22
|
+
"Feature",
|
|
23
|
+
"Bug Fix",
|
|
24
|
+
"Refactor",
|
|
25
|
+
"Performance",
|
|
26
|
+
"Security",
|
|
27
|
+
"Business Rule",
|
|
28
|
+
]);
|
|
29
|
+
const schema = z.object({
|
|
30
|
+
workspace_root: SafeString(4096),
|
|
31
|
+
/** ISO 8601 lower bound on `started_at`. */
|
|
32
|
+
since: SafeString(40).optional(),
|
|
33
|
+
/** Cap output to the most recent N folded runs. */
|
|
34
|
+
limit: z.number().int().positive().max(5000).optional(),
|
|
35
|
+
/** Restrict to runs that included this agent in any role. */
|
|
36
|
+
agent: z.enum(AGENT_NAMES_TUPLE).optional(),
|
|
37
|
+
/** Restrict to a single terminal verdict. */
|
|
38
|
+
verdict: VerdictEnum.optional(),
|
|
39
|
+
/** Restrict to a single dispatch mode. */
|
|
40
|
+
mode: ModeEnum.optional(),
|
|
41
|
+
/** Restrict to a single invocation kind. */
|
|
42
|
+
invocation: InvocationEnum.optional(),
|
|
43
|
+
/** Restrict to a single work type. */
|
|
44
|
+
work_type: WorkTypeEnum.optional(),
|
|
45
|
+
/**
|
|
46
|
+
* If true, return aggregate views (outcomes + health + trend) instead of
|
|
47
|
+
* the folded raw rows. The /squad:stats skill uses this; CLI inspections
|
|
48
|
+
* default to the raw list.
|
|
49
|
+
*/
|
|
50
|
+
aggregate: z.boolean().optional(),
|
|
51
|
+
/** Days of trend sparkline data to compute when aggregate=true. Default 14. */
|
|
52
|
+
trend_days: z.number().int().positive().max(90).optional(),
|
|
53
|
+
});
|
|
54
|
+
function serializeFolded(f) {
|
|
55
|
+
return {
|
|
56
|
+
id: f.id,
|
|
57
|
+
status: f.status,
|
|
58
|
+
synthesized_aborted: f.synthesized_aborted,
|
|
59
|
+
record: f.record,
|
|
60
|
+
est_tokens: getEstTokens(f.record),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function workTypeFilter(folded, wt) {
|
|
64
|
+
return folded.filter((f) => f.record.work_type === wt);
|
|
65
|
+
}
|
|
66
|
+
async function handler(input) {
|
|
67
|
+
// Read raw records. readRuns swallows ENOENT and returns []; missing-journal
|
|
68
|
+
// is a normal "no runs yet" state, not an error.
|
|
69
|
+
const records = await readRuns(input.workspace_root);
|
|
70
|
+
const totalInStore = records.length;
|
|
71
|
+
const folded = foldById(records);
|
|
72
|
+
// Apply work_type FIRST so it composes with limit semantically. If we applied
|
|
73
|
+
// it after applyFilters, `limit: N` would truncate by started_at BEFORE the
|
|
74
|
+
// work_type predicate, giving "bug fixes within the last N runs" instead of
|
|
75
|
+
// "last N bug fixes" — silently wrong. (senior-developer cycle-2 Major.)
|
|
76
|
+
let prefiltered = folded;
|
|
77
|
+
if (input.work_type) {
|
|
78
|
+
prefiltered = workTypeFilter(prefiltered, input.work_type);
|
|
79
|
+
}
|
|
80
|
+
const filtered = applyFilters(prefiltered, {
|
|
81
|
+
...(input.since !== undefined && { since: input.since }),
|
|
82
|
+
...(input.limit !== undefined && { limit: input.limit }),
|
|
83
|
+
...(input.agent !== undefined && { agent: input.agent }),
|
|
84
|
+
...(input.verdict !== undefined && { verdict: input.verdict }),
|
|
85
|
+
...(input.mode !== undefined && { mode: input.mode }),
|
|
86
|
+
...(input.invocation !== undefined && { invocation: input.invocation }),
|
|
87
|
+
});
|
|
88
|
+
// Filepath surfacing: we don't currently resolve the configured path here
|
|
89
|
+
// (the store handles its own resolution); reporting null when no records is
|
|
90
|
+
// honest about the "fresh repo" state.
|
|
91
|
+
const filePath = totalInStore > 0 ? `${input.workspace_root}/.squad/runs.jsonl` : null;
|
|
92
|
+
if (!input.aggregate) {
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
file: filePath,
|
|
96
|
+
total_in_store: totalInStore,
|
|
97
|
+
total_folded: filtered.length,
|
|
98
|
+
runs: filtered.map(serializeFolded),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const outcomes = aggregateOutcomes(filtered);
|
|
102
|
+
const health = aggregateHealth(filtered);
|
|
103
|
+
const trendBuckets = trendByDay(filtered, input.trend_days ?? 14);
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
file: filePath,
|
|
107
|
+
total_in_store: totalInStore,
|
|
108
|
+
total_folded: filtered.length,
|
|
109
|
+
outcomes: {
|
|
110
|
+
total_runs: outcomes.total_runs,
|
|
111
|
+
verdict_counts: outcomes.verdict_counts,
|
|
112
|
+
verdict_total: outcomes.verdict_total,
|
|
113
|
+
score_buckets: outcomes.score_buckets,
|
|
114
|
+
invocation_counts: outcomes.invocation_counts,
|
|
115
|
+
est_tokens_total: outcomes.est_tokens_total,
|
|
116
|
+
est_tokens_per_run_avg: outcomes.est_tokens_per_run_avg,
|
|
117
|
+
est_tokens_per_agent: Array.from(outcomes.est_tokens_per_agent.entries()).map(([agent, t]) => ({ agent, ...t })),
|
|
118
|
+
is_empty: outcomes.is_empty,
|
|
119
|
+
},
|
|
120
|
+
health: {
|
|
121
|
+
total_runs: health.total_runs,
|
|
122
|
+
in_flight: health.in_flight,
|
|
123
|
+
completed: health.completed,
|
|
124
|
+
aborted: health.aborted,
|
|
125
|
+
synthesized_aborted: health.synthesized_aborted,
|
|
126
|
+
avg_batch_duration_ms_per_agent: Array.from(health.avg_batch_duration_ms_per_agent.entries()).map(([agent, avg_ms]) => ({ agent, avg_ms })),
|
|
127
|
+
avg_total_duration_ms: health.avg_total_duration_ms,
|
|
128
|
+
},
|
|
129
|
+
trend: { days: input.trend_days ?? 14, counts: trendBuckets },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export const listRunsToolDef = {
|
|
133
|
+
name: "list_runs",
|
|
134
|
+
description: "Read tool for `.squad/runs.jsonl`. Folds the two-phase (in_flight, terminal) row pair by id, " +
|
|
135
|
+
"applies filters (since / limit / agent / verdict / mode / invocation / work_type), and returns " +
|
|
136
|
+
"either the folded list (aggregate=false, default) or a precomputed aggregate bundle " +
|
|
137
|
+
"(outcomes + health + trend sparkline buckets) when aggregate=true. Missing-journal returns " +
|
|
138
|
+
"an empty result, not an error. Read-only — never writes.",
|
|
139
|
+
schema,
|
|
140
|
+
handler,
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=list-runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-runs.js","sourceRoot":"","sources":["../../src/tools/list-runs.ts"],"names":[],"mappings":"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"}
|