@xhulz/nina 0.55.1 → 0.56.0
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/package.json +1 -1
- package/releases/0.56.0/core/GAPS.md +125 -0
- package/releases/0.56.0/core/answered.json +68 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.56.0/core/tree/.claude/agents/architect.md +130 -0
- package/releases/0.56.0/core/tree/.claude/agents/dba.md +119 -0
- package/releases/0.56.0/core/tree/.claude/agents/design-reviewer.md +75 -0
- package/releases/0.56.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.56.0/core/tree/.claude/agents/implementer.md +163 -0
- package/releases/0.56.0/core/tree/.claude/agents/integration-tester.md +170 -0
- package/releases/0.56.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.56.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.56.0/core/tree/.claude/agents/reviewer.md +187 -0
- package/releases/0.56.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.56.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.56.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.56.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.56.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.56.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.56.0/core/tree/.claude/patterns.md +251 -0
- package/releases/0.56.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.56.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.56.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.56.0/core/tree/.claude/router.md +205 -0
- package/releases/0.56.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.56.0/core/tree/CLAUDE.md +166 -0
- package/releases/0.56.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.56.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.56.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.56.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.56.0/core/vocabulary.json +14 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.56.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.56.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.56.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.56.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.56.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.56.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.56.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.56.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/export.mjs +5 -5
- package/src/commands/langfuse.mjs +30 -1
- package/src/commands/stats.mjs +7 -0
- package/src/dashboard.html +2 -1
- package/src/transcripts.mjs +227 -1
package/src/transcripts.mjs
CHANGED
|
@@ -124,6 +124,220 @@ const WRITES = { Edit: 'file_path', Write: 'file_path', MultiEdit: 'file_path',
|
|
|
124
124
|
/** How many lines a text written through an edit tool holds: none for an empty one, a last newline ending one. */
|
|
125
125
|
const linesIn = (text) => (typeof text === 'string' && text.length > 0 ? text.replace(/\n$/, '').split('\n').length : 0);
|
|
126
126
|
|
|
127
|
+
/** A temporary directory named in a command: what it writes there is scratch, not the tree. */
|
|
128
|
+
const TEMP = /(?:^|[^\w.-])(?:\/private)?\/tmp\/|\/var\/folders\/|\$\{?TMPDIR\b|\bmktemp\b|\/scratchpad\b/;
|
|
129
|
+
|
|
130
|
+
/** A script, in a heredoc or a quoted `-e`, that opens a file to write it. */
|
|
131
|
+
const SCRIPT_WRITES = /\bopen\([^)\n]*,\s*(?:mode\s*=\s*)?['"](?:w|a|x|r\+)[bt+]?['"]|\.write_(?:text|bytes)\(|\b(?:writeFileSync|appendFileSync|writeFile|appendFile|createWriteStream)\(/;
|
|
132
|
+
|
|
133
|
+
/** A script's write to a file it names as a literal: the path, judged as the shell's own writes are. */
|
|
134
|
+
const LITERAL_WRITES = [
|
|
135
|
+
/\bopen\(\s*(['"])([^'"\n]+)\1\s*,\s*(?:mode\s*=\s*)?['"](?:w|a|x|r\+)/g,
|
|
136
|
+
/\bPath\(\s*(['"])([^'"\n]+)\1\s*\)\.write_(?:text|bytes)\(/g,
|
|
137
|
+
/\b(?:writeFileSync|appendFileSync|writeFile|appendFile|createWriteStream)\(\s*(['"`])([^'"`\n]+)\1/g,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/** A redirect's own words, which are not a command's last argument. */
|
|
141
|
+
const REDIRECTS = /(?:^|\s)(?:\d?[<>]{1,2}\|?|&>>?)(?:&\d+|\s*\S+)/g;
|
|
142
|
+
|
|
143
|
+
/** What may stand before a simple command's own name: a function it opens, `do`, `then`, a group, `nohup`. */
|
|
144
|
+
const LEADS = String.raw`^\s*(?:[A-Za-z_][\w-]*\(\)\s*)?(?:(?:do|then|else|time|nohup|\{|\(|!)\s*)*`;
|
|
145
|
+
|
|
146
|
+
/** A simple command that opens a function's body. */
|
|
147
|
+
const FUNCTION = /^\s*(?:function\s+[A-Za-z_][\w-]*\s*(?:\(\))?|[A-Za-z_][\w-]*\s*\(\))\s*\{/;
|
|
148
|
+
|
|
149
|
+
/** A `cd` or `pushd`, and where it goes. */
|
|
150
|
+
const CD = new RegExp(String.raw`${LEADS}(?:cd|pushd)\b(?:\s+(\S+))?`);
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The shell's own ways to write a file, read in a simple command's words, each with where it writes: the redirect's
|
|
154
|
+
* or `tee`'s file, the last word of an in-place `sed` or `perl`, of a `cp` or a `mv`; a formatter's `--write` or
|
|
155
|
+
* `--fix`, `git apply` and `patch` write where the command stands.
|
|
156
|
+
*/
|
|
157
|
+
const SHELL_WRITES = [
|
|
158
|
+
[/(?:^|[\s;&|(){}])\d?>>?\|?\s*(?!&)(?!\/dev\/(?:null|stdout|stderr)\b)([^\s;&|<>()]+)/, 1],
|
|
159
|
+
[/(?:^|[\s;&|(])tee\s+(?:-a\s+)?(?!\/dev\/null\b)([^\s;&|<>()-][^\s;&|<>()]*)/, 1],
|
|
160
|
+
[new RegExp(String.raw`${LEADS}(?:sed|perl)\s+(?:-\S*\s+)*(?:-[a-zA-Z]*i\b|--in-place)`), 'last'],
|
|
161
|
+
[new RegExp(String.raw`${LEADS}(?:cp|mv)\s`), 'last'],
|
|
162
|
+
[/\s--(?:write|fix)(?=\s|=|$)/, null],
|
|
163
|
+
[new RegExp(String.raw`${LEADS}(?:git\s+apply|patch)\b`), null],
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
/** A simple command that runs an interpreter, whose script — a heredoc, a `-e` or `-c` — may open a file to write. */
|
|
167
|
+
const INTERPRETER = new RegExp(String.raw`${LEADS}(?:[A-Za-z_]\w*=\S*\s+)*(?:(?:pnpm|npx)\s+(?:exec\s+)?)?(?:python3?|node|deno|bun|perl|ruby|tsx)\b`);
|
|
168
|
+
|
|
169
|
+
/** A quoted word that is only a path or a variable, kept as a word rather than emptied as a string. */
|
|
170
|
+
const PLAIN = /^[\w$./{}~@:+,=-]*$/;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A command's simple commands, in order, as the shell splits them at `&&`, `||`, `;`, `|`, `&` and line ends outside
|
|
174
|
+
* quotes: each with its text as written, a heredoc's body joined to the command that opened it, and its words — a
|
|
175
|
+
* quoted path kept, any other quoted string emptied, no body — so that `a > b` in a script, or an arrow function in
|
|
176
|
+
* `node -e "…"`, is no redirect.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} command
|
|
179
|
+
* @returns {{raw: string, words: string}[]}
|
|
180
|
+
*/
|
|
181
|
+
function simpleCommands(command) {
|
|
182
|
+
const out = [];
|
|
183
|
+
let raw = '';
|
|
184
|
+
let words = '';
|
|
185
|
+
let quote = null;
|
|
186
|
+
let opened = 0;
|
|
187
|
+
const bodies = [];
|
|
188
|
+
const end = () => {
|
|
189
|
+
if (raw.trim()) out.push({ raw, words });
|
|
190
|
+
raw = '';
|
|
191
|
+
words = '';
|
|
192
|
+
};
|
|
193
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
194
|
+
const c = command[i];
|
|
195
|
+
const prev = command[i - 1];
|
|
196
|
+
const next = command[i + 1];
|
|
197
|
+
if (quote) {
|
|
198
|
+
raw += c;
|
|
199
|
+
if (c === '\\' && quote === '"' && next !== undefined) {
|
|
200
|
+
raw += next;
|
|
201
|
+
i += 1;
|
|
202
|
+
} else if (c === quote) {
|
|
203
|
+
const inside = raw.slice(opened, -1);
|
|
204
|
+
words += PLAIN.test(inside) ? inside : '""';
|
|
205
|
+
quote = null;
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (c === '\\' && next !== undefined) {
|
|
210
|
+
// A backslash before a line end joins the lines; before anything else, it keeps that character as it is.
|
|
211
|
+
raw += next === '\n' ? ' ' : c + next;
|
|
212
|
+
words += next === '\n' ? ' ' : c + next;
|
|
213
|
+
i += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (c === '$' && next === '(' && command[i + 2] === '(') {
|
|
217
|
+
// Arithmetic, `$((1 << n))` or `$((a > b))`: neither a heredoc nor a redirect.
|
|
218
|
+
let depth = 0;
|
|
219
|
+
let j = i + 1;
|
|
220
|
+
for (; j < command.length; j += 1) {
|
|
221
|
+
if (command[j] === '(') depth += 1;
|
|
222
|
+
else if (command[j] === ')' && (depth -= 1) === 0) break;
|
|
223
|
+
}
|
|
224
|
+
raw += command.slice(i, j + 1);
|
|
225
|
+
words += '0';
|
|
226
|
+
i = j;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (c === "'" || c === '"') {
|
|
230
|
+
quote = c;
|
|
231
|
+
raw += c;
|
|
232
|
+
opened = raw.length;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// A comment runs to the line's end: what it says is not run.
|
|
236
|
+
if (c === '#' && (prev === undefined || /[\s;&|(]/.test(prev))) {
|
|
237
|
+
while (i + 1 < command.length && command[i + 1] !== '\n') i += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const heredoc = c === '<' && prev !== '<' ? /^<<(-?)\s*\\?(['"]?)([A-Za-z_][\w.-]*)\2/.exec(command.slice(i)) : null;
|
|
241
|
+
if (heredoc) {
|
|
242
|
+
bodies.push({ delimiter: heredoc[3], tabs: heredoc[1] === '-' });
|
|
243
|
+
raw += heredoc[0];
|
|
244
|
+
words += '<<';
|
|
245
|
+
i += heredoc[0].length - 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (c === '\n') {
|
|
249
|
+
// Each heredoc opened on the line: the lines up to its delimiter, kept with the command that opened it.
|
|
250
|
+
let at = i + 1;
|
|
251
|
+
for (const { delimiter, tabs } of bodies.splice(0)) {
|
|
252
|
+
while (at < command.length) {
|
|
253
|
+
const stop = command.indexOf('\n', at);
|
|
254
|
+
const line = command.slice(at, stop === -1 ? command.length : stop);
|
|
255
|
+
raw += `\n${line}`;
|
|
256
|
+
at = stop === -1 ? command.length : stop + 1;
|
|
257
|
+
if ((tabs ? line.replace(/^\t+/, '') : line) === delimiter) break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
i = at - 1;
|
|
261
|
+
end();
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const pair = (c === '&' || c === '|') && next === c;
|
|
265
|
+
const pipe = c === '|' && prev !== '>';
|
|
266
|
+
const background = c === '&' && prev !== '>' && prev !== '<' && next !== '>';
|
|
267
|
+
if (c === ';' || pair || pipe || background) {
|
|
268
|
+
end();
|
|
269
|
+
if (pair || (c === '|' && next === '&')) i += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
raw += c;
|
|
273
|
+
words += c;
|
|
274
|
+
}
|
|
275
|
+
end();
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Whether a shell command writes a file outside a temporary directory: a redirect into a file, `tee`, `sed -i` or
|
|
281
|
+
* `perl -i`, `cp` or `mv`, a formatter's `--write` or `--fix`, `git apply` or `patch`, or an interpreter's script that
|
|
282
|
+
* opens a file to write it. Each write is placed where it lands: a path, a variable the command bound — to a temporary
|
|
283
|
+
* directory or not — or, for a relative one, where the command stands after its `cd`s, which starts in the project
|
|
284
|
+
* and keeps its place through a relative `cd`. A variable the command did not bind, a function's `$1`, is nowhere
|
|
285
|
+
* known, and not counted. One project's implementers wrote their files so, through a script that kept an escape the
|
|
286
|
+
* edit tools decode, and the count of the edit tools read those rounds as having written nothing. Read from the
|
|
287
|
+
* command alone, it is a sign, not a count: a script run from a file is missed, and so is one that writes the tree
|
|
288
|
+
* through a variable while it names a temporary directory.
|
|
289
|
+
*
|
|
290
|
+
* @param {string} command - A Bash tool call's command.
|
|
291
|
+
* @returns {boolean}
|
|
292
|
+
*/
|
|
293
|
+
export function writesThroughShell(command) {
|
|
294
|
+
if (typeof command !== 'string') return false;
|
|
295
|
+
const vars = new Map();
|
|
296
|
+
let here = 'tree';
|
|
297
|
+
// Where each open subshell's `(` found the command standing: its `cd`s end with it.
|
|
298
|
+
const outside = [];
|
|
299
|
+
// A function's body runs where it is called, on what it is given: nowhere this command says.
|
|
300
|
+
let functions = 0;
|
|
301
|
+
let standing = here;
|
|
302
|
+
const place = (target) => {
|
|
303
|
+
if (typeof target !== 'string' || target === '') return standing;
|
|
304
|
+
const name = /^\$\{?(\w+|[@*#?$!-])/.exec(target);
|
|
305
|
+
if (name) return /^\$\{?TMPDIR\b/.test(target) ? 'scratch' : (vars.get(name[1]) ?? 'unknown');
|
|
306
|
+
if (TEMP.test(target) || target.startsWith('/dev/')) return 'scratch';
|
|
307
|
+
if (target.startsWith('~') || target.includes('""')) return 'unknown';
|
|
308
|
+
return target.startsWith('/') ? 'tree' : standing;
|
|
309
|
+
};
|
|
310
|
+
for (const { raw, words } of simpleCommands(command)) {
|
|
311
|
+
const groups = words.replace(/\$\([^()]*\)/g, '');
|
|
312
|
+
for (let n = /^\s*(\(*)/.exec(groups)[1].length; n > 0; n -= 1) outside.push(here);
|
|
313
|
+
for (let n = (groups.match(/\)/g) ?? []).length - (groups.match(/\(/g) ?? []).length; n > 0 && outside.length; n -= 1) here = outside.pop();
|
|
314
|
+
if (FUNCTION.test(words)) functions += 1;
|
|
315
|
+
standing = functions > 0 ? 'unknown' : here;
|
|
316
|
+
for (const [, name, value] of words.matchAll(/(?:^|\s)(?:export\s+)?([A-Za-z_]\w*)=(\S*)/g)) vars.set(name, place(value));
|
|
317
|
+
const cd = CD.exec(words);
|
|
318
|
+
if (cd) {
|
|
319
|
+
if (functions === 0) here = cd[1] === undefined ? 'unknown' : place(cd[1]);
|
|
320
|
+
} else {
|
|
321
|
+
for (const [rule, at] of SHELL_WRITES) {
|
|
322
|
+
const found = rule.exec(words);
|
|
323
|
+
if (!found) continue;
|
|
324
|
+
const target = at === 'last' ? words.replace(REDIRECTS, ' ').trim().split(/\s+/).at(-1) : at === null ? undefined : found[at];
|
|
325
|
+
if (place(target) === 'tree') return true;
|
|
326
|
+
}
|
|
327
|
+
if (INTERPRETER.test(raw) && SCRIPT_WRITES.test(raw)) {
|
|
328
|
+
// A script names its files inside it: a literal one is placed; with none, a script that names a temporary
|
|
329
|
+
// directory is taken to write there.
|
|
330
|
+
const literal = LITERAL_WRITES.flatMap((re) => [...raw.matchAll(re)].map((m) => m[2]));
|
|
331
|
+
if (literal.some((path) => place(path) === 'tree')) return true;
|
|
332
|
+
const names = TEMP.test(raw) || [...vars].some(([v, p]) => p !== 'tree' && new RegExp(`\\$\\{?${v}\\b`).test(raw));
|
|
333
|
+
if (literal.length === 0 && standing === 'tree' && !names) return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (functions > 0 && /(?:^|[\s;])\}\s*$/.test(words)) functions -= 1;
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
127
341
|
/** The line under a declared verdict that names what the report sends back. */
|
|
128
342
|
const ISSUES = /^\s*ISSUES:(.*)$/;
|
|
129
343
|
|
|
@@ -646,7 +860,7 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
646
860
|
if (
|
|
647
861
|
run.agent_read_bytes === size &&
|
|
648
862
|
!('resumes' in run) &&
|
|
649
|
-
[...records, ...later].every((r) => settled(r) && 'round' in r && 'lessons_read' in r && 'tokens' in r && 'files_touched' in r && 'lines_written' in r && 'effort' in r && 'turns' in r && 'prompt_chars' in r)
|
|
863
|
+
[...records, ...later].every((r) => settled(r) && 'round' in r && 'lessons_read' in r && 'tokens' in r && 'files_touched' in r && 'lines_written' in r && 'shell_writes' in r && 'effort' in r && 'turns' in r && 'prompt_chars' in r)
|
|
650
864
|
) {
|
|
651
865
|
continue;
|
|
652
866
|
}
|
|
@@ -679,6 +893,8 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
679
893
|
* run wrote, not its diff: an edit made twice counts twice, and a file a shell command changed not at all.
|
|
680
894
|
*/
|
|
681
895
|
lines: new Map(),
|
|
896
|
+
/** Its shell commands that wrote a file outside a temporary directory, which no count of lines reaches. */
|
|
897
|
+
shellWrites: 0,
|
|
682
898
|
/** The tool calls the round made, by id: over its turns, how many calls each turn sent at once. */
|
|
683
899
|
calls: new Set(),
|
|
684
900
|
/** How long the prompt that opened the round was, in characters; never the prompt. */
|
|
@@ -726,6 +942,15 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
726
942
|
// A torn line loses one edit, not the run's.
|
|
727
943
|
}
|
|
728
944
|
}
|
|
945
|
+
if (line.includes('"name":"Bash"')) {
|
|
946
|
+
try {
|
|
947
|
+
for (const block of JSON.parse(line)?.message?.content ?? []) {
|
|
948
|
+
if (block?.type === 'tool_use' && block.name === 'Bash' && writesThroughShell(block.input?.command)) round.shellWrites += 1;
|
|
949
|
+
}
|
|
950
|
+
} catch {
|
|
951
|
+
// A torn line loses one command, not the run's.
|
|
952
|
+
}
|
|
953
|
+
}
|
|
729
954
|
if (line.includes('"usage"')) {
|
|
730
955
|
try {
|
|
731
956
|
const row = JSON.parse(line);
|
|
@@ -808,6 +1033,7 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
808
1033
|
record.effort = spent.effort;
|
|
809
1034
|
record.files_touched = round.written.size;
|
|
810
1035
|
record.lines_written = [...round.lines.values()].reduce((a, b) => a + b, 0);
|
|
1036
|
+
record.shell_writes = round.shellWrites;
|
|
811
1037
|
record.skills = [...round.skills];
|
|
812
1038
|
record.lessons_read = round.lessonsRead.size;
|
|
813
1039
|
record.lessons_listed = round.lessonsListed;
|