@workscribe/cli 3.0.1 → 3.2.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/README.md +1 -1
- package/dist/index.js +688 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/workscribe/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -378,7 +378,7 @@ var init_logger = __esm({
|
|
|
378
378
|
success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
|
|
379
379
|
warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
|
|
380
380
|
error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
|
|
381
|
-
/** A warning on stderr — keeps stdout clean for JSON output
|
|
381
|
+
/** A warning on stderr — keeps stdout clean for JSON and plain text output, and pipes. */
|
|
382
382
|
notice: (msg) => process.stderr.write(`${import_chalk.default.yellow("\u26A0")} ${msg}
|
|
383
383
|
`),
|
|
384
384
|
plain: (msg) => console.log(msg)
|
|
@@ -972,6 +972,13 @@ function maskQuotedText(text) {
|
|
|
972
972
|
}
|
|
973
973
|
return masked.join("");
|
|
974
974
|
}
|
|
975
|
+
function maskLiteralText(text) {
|
|
976
|
+
const masked = Array.from({ length: text.length }, () => "_");
|
|
977
|
+
for (const { index, quote } of quotedCharacters(text)) {
|
|
978
|
+
if (quote !== "'") masked[index] = text[index];
|
|
979
|
+
}
|
|
980
|
+
return masked.join("");
|
|
981
|
+
}
|
|
975
982
|
function startsWithWord(step2, word) {
|
|
976
983
|
return step2 === word || step2.startsWith(`${word} `) || step2.startsWith(`${word} `);
|
|
977
984
|
}
|
|
@@ -1175,57 +1182,6 @@ var init_exclusions = __esm({
|
|
|
1175
1182
|
}
|
|
1176
1183
|
});
|
|
1177
1184
|
|
|
1178
|
-
// src/normalize/compound.ts
|
|
1179
|
-
function rankOf(category) {
|
|
1180
|
-
const index = STEP_CATEGORY_PRIORITY.indexOf(category);
|
|
1181
|
-
if (index !== -1) return index;
|
|
1182
|
-
if (category === UNCLASSIFIED.category) return UNCLASSIFIED_RANK;
|
|
1183
|
-
if (DEFAULT_EXCLUDED_CATEGORIES.has(category)) return EXCLUDED_CATEGORY_RANK;
|
|
1184
|
-
return OTHER_CATEGORY_RANK;
|
|
1185
|
-
}
|
|
1186
|
-
function primaryStepEvent(steps, matchCommand) {
|
|
1187
|
-
const events = steps.map((step2) => ({ ...matchCommand(simpleCommandOf(step2), step2), matchedStep: step2 }));
|
|
1188
|
-
return events.reduce(
|
|
1189
|
-
(best, event) => rankOf(event.category) < rankOf(best.category) ? event : best,
|
|
1190
|
-
UNCLASSIFIED
|
|
1191
|
-
);
|
|
1192
|
-
}
|
|
1193
|
-
function classifyCompound(command, matchCommand) {
|
|
1194
|
-
const workSteps = commandSteps(command).filter((step2) => !isGlueStep(step2));
|
|
1195
|
-
const wholeCommandEvent = {
|
|
1196
|
-
...matchCommand(command, command),
|
|
1197
|
-
matchedStep: workSteps.length === 1 ? workSteps[0] : command
|
|
1198
|
-
};
|
|
1199
|
-
const isRecognisedSingleCommand = workSteps.length <= 1 && wholeCommandEvent.category !== UNCLASSIFIED.category;
|
|
1200
|
-
if (isRecognisedSingleCommand || workSteps.length === 0) return wholeCommandEvent;
|
|
1201
|
-
const primaryEvent = primaryStepEvent(workSteps, matchCommand);
|
|
1202
|
-
return primaryEvent.category === UNCLASSIFIED.category ? wholeCommandEvent : primaryEvent;
|
|
1203
|
-
}
|
|
1204
|
-
var UNCLASSIFIED, STEP_CATEGORY_PRIORITY, OTHER_CATEGORY_RANK, EXCLUDED_CATEGORY_RANK, UNCLASSIFIED_RANK;
|
|
1205
|
-
var init_compound = __esm({
|
|
1206
|
-
"src/normalize/compound.ts"() {
|
|
1207
|
-
"use strict";
|
|
1208
|
-
init_command_steps();
|
|
1209
|
-
init_exclusions();
|
|
1210
|
-
UNCLASSIFIED = { category: "other", metadata: {} };
|
|
1211
|
-
STEP_CATEGORY_PRIORITY = [
|
|
1212
|
-
"testing",
|
|
1213
|
-
"building",
|
|
1214
|
-
"releasing",
|
|
1215
|
-
"shipping",
|
|
1216
|
-
"dependencies",
|
|
1217
|
-
"database",
|
|
1218
|
-
"infrastructure",
|
|
1219
|
-
"running",
|
|
1220
|
-
"files",
|
|
1221
|
-
"versioning"
|
|
1222
|
-
];
|
|
1223
|
-
OTHER_CATEGORY_RANK = STEP_CATEGORY_PRIORITY.length;
|
|
1224
|
-
EXCLUDED_CATEGORY_RANK = OTHER_CATEGORY_RANK + 1;
|
|
1225
|
-
UNCLASSIFIED_RANK = EXCLUDED_CATEGORY_RANK + 1;
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
1185
|
// src/normalize/git-invocation.ts
|
|
1230
1186
|
function parseGitInvocation(command) {
|
|
1231
1187
|
const words = shellWords(command.trim());
|
|
@@ -1282,6 +1238,198 @@ var init_git_invocation = __esm({
|
|
|
1282
1238
|
}
|
|
1283
1239
|
});
|
|
1284
1240
|
|
|
1241
|
+
// src/normalize/read-only.ts
|
|
1242
|
+
function hasNoOption(args, options) {
|
|
1243
|
+
return !args.some((arg) => options.some((option) => arg === option || arg.startsWith(`${option}=`)));
|
|
1244
|
+
}
|
|
1245
|
+
function runsInlineCode(args, inlineFlags) {
|
|
1246
|
+
const firstMeaningful = args.find(
|
|
1247
|
+
(arg) => inlineFlags.includes(arg) || arg.startsWith("--eval=") || !arg.startsWith("-")
|
|
1248
|
+
);
|
|
1249
|
+
return firstMeaningful !== void 0 && (inlineFlags.includes(firstMeaningful) || firstMeaningful.startsWith("--eval="));
|
|
1250
|
+
}
|
|
1251
|
+
function readSedShortOptions(option, nextArg, parsed) {
|
|
1252
|
+
const letters = option.slice(1);
|
|
1253
|
+
const expressionAt = letters.indexOf("e");
|
|
1254
|
+
if (SED_UNSAFE_SHORT_OPTION.test(letters) || expressionAt !== -1 && expressionAt !== letters.length - 1) {
|
|
1255
|
+
parsed.isSafe = false;
|
|
1256
|
+
}
|
|
1257
|
+
if (letters.includes("n")) parsed.isQuiet = true;
|
|
1258
|
+
if (expressionAt === -1) return 0;
|
|
1259
|
+
parsed.scripts.push(nextArg ?? "");
|
|
1260
|
+
return 1;
|
|
1261
|
+
}
|
|
1262
|
+
function readSedArguments(args) {
|
|
1263
|
+
const parsed = { isQuiet: false, isSafe: true, scripts: [] };
|
|
1264
|
+
const positional = [];
|
|
1265
|
+
for (let index = 0; index < args.length; index++) {
|
|
1266
|
+
const arg = args[index];
|
|
1267
|
+
if (arg === "--quiet" || arg === "--silent") parsed.isQuiet = true;
|
|
1268
|
+
else if (arg.startsWith("--in-place") || arg.startsWith("--file")) parsed.isSafe = false;
|
|
1269
|
+
else if (arg === "--expression") parsed.scripts.push(args[++index] ?? "");
|
|
1270
|
+
else if (arg.startsWith("--expression=")) parsed.scripts.push(arg.slice("--expression=".length));
|
|
1271
|
+
else if (SED_SHORT_OPTIONS.test(arg)) index += readSedShortOptions(arg, args[index + 1], parsed);
|
|
1272
|
+
else if (!arg.startsWith("-")) positional.push(arg);
|
|
1273
|
+
}
|
|
1274
|
+
return { ...parsed, positional };
|
|
1275
|
+
}
|
|
1276
|
+
function sedOnlyPrints(args) {
|
|
1277
|
+
const { isQuiet, isSafe, scripts, positional } = readSedArguments(args);
|
|
1278
|
+
const allScripts = scripts.length > 0 ? scripts : positional.slice(0, 1);
|
|
1279
|
+
if (!isQuiet || !isSafe || allScripts.length === 0) return false;
|
|
1280
|
+
return allScripts.every(
|
|
1281
|
+
(script) => script.split(";").filter((part) => part.trim() !== "").every((part) => SED_PRINT_COMMAND.test(part.trim()))
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
function isGitRead(command) {
|
|
1285
|
+
const invocation = parseGitInvocation(command);
|
|
1286
|
+
if (!invocation?.subcommand) return false;
|
|
1287
|
+
const args = invocation.args.map(({ word }) => word);
|
|
1288
|
+
if (invocation.subcommand === "branch") return args.length === 0;
|
|
1289
|
+
return GIT_READS.has(invocation.subcommand) && hasNoOption(args, GIT_UNSAFE_OPTIONS);
|
|
1290
|
+
}
|
|
1291
|
+
function isReadingStage(stage, checks) {
|
|
1292
|
+
const command = simpleCommandOf(stage);
|
|
1293
|
+
const [name, ...args] = shellWords(command).map(({ word }) => word);
|
|
1294
|
+
if (name === "git") return isGitRead(command);
|
|
1295
|
+
return Object.hasOwn(checks, name ?? "") && checks[name](args);
|
|
1296
|
+
}
|
|
1297
|
+
function substitutionsOnlyRead(step2) {
|
|
1298
|
+
if (!containsCommandSubstitution(step2)) return true;
|
|
1299
|
+
if (PROCESS_SUBSTITUTION.test(maskQuotedText(step2))) return false;
|
|
1300
|
+
const literalMasked = maskLiteralText(step2);
|
|
1301
|
+
const commands = substitutionCommands(literalMasked);
|
|
1302
|
+
const openers = literalMasked.split("$(").length - 1 + Math.ceil((literalMasked.split("`").length - 1) / 2);
|
|
1303
|
+
return commands.length === openers && commands.every(isReadOnlyCommand);
|
|
1304
|
+
}
|
|
1305
|
+
function isReadOnlyStep(step2) {
|
|
1306
|
+
if (!substitutionsOnlyRead(step2)) return false;
|
|
1307
|
+
const [firstStage = "", ...laterStages] = splitOutsideQuotes(step2, PIPE_SEPARATORS2);
|
|
1308
|
+
return isReadingStage(firstStage, READERS) && laterStages.every((stage) => isReadingStage(stage, { ...READERS, ...PIPE_FILTERS }));
|
|
1309
|
+
}
|
|
1310
|
+
function writesToFile(step2) {
|
|
1311
|
+
return [...maskQuotedText(step2).matchAll(OUTPUT_REDIRECTION)].some(([, target]) => !HARMLESS_TARGET.test(target));
|
|
1312
|
+
}
|
|
1313
|
+
function isReadOnlyCommand(command) {
|
|
1314
|
+
const steps = commandSteps(command);
|
|
1315
|
+
if (steps.some(writesToFile)) return false;
|
|
1316
|
+
const workSteps = steps.filter((step2) => !isGlueStep(step2));
|
|
1317
|
+
return workSteps.length > 0 && workSteps.every(isReadOnlyStep);
|
|
1318
|
+
}
|
|
1319
|
+
function readOnlyMetadata(command) {
|
|
1320
|
+
const firstWorkStep = commandSteps(command).find((step2) => !isGlueStep(step2)) ?? "";
|
|
1321
|
+
const [firstStage = ""] = splitOutsideQuotes(firstWorkStep, PIPE_SEPARATORS2);
|
|
1322
|
+
const simple = simpleCommandOf(firstStage);
|
|
1323
|
+
const invocation = parseGitInvocation(simple);
|
|
1324
|
+
return invocation ? { subcommand: invocation.subcommand } : { tool: shellWords(simple)[0]?.word ?? null };
|
|
1325
|
+
}
|
|
1326
|
+
var READ_ONLY_CATEGORY, PIPE_SEPARATORS2, ALWAYS_READS, GIT_READS, GIT_UNSAFE_OPTIONS, FIND_UNSAFE_ACTION, SED_ADDRESS, SED_PRINT_COMMAND, SED_SHORT_OPTIONS, SED_UNSAFE_SHORT_OPTION, OUTPUT_REDIRECTION, HARMLESS_TARGET, PROCESS_SUBSTITUTION, READERS, PIPE_FILTERS;
|
|
1327
|
+
var init_read_only = __esm({
|
|
1328
|
+
"src/normalize/read-only.ts"() {
|
|
1329
|
+
"use strict";
|
|
1330
|
+
init_command_steps();
|
|
1331
|
+
init_shell_syntax();
|
|
1332
|
+
init_git_invocation();
|
|
1333
|
+
READ_ONLY_CATEGORY = "exploring";
|
|
1334
|
+
PIPE_SEPARATORS2 = ["|"];
|
|
1335
|
+
ALWAYS_READS = () => true;
|
|
1336
|
+
GIT_READS = /* @__PURE__ */ new Set(["diff", "log", "show", "status"]);
|
|
1337
|
+
GIT_UNSAFE_OPTIONS = ["--output", "--ext-diff", "--textconv"];
|
|
1338
|
+
FIND_UNSAFE_ACTION = /^-(?:delete|exec|execdir|ok|okdir|fls|fprint\w*)$/;
|
|
1339
|
+
SED_ADDRESS = String.raw`(?:\d+|\$|/(?:[^/\\]|\\.)*/)`;
|
|
1340
|
+
SED_PRINT_COMMAND = new RegExp(String.raw`^(?:${SED_ADDRESS}(?:,${SED_ADDRESS})?)?\s*!?\s*[p=]$`);
|
|
1341
|
+
SED_SHORT_OPTIONS = /^-[a-zA-Z]+$/;
|
|
1342
|
+
SED_UNSAFE_SHORT_OPTION = /[iIf]/;
|
|
1343
|
+
OUTPUT_REDIRECTION = /(?:&>>?|\d*>>?\|?)\s*(&?[^\s;|&()]*)/g;
|
|
1344
|
+
HARMLESS_TARGET = /^(?:&\d+|\/dev\/null)$/;
|
|
1345
|
+
PROCESS_SUBSTITUTION = /[<>]\(/;
|
|
1346
|
+
READERS = {
|
|
1347
|
+
grep: ALWAYS_READS,
|
|
1348
|
+
egrep: ALWAYS_READS,
|
|
1349
|
+
fgrep: ALWAYS_READS,
|
|
1350
|
+
ag: ALWAYS_READS,
|
|
1351
|
+
ack: ALWAYS_READS,
|
|
1352
|
+
cat: ALWAYS_READS,
|
|
1353
|
+
bat: ALWAYS_READS,
|
|
1354
|
+
less: ALWAYS_READS,
|
|
1355
|
+
more: ALWAYS_READS,
|
|
1356
|
+
head: ALWAYS_READS,
|
|
1357
|
+
tail: ALWAYS_READS,
|
|
1358
|
+
ls: ALWAYS_READS,
|
|
1359
|
+
wc: ALWAYS_READS,
|
|
1360
|
+
// `--pre` runs a command on every file
|
|
1361
|
+
rg: (args) => hasNoOption(args, ["--pre"]),
|
|
1362
|
+
ripgrep: (args) => hasNoOption(args, ["--pre"]),
|
|
1363
|
+
find: (args) => !args.some((arg) => FIND_UNSAFE_ACTION.test(arg)),
|
|
1364
|
+
sed: sedOnlyPrints,
|
|
1365
|
+
python: (args) => runsInlineCode(args, ["-c", "-"]),
|
|
1366
|
+
python3: (args) => runsInlineCode(args, ["-c", "-"]),
|
|
1367
|
+
node: (args) => runsInlineCode(args, ["-e", "--eval"])
|
|
1368
|
+
};
|
|
1369
|
+
PIPE_FILTERS = {
|
|
1370
|
+
sort: (args) => hasNoOption(args, ["-o", "--output"]) && !args.some((arg) => /^-[a-zA-Z]*o/.test(arg)),
|
|
1371
|
+
// `uniq in out` writes to `out`
|
|
1372
|
+
uniq: (args) => args.filter((arg) => !arg.startsWith("-")).length <= 1,
|
|
1373
|
+
cut: ALWAYS_READS,
|
|
1374
|
+
tr: ALWAYS_READS,
|
|
1375
|
+
jq: ALWAYS_READS
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
// src/normalize/compound.ts
|
|
1381
|
+
function rankOf(category) {
|
|
1382
|
+
const index = STEP_CATEGORY_PRIORITY.indexOf(category);
|
|
1383
|
+
if (index !== -1) return index;
|
|
1384
|
+
if (category === UNCLASSIFIED.category) return UNCLASSIFIED_RANK;
|
|
1385
|
+
if (category === READ_ONLY_CATEGORY) return READ_ONLY_RANK;
|
|
1386
|
+
if (DEFAULT_EXCLUDED_CATEGORIES.has(category)) return EXCLUDED_CATEGORY_RANK;
|
|
1387
|
+
return OTHER_CATEGORY_RANK;
|
|
1388
|
+
}
|
|
1389
|
+
function primaryStepEvent(stepEvents) {
|
|
1390
|
+
return stepEvents.reduce((best, event) => rankOf(event.category) < rankOf(best.category) ? event : best);
|
|
1391
|
+
}
|
|
1392
|
+
function classifyCompound(command, matchCommand) {
|
|
1393
|
+
const workSteps = commandSteps(command).filter((step2) => !isGlueStep(step2));
|
|
1394
|
+
const wholeCommandEvent = {
|
|
1395
|
+
...matchCommand(command, command),
|
|
1396
|
+
matchedStep: workSteps.length === 1 ? workSteps[0] : command
|
|
1397
|
+
};
|
|
1398
|
+
const isRecognisedSingleCommand = workSteps.length <= 1 && wholeCommandEvent.category !== UNCLASSIFIED.category;
|
|
1399
|
+
if (isRecognisedSingleCommand || workSteps.length === 0) return wholeCommandEvent;
|
|
1400
|
+
const stepEvents = workSteps.map((step2) => ({ ...matchCommand(simpleCommandOf(step2), step2), matchedStep: step2 }));
|
|
1401
|
+
const primaryEvent = primaryStepEvent(stepEvents);
|
|
1402
|
+
if (primaryEvent.category !== UNCLASSIFIED.category) return primaryEvent;
|
|
1403
|
+
const isAnyStepRecognised = stepEvents.some((event) => event.category !== UNCLASSIFIED.category);
|
|
1404
|
+
return isAnyStepRecognised ? { ...UNCLASSIFIED, matchedStep: command } : wholeCommandEvent;
|
|
1405
|
+
}
|
|
1406
|
+
var UNCLASSIFIED, STEP_CATEGORY_PRIORITY, OTHER_CATEGORY_RANK, EXCLUDED_CATEGORY_RANK, UNCLASSIFIED_RANK, READ_ONLY_RANK;
|
|
1407
|
+
var init_compound = __esm({
|
|
1408
|
+
"src/normalize/compound.ts"() {
|
|
1409
|
+
"use strict";
|
|
1410
|
+
init_command_steps();
|
|
1411
|
+
init_exclusions();
|
|
1412
|
+
init_read_only();
|
|
1413
|
+
UNCLASSIFIED = { category: "other", metadata: {} };
|
|
1414
|
+
STEP_CATEGORY_PRIORITY = [
|
|
1415
|
+
"testing",
|
|
1416
|
+
"building",
|
|
1417
|
+
"releasing",
|
|
1418
|
+
"shipping",
|
|
1419
|
+
"dependencies",
|
|
1420
|
+
"database",
|
|
1421
|
+
"infrastructure",
|
|
1422
|
+
"running",
|
|
1423
|
+
"files",
|
|
1424
|
+
"versioning"
|
|
1425
|
+
];
|
|
1426
|
+
OTHER_CATEGORY_RANK = STEP_CATEGORY_PRIORITY.length;
|
|
1427
|
+
EXCLUDED_CATEGORY_RANK = OTHER_CATEGORY_RANK + 1;
|
|
1428
|
+
UNCLASSIFIED_RANK = EXCLUDED_CATEGORY_RANK + 1;
|
|
1429
|
+
READ_ONLY_RANK = UNCLASSIFIED_RANK + 1;
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1285
1433
|
// src/normalize/commit-message.ts
|
|
1286
1434
|
function firstLineOf(text) {
|
|
1287
1435
|
const line = text?.split("\n").map((part) => part.trim()).find((part) => part !== "");
|
|
@@ -1435,7 +1583,7 @@ function matchRules(command, context) {
|
|
|
1435
1583
|
function normalizeCommand(command) {
|
|
1436
1584
|
return classifyCompound(command, (simple, raw) => matchRules(simple, { raw, original: command }));
|
|
1437
1585
|
}
|
|
1438
|
-
var NX_TARGET_CATEGORIES, NX_COMMAND, nxRule, BASE_RULES, activeRules;
|
|
1586
|
+
var NX_TARGET_CATEGORIES, NX_COMMAND, nxRule, readOnlyRule, BASE_RULES, activeRules;
|
|
1439
1587
|
var init_patterns = __esm({
|
|
1440
1588
|
"src/normalize/patterns.ts"() {
|
|
1441
1589
|
"use strict";
|
|
@@ -1446,6 +1594,7 @@ var init_patterns = __esm({
|
|
|
1446
1594
|
init_commit_message();
|
|
1447
1595
|
init_dependency_metadata();
|
|
1448
1596
|
init_git_invocation();
|
|
1597
|
+
init_read_only();
|
|
1449
1598
|
NX_TARGET_CATEGORIES = {
|
|
1450
1599
|
build: "building",
|
|
1451
1600
|
test: "testing",
|
|
@@ -1463,7 +1612,13 @@ var init_patterns = __esm({
|
|
|
1463
1612
|
return category ? { category, metadata: { tool: "nx", target } } : null;
|
|
1464
1613
|
}
|
|
1465
1614
|
};
|
|
1615
|
+
readOnlyRule = {
|
|
1616
|
+
match(_cmd, { raw }) {
|
|
1617
|
+
return isReadOnlyCommand(raw) ? { category: READ_ONLY_CATEGORY, metadata: readOnlyMetadata(raw) } : null;
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1466
1620
|
BASE_RULES = [
|
|
1621
|
+
readOnlyRule,
|
|
1467
1622
|
// ─── Git operations ───────────────────────────────────────────────────────
|
|
1468
1623
|
makeRule(/^git\s+commit\b/, "shipping", (_m, _cmd, { raw, original }) => {
|
|
1469
1624
|
const commitMessage = commitMessageOf(raw, original);
|
|
@@ -1648,13 +1803,6 @@ var init_patterns = __esm({
|
|
|
1648
1803
|
tool: "bun",
|
|
1649
1804
|
script: cmd.split(/\s+/)[2]
|
|
1650
1805
|
})),
|
|
1651
|
-
// ─── Code search ─────────────────────────────────────────────────────────
|
|
1652
|
-
makeRule(/^(grep|egrep|fgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
|
|
1653
|
-
makeRule(/^(rg|ag|ack|ripgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
|
|
1654
|
-
makeRule(/^find\s/, "exploring", () => ({ tool: "find" })),
|
|
1655
|
-
// ─── Code read ────────────────────────────────────────────────────────────
|
|
1656
|
-
makeRule(/^(cat|bat|less|more)\s+\S+/, "exploring", (match) => ({ tool: match[1] })),
|
|
1657
|
-
makeRule(/^(head|tail)\b/, "exploring", (match) => ({ tool: match[1] })),
|
|
1658
1806
|
// ─── File operations ──────────────────────────────────────────────────────
|
|
1659
1807
|
makeRule(/^(cp|rsync)\b/, "files", (match) => ({ tool: match[1] })),
|
|
1660
1808
|
makeRule(/^(mv)\b/, "files", () => ({ tool: "mv" })),
|
|
@@ -2635,6 +2783,29 @@ var init_db = __esm({
|
|
|
2635
2783
|
});
|
|
2636
2784
|
|
|
2637
2785
|
// src/capture/redact.ts
|
|
2786
|
+
function sameCommand(maxParts) {
|
|
2787
|
+
const quoted = String.raw`"[^"]{0,${MAX_QUOTED_LENGTH}}"|'[^']{0,${MAX_QUOTED_LENGTH}}'`;
|
|
2788
|
+
return String.raw`(?:(?:${quoted}|\\\n|[^|;&\n"']){0,${maxParts}}?|[^|;&\n]{0,${maxParts}}?)`;
|
|
2789
|
+
}
|
|
2790
|
+
function valueAfter(prefix, flags = "g") {
|
|
2791
|
+
return new RegExp(`(?<=${prefix})${VALUE}`, flags);
|
|
2792
|
+
}
|
|
2793
|
+
function toolFlagValue(tools, flag) {
|
|
2794
|
+
return valueAfter(String.raw`${TOOL_WORD_START}(?:${tools})${TOOL_WORD_END}${SAME_COMMAND}\s${flag}`);
|
|
2795
|
+
}
|
|
2796
|
+
function quotedValuesAfterName(prefix, name, flags = "g") {
|
|
2797
|
+
return [
|
|
2798
|
+
new RegExp(String.raw`(?<=${prefix}"${name})[^"\s][^"\n]*`, flags),
|
|
2799
|
+
new RegExp(String.raw`(?<=${prefix}'${name})[^'\s][^'\n]*`, flags)
|
|
2800
|
+
];
|
|
2801
|
+
}
|
|
2802
|
+
function valuesAfterName(prefix, name, flags = "g") {
|
|
2803
|
+
return [
|
|
2804
|
+
...quotedValuesAfterName(prefix, name, flags),
|
|
2805
|
+
new RegExp(String.raw`(?<=${prefix}${name})(?:${QUOTED})`, flags),
|
|
2806
|
+
new RegExp(String.raw`(?<=${prefix}${name})[^\s"'|;&]+`, flags)
|
|
2807
|
+
];
|
|
2808
|
+
}
|
|
2638
2809
|
function redact(command, extraPatterns = []) {
|
|
2639
2810
|
let result = command;
|
|
2640
2811
|
const allPatterns = [...BUILTIN_PATTERNS];
|
|
@@ -2646,22 +2817,73 @@ function redact(command, extraPatterns = []) {
|
|
|
2646
2817
|
}
|
|
2647
2818
|
for (const pattern of allPatterns) {
|
|
2648
2819
|
pattern.lastIndex = 0;
|
|
2649
|
-
result = result.replace(pattern,
|
|
2820
|
+
result = result.replace(pattern, REDACTED_TEXT);
|
|
2650
2821
|
}
|
|
2651
2822
|
return result;
|
|
2652
2823
|
}
|
|
2653
|
-
var BUILTIN_PATTERNS;
|
|
2824
|
+
var QUOTED, WORD_REST, VALUE, VALUE_OR_DASHED, MAX_COMMAND_PARTS, MAX_QUOTED_LENGTH, MAX_PARTS_BEFORE_LOGIN, MAX_PARTS_AFTER_LOGIN, SAME_COMMAND, NOT_PART_OF_A_WORD, NOT_A_NAME, NOT_IN_A_MESSAGE, TOOL_WORD_START, TOOL_WORD_END, NOT_A_PORT_MAPPING, LONG_SECRET_FLAGS, LONG_FLAG_VALUE_START, MAX_SORT_FIELD_DIGITS, SORT_FIELD, NOT_A_KEY, MAX_SLUG_WORD_LENGTH, MAX_SLUG_WORDS_AFTER_FIRST, SLUG_WORD, UUID_IN_A_KEY, SLUG_AFTER_SK, CONTAINER_LOGIN_TOOLS, EVERYDAY_HEADERS, DISTINCTIVE_HEADERS, HEADER_FLAG, SENSITIVE_VARIABLE_WORDS, QUERY_SECRET_NAME, JSON_SECRET_NAME, BUILTIN_PATTERNS, REDACTED_TEXT;
|
|
2654
2825
|
var init_redact = __esm({
|
|
2655
2826
|
"src/capture/redact.ts"() {
|
|
2656
2827
|
"use strict";
|
|
2828
|
+
QUOTED = String.raw`"[^"]*"|'[^']*'`;
|
|
2829
|
+
WORD_REST = String.raw`(?:[^\s"');&|]|["');&|](?=[^\s;&|)]))*`;
|
|
2830
|
+
VALUE = String.raw`(?:${QUOTED}|[^\s"'\-]${WORD_REST})`;
|
|
2831
|
+
VALUE_OR_DASHED = String.raw`(?:${QUOTED}|[^\s"']${WORD_REST})`;
|
|
2832
|
+
MAX_COMMAND_PARTS = 1e3;
|
|
2833
|
+
MAX_QUOTED_LENGTH = 64e3;
|
|
2834
|
+
MAX_PARTS_BEFORE_LOGIN = 200;
|
|
2835
|
+
MAX_PARTS_AFTER_LOGIN = 300;
|
|
2836
|
+
SAME_COMMAND = sameCommand(MAX_COMMAND_PARTS);
|
|
2837
|
+
NOT_PART_OF_A_WORD = String.raw`(?<![\w.:@-])`;
|
|
2838
|
+
NOT_A_NAME = String.raw`(?<!--name(?:\s+|=)|\bssh\s+)`;
|
|
2839
|
+
NOT_IN_A_MESSAGE = String.raw`(?<!\s(?:-[as]*m|--message|--title|--body|--subject)[\s=]*["'][^"'\n]{0,${MAX_COMMAND_PARTS}})`;
|
|
2840
|
+
TOOL_WORD_START = `${NOT_IN_A_MESSAGE}${NOT_A_NAME}${NOT_PART_OF_A_WORD}`;
|
|
2841
|
+
TOOL_WORD_END = String.raw`(?:\.exe)?["'\x60)]?(?=\s)`;
|
|
2842
|
+
NOT_A_PORT_MAPPING = String.raw`(?!(?:[\d.]+:)?\d+:\d+(?:\/\w+)?(?=\s|$))`;
|
|
2843
|
+
LONG_SECRET_FLAGS = [
|
|
2844
|
+
"password",
|
|
2845
|
+
"(?!no-)[\\w-]*-password",
|
|
2846
|
+
"passwd",
|
|
2847
|
+
"passphrase",
|
|
2848
|
+
"pass",
|
|
2849
|
+
"pwd",
|
|
2850
|
+
"secret(?:-key)?",
|
|
2851
|
+
"client[-_]secret",
|
|
2852
|
+
"token",
|
|
2853
|
+
"access-token",
|
|
2854
|
+
"refresh-token",
|
|
2855
|
+
"auth[-_]?token",
|
|
2856
|
+
"private-key",
|
|
2857
|
+
"api[-_]?key"
|
|
2858
|
+
].join("|");
|
|
2859
|
+
LONG_FLAG_VALUE_START = String.raw`(?:=|\s+(?!--))`;
|
|
2860
|
+
MAX_SORT_FIELD_DIGITS = 2;
|
|
2861
|
+
SORT_FIELD = String.raw`\d{1,${MAX_SORT_FIELD_DIGITS}}`;
|
|
2862
|
+
NOT_A_KEY = String.raw`(?:${SORT_FIELD}(?:[.,]${SORT_FIELD})*|[\w./~-]*\.(?:key|pem|crt|p12|pfx|der)|[\w./~-]*\/id_\w+)(?=\s|$)`;
|
|
2863
|
+
MAX_SLUG_WORD_LENGTH = 15;
|
|
2864
|
+
MAX_SLUG_WORDS_AFTER_FIRST = 8;
|
|
2865
|
+
SLUG_WORD = `[a-z0-9]{1,${MAX_SLUG_WORD_LENGTH}}`;
|
|
2866
|
+
UUID_IN_A_KEY = String.raw`[a-z-]{0,20}[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`;
|
|
2867
|
+
SLUG_AFTER_SK = String.raw`(?!${UUID_IN_A_KEY})${SLUG_WORD}(?:-${SLUG_WORD}){0,${MAX_SLUG_WORDS_AFTER_FIRST}}(?![\w-])`;
|
|
2868
|
+
CONTAINER_LOGIN_TOOLS = "docker|podman|podman-remote|nerdctl|helm";
|
|
2869
|
+
EVERYDAY_HEADERS = "api-key|cookie|set-cookie";
|
|
2870
|
+
DISTINCTIVE_HEADERS = "x-api-key|x-auth-token|x-access-token";
|
|
2871
|
+
HEADER_FLAG = String.raw`(?<![\w-])(?:-\w*H|--(?:proxy-)?header)[\s=]*`;
|
|
2872
|
+
SENSITIVE_VARIABLE_WORDS = "TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL|_PWD";
|
|
2873
|
+
QUERY_SECRET_NAME = String.raw`key|[\w-]*(?:token|secret|password|passwd|pwd|api[-_]?key)`;
|
|
2874
|
+
JSON_SECRET_NAME = String.raw`[\w-]*(?:token|secret|password|passwd|pwd|api[-_]?key)`;
|
|
2657
2875
|
BUILTIN_PATTERNS = [
|
|
2658
2876
|
// AWS access keys
|
|
2659
2877
|
/AKIA[0-9A-Z]{16}/g,
|
|
2660
2878
|
// AWS secret access keys (40 char base64-ish after common env vars)
|
|
2661
|
-
|
|
2879
|
+
new RegExp(String.raw`(?<=AWS_SECRET_ACCESS_KEY[=\s]+)${VALUE_OR_DASHED}`, "gi"),
|
|
2662
2880
|
// GitHub tokens
|
|
2663
|
-
|
|
2881
|
+
/\bgh[pousr]_[a-zA-Z0-9]{36}/g,
|
|
2664
2882
|
/github_pat_[a-zA-Z0-9_]{82}/g,
|
|
2883
|
+
// AI provider, Google and npm keys
|
|
2884
|
+
new RegExp(String.raw`\bsk-(?!${SLUG_AFTER_SK})[\w-]{20,}`, "g"),
|
|
2885
|
+
/\bAIza[a-zA-Z0-9_-]{30,}/g,
|
|
2886
|
+
/\bnpm_[a-zA-Z0-9]{30,}/g,
|
|
2665
2887
|
// GitLab tokens
|
|
2666
2888
|
/glpat-[a-zA-Z0-9\-]{20}/g,
|
|
2667
2889
|
// Generic JWTs (three base64url segments)
|
|
@@ -2671,17 +2893,85 @@ var init_redact = __esm({
|
|
|
2671
2893
|
/https:\/\/hooks\.slack\.com\/services\/[A-Z0-9]+\/[A-Z0-9]+\/[a-zA-Z0-9]+/g,
|
|
2672
2894
|
// Stripe keys
|
|
2673
2895
|
/sk_(live|test)_[a-zA-Z0-9]{24,}/g,
|
|
2674
|
-
// Values after long-form secret flags
|
|
2675
|
-
|
|
2896
|
+
// Values after long-form secret flags, `--flag value` or `--flag=value`. `--password-stdin` isn't one: no match on its
|
|
2897
|
+
// `-stdin`. A bare short flag means nothing without knowing the tool: see below
|
|
2898
|
+
new RegExp(String.raw`(?<=(?<![\w-])--(?:${LONG_SECRET_FLAGS})${LONG_FLAG_VALUE_START})${VALUE_OR_DASHED}`, "gi"),
|
|
2899
|
+
new RegExp(String.raw`(?<=(?<![\w-])--key${LONG_FLAG_VALUE_START})(?!${NOT_A_KEY})${VALUE_OR_DASHED}`, "gi"),
|
|
2900
|
+
// A password attached to the flag: `mysql -pSECRET`. `mysql -p db` prompts, so there's no value to remove, and
|
|
2901
|
+
// `psql -p 5432` is a port, so psql isn't listed
|
|
2902
|
+
toolFlagValue(
|
|
2903
|
+
"mysql|mysqldump|mysqladmin|mysqlimport|mysqlshow|mysqlcheck|mariadb|mariadb-dump|mariadb-admin|mariadb-import|mariadb-show|mariadb-check",
|
|
2904
|
+
"-p"
|
|
2905
|
+
),
|
|
2906
|
+
// Short flags that take a secret, for the tools where they do
|
|
2907
|
+
toolFlagValue("sshpass", String.raw`-p\s*`),
|
|
2908
|
+
toolFlagValue("mongosh|mongo|mongodump|mongorestore|mongoexport|mongoimport", String.raw`-p\s*${NOT_A_PORT_MAPPING}`),
|
|
2909
|
+
toolFlagValue("redis-cli", String.raw`-a\s*`),
|
|
2910
|
+
toolFlagValue("ssh-keygen", String.raw`-[NP]\s*`),
|
|
2911
|
+
toolFlagValue("zip|unzip", String.raw`-P\s*`),
|
|
2912
|
+
valueAfter(
|
|
2913
|
+
String.raw`${TOOL_WORD_START}(?:${CONTAINER_LOGIN_TOOLS})${TOOL_WORD_END}[^|;&\n]{0,${MAX_PARTS_BEFORE_LOGIN}}?\slogin` + String.raw`${TOOL_WORD_END}${sameCommand(MAX_PARTS_AFTER_LOGIN)}\s-p\s*${NOT_A_PORT_MAPPING}`
|
|
2914
|
+
),
|
|
2915
|
+
// openssl's `-pass pass:SECRET`, also -passin and -passout
|
|
2916
|
+
/(?<=\s-pass(?:in|out|word)?\s+pass:)\S+/g,
|
|
2917
|
+
// curl's cookies, which also covers a cookie file's name: `curl -b s=SECRET`
|
|
2918
|
+
valueAfter(String.raw`\bcurl\b["']?${SAME_COMMAND}\s(?:--cookie|-\w*b)[\s=]+`),
|
|
2919
|
+
// The password half of `curl -u user:password`
|
|
2920
|
+
...valuesAfterName(String.raw`\bcurl\b["']?${SAME_COMMAND}\s(?:-\w*u[\s=]*|--user[\s=]+)`, String.raw`[^:\s"']*:`),
|
|
2921
|
+
// Tokens handed over on stdin: `echo SECRET | docker login --password-stdin`, `gh auth login --with-token <<< SECRET`
|
|
2922
|
+
new RegExp(
|
|
2923
|
+
String.raw`(?<=\b(?:echo(?:\s+-[neE]+)*|printf(?:\s+["']?%s(?:\\n)?["']?)?)\s+)${VALUE}` + String.raw`(?=\s*\|[^;&\n]{0,${MAX_COMMAND_PARTS}}?--(?:password-stdin|with-token))`,
|
|
2924
|
+
"g"
|
|
2925
|
+
),
|
|
2926
|
+
valueAfter(String.raw`--(?:password-stdin|with-token)\s*<<<\s*`),
|
|
2927
|
+
// Headers. A scheme or a header flag is required for Authorization, so prose like "Authorization: added middleware"
|
|
2928
|
+
// stays. An everyday word needs to look like a header, so `git commit -m "cookie: rotate on login"` stays: a header
|
|
2929
|
+
// flag, `Cookie:value` with no space, or a quoted `"Cookie: value"` outside a message
|
|
2930
|
+
/(?<=\bauthorization:\s*(?:bearer|basic|token)\s+)[^\s"']+/gi,
|
|
2931
|
+
/(?<=\bbearer\s+)(?=[A-Za-z0-9._~+/=-]*\d)[A-Za-z0-9._~+/=-]{8,}/gi,
|
|
2932
|
+
...valuesAfterName(HEADER_FLAG, String.raw`(?:${EVERYDAY_HEADERS}):\s*`, "gi"),
|
|
2933
|
+
...valuesAfterName(HEADER_FLAG, String.raw`authorization:\s*(?!(?:bearer|basic|token)\s)`, "gi"),
|
|
2934
|
+
...quotedValuesAfterName("", String.raw`${NOT_IN_A_MESSAGE}(?:${EVERYDAY_HEADERS}):\s*`, "gi"),
|
|
2935
|
+
...valuesAfterName("", String.raw`(?<![\w-])(?:${EVERYDAY_HEADERS}):`, "gi"),
|
|
2936
|
+
...valuesAfterName("", String.raw`\b(?:${DISTINCTIVE_HEADERS}):\s*`, "gi"),
|
|
2937
|
+
// Private keys, whole blocks, as heredocs and `echo` can carry them
|
|
2938
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
2939
|
+
// password=... inside a string or query: `psql "host=x password=SECRET"`, `--set db.password=SECRET`
|
|
2940
|
+
/(?<=(?:^|[\s"'?&=.])(?:password|passwd|pwd)=)[^\s"';&]+/gi,
|
|
2941
|
+
// A secret in a query string or in JSON: `curl "x.io?token=SECRET"`, `curl -d '{"password":"SECRET"}'`
|
|
2942
|
+
new RegExp(String.raw`(?<=[?&](?:${QUERY_SECRET_NAME})=)[^\s"'&#]+`, "gi"),
|
|
2943
|
+
new RegExp(String.raw`(?<=["'](?:${JSON_SECRET_NAME})\\?["']\s*:\s*\\?")[^"\\\n]+`, "gi"),
|
|
2944
|
+
// npm's auth settings, with or without `=`: `npm config set //registry/:_password SECRET`
|
|
2945
|
+
/(?<=:_(?:password|auth|authToken)[=\s]+)\S+/g,
|
|
2676
2946
|
// export VAR=VALUE where VAR looks sensitive
|
|
2677
|
-
|
|
2947
|
+
new RegExp(String.raw`(?<=export\s+(?:[A-Z_]*(?:${SENSITIVE_VARIABLE_WORDS})[A-Z_]*)=)${VALUE_OR_DASHED}`, "gi"),
|
|
2678
2948
|
// Inline assignment: VAR_WITH_SENSITIVE_KEYWORD=value cmd (e.g. API_KEY=secret npm start)
|
|
2679
2949
|
// Lookbehind matches only the variable-name= prefix, so only the value is replaced
|
|
2680
|
-
|
|
2950
|
+
new RegExp(String.raw`(?<=[A-Z_][A-Z0-9_]*(?:${SENSITIVE_VARIABLE_WORDS})[A-Z0-9_]*=)${VALUE_OR_DASHED}`, "gi"),
|
|
2951
|
+
// The same with nothing before the keyword: `TOKEN=SECRET npm start`. Capitals only, so `--author=me` and `key=value` stay
|
|
2952
|
+
new RegExp(
|
|
2953
|
+
String.raw`(?<=(?:^|[\s;&|(])(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIALS?)=)${VALUE_OR_DASHED}`,
|
|
2954
|
+
"g"
|
|
2955
|
+
),
|
|
2681
2956
|
// Credentials embedded in connection string URLs (e.g. DATABASE_URL=postgres://user:pass@host/db)
|
|
2682
2957
|
// Matches the password segment between :// user : password @ — preserves scheme and host
|
|
2683
|
-
/(?<=[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:@\s/]
|
|
2958
|
+
/(?<=[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:@\s/]*:)[^@\s]+(?=@)/g,
|
|
2959
|
+
// A token as the whole userinfo of an https URL: `git clone https://TOKEN@github.com/org/repo`
|
|
2960
|
+
/(?<=https?:\/\/)[^\s:@/?#]+(?=@)/g
|
|
2684
2961
|
];
|
|
2962
|
+
REDACTED_TEXT = "[REDACTED]";
|
|
2963
|
+
}
|
|
2964
|
+
});
|
|
2965
|
+
|
|
2966
|
+
// src/capture/hint.ts
|
|
2967
|
+
function commandHint(safeCommand) {
|
|
2968
|
+
return safeCommand.trim().slice(0, HINT_LENGTH);
|
|
2969
|
+
}
|
|
2970
|
+
var HINT_LENGTH;
|
|
2971
|
+
var init_hint = __esm({
|
|
2972
|
+
"src/capture/hint.ts"() {
|
|
2973
|
+
"use strict";
|
|
2974
|
+
HINT_LENGTH = 120;
|
|
2685
2975
|
}
|
|
2686
2976
|
});
|
|
2687
2977
|
|
|
@@ -2853,13 +3143,13 @@ var init_git_snapshot = __esm({
|
|
|
2853
3143
|
});
|
|
2854
3144
|
|
|
2855
3145
|
// src/capture/aider.ts
|
|
2856
|
-
function readLastAiderPrompt(cwd) {
|
|
3146
|
+
function readLastAiderPrompt(cwd, extraRedactions = []) {
|
|
2857
3147
|
try {
|
|
2858
3148
|
const histPath = import_path8.default.join(cwd, HISTORY_FILE);
|
|
2859
3149
|
const content = import_fs10.default.readFileSync(histPath, "utf-8");
|
|
2860
3150
|
const matches = [...content.matchAll(/^#### (.+)$/gm)];
|
|
2861
3151
|
if (!matches.length) return null;
|
|
2862
|
-
return matches[matches.length - 1][1].trim().slice(0, PROMPT_MAX_LENGTH);
|
|
3152
|
+
return redact(matches[matches.length - 1][1].trim(), extraRedactions).slice(0, PROMPT_MAX_LENGTH);
|
|
2863
3153
|
} catch {
|
|
2864
3154
|
return null;
|
|
2865
3155
|
}
|
|
@@ -2870,6 +3160,7 @@ var init_aider = __esm({
|
|
|
2870
3160
|
"use strict";
|
|
2871
3161
|
import_fs10 = __toESM(require("fs"));
|
|
2872
3162
|
import_path8 = __toESM(require("path"));
|
|
3163
|
+
init_redact();
|
|
2873
3164
|
HISTORY_FILE = ".aider.chat.history.md";
|
|
2874
3165
|
PROMPT_MAX_LENGTH = 200;
|
|
2875
3166
|
}
|
|
@@ -2878,7 +3169,7 @@ var init_aider = __esm({
|
|
|
2878
3169
|
// src/capture/workscribe-command.ts
|
|
2879
3170
|
function isWorkscribeStep(step2) {
|
|
2880
3171
|
if (containsCommandSubstitution(step2)) return false;
|
|
2881
|
-
const stages = splitOutsideQuotes(step2,
|
|
3172
|
+
const stages = splitOutsideQuotes(step2, PIPE_SEPARATORS3);
|
|
2882
3173
|
if (startsWithWord(stages[0] ?? "", "workscribe")) return true;
|
|
2883
3174
|
const isTextOrWorkscribe = (stage) => startsWithWord(stage, "workscribe") || TEXT_SOURCES.some((source) => startsWithWord(stage, source));
|
|
2884
3175
|
return stages.every(isTextOrWorkscribe) && stages.some((stage) => startsWithWord(stage, "workscribe"));
|
|
@@ -2888,13 +3179,13 @@ function isWorkscribeOnlyCommand(rawCommand) {
|
|
|
2888
3179
|
const workSteps = steps.filter((step2) => !isGlueStep(step2));
|
|
2889
3180
|
return workSteps.length > 0 && workSteps.every(isWorkscribeStep);
|
|
2890
3181
|
}
|
|
2891
|
-
var
|
|
3182
|
+
var PIPE_SEPARATORS3;
|
|
2892
3183
|
var init_workscribe_command = __esm({
|
|
2893
3184
|
"src/capture/workscribe-command.ts"() {
|
|
2894
3185
|
"use strict";
|
|
2895
3186
|
init_shell_syntax();
|
|
2896
3187
|
init_command_steps();
|
|
2897
|
-
|
|
3188
|
+
PIPE_SEPARATORS3 = ["|"];
|
|
2898
3189
|
}
|
|
2899
3190
|
});
|
|
2900
3191
|
|
|
@@ -3115,7 +3406,7 @@ function captureCommand(opts) {
|
|
|
3115
3406
|
if (Date.now() - lastTime < TASK_RUN_DEDUP_WINDOW_MS) return;
|
|
3116
3407
|
}
|
|
3117
3408
|
}
|
|
3118
|
-
const command2 = opts.command
|
|
3409
|
+
const command2 = opts.command === void 0 ? opts.file ?? opts.type : redact(opts.command, config.redact.extra);
|
|
3119
3410
|
const context2 = getRepoContext(cwd, config);
|
|
3120
3411
|
const { repoName: repoName2, branch: branch2 } = context2;
|
|
3121
3412
|
const ideEvent = {
|
|
@@ -3149,17 +3440,17 @@ function captureCommand(opts) {
|
|
|
3149
3440
|
if (category === "other") {
|
|
3150
3441
|
const firstToken = safeCommand.trim().split(/\s+/)[0];
|
|
3151
3442
|
if (firstToken) metadata.tool = firstToken;
|
|
3152
|
-
metadata.hint = safeCommand
|
|
3443
|
+
metadata.hint = commandHint(safeCommand);
|
|
3153
3444
|
}
|
|
3154
3445
|
if (category === "ai_session") {
|
|
3155
3446
|
if (opts.gitBefore) {
|
|
3156
3447
|
const commits = getCommitsSince(opts.gitBefore, workDirectory);
|
|
3157
|
-
if (commits.length > 0) metadata.commits = commits;
|
|
3448
|
+
if (commits.length > 0) metadata.commits = commits.map((subject) => redact(subject, config.redact.extra));
|
|
3158
3449
|
}
|
|
3159
3450
|
if (config.capture.includeAiderPrompt !== false) {
|
|
3160
3451
|
const toolEntry = loadAIToolRegistry().find((entry) => entry.tool === metadata.tool);
|
|
3161
3452
|
if (toolEntry?.contextSource === "history-file") {
|
|
3162
|
-
const prompt = readLastAiderPrompt(cwd);
|
|
3453
|
+
const prompt = readLastAiderPrompt(cwd, config.redact.extra);
|
|
3163
3454
|
if (prompt) metadata.prompt = prompt;
|
|
3164
3455
|
}
|
|
3165
3456
|
}
|
|
@@ -3192,6 +3483,7 @@ var init_capture = __esm({
|
|
|
3192
3483
|
import_fs14 = __toESM(require("fs"));
|
|
3193
3484
|
init_db();
|
|
3194
3485
|
init_redact();
|
|
3486
|
+
init_hint();
|
|
3195
3487
|
init_git();
|
|
3196
3488
|
init_git_snapshot();
|
|
3197
3489
|
init_aider();
|
|
@@ -4373,10 +4665,10 @@ function installIntoFolder(options) {
|
|
|
4373
4665
|
const outcome = installSkill({
|
|
4374
4666
|
sourceDir: options.sourceDir,
|
|
4375
4667
|
skillsFolder: target.skillsFolder,
|
|
4376
|
-
version: "3.0
|
|
4668
|
+
version: "3.2.0",
|
|
4377
4669
|
force: options.force
|
|
4378
4670
|
});
|
|
4379
|
-
return describeInstallOutcome({ ...target, outcome, version: "3.0
|
|
4671
|
+
return describeInstallOutcome({ ...target, outcome, version: "3.2.0" });
|
|
4380
4672
|
} catch (error) {
|
|
4381
4673
|
return describeInstallFailure({ ...target, error });
|
|
4382
4674
|
}
|
|
@@ -4395,8 +4687,8 @@ function removeSkills(agents, options) {
|
|
|
4395
4687
|
}
|
|
4396
4688
|
function formatAgentSkillLine(agent, homeDir = import_os6.default.homedir()) {
|
|
4397
4689
|
const location = displayPath(import_path19.default.join(homeDir, agent.skillsFolder, SKILL_NAME), homeDir);
|
|
4398
|
-
const installState = readInstallState(import_path19.default.join(homeDir, agent.skillsFolder), "3.0
|
|
4399
|
-
const status = describeSkillStatus(installState, "3.0
|
|
4690
|
+
const installState = readInstallState(import_path19.default.join(homeDir, agent.skillsFolder), "3.2.0");
|
|
4691
|
+
const status = describeSkillStatus(installState, "3.2.0");
|
|
4400
4692
|
return `${agent.name.padEnd(AGENT_NAME_COLUMN_WIDTH)} ${STATUS_COLOURS[status.level](status.text)} ${import_chalk2.default.dim(location)}`;
|
|
4401
4693
|
}
|
|
4402
4694
|
function formatUndetectedAgentLine(agent) {
|
|
@@ -4688,10 +4980,10 @@ async function runAIToolsStep() {
|
|
|
4688
4980
|
}
|
|
4689
4981
|
async function offerSkillInstall(group, sourceDir) {
|
|
4690
4982
|
const target = describeTarget(group);
|
|
4691
|
-
const state = readInstallState(target.skillsFolder, "3.0
|
|
4983
|
+
const state = readInstallState(target.skillsFolder, "3.2.0");
|
|
4692
4984
|
const knownOutcome = outcomeWithoutPrompt(state);
|
|
4693
4985
|
if (knownOutcome) {
|
|
4694
|
-
printMessage(describeInstallOutcome({ ...target, outcome: knownOutcome, version: "3.0
|
|
4986
|
+
printMessage(describeInstallOutcome({ ...target, outcome: knownOutcome, version: "3.2.0" }));
|
|
4695
4987
|
return;
|
|
4696
4988
|
}
|
|
4697
4989
|
const { installSkill: shouldInstall } = await (0, import_prompts2.default)({
|
|
@@ -5668,6 +5960,184 @@ var init_ticket_refs = __esm({
|
|
|
5668
5960
|
}
|
|
5669
5961
|
});
|
|
5670
5962
|
|
|
5963
|
+
// src/db/jobs/scrub-secrets.ts
|
|
5964
|
+
function scrubValue(value, command) {
|
|
5965
|
+
if (command.after.includes(value)) return value;
|
|
5966
|
+
const redacted = redact(value);
|
|
5967
|
+
if (redacted !== value) return redacted;
|
|
5968
|
+
return command.before.includes(value) ? REDACTED_TEXT : value;
|
|
5969
|
+
}
|
|
5970
|
+
function scrubStrings(value, command) {
|
|
5971
|
+
if (typeof value === "string") return scrubValue(value, command);
|
|
5972
|
+
if (Array.isArray(value)) return value.map((item) => scrubStrings(item, command));
|
|
5973
|
+
if (value === null || typeof value !== "object") return value;
|
|
5974
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, scrubStrings(item, command)]));
|
|
5975
|
+
}
|
|
5976
|
+
function hasHint(value) {
|
|
5977
|
+
return typeof value?.hint === "string";
|
|
5978
|
+
}
|
|
5979
|
+
function scrubMetadata(metadata, command) {
|
|
5980
|
+
if (metadata === null) return null;
|
|
5981
|
+
let parsed;
|
|
5982
|
+
try {
|
|
5983
|
+
parsed = JSON.parse(metadata);
|
|
5984
|
+
} catch {
|
|
5985
|
+
return redact(metadata);
|
|
5986
|
+
}
|
|
5987
|
+
const scrubbed = scrubStrings(parsed, command);
|
|
5988
|
+
if (command.after !== command.before && hasHint(scrubbed)) scrubbed.hint = commandHint(command.after);
|
|
5989
|
+
const rewritten = JSON.stringify(scrubbed);
|
|
5990
|
+
return rewritten === JSON.stringify(parsed) ? metadata : rewritten;
|
|
5991
|
+
}
|
|
5992
|
+
function scrubEvent({ command, metadata }) {
|
|
5993
|
+
const scrubbedCommand = redact(command);
|
|
5994
|
+
const scrubbedMetadata = scrubMetadata(metadata, { before: command, after: scrubbedCommand });
|
|
5995
|
+
if (scrubbedCommand === command && scrubbedMetadata === metadata) return null;
|
|
5996
|
+
return { command: scrubbedCommand, metadata: scrubbedMetadata };
|
|
5997
|
+
}
|
|
5998
|
+
function readEvents(db, afterId, limit) {
|
|
5999
|
+
return db.prepare(
|
|
6000
|
+
`SELECT id, command, metadata FROM events
|
|
6001
|
+
WHERE id > ? AND command IS NOT file_path
|
|
6002
|
+
ORDER BY id LIMIT ?`
|
|
6003
|
+
).all(afterId, limit);
|
|
6004
|
+
}
|
|
6005
|
+
function runBatch5(db, afterId, limit) {
|
|
6006
|
+
const events = readEvents(db, afterId, limit);
|
|
6007
|
+
if (events.length === 0) return { lastId: null, issues: [] };
|
|
6008
|
+
const update = db.prepare("UPDATE events SET command = ?, metadata = ? WHERE id = ?");
|
|
6009
|
+
for (const event of events) {
|
|
6010
|
+
const scrubbed = scrubEvent(event);
|
|
6011
|
+
if (scrubbed) update.run(scrubbed.command, scrubbed.metadata, event.id);
|
|
6012
|
+
}
|
|
6013
|
+
return { lastId: events[events.length - 1].id, issues: [] };
|
|
6014
|
+
}
|
|
6015
|
+
function verify4(db) {
|
|
6016
|
+
let checked = 0;
|
|
6017
|
+
let unresolved = 0;
|
|
6018
|
+
let afterId = 0;
|
|
6019
|
+
for (; ; ) {
|
|
6020
|
+
const events = readEvents(db, afterId, VERIFY_PAGE_SIZE);
|
|
6021
|
+
if (events.length === 0) break;
|
|
6022
|
+
checked += events.length;
|
|
6023
|
+
unresolved += events.filter((event) => scrubEvent(event) !== null).length;
|
|
6024
|
+
afterId = events[events.length - 1].id;
|
|
6025
|
+
}
|
|
6026
|
+
const notes = unresolved > 0 ? [`events that would still change: ${unresolved}`] : [];
|
|
6027
|
+
return { checked, resolved: checked - unresolved, unresolved, hasPassed: true, notes };
|
|
6028
|
+
}
|
|
6029
|
+
function cleanUpFile(db) {
|
|
6030
|
+
db.exec(`PRAGMA busy_timeout = ${CLEANUP_LOCK_WAIT_MS}`);
|
|
6031
|
+
try {
|
|
6032
|
+
if (getMetaValue(db, CLEANUP_KEY) === CLEANUP_NEEDS_VACUUM) {
|
|
6033
|
+
db.exec("VACUUM");
|
|
6034
|
+
setMetaValue(db, CLEANUP_KEY, CLEANUP_NEEDS_CHECKPOINT);
|
|
6035
|
+
}
|
|
6036
|
+
const result = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
6037
|
+
if (result?.busy) {
|
|
6038
|
+
debugLog(`data job ${JOB_NAME}: file cleanup`, "left for later, a reader is busy");
|
|
6039
|
+
return;
|
|
6040
|
+
}
|
|
6041
|
+
deleteMetaValue(db, CLEANUP_KEY);
|
|
6042
|
+
} finally {
|
|
6043
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
var JOB_NAME, VERIFY_PAGE_SIZE, CLEANUP_KEY, CLEANUP_NEEDS_VACUUM, CLEANUP_NEEDS_CHECKPOINT, CLEANUP_LOCK_WAIT_MS, scrubSecretsJob;
|
|
6047
|
+
var init_scrub_secrets = __esm({
|
|
6048
|
+
"src/db/jobs/scrub-secrets.ts"() {
|
|
6049
|
+
"use strict";
|
|
6050
|
+
init_transaction();
|
|
6051
|
+
init_store2();
|
|
6052
|
+
init_hint();
|
|
6053
|
+
init_redact();
|
|
6054
|
+
init_logger();
|
|
6055
|
+
JOB_NAME = "scrub-secrets";
|
|
6056
|
+
VERIFY_PAGE_SIZE = 500;
|
|
6057
|
+
CLEANUP_KEY = "scrub-secrets:file-cleanup";
|
|
6058
|
+
CLEANUP_NEEDS_VACUUM = "vacuum";
|
|
6059
|
+
CLEANUP_NEEDS_CHECKPOINT = "checkpoint";
|
|
6060
|
+
CLEANUP_LOCK_WAIT_MS = 200;
|
|
6061
|
+
scrubSecretsJob = {
|
|
6062
|
+
name: JOB_NAME,
|
|
6063
|
+
version: 1,
|
|
6064
|
+
description: "Remove secrets from stored commands and metadata",
|
|
6065
|
+
// Redacting what's already redacted changes nothing, so a rerun only needs the file cleaned again
|
|
6066
|
+
prepare: (db) => setMetaValue(db, CLEANUP_KEY, CLEANUP_NEEDS_VACUUM),
|
|
6067
|
+
runBatch: runBatch5,
|
|
6068
|
+
verify: verify4,
|
|
6069
|
+
cleanup: { isPending: (db) => getMetaValue(db, CLEANUP_KEY) !== null, run: cleanUpFile }
|
|
6070
|
+
};
|
|
6071
|
+
}
|
|
6072
|
+
});
|
|
6073
|
+
|
|
6074
|
+
// src/db/jobs/read-only-commands.ts
|
|
6075
|
+
function parsedObject(metadata) {
|
|
6076
|
+
try {
|
|
6077
|
+
const parsed = JSON.parse(metadata);
|
|
6078
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
6079
|
+
} catch {
|
|
6080
|
+
return null;
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
function movedMetadata({ command, metadata }) {
|
|
6084
|
+
if (metadata === null) return JSON.stringify(readOnlyMetadata(command));
|
|
6085
|
+
const parsed = parsedObject(metadata);
|
|
6086
|
+
if (parsed === null) return metadata;
|
|
6087
|
+
const { hint: _hint, tool: _tool, ...kept } = parsed;
|
|
6088
|
+
return JSON.stringify({ ...kept, ...readOnlyMetadata(command) });
|
|
6089
|
+
}
|
|
6090
|
+
function readCandidates(db, afterId, limit) {
|
|
6091
|
+
return db.prepare(
|
|
6092
|
+
`SELECT id, command, metadata FROM events
|
|
6093
|
+
WHERE id > ? AND category IN (${CATEGORY_PLACEHOLDERS})
|
|
6094
|
+
ORDER BY id LIMIT ?`
|
|
6095
|
+
).all(afterId, ...MOVABLE_CATEGORIES, limit);
|
|
6096
|
+
}
|
|
6097
|
+
function runBatch6(db, afterId, limit) {
|
|
6098
|
+
const events = readCandidates(db, afterId, limit);
|
|
6099
|
+
if (events.length === 0) return { lastId: null, issues: [] };
|
|
6100
|
+
const update = db.prepare("UPDATE events SET category = ?, metadata = ? WHERE id = ?");
|
|
6101
|
+
for (const event of events) {
|
|
6102
|
+
if (isReadOnlyCommand(event.command)) update.run(READ_ONLY_CATEGORY, movedMetadata(event), event.id);
|
|
6103
|
+
}
|
|
6104
|
+
return { lastId: events[events.length - 1].id, issues: [] };
|
|
6105
|
+
}
|
|
6106
|
+
function verify5(db) {
|
|
6107
|
+
let checked = 0;
|
|
6108
|
+
let unresolved = 0;
|
|
6109
|
+
let afterId = 0;
|
|
6110
|
+
for (; ; ) {
|
|
6111
|
+
const events = readCandidates(db, afterId, VERIFY_PAGE_SIZE2);
|
|
6112
|
+
if (events.length === 0) break;
|
|
6113
|
+
checked += events.length;
|
|
6114
|
+
unresolved += events.filter((event) => isReadOnlyCommand(event.command)).length;
|
|
6115
|
+
afterId = events[events.length - 1].id;
|
|
6116
|
+
}
|
|
6117
|
+
const notes = unresolved > 0 ? [`read-only events not moved: ${unresolved}`] : [];
|
|
6118
|
+
return { checked, resolved: checked - unresolved, unresolved, hasPassed: true, notes };
|
|
6119
|
+
}
|
|
6120
|
+
var JOB_NAME2, VERIFY_PAGE_SIZE2, MOVABLE_CATEGORIES, CATEGORY_PLACEHOLDERS, readOnlyCommandsJob;
|
|
6121
|
+
var init_read_only_commands = __esm({
|
|
6122
|
+
"src/db/jobs/read-only-commands.ts"() {
|
|
6123
|
+
"use strict";
|
|
6124
|
+
init_read_only();
|
|
6125
|
+
JOB_NAME2 = "read-only-commands";
|
|
6126
|
+
VERIFY_PAGE_SIZE2 = 500;
|
|
6127
|
+
MOVABLE_CATEGORIES = ["versioning", "other", "files", "running"];
|
|
6128
|
+
CATEGORY_PLACEHOLDERS = MOVABLE_CATEGORIES.map(() => "?").join(", ");
|
|
6129
|
+
readOnlyCommandsJob = {
|
|
6130
|
+
name: JOB_NAME2,
|
|
6131
|
+
version: 1,
|
|
6132
|
+
description: "Move stored read-only commands out of summaries",
|
|
6133
|
+
// Moved rows are simply not candidates any more, so a rerun has nothing to clear first
|
|
6134
|
+
prepare: () => void 0,
|
|
6135
|
+
runBatch: runBatch6,
|
|
6136
|
+
verify: verify5
|
|
6137
|
+
};
|
|
6138
|
+
}
|
|
6139
|
+
});
|
|
6140
|
+
|
|
5671
6141
|
// src/db/jobs/index.ts
|
|
5672
6142
|
var JOBS;
|
|
5673
6143
|
var init_jobs = __esm({
|
|
@@ -5678,10 +6148,14 @@ var init_jobs = __esm({
|
|
|
5678
6148
|
init_summary_anchors();
|
|
5679
6149
|
init_drop_old_sessions();
|
|
5680
6150
|
init_ticket_refs();
|
|
6151
|
+
init_scrub_secrets();
|
|
6152
|
+
init_read_only_commands();
|
|
5681
6153
|
JOBS = [
|
|
5682
6154
|
createProjectDimensionsJob({ probeDirectory }),
|
|
5683
6155
|
summaryAnchorsJob,
|
|
5684
6156
|
ticketRefsJob,
|
|
6157
|
+
scrubSecretsJob,
|
|
6158
|
+
readOnlyCommandsJob,
|
|
5685
6159
|
dropOldSessionsJob
|
|
5686
6160
|
];
|
|
5687
6161
|
}
|
|
@@ -5846,9 +6320,22 @@ function recordFailure(db, job, message) {
|
|
|
5846
6320
|
debugLog(`data job ${job.name}: couldn't record the failure`, error);
|
|
5847
6321
|
}
|
|
5848
6322
|
}
|
|
6323
|
+
function runPendingCleanups(db, jobs) {
|
|
6324
|
+
for (const job of jobs) {
|
|
6325
|
+
if (!job.cleanup || !isJobDone(getJobRow(db, job.name), job)) continue;
|
|
6326
|
+
try {
|
|
6327
|
+
if (job.cleanup.isPending(db)) job.cleanup.run(db);
|
|
6328
|
+
} catch (error) {
|
|
6329
|
+
debugLog(`data job ${job.name}: cleanup`, error);
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
5849
6333
|
function runPendingJobs(db, jobs, options) {
|
|
5850
6334
|
const pending = jobs.filter((job) => !isJobDone(getJobRow(db, job.name), job));
|
|
5851
|
-
if (pending.length === 0)
|
|
6335
|
+
if (pending.length === 0) {
|
|
6336
|
+
runPendingCleanups(db, jobs);
|
|
6337
|
+
return { backup: "not-needed", jobs: [] };
|
|
6338
|
+
}
|
|
5852
6339
|
const backup = options.ensureBackup();
|
|
5853
6340
|
if (backup !== "ready") return { backup, jobs: [] };
|
|
5854
6341
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -5865,6 +6352,7 @@ function runPendingJobs(db, jobs, options) {
|
|
|
5865
6352
|
results.push(result);
|
|
5866
6353
|
if (result.outcome !== "done") break;
|
|
5867
6354
|
}
|
|
6355
|
+
runPendingCleanups(db, jobs);
|
|
5868
6356
|
return { backup: "ready", jobs: results };
|
|
5869
6357
|
}
|
|
5870
6358
|
var DEFAULT_BATCH_SIZE, BATCH_LOCK_WAIT_MS, NEWER_VERSION_ERROR, SupersededError;
|
|
@@ -6565,6 +7053,7 @@ function eventRowToPayload(row) {
|
|
|
6565
7053
|
branch: row.branch,
|
|
6566
7054
|
capturedAt: row.captured_at,
|
|
6567
7055
|
...row.source ? { source: row.source } : {},
|
|
7056
|
+
...row.agent_session_id == null ? {} : { agentSessionId: row.agent_session_id },
|
|
6568
7057
|
...gitMovesField(row)
|
|
6569
7058
|
};
|
|
6570
7059
|
if (row.category !== "other" || typeof metadata.hint !== "string") return payload;
|
|
@@ -7378,8 +7867,52 @@ var init_sessions = __esm({
|
|
|
7378
7867
|
}
|
|
7379
7868
|
});
|
|
7380
7869
|
|
|
7870
|
+
// src/ai/engines/template/utils.ts
|
|
7871
|
+
function pluralize(count, noun) {
|
|
7872
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
7873
|
+
}
|
|
7874
|
+
function stringsIn(value) {
|
|
7875
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
7876
|
+
}
|
|
7877
|
+
function unique(values) {
|
|
7878
|
+
return [...new Set(values)];
|
|
7879
|
+
}
|
|
7880
|
+
function eventsOf(events, category) {
|
|
7881
|
+
return events.filter((event) => event.category === category);
|
|
7882
|
+
}
|
|
7883
|
+
function toBulletList(bullets) {
|
|
7884
|
+
return bullets.map((bullet) => `- ${bullet}`).join("\n");
|
|
7885
|
+
}
|
|
7886
|
+
var init_utils3 = __esm({
|
|
7887
|
+
"src/ai/engines/template/utils.ts"() {
|
|
7888
|
+
"use strict";
|
|
7889
|
+
}
|
|
7890
|
+
});
|
|
7891
|
+
|
|
7892
|
+
// src/ai/engines/template/agents.ts
|
|
7893
|
+
function joinNames(names) {
|
|
7894
|
+
if (names.length <= 1) return names.join("");
|
|
7895
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
7896
|
+
}
|
|
7897
|
+
function describeAgents(events) {
|
|
7898
|
+
const agentEvents = events.filter((event) => event.source !== void 0 && AGENT_NAMES.has(event.source));
|
|
7899
|
+
if (agentEvents.length === 0) return null;
|
|
7900
|
+
const names = [...new Set(agentEvents.map((event) => AGENT_NAMES.get(event.source)))];
|
|
7901
|
+
const sessionIds = new Set(agentEvents.flatMap((event) => event.agentSessionId ?? []));
|
|
7902
|
+
return sessionIds.size > 0 ? `${joinNames(names)} (${pluralize(sessionIds.size, "session")})` : joinNames(names);
|
|
7903
|
+
}
|
|
7904
|
+
var AGENT_NAMES;
|
|
7905
|
+
var init_agents2 = __esm({
|
|
7906
|
+
"src/ai/engines/template/agents.ts"() {
|
|
7907
|
+
"use strict";
|
|
7908
|
+
init_ai_tools();
|
|
7909
|
+
init_utils3();
|
|
7910
|
+
AGENT_NAMES = new Map(ai_tools_default.flatMap((tool) => tool.hook ? [[tool.hook, tool.displayName]] : []));
|
|
7911
|
+
}
|
|
7912
|
+
});
|
|
7913
|
+
|
|
7381
7914
|
// src/ai/engines/template/constants.ts
|
|
7382
|
-
var MAX_SESSION_BULLETS, MAX_COMMIT_BULLETS, MAX_DAY_BULLETS, MAX_LISTED_PACKAGES,
|
|
7915
|
+
var MAX_SESSION_BULLETS, MAX_COMMIT_BULLETS, MAX_DAY_BULLETS, MAX_LISTED_PACKAGES, TEST_WORDING, BUILD_WORDING, STORY_TEST_WORDING, STORY_BUILD_WORDING;
|
|
7383
7916
|
var init_constants2 = __esm({
|
|
7384
7917
|
"src/ai/engines/template/constants.ts"() {
|
|
7385
7918
|
"use strict";
|
|
@@ -7387,7 +7920,6 @@ var init_constants2 = __esm({
|
|
|
7387
7920
|
MAX_COMMIT_BULLETS = 3;
|
|
7388
7921
|
MAX_DAY_BULLETS = 5;
|
|
7389
7922
|
MAX_LISTED_PACKAGES = 6;
|
|
7390
|
-
AGENT_SOURCES = ["claude-code"];
|
|
7391
7923
|
TEST_WORDING = {
|
|
7392
7924
|
category: "testing",
|
|
7393
7925
|
resolved: "Resolved failing tests",
|
|
@@ -7416,28 +7948,6 @@ var init_constants2 = __esm({
|
|
|
7416
7948
|
}
|
|
7417
7949
|
});
|
|
7418
7950
|
|
|
7419
|
-
// src/ai/engines/template/utils.ts
|
|
7420
|
-
function pluralize(count, noun) {
|
|
7421
|
-
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
7422
|
-
}
|
|
7423
|
-
function stringsIn(value) {
|
|
7424
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
7425
|
-
}
|
|
7426
|
-
function unique(values) {
|
|
7427
|
-
return [...new Set(values)];
|
|
7428
|
-
}
|
|
7429
|
-
function eventsOf(events, category) {
|
|
7430
|
-
return events.filter((event) => event.category === category);
|
|
7431
|
-
}
|
|
7432
|
-
function toBulletList(bullets) {
|
|
7433
|
-
return bullets.map((bullet) => `- ${bullet}`).join("\n");
|
|
7434
|
-
}
|
|
7435
|
-
var init_utils3 = __esm({
|
|
7436
|
-
"src/ai/engines/template/utils.ts"() {
|
|
7437
|
-
"use strict";
|
|
7438
|
-
}
|
|
7439
|
-
});
|
|
7440
|
-
|
|
7441
7951
|
// src/ai/engines/template/describe.ts
|
|
7442
7952
|
function describeNotes(events) {
|
|
7443
7953
|
return eventsOf(events, "note").flatMap((event) => stringsIn([event.metadata.text]).map((text) => `Note: ${text}`));
|
|
@@ -7509,8 +8019,8 @@ function describePushes(events) {
|
|
|
7509
8019
|
return targets.length > 0 ? `Pushed to ${targets.join(", ")}` : null;
|
|
7510
8020
|
}
|
|
7511
8021
|
function describeAgentWork(events) {
|
|
7512
|
-
const
|
|
7513
|
-
return
|
|
8022
|
+
const agents = describeAgents(events);
|
|
8023
|
+
return agents === null ? null : `Worked with ${agents}`;
|
|
7514
8024
|
}
|
|
7515
8025
|
function describeEdits(events) {
|
|
7516
8026
|
const saves = eventsOf(events, "code_edit").length;
|
|
@@ -7542,6 +8052,7 @@ var init_describe = __esm({
|
|
|
7542
8052
|
"use strict";
|
|
7543
8053
|
init_commits();
|
|
7544
8054
|
init_exclusions();
|
|
8055
|
+
init_agents2();
|
|
7545
8056
|
init_constants2();
|
|
7546
8057
|
init_utils3();
|
|
7547
8058
|
}
|
|
@@ -7681,12 +8192,10 @@ function branchBullets(group) {
|
|
|
7681
8192
|
].filter((bullet) => bullet !== null);
|
|
7682
8193
|
}
|
|
7683
8194
|
function describeActivityLine(allEvents, events) {
|
|
7684
|
-
const
|
|
7685
|
-
(event) => event.source !== void 0 && AGENT_SOURCES.includes(event.source)
|
|
7686
|
-
).length;
|
|
8195
|
+
const agents = describeAgents(allEvents);
|
|
7687
8196
|
const saves = eventsOf(events, "code_edit").length;
|
|
7688
8197
|
const parts = [
|
|
7689
|
-
|
|
8198
|
+
agents === null ? null : `With ${agents}`,
|
|
7690
8199
|
saves > 0 ? `${pluralize(saves, "file save")} in the editor` : null
|
|
7691
8200
|
].filter((part) => part !== null);
|
|
7692
8201
|
return parts.length > 0 ? parts.join(" \xB7 ") : null;
|
|
@@ -7731,6 +8240,7 @@ var init_story = __esm({
|
|
|
7731
8240
|
"src/ai/engines/template/story.ts"() {
|
|
7732
8241
|
"use strict";
|
|
7733
8242
|
init_heading();
|
|
8243
|
+
init_agents2();
|
|
7734
8244
|
init_constants2();
|
|
7735
8245
|
init_describe();
|
|
7736
8246
|
init_branch_moves();
|
|
@@ -8120,6 +8630,28 @@ var init_day = __esm({
|
|
|
8120
8630
|
}
|
|
8121
8631
|
});
|
|
8122
8632
|
|
|
8633
|
+
// src/summary/output-format.ts
|
|
8634
|
+
function keepsStdoutClean(format) {
|
|
8635
|
+
return format === "json" || format === "text";
|
|
8636
|
+
}
|
|
8637
|
+
var SUMMARY_FORMATS;
|
|
8638
|
+
var init_output_format = __esm({
|
|
8639
|
+
"src/summary/output-format.ts"() {
|
|
8640
|
+
"use strict";
|
|
8641
|
+
SUMMARY_FORMATS = ["markdown", "json", "text"];
|
|
8642
|
+
}
|
|
8643
|
+
});
|
|
8644
|
+
|
|
8645
|
+
// src/summary/plain-text.ts
|
|
8646
|
+
function toPlainText(markdown) {
|
|
8647
|
+
return markdown.replace(/^#{1,6} +/gm, "").replace(/^([ \t]*)[*•] +/gm, "$1- ").replace(/\*\*([^*\n]+)\*\*/g, "$1").replace(/`([^`\n]+)`/g, "$1").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, "$1 ($2)").trim();
|
|
8648
|
+
}
|
|
8649
|
+
var init_plain_text = __esm({
|
|
8650
|
+
"src/summary/plain-text.ts"() {
|
|
8651
|
+
"use strict";
|
|
8652
|
+
}
|
|
8653
|
+
});
|
|
8654
|
+
|
|
8123
8655
|
// src/cli/day-notes.ts
|
|
8124
8656
|
function printExcludedFooter(sessionPayloads, excluded) {
|
|
8125
8657
|
const excludedCounts = countExcludedEvents(sessionPayloads, excluded);
|
|
@@ -8195,10 +8727,11 @@ async function runWeeklySummary({ options, config, provider, loadDay }) {
|
|
|
8195
8727
|
}
|
|
8196
8728
|
const payload = { weekStart, weekEnd: today, by: options.by ?? "project", days };
|
|
8197
8729
|
const summary = await writeWeeklySummary(provider, payload);
|
|
8198
|
-
const
|
|
8199
|
-
if (
|
|
8730
|
+
const format = options.format ?? config.summary?.format;
|
|
8731
|
+
if (format === "json") log.plain(weekJson(payload, config.summary?.includeCategories ?? []));
|
|
8732
|
+
else if (format === "text") log.plain(toPlainText(summary));
|
|
8200
8733
|
else printSummary(summary);
|
|
8201
|
-
await postSummary({ options, config, summary, statusToStderr:
|
|
8734
|
+
await postSummary({ options, config, summary, statusToStderr: keepsStdoutClean(format) });
|
|
8202
8735
|
}
|
|
8203
8736
|
var WEEK_GIT_BUDGET_MS;
|
|
8204
8737
|
var init_weekly = __esm({
|
|
@@ -8209,6 +8742,8 @@ var init_weekly = __esm({
|
|
|
8209
8742
|
init_payload();
|
|
8210
8743
|
init_webhook2();
|
|
8211
8744
|
init_day_commits();
|
|
8745
|
+
init_output_format();
|
|
8746
|
+
init_plain_text();
|
|
8212
8747
|
init_week();
|
|
8213
8748
|
WEEK_GIT_BUDGET_MS = DAY_GIT_BUDGET_MS;
|
|
8214
8749
|
}
|
|
@@ -8224,14 +8759,24 @@ function parseWeekLayout(value) {
|
|
|
8224
8759
|
if (!layout) fail("--by must be project or day");
|
|
8225
8760
|
return layout;
|
|
8226
8761
|
}
|
|
8762
|
+
function addSummaryFormatOption(command) {
|
|
8763
|
+
return command.option("--format <format>", "print as markdown, json or text, overriding summary.format");
|
|
8764
|
+
}
|
|
8765
|
+
function parseSummaryFormat(value) {
|
|
8766
|
+
if (value === void 0) return void 0;
|
|
8767
|
+
const format = SUMMARY_FORMATS.find((candidate) => candidate === value);
|
|
8768
|
+
if (!format) fail("--format must be markdown, json or text");
|
|
8769
|
+
return format;
|
|
8770
|
+
}
|
|
8227
8771
|
async function runWeekCommand(options) {
|
|
8228
8772
|
const by = parseWeekLayout(options.by);
|
|
8773
|
+
const format = parseSummaryFormat(options.format);
|
|
8229
8774
|
const config = readConfig();
|
|
8230
8775
|
printLegacyAiNotice(config);
|
|
8231
8776
|
requireThreadData();
|
|
8232
8777
|
try {
|
|
8233
8778
|
await runWeeklySummary({
|
|
8234
|
-
options: { ...options, by },
|
|
8779
|
+
options: { ...options, by, format },
|
|
8235
8780
|
config,
|
|
8236
8781
|
provider: createSummaryWriter(config),
|
|
8237
8782
|
loadDay: loadDayThreads
|
|
@@ -8242,13 +8787,14 @@ async function runWeekCommand(options) {
|
|
|
8242
8787
|
}
|
|
8243
8788
|
function registerWeek(program) {
|
|
8244
8789
|
const weekCommand = program.command("week").description("Summarize the current week (Mon\u2013today) \u2014 the same as summary --week").option("--fresh", "accepted for 1.x scripts \u2014 the week is always rendered fresh").option("--slack", "post to Slack webhook from config").option("--webhook <url>", "post to a custom webhook URL");
|
|
8245
|
-
addWeekLayoutOption(weekCommand).action(runWeekCommand);
|
|
8790
|
+
addSummaryFormatOption(addWeekLayoutOption(weekCommand)).action(runWeekCommand);
|
|
8246
8791
|
}
|
|
8247
8792
|
var WEEK_LAYOUTS;
|
|
8248
8793
|
var init_week2 = __esm({
|
|
8249
8794
|
"src/cli/week.ts"() {
|
|
8250
8795
|
"use strict";
|
|
8251
8796
|
init_config();
|
|
8797
|
+
init_output_format();
|
|
8252
8798
|
init_weekly();
|
|
8253
8799
|
init_writer();
|
|
8254
8800
|
init_notices();
|
|
@@ -8293,8 +8839,9 @@ function printCapturedEvents(sessionPayloads, excluded) {
|
|
|
8293
8839
|
}
|
|
8294
8840
|
function registerSummary(program) {
|
|
8295
8841
|
const summaryCommand = program.command("summary").description("Generate a work summary").option("--date <YYYY-MM-DD>", "date to summarize (default: today)").option("--week", "summarize the current week").option("--fresh", "ignore saved summaries and render the template").option("--verbose", "show all captured events, including those left out of the summary").option("--slack", "post summary to Slack webhook from config").option("--webhook <url>", "post summary to a custom webhook URL");
|
|
8296
|
-
addWeekLayoutOption(summaryCommand).action(async (options) => {
|
|
8842
|
+
addSummaryFormatOption(addWeekLayoutOption(summaryCommand)).action(async (options) => {
|
|
8297
8843
|
if (options.by !== void 0 && !options.week) fail("--by only applies to --week");
|
|
8844
|
+
const formatOption = parseSummaryFormat(options.format);
|
|
8298
8845
|
if (options.week) {
|
|
8299
8846
|
await runWeekCommand(options);
|
|
8300
8847
|
return;
|
|
@@ -8317,7 +8864,8 @@ function registerSummary(program) {
|
|
|
8317
8864
|
log.plain(`No activity captured for ${date}`);
|
|
8318
8865
|
return;
|
|
8319
8866
|
}
|
|
8320
|
-
const
|
|
8867
|
+
const format = formatOption ?? config.summary?.format;
|
|
8868
|
+
const keepsStdoutToOutput = keepsStdoutClean(format);
|
|
8321
8869
|
let day;
|
|
8322
8870
|
try {
|
|
8323
8871
|
day = await renderDay({ date, blocks, config, provider, fresh: options.fresh });
|
|
@@ -8328,11 +8876,13 @@ function registerSummary(program) {
|
|
|
8328
8876
|
const { sessionPayloads, saved, fullSummary, needsGeneration } = day;
|
|
8329
8877
|
const excluded = buildExcludedSet(config.summary?.includeCategories ?? []);
|
|
8330
8878
|
if (needsGeneration && options.verbose) {
|
|
8331
|
-
if (
|
|
8879
|
+
if (keepsStdoutToOutput) log.notice(`--verbose is skipped with the ${format} format, to keep the output clean`);
|
|
8332
8880
|
else printCapturedEvents(sessionPayloads, excluded);
|
|
8333
8881
|
}
|
|
8334
|
-
if (
|
|
8882
|
+
if (format === "json") {
|
|
8335
8883
|
log.plain(toJsonOutput(day, date));
|
|
8884
|
+
} else if (format === "text") {
|
|
8885
|
+
log.plain(toPlainText(fullSummary));
|
|
8336
8886
|
} else {
|
|
8337
8887
|
log.plain("");
|
|
8338
8888
|
log.plain(fullSummary);
|
|
@@ -8340,7 +8890,7 @@ function registerSummary(program) {
|
|
|
8340
8890
|
printSavedNote(saved);
|
|
8341
8891
|
if (needsGeneration) printExcludedFooter(sessionPayloads, excluded);
|
|
8342
8892
|
}
|
|
8343
|
-
await postSummary({ options, config, summary: fullSummary, statusToStderr:
|
|
8893
|
+
await postSummary({ options, config, summary: fullSummary, statusToStderr: keepsStdoutToOutput });
|
|
8344
8894
|
});
|
|
8345
8895
|
}
|
|
8346
8896
|
var import_chalk8;
|
|
@@ -8357,6 +8907,8 @@ var init_summary = __esm({
|
|
|
8357
8907
|
init_notices();
|
|
8358
8908
|
init_webhook2();
|
|
8359
8909
|
init_day();
|
|
8910
|
+
init_output_format();
|
|
8911
|
+
init_plain_text();
|
|
8360
8912
|
init_day_notes();
|
|
8361
8913
|
init_thread_day();
|
|
8362
8914
|
init_week2();
|
|
@@ -8980,7 +9532,8 @@ var init_profile = __esm({
|
|
|
8980
9532
|
function registerNote(program) {
|
|
8981
9533
|
program.command("note <text>").description("Attach a free-text annotation to the current session").action((text) => {
|
|
8982
9534
|
const directory = currentDirectory();
|
|
8983
|
-
const
|
|
9535
|
+
const config = readConfig();
|
|
9536
|
+
const context = getRepoContext(directory, config);
|
|
8984
9537
|
const { repoName, branch } = context;
|
|
8985
9538
|
insertEvent({
|
|
8986
9539
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8991,7 +9544,7 @@ function registerNote(program) {
|
|
|
8991
9544
|
repo_name: repoName,
|
|
8992
9545
|
branch,
|
|
8993
9546
|
category: "note",
|
|
8994
|
-
metadata: JSON.stringify({ text }),
|
|
9547
|
+
metadata: JSON.stringify({ text: redact(text, config.redact.extra) }),
|
|
8995
9548
|
source: null,
|
|
8996
9549
|
file_path: null,
|
|
8997
9550
|
dimensions: eventDimensions(context)
|
|
@@ -9005,6 +9558,7 @@ var init_note = __esm({
|
|
|
9005
9558
|
init_db();
|
|
9006
9559
|
init_git();
|
|
9007
9560
|
init_dimensions3();
|
|
9561
|
+
init_redact();
|
|
9008
9562
|
init_config();
|
|
9009
9563
|
init_working_directory();
|
|
9010
9564
|
init_logger();
|
|
@@ -9034,7 +9588,7 @@ function registerBugReport(program) {
|
|
|
9034
9588
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9035
9589
|
const reportPath = import_path30.default.join(WORKSCRIBE_DIR, `bug-report-${timestamp}.txt`);
|
|
9036
9590
|
const systemInfo = [
|
|
9037
|
-
`Workscribe version : ${"3.0
|
|
9591
|
+
`Workscribe version : ${"3.2.0"}`,
|
|
9038
9592
|
`Node.js version : ${process.version}`,
|
|
9039
9593
|
`Platform : ${process.platform}`,
|
|
9040
9594
|
`Architecture : ${process.arch}`,
|
|
@@ -9065,7 +9619,7 @@ function registerBugReport(program) {
|
|
|
9065
9619
|
process.exit(1);
|
|
9066
9620
|
}
|
|
9067
9621
|
const params = new URLSearchParams({
|
|
9068
|
-
title: `Bug report \u2014 workscribe ${"3.0
|
|
9622
|
+
title: `Bug report \u2014 workscribe ${"3.2.0"}`,
|
|
9069
9623
|
body: `<!-- Attach ${import_path30.default.basename(reportPath)} or paste the relevant sections below -->
|
|
9070
9624
|
|
|
9071
9625
|
**Describe the issue:**
|
|
@@ -9156,7 +9710,7 @@ function reportRun(result) {
|
|
|
9156
9710
|
function registerUpgrade(program) {
|
|
9157
9711
|
program.command("upgrade").description("Bring your stored data up to date after updating Workscribe (backs it up first)").action(() => {
|
|
9158
9712
|
const announced = /* @__PURE__ */ new Set();
|
|
9159
|
-
const result = runDataJobs("3.0
|
|
9713
|
+
const result = runDataJobs("3.2.0", {
|
|
9160
9714
|
onProgress: ({ job }) => {
|
|
9161
9715
|
if (announced.has(job.name)) return;
|
|
9162
9716
|
announced.add(job.name);
|
|
@@ -9646,7 +10200,7 @@ function isNewer(latest, current) {
|
|
|
9646
10200
|
return latestPatch > currentPatch;
|
|
9647
10201
|
}
|
|
9648
10202
|
function checkForUpdate() {
|
|
9649
|
-
const current = "3.0
|
|
10203
|
+
const current = "3.2.0";
|
|
9650
10204
|
const cache = readCache();
|
|
9651
10205
|
if (cache && isNewer(cache.latestVersion, current)) {
|
|
9652
10206
|
process.stderr.write(
|
|
@@ -9669,7 +10223,7 @@ function checkForUpdate() {
|
|
|
9669
10223
|
function buildTelemetryPayload(config) {
|
|
9670
10224
|
return {
|
|
9671
10225
|
installId: config.telemetry.installId,
|
|
9672
|
-
cliVersion: "3.0
|
|
10226
|
+
cliVersion: "3.2.0",
|
|
9673
10227
|
platform: import_os12.default.platform(),
|
|
9674
10228
|
nodeVersion: process.version
|
|
9675
10229
|
};
|
|
@@ -9747,10 +10301,10 @@ var require_main = __commonJS({
|
|
|
9747
10301
|
pingTelemetry();
|
|
9748
10302
|
}
|
|
9749
10303
|
if (shouldRunDataJobs(process.argv.slice(2))) {
|
|
9750
|
-
runDataJobsBeforeCommand(invokedCommand, "3.0
|
|
10304
|
+
runDataJobsBeforeCommand(invokedCommand, "3.2.0");
|
|
9751
10305
|
}
|
|
9752
10306
|
var program = new import_commander.Command();
|
|
9753
|
-
program.name("workscribe").description("Automatic developer work journal").version("3.0
|
|
10307
|
+
program.name("workscribe").description("Automatic developer work journal").version("3.2.0");
|
|
9754
10308
|
registerCapture(program);
|
|
9755
10309
|
registerHook(program);
|
|
9756
10310
|
registerInit(program);
|