@wrongstack/plugins 1.0.8 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accessibility-auditor/index.d.ts +1 -1
- package/dist/accessibility-auditor.js +15 -3
- package/dist/agent-handoff.js +6 -6
- package/dist/auto-doc/index.d.ts +1 -1
- package/dist/auto-doc.js +31 -20
- package/dist/auto-i18n-extractor/index.d.ts +1 -1
- package/dist/auto-i18n-extractor.js +12 -8
- package/dist/branch-guard.js +3 -3
- package/dist/changelog-writer/index.d.ts +1 -1
- package/dist/changelog-writer.js +19 -10
- package/dist/checkpoint/index.d.ts +1 -1
- package/dist/checkpoint.js +38 -24
- package/dist/code-metrics/index.d.ts +1 -1
- package/dist/code-metrics.js +12 -4
- package/dist/commit-validator.js +5 -5
- package/dist/context-pins/index.d.ts +1 -1
- package/dist/context-pins.js +12 -9
- package/dist/cost-tracker.js +21 -9
- package/dist/cron/index.d.ts +1 -1
- package/dist/cron.js +18 -5
- package/dist/dead-code-detector/index.d.ts +1 -1
- package/dist/dead-code-detector.js +14 -12
- package/dist/duplicate-code-detector/index.d.ts +1 -1
- package/dist/duplicate-code-detector.js +11 -3
- package/dist/feature-flag-tracker/index.d.ts +1 -1
- package/dist/feature-flag-tracker.js +12 -4
- package/dist/file-watcher/index.d.ts +1 -1
- package/dist/file-watcher.js +37 -38
- package/dist/git-autocommit/index.d.ts +1 -1
- package/dist/git-autocommit.js +44 -39
- package/dist/gitignore-guard/index.d.ts +1 -1
- package/dist/gitignore-guard.js +12 -6
- package/dist/index.js +1553 -1086
- package/dist/interface-contract-guard/index.d.ts +1 -1
- package/dist/interface-contract-guard.js +12 -4
- package/dist/knowledge-graph/index.d.ts +1 -1
- package/dist/knowledge-graph.js +11 -9
- package/dist/loop-breaker.js +11 -4
- package/dist/migration-planner/index.d.ts +1 -1
- package/dist/migration-planner.js +6 -2
- package/dist/notify-hub/index.d.ts +1 -1
- package/dist/notify-hub.js +19 -12
- package/dist/performance-regression-gate/index.d.ts +1 -1
- package/dist/performance-regression-gate.js +33 -13
- package/dist/pr-drafter/index.d.ts +10 -1
- package/dist/pr-drafter.js +57 -26
- package/dist/refactor-suggester/index.d.ts +1 -1
- package/dist/refactor-suggester.js +17 -4
- package/dist/release-notes-generator.js +2 -2
- package/dist/secret-scanner/index.d.ts +1 -1
- package/dist/secret-scanner.js +11 -3
- package/dist/security-hotspot-scanner/index.d.ts +1 -1
- package/dist/security-hotspot-scanner.js +6 -2
- package/dist/semantic-search-indexer/index.d.ts +1 -1
- package/dist/semantic-search-indexer.js +19 -3
- package/dist/semver-bump/index.d.ts +1 -1
- package/dist/semver-bump.js +57 -37
- package/dist/session-recap.js +4 -2
- package/dist/shell-check/index.d.ts +1 -1
- package/dist/shell-check.js +30 -39
- package/dist/smart-rename/index.d.ts +1 -1
- package/dist/smart-rename.js +23 -10
- package/dist/template-engine/index.d.ts +1 -1
- package/dist/template-engine.js +51 -38
- package/dist/test-flake-detector/index.d.ts +1 -1
- package/dist/test-flake-detector.js +11 -5
- package/dist/test-generator/index.d.ts +1 -1
- package/dist/test-generator.js +12 -8
- package/dist/todo-tracker/index.d.ts +68 -1
- package/dist/todo-tracker.js +579 -395
- package/dist/token-budget.js +7 -4
- package/dist/token-throttle.js +6 -3
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -13,8 +13,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
13
13
|
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
14
14
|
|
|
15
15
|
// src/accessibility-auditor/index.ts
|
|
16
|
-
import { readFile } from "node:fs/promises";
|
|
16
|
+
import { readFile, stat } from "node:fs/promises";
|
|
17
17
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
18
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
18
19
|
|
|
19
20
|
// src/runtime/index.ts
|
|
20
21
|
var runtime_exports = {};
|
|
@@ -53,6 +54,13 @@ function readConfig(raw) {
|
|
|
53
54
|
onWriteEdit: (r["onWriteEdit"] ?? r["on_write_edit"] ?? r["onSave"]) !== false
|
|
54
55
|
};
|
|
55
56
|
}
|
|
57
|
+
async function assertPathExists(rawPath) {
|
|
58
|
+
try {
|
|
59
|
+
await stat(resolve(process.cwd(), rawPath));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
throw new Error(`path not found: ${rawPath}`, { cause: err });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
56
64
|
function normalizeExtensions(exts) {
|
|
57
65
|
return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
58
66
|
}
|
|
@@ -381,12 +389,16 @@ var plugin = {
|
|
|
381
389
|
category: "Diagnostics",
|
|
382
390
|
mutating: false,
|
|
383
391
|
async execute(input) {
|
|
384
|
-
if (!cfg.enabled)
|
|
392
|
+
if (!cfg.enabled) throw new Error("accessibility-auditor is disabled");
|
|
385
393
|
const raw = input;
|
|
386
394
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
|
|
387
395
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
388
|
-
|
|
396
|
+
throw new ToolValidationError({
|
|
397
|
+
message: "path must be inside the project",
|
|
398
|
+
field: "path"
|
|
399
|
+
});
|
|
389
400
|
}
|
|
401
|
+
await assertPathExists(rawPath);
|
|
390
402
|
state.auditCount += 1;
|
|
391
403
|
const result = await auditPath(rawPath, cfg);
|
|
392
404
|
state.fileCount += result.fileCount;
|
|
@@ -701,8 +713,8 @@ var plugin2 = {
|
|
|
701
713
|
category: "Coordination",
|
|
702
714
|
mutating: false,
|
|
703
715
|
async execute(input) {
|
|
704
|
-
if (!cfg.enabled)
|
|
705
|
-
if (!mailbox)
|
|
716
|
+
if (!cfg.enabled) throw new Error("agent-handoff is disabled");
|
|
717
|
+
if (!mailbox) throw new Error("mailbox not available");
|
|
706
718
|
const raw = input;
|
|
707
719
|
const summary = (typeof raw["summary"] === "string" ? raw["summary"] : void 0) ?? (typeof raw["note"] === "string" ? raw["note"] : void 0) ?? (typeof raw["message"] === "string" ? raw["message"] : void 0) ?? (typeof raw["content"] === "string" ? raw["content"] : void 0) ?? (typeof raw["text"] === "string" ? raw["text"] : void 0) ?? (typeof raw["body"] === "string" ? raw["body"] : void 0);
|
|
708
720
|
const task = (typeof raw["task"] === "string" ? raw["task"] : void 0) ?? (typeof raw["title"] === "string" ? raw["title"] : void 0) ?? (typeof raw["subject"] === "string" ? raw["subject"] : void 0);
|
|
@@ -729,10 +741,10 @@ var plugin2 = {
|
|
|
729
741
|
return { ok: true, messageId: result.id, to: recipient };
|
|
730
742
|
} catch (err) {
|
|
731
743
|
state2.errorCount += 1;
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
744
|
+
throw new Error(
|
|
745
|
+
`handoff_note: mailbox send to "${recipient}" failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
746
|
+
{ cause: err }
|
|
747
|
+
);
|
|
736
748
|
}
|
|
737
749
|
}
|
|
738
750
|
});
|
|
@@ -1171,6 +1183,7 @@ var api_compatibility_gate_default = plugin3;
|
|
|
1171
1183
|
|
|
1172
1184
|
// src/auto-doc/index.ts
|
|
1173
1185
|
import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
|
|
1186
|
+
import { ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
|
|
1174
1187
|
var AUTO_DOC_API_VERSION = "^0.1.10";
|
|
1175
1188
|
function resolveProjectPath(rawPath, cwd = process.cwd()) {
|
|
1176
1189
|
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
@@ -1334,41 +1347,37 @@ function injectDocComment(content, entity, doc) {
|
|
|
1334
1347
|
async function runAutoDoc(input, api) {
|
|
1335
1348
|
const rawInput = input;
|
|
1336
1349
|
if (rawInput["files"] !== void 0 && !Array.isArray(rawInput["files"])) {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
changes: []
|
|
1342
|
-
};
|
|
1350
|
+
throw new ToolValidationError2({
|
|
1351
|
+
message: "input.files must be an array of file paths",
|
|
1352
|
+
field: "files"
|
|
1353
|
+
});
|
|
1343
1354
|
}
|
|
1344
1355
|
const rawFiles = rawInput["files"] ?? rawInput["file"] ?? rawInput["path"] ?? rawInput["filePath"] ?? rawInput["TargetFile"] ?? rawInput["targetFile"];
|
|
1345
1356
|
const files = Array.isArray(rawFiles) ? rawFiles.filter((f) => typeof f === "string" && f.trim().length > 0).map((f) => f.trim()) : typeof rawFiles === "string" && rawFiles.trim().length > 0 ? [rawFiles.trim()] : void 0;
|
|
1346
1357
|
if (!files || !Array.isArray(files)) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
changes: []
|
|
1352
|
-
};
|
|
1358
|
+
throw new ToolValidationError2({
|
|
1359
|
+
message: "input.files must be an array of file paths",
|
|
1360
|
+
field: "files"
|
|
1361
|
+
});
|
|
1353
1362
|
}
|
|
1354
1363
|
if (files.length === 0) {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
changes: []
|
|
1360
|
-
};
|
|
1364
|
+
throw new ToolValidationError2({
|
|
1365
|
+
message: "input.files is empty \u2014 provide at least one file path",
|
|
1366
|
+
field: "files"
|
|
1367
|
+
});
|
|
1361
1368
|
}
|
|
1362
1369
|
const extConfig = api.config.extensions?.["auto-doc"] ?? {};
|
|
1363
1370
|
const includeTypes = extConfig["includeTypes"] ?? false;
|
|
1364
1371
|
const useLlm = (input.use_llm ?? rawInput["useLlm"] ?? extConfig["useLlm"] ?? false) === true && Boolean(api.llm);
|
|
1365
1372
|
const maxLlmEntities = typeof extConfig["maxLlmEntities"] === "number" && extConfig["maxLlmEntities"] >= 0 ? extConfig["maxLlmEntities"] : 25;
|
|
1366
1373
|
const results = [];
|
|
1374
|
+
const skipped = [];
|
|
1367
1375
|
let llmBudget = maxLlmEntities;
|
|
1368
1376
|
for (const rawFile of files) {
|
|
1369
1377
|
const safeFile = resolveProjectPath(rawFile);
|
|
1370
1378
|
if (!safeFile) {
|
|
1371
1379
|
api.log.warn(`auto-doc: skipped file outside project directory: ${rawFile}`);
|
|
1380
|
+
skipped.push({ file: rawFile, reason: "outside project directory" });
|
|
1372
1381
|
continue;
|
|
1373
1382
|
}
|
|
1374
1383
|
try {
|
|
@@ -1376,8 +1385,12 @@ async function runAutoDoc(input, api) {
|
|
|
1376
1385
|
let content;
|
|
1377
1386
|
try {
|
|
1378
1387
|
content = readFileSync17(safeFile, "utf-8");
|
|
1379
|
-
} catch {
|
|
1388
|
+
} catch (err) {
|
|
1380
1389
|
api.log.warn(`auto-doc: could not read file ${safeFile}`);
|
|
1390
|
+
skipped.push({
|
|
1391
|
+
file: safeFile,
|
|
1392
|
+
reason: `could not read: ${err instanceof Error ? err.message : String(err)}`
|
|
1393
|
+
});
|
|
1381
1394
|
continue;
|
|
1382
1395
|
}
|
|
1383
1396
|
const entities = parseSource(content);
|
|
@@ -1417,11 +1430,21 @@ async function runAutoDoc(input, api) {
|
|
|
1417
1430
|
}
|
|
1418
1431
|
} catch (err) {
|
|
1419
1432
|
api.log.error(`auto-doc: error processing ${safeFile}: ${err}`);
|
|
1433
|
+
skipped.push({
|
|
1434
|
+
file: safeFile,
|
|
1435
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
1436
|
+
});
|
|
1420
1437
|
}
|
|
1421
1438
|
}
|
|
1439
|
+
if (skipped.length === files.length) {
|
|
1440
|
+
throw new Error(
|
|
1441
|
+
`auto_doc processed no files: ${skipped.map((s) => `${s.file} (${s.reason})`).join("; ")}`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1422
1444
|
return {
|
|
1423
1445
|
ok: true,
|
|
1424
|
-
filesProcessed: files.length,
|
|
1446
|
+
filesProcessed: files.length - skipped.length,
|
|
1447
|
+
...skipped.length > 0 ? { skipped } : {},
|
|
1425
1448
|
changes: results,
|
|
1426
1449
|
llm: useLlm ? { docs: state4.llmDocs, fallbacks: state4.llmFallbacks } : void 0
|
|
1427
1450
|
};
|
|
@@ -1738,6 +1761,7 @@ var auto_escalate_default = plugin5;
|
|
|
1738
1761
|
|
|
1739
1762
|
// src/auto-i18n-extractor/index.ts
|
|
1740
1763
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1764
|
+
import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core/types";
|
|
1741
1765
|
var API_VERSION4 = "^0.1.10";
|
|
1742
1766
|
var state6 = {
|
|
1743
1767
|
filesScanned: 0,
|
|
@@ -1961,28 +1985,31 @@ ${lines.join("\n")}${more}`;
|
|
|
1961
1985
|
category: "Diagnostics",
|
|
1962
1986
|
mutating: false,
|
|
1963
1987
|
async execute(input) {
|
|
1964
|
-
if (!cfg.enabled)
|
|
1988
|
+
if (!cfg.enabled) throw new Error("auto-i18n-extractor is disabled");
|
|
1965
1989
|
const raw = input;
|
|
1966
1990
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["TargetFile"] === "string" && raw["TargetFile"].trim().length > 0 ? raw["TargetFile"].trim() : void 0) ?? (typeof raw["targetFile"] === "string" && raw["targetFile"].trim().length > 0 ? raw["targetFile"].trim() : void 0) ?? (typeof raw["filePath"] === "string" && raw["filePath"].trim().length > 0 ? raw["filePath"].trim() : void 0) ?? (typeof raw["file_path"] === "string" && raw["file_path"].trim().length > 0 ? raw["file_path"].trim() : void 0) ?? (typeof raw["file"] === "string" && raw["file"].trim().length > 0 ? raw["file"].trim() : void 0);
|
|
1967
1991
|
const filePath = typeof rawPath === "string" ? rawPath.trim() : "";
|
|
1968
1992
|
if (!filePath) {
|
|
1969
|
-
|
|
1993
|
+
throw new ToolValidationError3({ message: "path is required", field: "path" });
|
|
1970
1994
|
}
|
|
1971
1995
|
if (!(0, runtime_exports.withinProject)(filePath)) {
|
|
1972
|
-
|
|
1996
|
+
throw new ToolValidationError3({
|
|
1997
|
+
message: "path must be inside the project",
|
|
1998
|
+
field: "path"
|
|
1999
|
+
});
|
|
1973
2000
|
}
|
|
1974
2001
|
const ext = fileExtension(filePath);
|
|
1975
2002
|
if (!cfg.fileExtensions.includes(ext)) {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
};
|
|
2003
|
+
throw new ToolValidationError3({
|
|
2004
|
+
message: `unsupported extension "${ext}"; allowed: ${cfg.fileExtensions.join(", ")}`,
|
|
2005
|
+
field: "path"
|
|
2006
|
+
});
|
|
1980
2007
|
}
|
|
1981
2008
|
state6.filesScanned += 1;
|
|
1982
2009
|
const content = readSourceFile(filePath);
|
|
1983
2010
|
if (content === null) {
|
|
1984
2011
|
state6.readErrors += 1;
|
|
1985
|
-
|
|
2012
|
+
throw new Error(`could not read ${filePath}`);
|
|
1986
2013
|
}
|
|
1987
2014
|
const extracted = extractStrings(content, cfg);
|
|
1988
2015
|
state6.stringsFound += extracted.length;
|
|
@@ -2169,13 +2196,13 @@ async function detectUncommittedChanges(cwd, signal) {
|
|
|
2169
2196
|
}
|
|
2170
2197
|
function detectGitCommand(command) {
|
|
2171
2198
|
const cmd = command.trim();
|
|
2172
|
-
if (/\bgit\s+commit
|
|
2199
|
+
if (/\bgit\s+commit(?![a-zA-Z0-9_-])/.test(cmd)) {
|
|
2173
2200
|
return { type: "commit", snippet: cmd.slice(0, 120) };
|
|
2174
2201
|
}
|
|
2175
|
-
if (/\bgit\s+push
|
|
2202
|
+
if (/\bgit\s+push(?![a-zA-Z0-9_-])/.test(cmd)) {
|
|
2176
2203
|
return { type: "push", snippet: cmd.slice(0, 120) };
|
|
2177
2204
|
}
|
|
2178
|
-
if (/\bgit\s+merge
|
|
2205
|
+
if (/\bgit\s+merge(?![a-zA-Z0-9_-])/.test(cmd)) {
|
|
2179
2206
|
return { type: "merge", snippet: cmd.slice(0, 120) };
|
|
2180
2207
|
}
|
|
2181
2208
|
return null;
|
|
@@ -2389,6 +2416,7 @@ var branch_guard_default = plugin7;
|
|
|
2389
2416
|
// src/changelog-writer/index.ts
|
|
2390
2417
|
import { readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
2391
2418
|
import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4 } from "node:path";
|
|
2419
|
+
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
2392
2420
|
var state8 = {
|
|
2393
2421
|
entries: [],
|
|
2394
2422
|
filesTouched: new runtime_exports.BoundedSet({ max: 2e3 }),
|
|
@@ -2685,11 +2713,13 @@ var plugin8 = {
|
|
|
2685
2713
|
category: "Docs",
|
|
2686
2714
|
mutating: false,
|
|
2687
2715
|
async execute(input) {
|
|
2688
|
-
if (!cfg.enabled)
|
|
2716
|
+
if (!cfg.enabled) throw new Error("changelog-writer is disabled");
|
|
2689
2717
|
const raw = input ?? {};
|
|
2690
2718
|
const rawText = input.text ?? raw["message"] ?? raw["entry"] ?? raw["content"] ?? raw["description"] ?? raw["desc"] ?? raw["summary"] ?? raw["body"] ?? raw["note"] ?? raw["item"];
|
|
2691
2719
|
const text = String(rawText ?? "").trim();
|
|
2692
|
-
if (!text)
|
|
2720
|
+
if (!text) {
|
|
2721
|
+
throw new ToolValidationError4({ message: "entry text must not be empty", field: "text" });
|
|
2722
|
+
}
|
|
2693
2723
|
const section = SECTION_ORDER.includes(input.section) ? input.section : "Changed";
|
|
2694
2724
|
addEntry({ section, text, origin: "manual", when: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2695
2725
|
return { ok: true, pendingEntries: state8.entries.length };
|
|
@@ -2744,12 +2774,12 @@ var plugin8 = {
|
|
|
2744
2774
|
category: "Docs",
|
|
2745
2775
|
mutating: true,
|
|
2746
2776
|
async execute(input) {
|
|
2747
|
-
if (!cfg.enabled)
|
|
2777
|
+
if (!cfg.enabled) throw new Error("changelog-writer is disabled");
|
|
2748
2778
|
if (!cfg.filePath) {
|
|
2749
|
-
|
|
2779
|
+
throw new Error("filePath must stay within the current project directory");
|
|
2750
2780
|
}
|
|
2751
2781
|
if (state8.entries.length === 0) {
|
|
2752
|
-
|
|
2782
|
+
throw new Error("no pending entries \u2014 add some with changelog_add first");
|
|
2753
2783
|
}
|
|
2754
2784
|
const rawInput = input ?? {};
|
|
2755
2785
|
const shouldPolish = input.polish === true || rawInput["use_llm"] === true || rawInput["useLlm"] === true || rawInput["ai"] === true || rawInput["use_ai"] === true;
|
|
@@ -2757,16 +2787,22 @@ var plugin8 = {
|
|
|
2757
2787
|
let existing = null;
|
|
2758
2788
|
try {
|
|
2759
2789
|
existing = readFileSync3(cfg.filePath, "utf-8");
|
|
2760
|
-
} catch {
|
|
2790
|
+
} catch (err) {
|
|
2791
|
+
if (err.code !== "ENOENT") {
|
|
2792
|
+
throw new Error(
|
|
2793
|
+
`failed to read ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2794
|
+
{ cause: err }
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2761
2797
|
existing = null;
|
|
2762
2798
|
}
|
|
2763
2799
|
try {
|
|
2764
2800
|
writeFileSync(cfg.filePath, mergeIntoChangelog(existing, block));
|
|
2765
2801
|
} catch (err) {
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2802
|
+
throw new Error(
|
|
2803
|
+
`failed to write ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2804
|
+
{ cause: err }
|
|
2805
|
+
);
|
|
2770
2806
|
}
|
|
2771
2807
|
const written = state8.entries.length;
|
|
2772
2808
|
state8.entries = [];
|
|
@@ -2823,8 +2859,9 @@ var plugin8 = {
|
|
|
2823
2859
|
var changelog_writer_default = plugin8;
|
|
2824
2860
|
|
|
2825
2861
|
// src/checkpoint/index.ts
|
|
2826
|
-
import { mkdir, readFile as readFile3, realpath, stat, writeFile } from "node:fs/promises";
|
|
2862
|
+
import { mkdir, readFile as readFile3, realpath, stat as stat2, writeFile } from "node:fs/promises";
|
|
2827
2863
|
import { dirname as dirname2, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve5 } from "node:path";
|
|
2864
|
+
import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
2828
2865
|
var state9 = {
|
|
2829
2866
|
snapshots: [],
|
|
2830
2867
|
nextId: 1,
|
|
@@ -2887,7 +2924,7 @@ async function realResolutionEscapes(absPath, root) {
|
|
|
2887
2924
|
}
|
|
2888
2925
|
async function pathExists(p) {
|
|
2889
2926
|
try {
|
|
2890
|
-
await
|
|
2927
|
+
await stat2(p);
|
|
2891
2928
|
return true;
|
|
2892
2929
|
} catch {
|
|
2893
2930
|
return false;
|
|
@@ -2903,18 +2940,24 @@ function hashContent(s) {
|
|
|
2903
2940
|
}
|
|
2904
2941
|
async function captureFile(path, maxBytes) {
|
|
2905
2942
|
try {
|
|
2906
|
-
const st = await
|
|
2943
|
+
const st = await stat2(path);
|
|
2907
2944
|
if (st.size > maxBytes) return "too-large";
|
|
2908
2945
|
const content = await readFile3(path, "utf-8");
|
|
2909
2946
|
return { path, content, bytes: st.size };
|
|
2910
|
-
} catch {
|
|
2947
|
+
} catch (err) {
|
|
2948
|
+
if (err.code !== "ENOENT") {
|
|
2949
|
+
throw new Error(
|
|
2950
|
+
`could not snapshot ${path}: ${err instanceof Error ? err.message : String(err)}`,
|
|
2951
|
+
{ cause: err }
|
|
2952
|
+
);
|
|
2953
|
+
}
|
|
2911
2954
|
return { path, content: null, bytes: 0 };
|
|
2912
2955
|
}
|
|
2913
2956
|
}
|
|
2914
2957
|
async function captureFileForHook(path, maxBytes, signal) {
|
|
2915
2958
|
try {
|
|
2916
2959
|
signal.throwIfAborted();
|
|
2917
|
-
const st = await
|
|
2960
|
+
const st = await stat2(path);
|
|
2918
2961
|
if (st.size > maxBytes) return "too-large";
|
|
2919
2962
|
const content = await readFile3(path, "utf-8");
|
|
2920
2963
|
signal.throwIfAborted();
|
|
@@ -3049,7 +3092,7 @@ var plugin9 = {
|
|
|
3049
3092
|
category: "Safety",
|
|
3050
3093
|
mutating: false,
|
|
3051
3094
|
async execute(input) {
|
|
3052
|
-
if (!cfg.enabled)
|
|
3095
|
+
if (!cfg.enabled) throw new Error("checkpoint is disabled");
|
|
3053
3096
|
let paths = [];
|
|
3054
3097
|
const rawInput = input;
|
|
3055
3098
|
const raw = rawInput["paths"] ?? rawInput["path"] ?? rawInput["files"] ?? rawInput["file"] ?? rawInput["filePath"] ?? rawInput["file_path"] ?? rawInput["TargetFile"] ?? rawInput["targetFile"];
|
|
@@ -3058,16 +3101,25 @@ var plugin9 = {
|
|
|
3058
3101
|
} else if (Array.isArray(raw)) {
|
|
3059
3102
|
paths = raw.filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
3060
3103
|
}
|
|
3061
|
-
if (paths.length === 0)
|
|
3062
|
-
|
|
3104
|
+
if (paths.length === 0) {
|
|
3105
|
+
throw new ToolValidationError5({ message: "paths must not be empty", field: "paths" });
|
|
3106
|
+
}
|
|
3063
3107
|
const rejectedOutsideProject = [];
|
|
3064
|
-
|
|
3108
|
+
const safePaths = [];
|
|
3065
3109
|
for (const p of paths) {
|
|
3066
3110
|
const safePath = await resolveProjectPath3(p);
|
|
3067
|
-
if (
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3111
|
+
if (safePath) safePaths.push(safePath);
|
|
3112
|
+
else rejectedOutsideProject.push(p);
|
|
3113
|
+
}
|
|
3114
|
+
if (rejectedOutsideProject.length > 0) {
|
|
3115
|
+
throw new ToolValidationError5({
|
|
3116
|
+
message: `paths must stay within the current project directory: ${rejectedOutsideProject.join(", ")}`,
|
|
3117
|
+
field: "paths"
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
const files = [];
|
|
3121
|
+
let skipped = 0;
|
|
3122
|
+
for (const safePath of safePaths) {
|
|
3071
3123
|
const captured = await captureFile(safePath, cfg.maxFileBytes);
|
|
3072
3124
|
if (captured === "too-large") {
|
|
3073
3125
|
skipped += 1;
|
|
@@ -3076,15 +3128,10 @@ var plugin9 = {
|
|
|
3076
3128
|
}
|
|
3077
3129
|
files.push(captured);
|
|
3078
3130
|
}
|
|
3079
|
-
if (rejectedOutsideProject.length > 0) {
|
|
3080
|
-
return {
|
|
3081
|
-
ok: false,
|
|
3082
|
-
error: "paths must stay within the current project directory",
|
|
3083
|
-
rejectedOutsideProject
|
|
3084
|
-
};
|
|
3085
|
-
}
|
|
3086
3131
|
if (files.length === 0) {
|
|
3087
|
-
|
|
3132
|
+
throw new Error(
|
|
3133
|
+
`all files were skipped (larger than maxFileBytes=${cfg.maxFileBytes}); nothing was snapshotted`
|
|
3134
|
+
);
|
|
3088
3135
|
}
|
|
3089
3136
|
const snapshot = {
|
|
3090
3137
|
id: `cp-${state9.nextId++}`,
|
|
@@ -3159,21 +3206,18 @@ var plugin9 = {
|
|
|
3159
3206
|
category: "Safety",
|
|
3160
3207
|
mutating: true,
|
|
3161
3208
|
async execute(input) {
|
|
3162
|
-
if (!cfg.enabled)
|
|
3209
|
+
if (!cfg.enabled) throw new Error("checkpoint is disabled");
|
|
3163
3210
|
const raw = input ?? {};
|
|
3164
3211
|
const rawId = (typeof input.id === "string" && input.id.trim().length > 0 ? input.id.trim() : void 0) ?? (typeof raw["snapshotId"] === "string" ? raw["snapshotId"] : void 0) ?? (typeof raw["snapshot_id"] === "string" ? raw["snapshot_id"] : void 0);
|
|
3165
3212
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0);
|
|
3166
3213
|
const snapshot = rawId ? state9.snapshots.find((s) => s.id === rawId) : state9.snapshots[state9.snapshots.length - 1];
|
|
3167
3214
|
if (!snapshot) {
|
|
3168
|
-
|
|
3169
|
-
ok: false,
|
|
3170
|
-
error: rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet"
|
|
3171
|
-
};
|
|
3215
|
+
throw new Error(rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet");
|
|
3172
3216
|
}
|
|
3173
3217
|
const targetPath = rawPath ? await resolveProjectPath3(rawPath) ?? rawPath : null;
|
|
3174
3218
|
const targets = targetPath ? snapshot.files.filter((f) => f.path === targetPath || f.path === rawPath) : snapshot.files;
|
|
3175
3219
|
if (targets.length === 0) {
|
|
3176
|
-
|
|
3220
|
+
throw new Error(`snapshot ${snapshot.id} has no entry for "${rawPath}"`);
|
|
3177
3221
|
}
|
|
3178
3222
|
const restored = [];
|
|
3179
3223
|
const createdByTool = [];
|
|
@@ -3195,8 +3239,14 @@ var plugin9 = {
|
|
|
3195
3239
|
state9.restores += 1;
|
|
3196
3240
|
api.metrics.counter("restores");
|
|
3197
3241
|
}
|
|
3242
|
+
if (errors.length > 0) {
|
|
3243
|
+
const failed = errors.map((e) => `${e.path} (${e.error})`).join("; ");
|
|
3244
|
+
throw new Error(
|
|
3245
|
+
`checkpoint_restore ${snapshot.id}: ${errors.length} file(s) failed to restore: ${failed}` + (restored.length > 0 ? `. Restored: ${restored.join(", ")}` : "")
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3198
3248
|
return {
|
|
3199
|
-
ok:
|
|
3249
|
+
ok: true,
|
|
3200
3250
|
snapshotId: snapshot.id,
|
|
3201
3251
|
restored,
|
|
3202
3252
|
notRestoredFileDidNotExist: createdByTool,
|
|
@@ -3255,8 +3305,9 @@ var plugin9 = {
|
|
|
3255
3305
|
var checkpoint_default = plugin9;
|
|
3256
3306
|
|
|
3257
3307
|
// src/code-metrics/index.ts
|
|
3258
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
3308
|
+
import { readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
3259
3309
|
import { isAbsolute as isAbsolute6, relative as relative6, resolve as resolve6 } from "node:path";
|
|
3310
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
3260
3311
|
var API_VERSION6 = "^0.1.10";
|
|
3261
3312
|
var state10 = {
|
|
3262
3313
|
measureCount: 0,
|
|
@@ -3492,19 +3543,26 @@ var plugin10 = {
|
|
|
3492
3543
|
category: "Diagnostics",
|
|
3493
3544
|
mutating: false,
|
|
3494
3545
|
async execute(input) {
|
|
3495
|
-
if (!cfg.enabled)
|
|
3546
|
+
if (!cfg.enabled) throw new Error("code-metrics is disabled");
|
|
3496
3547
|
const raw = input ?? {};
|
|
3497
3548
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
|
|
3498
3549
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
3499
|
-
|
|
3550
|
+
throw new ToolValidationError6({
|
|
3551
|
+
message: "path is outside the project root",
|
|
3552
|
+
field: "path"
|
|
3553
|
+
});
|
|
3500
3554
|
}
|
|
3501
3555
|
state10.measureCount += 1;
|
|
3502
3556
|
let result;
|
|
3503
3557
|
try {
|
|
3558
|
+
await stat3(resolve6(process.cwd(), rawPath));
|
|
3504
3559
|
result = await measurePath(rawPath, cfg);
|
|
3505
3560
|
} catch (err) {
|
|
3506
3561
|
state10.errorCount += 1;
|
|
3507
|
-
|
|
3562
|
+
throw new Error(
|
|
3563
|
+
`measure_code_metrics failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
3564
|
+
{ cause: err }
|
|
3565
|
+
);
|
|
3508
3566
|
}
|
|
3509
3567
|
state10.fileCount += result.files.length;
|
|
3510
3568
|
return {
|
|
@@ -3707,11 +3765,11 @@ function parseCommitMessage(message, cfg) {
|
|
|
3707
3765
|
}
|
|
3708
3766
|
var GIT_MESSAGE_FLAG_RE = new RegExp(
|
|
3709
3767
|
[
|
|
3710
|
-
// -m "…" | -m '…' | -m=… ; --message "…" | --message='…' | --message=…
|
|
3711
|
-
String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)"([^"]*)"`,
|
|
3712
|
-
String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)'([^']*)'`,
|
|
3768
|
+
// -m "…" | -am "…" | -m '…' | -m=… ; --message "…" | --message='…' | --message=…
|
|
3769
|
+
String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)"([^"]*)"`,
|
|
3770
|
+
String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)'([^']*)'`,
|
|
3713
3771
|
// Bare value: stops at whitespace or a shell separator.
|
|
3714
|
-
String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)([^\s;&|"']+)`
|
|
3772
|
+
String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)([^\s;&|"']+)`
|
|
3715
3773
|
].join("|"),
|
|
3716
3774
|
"g"
|
|
3717
3775
|
);
|
|
@@ -3821,7 +3879,7 @@ var plugin11 = {
|
|
|
3821
3879
|
} else if (toolName === "bash") {
|
|
3822
3880
|
const command = inp["command"] ?? inp["CommandLine"] ?? inp["cmd"] ?? inp["script"] ?? inp["input"];
|
|
3823
3881
|
if (typeof command !== "string") return;
|
|
3824
|
-
if (!/\bgit\s+commit
|
|
3882
|
+
if (!/\bgit\s+commit(?![a-zA-Z0-9_-])/.test(command)) return;
|
|
3825
3883
|
message = extractMessageFromBash(command);
|
|
3826
3884
|
if (!message) return;
|
|
3827
3885
|
} else {
|
|
@@ -4357,6 +4415,7 @@ var config_validator_default = plugin12;
|
|
|
4357
4415
|
// src/context-pins/index.ts
|
|
4358
4416
|
import * as fs from "node:fs";
|
|
4359
4417
|
import { dirname as dirname3, isAbsolute as isAbsolute7, relative as relative7, resolve as resolve7 } from "node:path";
|
|
4418
|
+
import { ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
4360
4419
|
import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
|
|
4361
4420
|
var state13 = {
|
|
4362
4421
|
pins: [],
|
|
@@ -4497,16 +4556,17 @@ var plugin13 = {
|
|
|
4497
4556
|
category: "Memory",
|
|
4498
4557
|
mutating: true,
|
|
4499
4558
|
async execute(input) {
|
|
4500
|
-
if (!cfg.enabled)
|
|
4559
|
+
if (!cfg.enabled) throw new Error("context-pins is disabled");
|
|
4501
4560
|
const raw = input ?? {};
|
|
4502
4561
|
const rawText = input.text ?? raw["pin"] ?? raw["content"] ?? raw["message"] ?? raw["note"] ?? raw["fact"] ?? raw["data"];
|
|
4503
4562
|
const text = String(rawText ?? "").trim();
|
|
4504
|
-
if (!text)
|
|
4563
|
+
if (!text) {
|
|
4564
|
+
throw new ToolValidationError7({ message: "pin text must not be empty", field: "text" });
|
|
4565
|
+
}
|
|
4505
4566
|
if (state13.pins.length >= cfg.maxPins) {
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
};
|
|
4567
|
+
throw new Error(
|
|
4568
|
+
`pin limit reached (${cfg.maxPins}). Remove a pin first with pin_remove.`
|
|
4569
|
+
);
|
|
4510
4570
|
}
|
|
4511
4571
|
const pin = {
|
|
4512
4572
|
id: `pin-${state13.nextId++}`,
|
|
@@ -4535,16 +4595,17 @@ var plugin13 = {
|
|
|
4535
4595
|
category: "Memory",
|
|
4536
4596
|
mutating: true,
|
|
4537
4597
|
async execute(input) {
|
|
4538
|
-
if (!cfg.enabled)
|
|
4598
|
+
if (!cfg.enabled) throw new Error("context-pins is disabled");
|
|
4539
4599
|
const raw = input ?? {};
|
|
4540
4600
|
const key = String(
|
|
4541
4601
|
input.id ?? input.label ?? raw["pinId"] ?? raw["pin_id"] ?? raw["name"] ?? raw["key"] ?? ""
|
|
4542
4602
|
).trim();
|
|
4543
|
-
if (!key)
|
|
4603
|
+
if (!key)
|
|
4604
|
+
throw new ToolValidationError7({ message: "id or label is required", field: "id" });
|
|
4544
4605
|
const before = state13.pins.length;
|
|
4545
4606
|
state13.pins = state13.pins.filter((p) => p.id !== key && p.label !== key);
|
|
4546
4607
|
const removed = before - state13.pins.length;
|
|
4547
|
-
if (removed === 0)
|
|
4608
|
+
if (removed === 0) throw new Error(`no pin matches "${key}"`);
|
|
4548
4609
|
state13.removals += removed;
|
|
4549
4610
|
api.metrics.counter("removals", removed);
|
|
4550
4611
|
const persisted = await persistPins(cfg.filePath);
|
|
@@ -4662,6 +4723,9 @@ function estimateCost(model, freshTokens, completionTokens, cachedTokens = 0) {
|
|
|
4662
4723
|
const outputCost = completionTokens / 1e6 * pricing.output;
|
|
4663
4724
|
return inputCost + outputCost;
|
|
4664
4725
|
}
|
|
4726
|
+
function toFiniteNumber(value) {
|
|
4727
|
+
return Number.isFinite(value) ? value : 0;
|
|
4728
|
+
}
|
|
4665
4729
|
var plugin14 = {
|
|
4666
4730
|
name: "cost-tracker",
|
|
4667
4731
|
version: "0.1.0",
|
|
@@ -4735,11 +4799,12 @@ var plugin14 = {
|
|
|
4735
4799
|
const input = v["input"];
|
|
4736
4800
|
const output = v["output"];
|
|
4737
4801
|
if (typeof input !== "number" || typeof output !== "number") continue;
|
|
4802
|
+
if (!Number.isFinite(input) || !Number.isFinite(output)) continue;
|
|
4738
4803
|
const cacheRead = v["cacheRead"];
|
|
4739
4804
|
pricingOverrides[model.toLowerCase()] = {
|
|
4740
4805
|
input,
|
|
4741
4806
|
output,
|
|
4742
|
-
...typeof cacheRead === "number" ? { cacheRead } : {}
|
|
4807
|
+
...typeof cacheRead === "number" && Number.isFinite(cacheRead) ? { cacheRead } : {}
|
|
4743
4808
|
};
|
|
4744
4809
|
}
|
|
4745
4810
|
}
|
|
@@ -4752,11 +4817,11 @@ var plugin14 = {
|
|
|
4752
4817
|
if (!providerModels) continue;
|
|
4753
4818
|
for (const [modelId, model] of Object.entries(providerModels)) {
|
|
4754
4819
|
const cost = model?.cost;
|
|
4755
|
-
if (cost && typeof cost.input === "number" && typeof cost.output === "number") {
|
|
4820
|
+
if (cost && typeof cost.input === "number" && typeof cost.output === "number" && Number.isFinite(cost.input) && Number.isFinite(cost.output)) {
|
|
4756
4821
|
bundledFromRegistry[modelId.toLowerCase()] = {
|
|
4757
4822
|
input: cost.input,
|
|
4758
4823
|
output: cost.output,
|
|
4759
|
-
...typeof cost.cache_read === "number" ? { cacheRead: cost.cache_read } : {}
|
|
4824
|
+
...typeof cost.cache_read === "number" && Number.isFinite(cost.cache_read) ? { cacheRead: cost.cache_read } : {}
|
|
4760
4825
|
};
|
|
4761
4826
|
hydrated += 1;
|
|
4762
4827
|
}
|
|
@@ -4784,14 +4849,22 @@ var plugin14 = {
|
|
|
4784
4849
|
const usage = payload.usage;
|
|
4785
4850
|
const model = payload.ctx?.model ?? "unknown";
|
|
4786
4851
|
const u = usage ?? {};
|
|
4787
|
-
const cachedTokens =
|
|
4788
|
-
|
|
4789
|
-
|
|
4852
|
+
const cachedTokens = toFiniteNumber(
|
|
4853
|
+
Number(u["cacheRead"] ?? u["cache_read_input_tokens"] ?? u["cached_prompt_tokens"] ?? 0)
|
|
4854
|
+
);
|
|
4855
|
+
const rawInput = toFiniteNumber(
|
|
4856
|
+
Number(u["input"] ?? u["prompt_tokens"] ?? u["inputTokens"] ?? u["promptTokens"] ?? 0)
|
|
4857
|
+
);
|
|
4858
|
+
const rawCacheWrite = toFiniteNumber(
|
|
4859
|
+
Number(u["cacheWrite"] ?? u["cache_creation_input_tokens"] ?? 0)
|
|
4860
|
+
);
|
|
4790
4861
|
const freshTokens = rawInput + rawCacheWrite;
|
|
4791
4862
|
const promptTokens = freshTokens + cachedTokens;
|
|
4792
|
-
const completionTokens =
|
|
4793
|
-
|
|
4794
|
-
|
|
4863
|
+
const completionTokens = toFiniteNumber(
|
|
4864
|
+
Number(
|
|
4865
|
+
u["output"] ?? u["completion_tokens"] ?? u["outputTokens"] ?? u["completionTokens"] ?? 0
|
|
4866
|
+
)
|
|
4867
|
+
);
|
|
4795
4868
|
const totalTokens = promptTokens + completionTokens;
|
|
4796
4869
|
const costUsd = estimateCost(model, freshTokens, completionTokens, cachedTokens);
|
|
4797
4870
|
const record = {
|
|
@@ -5012,6 +5085,7 @@ by model: ${top || "(none)"}`
|
|
|
5012
5085
|
var cost_tracker_default = plugin14;
|
|
5013
5086
|
|
|
5014
5087
|
// src/cron/index.ts
|
|
5088
|
+
import { ToolValidationError as ToolValidationError8 } from "@wrongstack/core/types";
|
|
5015
5089
|
var COORDINATION_CRON_CAPABILITY = "coordination.cron";
|
|
5016
5090
|
var API_VERSION9 = "^0.1.10";
|
|
5017
5091
|
var state14 = {
|
|
@@ -5180,16 +5254,28 @@ var plugin15 = {
|
|
|
5180
5254
|
const action = input["action"] ?? input["task"] ?? input["command"] ?? input["run"];
|
|
5181
5255
|
const enabled = input["enabled"] ?? true;
|
|
5182
5256
|
if (!name || typeof name !== "string" || name.trim() === "") {
|
|
5183
|
-
|
|
5257
|
+
throw new ToolValidationError8({
|
|
5258
|
+
message: "name is required and must be a non-empty string",
|
|
5259
|
+
field: "name"
|
|
5260
|
+
});
|
|
5184
5261
|
}
|
|
5185
5262
|
if (Number.isNaN(intervalMs) || rawInterval === void 0 || rawInterval === null) {
|
|
5186
|
-
|
|
5263
|
+
throw new ToolValidationError8({
|
|
5264
|
+
message: "intervalMs must be a number >= 1000",
|
|
5265
|
+
field: "intervalMs"
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
5268
|
+
if (typeof action !== "string" || action.trim() === "") {
|
|
5269
|
+
throw new ToolValidationError8({
|
|
5270
|
+
message: "action is required and must be a non-empty string",
|
|
5271
|
+
field: "action"
|
|
5272
|
+
});
|
|
5187
5273
|
}
|
|
5188
5274
|
if (state14.jobs.has(name)) {
|
|
5189
|
-
|
|
5275
|
+
throw new Error(`Cron job '${name}' already exists. Use cron_cancel first.`);
|
|
5190
5276
|
}
|
|
5191
5277
|
if (state14.jobs.size >= maxConcurrent) {
|
|
5192
|
-
|
|
5278
|
+
throw new Error(`Maximum concurrent jobs (${maxConcurrent}) reached.`);
|
|
5193
5279
|
}
|
|
5194
5280
|
const job = {
|
|
5195
5281
|
name,
|
|
@@ -5255,7 +5341,7 @@ var plugin15 = {
|
|
|
5255
5341
|
async execute(input) {
|
|
5256
5342
|
const name = input["name"] ?? input["jobName"] ?? input["job_name"] ?? input["job"] ?? input["id"];
|
|
5257
5343
|
if (!name || typeof name !== "string" || !state14.jobs.has(name)) {
|
|
5258
|
-
|
|
5344
|
+
throw new Error(`No cron job named '${name}'`);
|
|
5259
5345
|
}
|
|
5260
5346
|
cancelJob(name);
|
|
5261
5347
|
api.metrics.gauge("cron_active_jobs", state14.jobs.size);
|
|
@@ -5290,8 +5376,9 @@ var plugin15 = {
|
|
|
5290
5376
|
var cron_default = plugin15;
|
|
5291
5377
|
|
|
5292
5378
|
// src/dead-code-detector/index.ts
|
|
5293
|
-
import { readdir, readFile as readFile5, stat as
|
|
5379
|
+
import { readdir, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
5294
5380
|
import { extname as extname2, join, relative as relative8, resolve as resolve8 } from "node:path";
|
|
5381
|
+
import { ToolValidationError as ToolValidationError9 } from "@wrongstack/core/types";
|
|
5295
5382
|
var API_VERSION10 = "^0.1.10";
|
|
5296
5383
|
var state15 = {
|
|
5297
5384
|
scanCount: 0,
|
|
@@ -5438,14 +5525,8 @@ async function scan(root, depth, cfg) {
|
|
|
5438
5525
|
}
|
|
5439
5526
|
async function resolveScanRoot(rawPath) {
|
|
5440
5527
|
const resolved = resolve8(process.cwd(), rawPath);
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
if (!stats.isDirectory()) {
|
|
5444
|
-
return resolve8(resolved, "..");
|
|
5445
|
-
}
|
|
5446
|
-
} catch {
|
|
5447
|
-
}
|
|
5448
|
-
return resolved;
|
|
5528
|
+
const stats = await stat4(resolved);
|
|
5529
|
+
return stats.isDirectory() ? resolved : resolve8(resolved, "..");
|
|
5449
5530
|
}
|
|
5450
5531
|
function toPosix2(p) {
|
|
5451
5532
|
return p.replace(/\\/g, "/");
|
|
@@ -5564,22 +5645,29 @@ Consider removing the export if it is not part of the public API.`;
|
|
|
5564
5645
|
category: "Diagnostics",
|
|
5565
5646
|
mutating: false,
|
|
5566
5647
|
async execute(input) {
|
|
5567
|
-
if (!cfg.enabled)
|
|
5648
|
+
if (!cfg.enabled) throw new Error("dead-code-detector is disabled");
|
|
5568
5649
|
const raw = input ?? {};
|
|
5569
5650
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
|
|
5570
5651
|
const rawDepth = typeof input.depth === "number" ? input.depth : cfg.defaultDepth;
|
|
5571
5652
|
const depth = Math.max(0, Math.min(Math.floor(rawDepth), cfg.maxDepth));
|
|
5572
5653
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
5573
|
-
|
|
5654
|
+
throw new ToolValidationError9({
|
|
5655
|
+
message: "scan path is outside the project root",
|
|
5656
|
+
field: "path"
|
|
5657
|
+
});
|
|
5574
5658
|
}
|
|
5575
5659
|
state15.scanCount += 1;
|
|
5576
|
-
const scanRoot = await resolveScanRoot(rawPath);
|
|
5577
5660
|
let result;
|
|
5661
|
+
let scanRoot;
|
|
5578
5662
|
try {
|
|
5663
|
+
scanRoot = await resolveScanRoot(rawPath);
|
|
5579
5664
|
result = await scan(scanRoot, depth, cfg);
|
|
5580
5665
|
} catch (err) {
|
|
5581
5666
|
state15.errorCount += 1;
|
|
5582
|
-
|
|
5667
|
+
throw new Error(
|
|
5668
|
+
`dead_code_scan failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
5669
|
+
{ cause: err }
|
|
5670
|
+
);
|
|
5583
5671
|
}
|
|
5584
5672
|
return {
|
|
5585
5673
|
ok: true,
|
|
@@ -6982,8 +7070,9 @@ Consider mentioning these changes in the documentation.`;
|
|
|
6982
7070
|
var doc_sync_guard_default = plugin20;
|
|
6983
7071
|
|
|
6984
7072
|
// src/duplicate-code-detector/index.ts
|
|
6985
|
-
import { readFile as readFile6, realpath as realpath2, stat as
|
|
7073
|
+
import { readFile as readFile6, realpath as realpath2, stat as stat5 } from "node:fs/promises";
|
|
6986
7074
|
import { extname as extname4, isAbsolute as isAbsolute8, relative as relative9, resolve as resolve9, sep } from "node:path";
|
|
7075
|
+
import { ToolValidationError as ToolValidationError10 } from "@wrongstack/core/types";
|
|
6987
7076
|
var API_VERSION14 = "^0.1.10";
|
|
6988
7077
|
var HOOK_WARNING_COOLDOWN_MS = 6e4;
|
|
6989
7078
|
var state20 = {
|
|
@@ -7289,7 +7378,7 @@ var plugin21 = {
|
|
|
7289
7378
|
async function readCachedFingerprints(filePath, minLines) {
|
|
7290
7379
|
let st;
|
|
7291
7380
|
try {
|
|
7292
|
-
st = await
|
|
7381
|
+
st = await stat5(filePath);
|
|
7293
7382
|
} catch {
|
|
7294
7383
|
return null;
|
|
7295
7384
|
}
|
|
@@ -7404,19 +7493,26 @@ var plugin21 = {
|
|
|
7404
7493
|
category: "Diagnostics",
|
|
7405
7494
|
mutating: false,
|
|
7406
7495
|
async execute(input) {
|
|
7407
|
-
if (!cfg.enabled)
|
|
7496
|
+
if (!cfg.enabled) throw new Error("duplicate-code-detector is disabled");
|
|
7408
7497
|
const raw = input;
|
|
7409
7498
|
const rawPath = (typeof raw["path"] === "string" ? raw["path"] : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
|
|
7410
7499
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
7411
|
-
|
|
7500
|
+
throw new ToolValidationError10({
|
|
7501
|
+
message: "scan path is outside the project root",
|
|
7502
|
+
field: "path"
|
|
7503
|
+
});
|
|
7412
7504
|
}
|
|
7413
7505
|
state20.scanCount += 1;
|
|
7414
7506
|
let result;
|
|
7415
7507
|
try {
|
|
7508
|
+
await stat5(resolve9(process.cwd(), rawPath));
|
|
7416
7509
|
result = await scanPath(rawPath, cfg);
|
|
7417
7510
|
} catch (err) {
|
|
7418
7511
|
state20.errorCount += 1;
|
|
7419
|
-
|
|
7512
|
+
throw new Error(
|
|
7513
|
+
`detect_duplicate_code failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
7514
|
+
{ cause: err }
|
|
7515
|
+
);
|
|
7420
7516
|
}
|
|
7421
7517
|
state20.findingCount += result.findings.length;
|
|
7422
7518
|
return {
|
|
@@ -7800,8 +7896,9 @@ ${errorLine ?? "(no error line)"}
|
|
|
7800
7896
|
var error_lens_default = plugin22;
|
|
7801
7897
|
|
|
7802
7898
|
// src/feature-flag-tracker/index.ts
|
|
7803
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
7899
|
+
import { readFile as readFile7, stat as stat6 } from "node:fs/promises";
|
|
7804
7900
|
import { isAbsolute as isAbsolute9, relative as relative10, resolve as resolve10 } from "node:path";
|
|
7901
|
+
import { ToolValidationError as ToolValidationError11 } from "@wrongstack/core/types";
|
|
7805
7902
|
var API_VERSION15 = "^0.1.10";
|
|
7806
7903
|
var state22 = {
|
|
7807
7904
|
scanCount: 0,
|
|
@@ -8026,19 +8123,26 @@ Make sure flag behavior is intentional and consider updating flag inventory/docs
|
|
|
8026
8123
|
category: "Diagnostics",
|
|
8027
8124
|
mutating: false,
|
|
8028
8125
|
async execute(input) {
|
|
8029
|
-
if (!cfg.enabled)
|
|
8126
|
+
if (!cfg.enabled) throw new Error("feature-flag-tracker is disabled");
|
|
8030
8127
|
const raw = input ?? {};
|
|
8031
8128
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["file_path"] === "string" ? raw["file_path"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
|
|
8032
8129
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
8033
|
-
|
|
8130
|
+
throw new ToolValidationError11({
|
|
8131
|
+
message: "path is outside the project root",
|
|
8132
|
+
field: "path"
|
|
8133
|
+
});
|
|
8034
8134
|
}
|
|
8035
8135
|
state22.scanCount += 1;
|
|
8036
8136
|
let result;
|
|
8037
8137
|
try {
|
|
8138
|
+
await stat6(resolve10(process.cwd(), rawPath));
|
|
8038
8139
|
result = await scanPath2(rawPath, cfg);
|
|
8039
8140
|
} catch (err) {
|
|
8040
8141
|
state22.errorCount += 1;
|
|
8041
|
-
|
|
8142
|
+
throw new Error(
|
|
8143
|
+
`scan_feature_flags failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8144
|
+
{ cause: err }
|
|
8145
|
+
);
|
|
8042
8146
|
}
|
|
8043
8147
|
state22.flagCount += result.usages.length;
|
|
8044
8148
|
return {
|
|
@@ -8124,6 +8228,7 @@ var feature_flag_tracker_default = plugin23;
|
|
|
8124
8228
|
// src/file-watcher/index.ts
|
|
8125
8229
|
import { watch as fsWatch } from "node:fs";
|
|
8126
8230
|
import { join as join2 } from "node:path";
|
|
8231
|
+
import { ToolValidationError as ToolValidationError12 } from "@wrongstack/core/types";
|
|
8127
8232
|
var API_VERSION16 = "^0.1.10";
|
|
8128
8233
|
var watch_idCounter = 0;
|
|
8129
8234
|
function nextId() {
|
|
@@ -8311,11 +8416,10 @@ var plugin24 = {
|
|
|
8311
8416
|
let rawPaths;
|
|
8312
8417
|
if (explicitPaths !== void 0) {
|
|
8313
8418
|
if (!Array.isArray(explicitPaths)) {
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
};
|
|
8419
|
+
throw new ToolValidationError12({
|
|
8420
|
+
message: "paths must be an array of file/directory paths",
|
|
8421
|
+
field: "paths"
|
|
8422
|
+
});
|
|
8319
8423
|
}
|
|
8320
8424
|
rawPaths = explicitPaths;
|
|
8321
8425
|
} else {
|
|
@@ -8323,55 +8427,46 @@ var plugin24 = {
|
|
|
8323
8427
|
rawPaths = Array.isArray(fallback) ? fallback : typeof fallback === "string" && fallback.trim().length > 0 ? [fallback.trim()] : void 0;
|
|
8324
8428
|
}
|
|
8325
8429
|
if (!rawPaths || !Array.isArray(rawPaths)) {
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
};
|
|
8430
|
+
throw new ToolValidationError12({
|
|
8431
|
+
message: "paths must be an array of file/directory paths",
|
|
8432
|
+
field: "paths"
|
|
8433
|
+
});
|
|
8331
8434
|
}
|
|
8332
8435
|
const paths = [...new Set(rawPaths)];
|
|
8333
8436
|
if (paths.length === 0) {
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
};
|
|
8437
|
+
throw new ToolValidationError12({
|
|
8438
|
+
message: "paths array is empty \u2014 provide at least one path",
|
|
8439
|
+
field: "paths"
|
|
8440
|
+
});
|
|
8339
8441
|
}
|
|
8340
8442
|
if (paths.length > MAX_PATHS_PER_WATCH) {
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
};
|
|
8443
|
+
throw new ToolValidationError12({
|
|
8444
|
+
message: `a watch may contain at most ${MAX_PATHS_PER_WATCH} unique paths`,
|
|
8445
|
+
field: "paths"
|
|
8446
|
+
});
|
|
8346
8447
|
}
|
|
8347
8448
|
if (watches.size >= MAX_WATCH_GROUPS) {
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
watch_id: null
|
|
8352
|
-
};
|
|
8449
|
+
throw new Error(
|
|
8450
|
+
`active watch group limit reached (${MAX_WATCH_GROUPS}); stop a watch with watch_stop first`
|
|
8451
|
+
);
|
|
8353
8452
|
}
|
|
8354
8453
|
const activeFilesystemWatchers = [...watches.values()].reduce(
|
|
8355
8454
|
(total, handle2) => total + handle2.watchers.length,
|
|
8356
8455
|
0
|
|
8357
8456
|
);
|
|
8358
8457
|
if (activeFilesystemWatchers + paths.length > MAX_FILESYSTEM_WATCHERS) {
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
watch_id: null
|
|
8363
|
-
};
|
|
8458
|
+
throw new Error(
|
|
8459
|
+
`filesystem watcher limit reached (${MAX_FILESYSTEM_WATCHERS}); stop a watch with watch_stop first`
|
|
8460
|
+
);
|
|
8364
8461
|
}
|
|
8365
8462
|
const events = input["events"] ?? ["change", "add", "delete"];
|
|
8366
8463
|
const recursive = input["recursive"] ?? true;
|
|
8367
8464
|
const bad = paths.find((p) => !(0, runtime_exports.withinProject)(p));
|
|
8368
8465
|
if (bad !== void 0) {
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
rejectedOutsideProject: true
|
|
8374
|
-
};
|
|
8466
|
+
throw new ToolValidationError12({
|
|
8467
|
+
message: `path is outside the project root: ${bad}`,
|
|
8468
|
+
field: "paths"
|
|
8469
|
+
});
|
|
8375
8470
|
}
|
|
8376
8471
|
const id = nextId();
|
|
8377
8472
|
const handle = {
|
|
@@ -8383,8 +8478,15 @@ var plugin24 = {
|
|
|
8383
8478
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8384
8479
|
};
|
|
8385
8480
|
const watchedPaths = [];
|
|
8481
|
+
const failedPaths = [];
|
|
8386
8482
|
for (const p of paths) {
|
|
8387
8483
|
if (safeWatchDir(p, recursive, handle)) watchedPaths.push(p);
|
|
8484
|
+
else failedPaths.push(p);
|
|
8485
|
+
}
|
|
8486
|
+
if (watchedPaths.length === 0) {
|
|
8487
|
+
throw new Error(
|
|
8488
|
+
`could not watch any of the requested paths: ${failedPaths.join(", ")} (missing path or OS watcher limit)`
|
|
8489
|
+
);
|
|
8388
8490
|
}
|
|
8389
8491
|
handle.paths = watchedPaths;
|
|
8390
8492
|
watches.set(id, handle);
|
|
@@ -8393,9 +8495,10 @@ var plugin24 = {
|
|
|
8393
8495
|
ok: true,
|
|
8394
8496
|
watch_id: id,
|
|
8395
8497
|
paths: watchedPaths,
|
|
8498
|
+
...failedPaths.length > 0 ? { failedPaths } : {},
|
|
8396
8499
|
events,
|
|
8397
8500
|
recursive,
|
|
8398
|
-
message: `Started watching ${watchedPaths.length} path(s). Use watch_stop to cancel.`
|
|
8501
|
+
message: `Started watching ${watchedPaths.length} path(s). Use watch_stop to cancel.` + (failedPaths.length > 0 ? ` Could not watch: ${failedPaths.join(", ")}.` : "")
|
|
8399
8502
|
};
|
|
8400
8503
|
}
|
|
8401
8504
|
});
|
|
@@ -8417,7 +8520,7 @@ var plugin24 = {
|
|
|
8417
8520
|
const watch_id = typeof rawId === "string" ? rawId.trim() : "";
|
|
8418
8521
|
const handle = watches.get(watch_id);
|
|
8419
8522
|
if (!handle) {
|
|
8420
|
-
|
|
8523
|
+
throw new Error(`No active watch with ID: ${watch_id}`);
|
|
8421
8524
|
}
|
|
8422
8525
|
for (const w of handle.watchers) {
|
|
8423
8526
|
try {
|
|
@@ -8501,7 +8604,7 @@ var file_watcher_default = plugin24;
|
|
|
8501
8604
|
// src/format-on-save/index.ts
|
|
8502
8605
|
import { execFile as execFile5 } from "node:child_process";
|
|
8503
8606
|
import { createHash } from "node:crypto";
|
|
8504
|
-
import { access as access2, readFile as readFile8, stat as
|
|
8607
|
+
import { access as access2, readFile as readFile8, stat as stat7 } from "node:fs/promises";
|
|
8505
8608
|
var API_VERSION17 = "^0.1.10";
|
|
8506
8609
|
var state23 = {
|
|
8507
8610
|
invocationCount: 0,
|
|
@@ -8624,7 +8727,7 @@ async function formatFile(filePath, timeoutMs) {
|
|
|
8624
8727
|
const started = Date.now();
|
|
8625
8728
|
let bytesBefore;
|
|
8626
8729
|
try {
|
|
8627
|
-
bytesBefore = (await
|
|
8730
|
+
bytesBefore = (await stat7(filePath)).size;
|
|
8628
8731
|
} catch {
|
|
8629
8732
|
return null;
|
|
8630
8733
|
}
|
|
@@ -8662,7 +8765,7 @@ async function formatFile(filePath, timeoutMs) {
|
|
|
8662
8765
|
}
|
|
8663
8766
|
let bytesAfter;
|
|
8664
8767
|
try {
|
|
8665
|
-
bytesAfter = (await
|
|
8768
|
+
bytesAfter = (await stat7(filePath)).size;
|
|
8666
8769
|
} catch {
|
|
8667
8770
|
return null;
|
|
8668
8771
|
}
|
|
@@ -8877,6 +8980,7 @@ var format_on_save_default = plugin25;
|
|
|
8877
8980
|
import { execFile as execFile6 } from "node:child_process";
|
|
8878
8981
|
import { existsSync as existsSync3 } from "node:fs";
|
|
8879
8982
|
import { resolve as resolve11 } from "node:path";
|
|
8983
|
+
import { ToolValidationError as ToolValidationError13 } from "@wrongstack/core/types";
|
|
8880
8984
|
var API_VERSION18 = "^0.1.10";
|
|
8881
8985
|
var commitCount = { value: 0 };
|
|
8882
8986
|
var lastCommit = { hash: null, at: null };
|
|
@@ -9053,22 +9157,22 @@ async function simultaneousEditWarning(cwd) {
|
|
|
9053
9157
|
}
|
|
9054
9158
|
async function getStagedDiff(cwd) {
|
|
9055
9159
|
try {
|
|
9056
|
-
const
|
|
9160
|
+
const stat13 = await runGit3(["diff", "--cached", "--stat"], cwd);
|
|
9057
9161
|
const diff = await runGit3(["diff", "--cached"], cwd);
|
|
9058
9162
|
const MAX_DIFF = 2e4;
|
|
9059
9163
|
const truncated = diff.length > MAX_DIFF ? diff.slice(0, MAX_DIFF) + "\n\n... (diff truncated)" : diff;
|
|
9060
|
-
return { stat:
|
|
9164
|
+
return { stat: stat13 || "(no stat)", diff: truncated || "(clean)" };
|
|
9061
9165
|
} catch {
|
|
9062
9166
|
return { stat: "(unavailable)", diff: "(unavailable)" };
|
|
9063
9167
|
}
|
|
9064
9168
|
}
|
|
9065
9169
|
async function getScopedStagedDiff(paths, cwd) {
|
|
9066
9170
|
try {
|
|
9067
|
-
const
|
|
9171
|
+
const stat13 = await runGit3(["diff", "--cached", "--stat", "--", ...paths], cwd);
|
|
9068
9172
|
const diff = await runGit3(["diff", "--cached", "--", ...paths], cwd);
|
|
9069
9173
|
const MAX_DIFF = 2e4;
|
|
9070
9174
|
const truncated = diff.length > MAX_DIFF ? diff.slice(0, MAX_DIFF) + "\n\n... (diff truncated)" : diff;
|
|
9071
|
-
return { stat:
|
|
9175
|
+
return { stat: stat13 || "(no stat)", diff: truncated || "(clean)" };
|
|
9072
9176
|
} catch {
|
|
9073
9177
|
return { stat: "(unavailable)", diff: "(unavailable)" };
|
|
9074
9178
|
}
|
|
@@ -9109,14 +9213,14 @@ var VALID_TYPES = [
|
|
|
9109
9213
|
"build",
|
|
9110
9214
|
"revert"
|
|
9111
9215
|
];
|
|
9112
|
-
async function generateCommitFromDiff(api,
|
|
9216
|
+
async function generateCommitFromDiff(api, stat13, diff) {
|
|
9113
9217
|
if (!api.llm) return null;
|
|
9114
9218
|
try {
|
|
9115
9219
|
const result = await api.llm.complete(
|
|
9116
9220
|
`Write a Conventional Commits message for this staged git diff. Respond with ONLY a JSON object of the form {"type": string, "scope": string, "summary": string, "body": string}. type is one of: ${VALID_TYPES.join(", ")}. scope is a short area (empty string if unclear). summary is an imperative, lower-case, <=72-char subject with no trailing period. body is an optional short explanation (empty string if not needed). No prose outside the JSON.
|
|
9117
9221
|
|
|
9118
9222
|
Stat:
|
|
9119
|
-
${
|
|
9223
|
+
${stat13}
|
|
9120
9224
|
|
|
9121
9225
|
Diff:
|
|
9122
9226
|
${diff}`,
|
|
@@ -9257,7 +9361,10 @@ var plugin26 = {
|
|
|
9257
9361
|
const rawFiles = input["files"] ?? input["fileList"] ?? input["file_list"];
|
|
9258
9362
|
if (rawFiles !== void 0) {
|
|
9259
9363
|
if (!Array.isArray(rawFiles)) {
|
|
9260
|
-
|
|
9364
|
+
throw new ToolValidationError13({
|
|
9365
|
+
message: "files must be an array of file paths",
|
|
9366
|
+
field: "files"
|
|
9367
|
+
});
|
|
9261
9368
|
}
|
|
9262
9369
|
files = rawFiles;
|
|
9263
9370
|
} else if (typeof (input["file"] ?? input["file_path"]) === "string" && String(input["file"] ?? input["file_path"]).trim().length > 0) {
|
|
@@ -9271,20 +9378,26 @@ var plugin26 = {
|
|
|
9271
9378
|
const rawPaths = input["paths"] ?? input["pathList"] ?? input["path_list"];
|
|
9272
9379
|
if (rawPaths !== void 0) {
|
|
9273
9380
|
if (!Array.isArray(rawPaths)) {
|
|
9274
|
-
|
|
9381
|
+
throw new ToolValidationError13({
|
|
9382
|
+
message: "paths must be an array of pathspec patterns",
|
|
9383
|
+
field: "paths"
|
|
9384
|
+
});
|
|
9275
9385
|
}
|
|
9276
9386
|
pathspecs = rawPaths.filter((p) => typeof p === "string" && p.length > 0);
|
|
9277
9387
|
if (pathspecs.length === 0) {
|
|
9278
|
-
|
|
9388
|
+
throw new ToolValidationError13({
|
|
9389
|
+
message: "paths must contain at least one non-empty pattern",
|
|
9390
|
+
field: "paths"
|
|
9391
|
+
});
|
|
9279
9392
|
}
|
|
9280
9393
|
} else if (typeof input["path"] === "string" && input["path"].trim().length > 0) {
|
|
9281
9394
|
pathspecs = [input["path"].trim()];
|
|
9282
9395
|
}
|
|
9283
9396
|
if (rawPaths !== void 0 && files && files.length > 0) {
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
};
|
|
9397
|
+
throw new ToolValidationError13({
|
|
9398
|
+
message: "Pass either files (exact paths) or paths (pathspec globs), not both \u2014 the other would be silently ignored.",
|
|
9399
|
+
field: "paths"
|
|
9400
|
+
});
|
|
9288
9401
|
}
|
|
9289
9402
|
let commitScope;
|
|
9290
9403
|
let staged = [];
|
|
@@ -9292,10 +9405,10 @@ var plugin26 = {
|
|
|
9292
9405
|
try {
|
|
9293
9406
|
await stageFiles(pathspecs);
|
|
9294
9407
|
} catch (err) {
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9408
|
+
throw new Error(
|
|
9409
|
+
`Failed to stage files matching paths: ${err instanceof Error ? err.message : String(err)}`,
|
|
9410
|
+
{ cause: err }
|
|
9411
|
+
);
|
|
9299
9412
|
}
|
|
9300
9413
|
try {
|
|
9301
9414
|
staged = await getScopedStagedFiles(pathspecs);
|
|
@@ -9303,10 +9416,9 @@ var plugin26 = {
|
|
|
9303
9416
|
staged = [];
|
|
9304
9417
|
}
|
|
9305
9418
|
if (staged.length === 0) {
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
};
|
|
9419
|
+
throw new Error(
|
|
9420
|
+
"No changed files match the given paths \u2014 refusing to commit anything else."
|
|
9421
|
+
);
|
|
9310
9422
|
}
|
|
9311
9423
|
commitScope = staged;
|
|
9312
9424
|
try {
|
|
@@ -9318,10 +9430,10 @@ var plugin26 = {
|
|
|
9318
9430
|
try {
|
|
9319
9431
|
commitScope = await stageFiles(files);
|
|
9320
9432
|
} catch (err) {
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9433
|
+
throw new Error(
|
|
9434
|
+
`Failed to stage files: ${err instanceof Error ? err.message : String(err)}`,
|
|
9435
|
+
{ cause: err }
|
|
9436
|
+
);
|
|
9325
9437
|
}
|
|
9326
9438
|
try {
|
|
9327
9439
|
staged = await getStagedFiles();
|
|
@@ -9352,10 +9464,10 @@ var plugin26 = {
|
|
|
9352
9464
|
}
|
|
9353
9465
|
}
|
|
9354
9466
|
}
|
|
9355
|
-
const { stat:
|
|
9467
|
+
const { stat: stat13, diff: stagedDiff } = commitScope ? await getScopedStagedDiff(commitScope) : await getStagedDiff();
|
|
9356
9468
|
let generatedByLlm = false;
|
|
9357
9469
|
if (wantGenerate && staged.length > 0) {
|
|
9358
|
-
const g = await generateCommitFromDiff(api,
|
|
9470
|
+
const g = await generateCommitFromDiff(api, stat13, stagedDiff);
|
|
9359
9471
|
if (g) {
|
|
9360
9472
|
type = g.type;
|
|
9361
9473
|
if (g.scope) scope = g.scope;
|
|
@@ -9386,17 +9498,16 @@ var plugin26 = {
|
|
|
9386
9498
|
message: `Would create: ${summary || "update code"}`
|
|
9387
9499
|
};
|
|
9388
9500
|
}
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
};
|
|
9501
|
+
throw new ToolValidationError13({
|
|
9502
|
+
message: "type is required and must be a valid conventional commit type",
|
|
9503
|
+
field: "type"
|
|
9504
|
+
});
|
|
9393
9505
|
}
|
|
9394
9506
|
const msg = generateCommitMessage(type, scope, summary || "update code", body);
|
|
9395
9507
|
if (staged.length === 0) {
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
};
|
|
9508
|
+
throw new Error(
|
|
9509
|
+
'Nothing staged. Pass files (exact paths) or paths (pathspec globs) to scope this commit, stage with git add beforehand, or set extensions["git-autocommit"].autoStage=true to allow staging every changed file (legacy whole-tree behavior).'
|
|
9510
|
+
);
|
|
9400
9511
|
}
|
|
9401
9512
|
let scopeWarning = null;
|
|
9402
9513
|
if (commitScope) {
|
|
@@ -9426,7 +9537,7 @@ var plugin26 = {
|
|
|
9426
9537
|
stagedDiff: `
|
|
9427
9538
|
## Staged changes (dry run)
|
|
9428
9539
|
|
|
9429
|
-
${
|
|
9540
|
+
${stat13}
|
|
9430
9541
|
|
|
9431
9542
|
\`\`\`diff
|
|
9432
9543
|
${stagedDiff}
|
|
@@ -9438,20 +9549,19 @@ ${stagedDiff}
|
|
|
9438
9549
|
if (drifted.length > 0) {
|
|
9439
9550
|
const preview = drifted.slice(0, 10).join(", ");
|
|
9440
9551
|
const suffix = drifted.length > 10 ? ` and ${drifted.length - 10} more` : "";
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
};
|
|
9552
|
+
throw new Error(
|
|
9553
|
+
`Working tree changed after staging for: ${preview}${suffix}. A scoped commit takes working-tree content, so committing now could include changes that were never staged or previewed. Re-run the tool to re-stage the current content.`
|
|
9554
|
+
);
|
|
9445
9555
|
}
|
|
9446
9556
|
}
|
|
9447
9557
|
let hash = "";
|
|
9448
9558
|
try {
|
|
9449
9559
|
hash = await commitWithMessage(msg, void 0, commitScope);
|
|
9450
9560
|
} catch (err) {
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9561
|
+
throw new Error(
|
|
9562
|
+
`Failed to commit: ${err instanceof Error ? err.message : String(err)}`,
|
|
9563
|
+
{ cause: err }
|
|
9564
|
+
);
|
|
9455
9565
|
}
|
|
9456
9566
|
api.log.info("git-autocommit: created commit", { hash, type, scope });
|
|
9457
9567
|
commitCount.value += 1;
|
|
@@ -9483,17 +9593,15 @@ ${stagedDiff}
|
|
|
9483
9593
|
diff: `
|
|
9484
9594
|
## Staged diff
|
|
9485
9595
|
|
|
9486
|
-
${
|
|
9596
|
+
${stat13}
|
|
9487
9597
|
|
|
9488
9598
|
\`\`\`diff
|
|
9489
9599
|
${stagedDiff}
|
|
9490
9600
|
\`\`\``
|
|
9491
9601
|
};
|
|
9492
9602
|
} catch (err) {
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
error: `Uncaught error in git_autocommit: ${err instanceof Error ? err.message : String(err)}`
|
|
9496
|
-
};
|
|
9603
|
+
if (err instanceof Error) throw err;
|
|
9604
|
+
throw new Error(`Uncaught error in git_autocommit: ${String(err)}`, { cause: err });
|
|
9497
9605
|
}
|
|
9498
9606
|
}
|
|
9499
9607
|
});
|
|
@@ -9532,6 +9640,7 @@ var git_autocommit_default = plugin26;
|
|
|
9532
9640
|
// src/gitignore-guard/index.ts
|
|
9533
9641
|
import { access as access3, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
|
|
9534
9642
|
import { basename as basename3, isAbsolute as isAbsolute10, join as join3, relative as relative11, resolve as resolve12, sep as sep2 } from "node:path";
|
|
9643
|
+
import { ToolValidationError as ToolValidationError14 } from "@wrongstack/core/types";
|
|
9535
9644
|
var API_VERSION19 = "^0.1.10";
|
|
9536
9645
|
var DEFAULT_ARTIFACT_PATTERNS = Object.freeze([
|
|
9537
9646
|
"dist/",
|
|
@@ -9900,19 +10009,24 @@ var plugin27 = {
|
|
|
9900
10009
|
const rawInp = input ?? {};
|
|
9901
10010
|
const raw = rawInp["path"] ?? rawInp["filePath"] ?? rawInp["file_path"] ?? rawInp["TargetFile"] ?? rawInp["targetFile"] ?? rawInp["file"];
|
|
9902
10011
|
const rawPath = typeof raw === "string" ? raw.trim() : "";
|
|
9903
|
-
if (rawPath.length === 0)
|
|
10012
|
+
if (rawPath.length === 0) {
|
|
10013
|
+
throw new ToolValidationError14({ message: "path is required", field: "path" });
|
|
10014
|
+
}
|
|
9904
10015
|
const resolved = projectRelativePath(rawPath, process.cwd());
|
|
9905
10016
|
if (!resolved) {
|
|
9906
|
-
|
|
10017
|
+
throw new ToolValidationError14({
|
|
10018
|
+
message: `path outside project root: ${rawPath}`,
|
|
10019
|
+
field: "path"
|
|
10020
|
+
});
|
|
9907
10021
|
}
|
|
9908
10022
|
const cfg = readConfig22(api.config.extensions?.["gitignore-guard"]);
|
|
9909
10023
|
const explicit = typeof input?.pattern === "string" && input.pattern.trim().length > 0 ? input.pattern.trim() : null;
|
|
9910
10024
|
const pattern = explicit ?? classifyArtifact(resolved.rel, cfg.artifactPatterns);
|
|
9911
10025
|
if (!pattern) {
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
};
|
|
10026
|
+
throw new ToolValidationError14({
|
|
10027
|
+
message: `'${resolved.rel}' does not match any configured artifact pattern; pass an explicit pattern`,
|
|
10028
|
+
field: "pattern"
|
|
10029
|
+
});
|
|
9916
10030
|
}
|
|
9917
10031
|
const candidates = gitignoreCandidates(relDirOf(resolved.rel), resolved.root);
|
|
9918
10032
|
const nearest = await firstExistingGitignore(candidates);
|
|
@@ -10620,8 +10734,9 @@ var plugin29 = {
|
|
|
10620
10734
|
var injection_shield_default = plugin29;
|
|
10621
10735
|
|
|
10622
10736
|
// src/interface-contract-guard/index.ts
|
|
10623
|
-
import { readFile as readFile10 } from "node:fs/promises";
|
|
10737
|
+
import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
10624
10738
|
import { isAbsolute as isAbsolute12, relative as relative12, resolve as resolve13 } from "node:path";
|
|
10739
|
+
import { ToolValidationError as ToolValidationError15 } from "@wrongstack/core/types";
|
|
10625
10740
|
var API_VERSION21 = "^0.1.10";
|
|
10626
10741
|
var state27 = {
|
|
10627
10742
|
scanCount: 0,
|
|
@@ -10816,19 +10931,26 @@ If you changed member shapes, search the project for implementers/\`satisfies\`/
|
|
|
10816
10931
|
category: "Diagnostics",
|
|
10817
10932
|
mutating: false,
|
|
10818
10933
|
async execute(input) {
|
|
10819
|
-
if (!cfg.enabled)
|
|
10934
|
+
if (!cfg.enabled) throw new Error("interface-contract-guard is disabled");
|
|
10820
10935
|
const raw = input ?? {};
|
|
10821
10936
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? (typeof raw["file"] === "string" ? raw["file"] : void 0) ?? ".";
|
|
10822
10937
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
10823
|
-
|
|
10938
|
+
throw new ToolValidationError15({
|
|
10939
|
+
message: "path is outside the project root",
|
|
10940
|
+
field: "path"
|
|
10941
|
+
});
|
|
10824
10942
|
}
|
|
10825
10943
|
state27.scanCount += 1;
|
|
10826
10944
|
let result;
|
|
10827
10945
|
try {
|
|
10946
|
+
await stat8(resolve13(process.cwd(), rawPath));
|
|
10828
10947
|
result = await scanPath3(rawPath, cfg);
|
|
10829
10948
|
} catch (err) {
|
|
10830
10949
|
state27.errorCount += 1;
|
|
10831
|
-
|
|
10950
|
+
throw new Error(
|
|
10951
|
+
`check_interface_contracts failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
10952
|
+
{ cause: err }
|
|
10953
|
+
);
|
|
10832
10954
|
}
|
|
10833
10955
|
state27.findingCount += result.findings.length;
|
|
10834
10956
|
return {
|
|
@@ -10915,6 +11037,7 @@ var interface_contract_guard_default = plugin30;
|
|
|
10915
11037
|
// src/knowledge-graph/index.ts
|
|
10916
11038
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
10917
11039
|
import { dirname as dirname4, isAbsolute as isAbsolute13, relative as relative13, resolve as resolve14 } from "node:path";
|
|
11040
|
+
import { ToolValidationError as ToolValidationError16 } from "@wrongstack/core/types";
|
|
10918
11041
|
import { atomicWrite as atomicWrite2, ensureDir as ensureDir2 } from "@wrongstack/core/utils";
|
|
10919
11042
|
var API_VERSION22 = "^0.1.10";
|
|
10920
11043
|
var state28 = {
|
|
@@ -11085,12 +11208,9 @@ var plugin31 = {
|
|
|
11085
11208
|
category: "Memory",
|
|
11086
11209
|
mutating: true,
|
|
11087
11210
|
async execute(input) {
|
|
11088
|
-
if (!cfg.enabled)
|
|
11211
|
+
if (!cfg.enabled) throw new Error("knowledge-graph is disabled");
|
|
11089
11212
|
if (state28.facts.length >= cfg.maxFacts) {
|
|
11090
|
-
|
|
11091
|
-
ok: false,
|
|
11092
|
-
error: `fact limit reached (${cfg.maxFacts}). Remove old facts first.`
|
|
11093
|
-
};
|
|
11213
|
+
throw new Error(`fact limit reached (${cfg.maxFacts}). Remove old facts first.`);
|
|
11094
11214
|
}
|
|
11095
11215
|
const trim = (s) => String(s ?? "").trim().slice(0, cfg.maxFactChars);
|
|
11096
11216
|
const raw = input;
|
|
@@ -11104,7 +11224,10 @@ var plugin31 = {
|
|
|
11104
11224
|
input.object ?? raw["target"] ?? raw["val"] ?? raw["value"] ?? raw["obj"] ?? raw["targetEntity"]
|
|
11105
11225
|
);
|
|
11106
11226
|
if (!subject || !relation || !object) {
|
|
11107
|
-
|
|
11227
|
+
throw new ToolValidationError16({
|
|
11228
|
+
message: "subject, relation, and object are required",
|
|
11229
|
+
field: !subject ? "subject" : !relation ? "relation" : "object"
|
|
11230
|
+
});
|
|
11108
11231
|
}
|
|
11109
11232
|
const rawConf = typeof input.confidence === "string" ? input.confidence.trim().toLowerCase() : "";
|
|
11110
11233
|
const confidence = rawConf === "low" || rawConf === "high" || rawConf === "medium" ? rawConf : "medium";
|
|
@@ -11141,7 +11264,7 @@ var plugin31 = {
|
|
|
11141
11264
|
category: "Memory",
|
|
11142
11265
|
mutating: false,
|
|
11143
11266
|
async execute(input) {
|
|
11144
|
-
if (!cfg.enabled)
|
|
11267
|
+
if (!cfg.enabled) throw new Error("knowledge-graph is disabled");
|
|
11145
11268
|
state28.queries += 1;
|
|
11146
11269
|
const raw = input;
|
|
11147
11270
|
const rawQ = (typeof raw["query"] === "string" ? raw["query"] : void 0) ?? (typeof raw["q"] === "string" ? raw["q"] : void 0);
|
|
@@ -11186,16 +11309,17 @@ var plugin31 = {
|
|
|
11186
11309
|
category: "Memory",
|
|
11187
11310
|
mutating: true,
|
|
11188
11311
|
async execute(input) {
|
|
11189
|
-
if (!cfg.enabled)
|
|
11312
|
+
if (!cfg.enabled) throw new Error("knowledge-graph is disabled");
|
|
11190
11313
|
const before = state28.facts.length;
|
|
11191
11314
|
const raw = input ?? {};
|
|
11192
11315
|
const rawId = String(input.id ?? raw["factId"] ?? raw["fact_id"] ?? "").trim();
|
|
11316
|
+
if (!rawId) throw new ToolValidationError16({ message: "id is required", field: "id" });
|
|
11193
11317
|
const normalized = rawId.toLowerCase().startsWith("kg-") ? rawId.toLowerCase() : `kg-${rawId.toLowerCase()}`;
|
|
11194
11318
|
state28.facts = state28.facts.filter(
|
|
11195
11319
|
(f) => f.id.toLowerCase() !== normalized && f.id !== rawId
|
|
11196
11320
|
);
|
|
11197
11321
|
const removed = before - state28.facts.length;
|
|
11198
|
-
if (removed === 0)
|
|
11322
|
+
if (removed === 0) throw new Error(`no fact matches "${input.id ?? rawId}"`);
|
|
11199
11323
|
state28.removals += removed;
|
|
11200
11324
|
api.metrics.counter("removals", removed);
|
|
11201
11325
|
const persisted = await persistFacts(resolved);
|
|
@@ -12364,7 +12488,10 @@ var state32 = {
|
|
|
12364
12488
|
};
|
|
12365
12489
|
var DEFAULTS30 = {
|
|
12366
12490
|
enabled: true,
|
|
12367
|
-
|
|
12491
|
+
// Documented contract (feature matrix, plugin description): warn, then
|
|
12492
|
+
// block. A warn-only default meant an agent stuck re-issuing the same call
|
|
12493
|
+
// was never actually stopped unless the user happened to set any option.
|
|
12494
|
+
mode: "block",
|
|
12368
12495
|
warnAfter: 3,
|
|
12369
12496
|
blockAfter: 5,
|
|
12370
12497
|
oscillationWindow: 8,
|
|
@@ -12390,7 +12517,7 @@ function readConfig30(raw) {
|
|
|
12390
12517
|
const ignoreTools = Array.isArray(rawIgnore) ? rawIgnore.filter((t) => typeof t === "string") : [];
|
|
12391
12518
|
ignoreToolsSet = new Set(ignoreTools);
|
|
12392
12519
|
const rawMode = typeof (r["mode"] ?? r["action"] ?? r["behavior"]) === "string" ? String(r["mode"] ?? r["action"] ?? r["behavior"]).trim().toLowerCase() : void 0;
|
|
12393
|
-
const mode = rawMode === "warn"
|
|
12520
|
+
const mode = rawMode === "warn" || rawMode === "block" ? rawMode : DEFAULTS30.mode;
|
|
12394
12521
|
const rawOsc = r["oscillationWindow"] ?? r["oscillation_window"] ?? r["window"];
|
|
12395
12522
|
const rawMaxSteps = r["maxSteps"] ?? r["max_steps"] ?? r["stepLimit"] ?? r["step_limit"];
|
|
12396
12523
|
return {
|
|
@@ -12419,7 +12546,11 @@ function sortKeys(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
|
12419
12546
|
if (value === null || typeof value !== "object") return value;
|
|
12420
12547
|
if (seen.has(value)) return "[circular]";
|
|
12421
12548
|
if (depth >= CANONICALIZE_MAX_DEPTH) {
|
|
12422
|
-
|
|
12549
|
+
try {
|
|
12550
|
+
return `[deep:${JSON.stringify(value)}]`;
|
|
12551
|
+
} catch {
|
|
12552
|
+
return Array.isArray(value) ? `[array:${value.length}]` : "[deep-object]";
|
|
12553
|
+
}
|
|
12423
12554
|
}
|
|
12424
12555
|
seen.add(value);
|
|
12425
12556
|
try {
|
|
@@ -12507,7 +12638,7 @@ var plugin35 = {
|
|
|
12507
12638
|
mode: {
|
|
12508
12639
|
type: "string",
|
|
12509
12640
|
enum: ["warn", "block"],
|
|
12510
|
-
default: "
|
|
12641
|
+
default: "block",
|
|
12511
12642
|
description: "block = refuse the repeated call; warn = only inject context."
|
|
12512
12643
|
},
|
|
12513
12644
|
warnAfter: {
|
|
@@ -12839,6 +12970,7 @@ var loop_breaker_default = plugin35;
|
|
|
12839
12970
|
|
|
12840
12971
|
// src/migration-planner/index.ts
|
|
12841
12972
|
import { existsSync as existsSync5, readFileSync as readFileSync10 } from "node:fs";
|
|
12973
|
+
import { ToolValidationError as ToolValidationError17 } from "@wrongstack/core/types";
|
|
12842
12974
|
|
|
12843
12975
|
// src/runtime/llm.ts
|
|
12844
12976
|
import {
|
|
@@ -13107,14 +13239,14 @@ var plugin36 = {
|
|
|
13107
13239
|
const rawPath = inp["path"] ?? inp["TargetFile"] ?? inp["filePath"] ?? inp["targetFile"] ?? inp["file_path"] ?? inp["file"];
|
|
13108
13240
|
const path = typeof rawPath === "string" ? rawPath : void 0;
|
|
13109
13241
|
if (!path) return;
|
|
13110
|
-
const
|
|
13242
|
+
const basename8 = path.split(/[/\\]/).pop() ?? "";
|
|
13111
13243
|
if (!/^(package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?)$/i.test(
|
|
13112
|
-
|
|
13244
|
+
basename8
|
|
13113
13245
|
)) {
|
|
13114
13246
|
return;
|
|
13115
13247
|
}
|
|
13116
13248
|
return {
|
|
13117
|
-
additionalContext: `Manifest file ${
|
|
13249
|
+
additionalContext: `Manifest file ${basename8} changed. Consider running migration_plan if a dependency version was updated.`
|
|
13118
13250
|
};
|
|
13119
13251
|
};
|
|
13120
13252
|
state33.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
|
|
@@ -13212,7 +13344,7 @@ var plugin36 = {
|
|
|
13212
13344
|
category: "Planning",
|
|
13213
13345
|
mutating: false,
|
|
13214
13346
|
async execute(input, _ctx, execOpts) {
|
|
13215
|
-
if (!cfg.enabled)
|
|
13347
|
+
if (!cfg.enabled) throw new Error("migration-planner is disabled");
|
|
13216
13348
|
execOpts?.signal?.throwIfAborted();
|
|
13217
13349
|
const raw = input ?? {};
|
|
13218
13350
|
const rawPackage = input.packageName || raw["package"] || raw["pkg"] || raw["name"] || raw["package_name"] || raw["packageName"] || raw["dependency"] || raw["dep"] || raw["module"];
|
|
@@ -13223,7 +13355,10 @@ var plugin36 = {
|
|
|
13223
13355
|
const fromVersion = String(rawFrom ?? "").trim();
|
|
13224
13356
|
const toVersion = String(rawTo ?? "").trim();
|
|
13225
13357
|
if (!packageName || !fromVersion || !toVersion) {
|
|
13226
|
-
|
|
13358
|
+
throw new ToolValidationError17({
|
|
13359
|
+
message: "packageName, fromVersion, and toVersion are required",
|
|
13360
|
+
field: !packageName ? "packageName" : !fromVersion ? "fromVersion" : "toVersion"
|
|
13361
|
+
});
|
|
13227
13362
|
}
|
|
13228
13363
|
const changelog = readChangelog(packageName, cfg);
|
|
13229
13364
|
let breakingChanges;
|
|
@@ -13593,6 +13728,7 @@ var model_router_default = plugin37;
|
|
|
13593
13728
|
|
|
13594
13729
|
// src/notify-hub/index.ts
|
|
13595
13730
|
import { lookup } from "node:dns/promises";
|
|
13731
|
+
import { ToolValidationError as ToolValidationError18 } from "@wrongstack/core/types";
|
|
13596
13732
|
|
|
13597
13733
|
// src/notify-hub/webhook-channel.ts
|
|
13598
13734
|
function freshCircuit() {
|
|
@@ -13755,7 +13891,13 @@ var DEFAULTS33 = {
|
|
|
13755
13891
|
maxConsecutiveFailures: 5
|
|
13756
13892
|
};
|
|
13757
13893
|
function isPrivateIPv4(hostname) {
|
|
13758
|
-
const
|
|
13894
|
+
const hexMapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(hostname);
|
|
13895
|
+
const normalised = hexMapped ? [
|
|
13896
|
+
Number.parseInt(hexMapped[1], 16) >> 8,
|
|
13897
|
+
Number.parseInt(hexMapped[1], 16) & 255,
|
|
13898
|
+
Number.parseInt(hexMapped[2], 16) >> 8,
|
|
13899
|
+
Number.parseInt(hexMapped[2], 16) & 255
|
|
13900
|
+
].join(".") : hostname.replace(/^::ffff:/i, "");
|
|
13759
13901
|
const parts = normalised.split(".").map((p) => Number(p));
|
|
13760
13902
|
if (parts.length !== 4 || parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) {
|
|
13761
13903
|
return false;
|
|
@@ -13995,18 +14137,19 @@ var plugin38 = {
|
|
|
13995
14137
|
category: "Notifications",
|
|
13996
14138
|
mutating: true,
|
|
13997
14139
|
async execute(input) {
|
|
13998
|
-
if (!cfg.enabled)
|
|
14140
|
+
if (!cfg.enabled) throw new Error("notify-hub is disabled");
|
|
13999
14141
|
const ch = state35.channel;
|
|
14000
14142
|
if (!ch) {
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
};
|
|
14143
|
+
throw new Error(
|
|
14144
|
+
'no webhookUrl configured \u2014 set config.extensions["notify-hub"].webhookUrl to enable deliveries'
|
|
14145
|
+
);
|
|
14005
14146
|
}
|
|
14006
14147
|
const inp = input ?? {};
|
|
14007
14148
|
const rawMsg = inp["message"] ?? inp["body"] ?? inp["text"] ?? inp["content"] ?? inp["msg"];
|
|
14008
14149
|
const message = typeof rawMsg === "string" && rawMsg.trim().length > 0 ? rawMsg.trim() : "";
|
|
14009
|
-
if (!message)
|
|
14150
|
+
if (!message) {
|
|
14151
|
+
throw new ToolValidationError18({ message: "message is required", field: "message" });
|
|
14152
|
+
}
|
|
14010
14153
|
const rawTitle = inp["title"] ?? inp["subject"] ?? inp["header"];
|
|
14011
14154
|
const title = typeof rawTitle === "string" && rawTitle.trim() ? rawTitle.trim() : "WrongStack notification";
|
|
14012
14155
|
const result = await deliverViaChannel(ch, "manual", {
|
|
@@ -14015,11 +14158,10 @@ var plugin38 = {
|
|
|
14015
14158
|
level: input.level === "warning" || input.level === "critical" ? input.level : "info",
|
|
14016
14159
|
source: "manual"
|
|
14017
14160
|
});
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
};
|
|
14161
|
+
if (!result.ok) {
|
|
14162
|
+
throw new Error(`notification delivery failed: ${result.error ?? "unknown error"}`);
|
|
14163
|
+
}
|
|
14164
|
+
return { ok: true, circuitOpen: ch.circuitStatus().open };
|
|
14023
14165
|
}
|
|
14024
14166
|
});
|
|
14025
14167
|
api.tools.register({
|
|
@@ -14238,8 +14380,8 @@ function isRootPathScope(path) {
|
|
|
14238
14380
|
function isDirectoryAmbiguousPath(path) {
|
|
14239
14381
|
const normalized = normalizePath2(path).replace(/\/$/, "");
|
|
14240
14382
|
if (isRootPathScope(normalized)) return true;
|
|
14241
|
-
const
|
|
14242
|
-
return path.endsWith("/") ||
|
|
14383
|
+
const basename8 = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
14384
|
+
return path.endsWith("/") || basename8.length > 0 && !basename8.includes(".");
|
|
14243
14385
|
}
|
|
14244
14386
|
function hasConfiguredProtectedDescendant(path, patterns) {
|
|
14245
14387
|
const normalized = normalizePath2(path).replace(/\/$/, "").toLowerCase();
|
|
@@ -15516,9 +15658,9 @@ var plugin39 = {
|
|
|
15516
15658
|
} catch {
|
|
15517
15659
|
}
|
|
15518
15660
|
}
|
|
15519
|
-
const
|
|
15520
|
-
states.set(api,
|
|
15521
|
-
latestState =
|
|
15661
|
+
const state59 = createState();
|
|
15662
|
+
states.set(api, state59);
|
|
15663
|
+
latestState = state59;
|
|
15522
15664
|
const cfg = readConfig34(api.config.extensions?.["path-guard"]);
|
|
15523
15665
|
const protectRes = cfg.protect.map(compilePathGlob);
|
|
15524
15666
|
const allowRes = cfg.allow.map(compilePathGlob);
|
|
@@ -15526,15 +15668,15 @@ var plugin39 = {
|
|
|
15526
15668
|
const subject = isScope ? `write scope "${path}" may include a protected path \u2014 narrow it or add an \`allow\` glob` : `"${path}" is a protected path`;
|
|
15527
15669
|
const matchContext = isScope ? 'its unresolved scope overlaps config.extensions["path-guard"].protect' : 'matched by config.extensions["path-guard"].protect';
|
|
15528
15670
|
if (cfg.mode === "block") {
|
|
15529
|
-
|
|
15530
|
-
|
|
15671
|
+
state59.blocks += 1;
|
|
15672
|
+
state59.lastBlock = { path, tool, when: (/* @__PURE__ */ new Date()).toISOString() };
|
|
15531
15673
|
api.metrics.counter("blocks");
|
|
15532
15674
|
return {
|
|
15533
15675
|
decision: "block",
|
|
15534
15676
|
reason: `path-guard: ${subject} (${matchContext}) \u2014 ${operation} refused. If this change is intentional, ask the user to do it, add an \`allow\` glob, or set mode: "warn".`
|
|
15535
15677
|
};
|
|
15536
15678
|
}
|
|
15537
|
-
|
|
15679
|
+
state59.warns += 1;
|
|
15538
15680
|
api.metrics.counter("warns");
|
|
15539
15681
|
return {
|
|
15540
15682
|
decision: "allow",
|
|
@@ -15542,12 +15684,12 @@ var plugin39 = {
|
|
|
15542
15684
|
};
|
|
15543
15685
|
};
|
|
15544
15686
|
const bumpRedos = () => {
|
|
15545
|
-
|
|
15687
|
+
state59.redosTimeouts += 1;
|
|
15546
15688
|
api.metrics.counter("redos_timeouts");
|
|
15547
15689
|
};
|
|
15548
15690
|
const hook = async (input) => {
|
|
15549
15691
|
if (!cfg.enabled) return;
|
|
15550
|
-
|
|
15692
|
+
state59.invocations += 1;
|
|
15551
15693
|
const toolName = input.toolName ?? "";
|
|
15552
15694
|
const ti = input.toolInput ?? {};
|
|
15553
15695
|
const command = typeof ti["command"] === "string" ? ti["command"] : "";
|
|
@@ -15633,7 +15775,7 @@ var plugin39 = {
|
|
|
15633
15775
|
}
|
|
15634
15776
|
return;
|
|
15635
15777
|
};
|
|
15636
|
-
|
|
15778
|
+
state59.hookUnregister = api.registerHook("PreToolUse", "*", hook, {
|
|
15637
15779
|
name: "path-guard",
|
|
15638
15780
|
stage: "validate",
|
|
15639
15781
|
failurePolicy: "closed",
|
|
@@ -15654,12 +15796,12 @@ var plugin39 = {
|
|
|
15654
15796
|
protect: cfg.protect,
|
|
15655
15797
|
allow: cfg.allow,
|
|
15656
15798
|
counters: {
|
|
15657
|
-
invocations:
|
|
15658
|
-
blocks:
|
|
15659
|
-
warns:
|
|
15660
|
-
redosTimeouts:
|
|
15799
|
+
invocations: state59.invocations,
|
|
15800
|
+
blocks: state59.blocks,
|
|
15801
|
+
warns: state59.warns,
|
|
15802
|
+
redosTimeouts: state59.redosTimeouts
|
|
15661
15803
|
},
|
|
15662
|
-
lastBlock:
|
|
15804
|
+
lastBlock: state59.lastBlock
|
|
15663
15805
|
};
|
|
15664
15806
|
}
|
|
15665
15807
|
});
|
|
@@ -15671,39 +15813,39 @@ var plugin39 = {
|
|
|
15671
15813
|
});
|
|
15672
15814
|
},
|
|
15673
15815
|
teardown(api) {
|
|
15674
|
-
const
|
|
15675
|
-
if (!
|
|
15676
|
-
if (
|
|
15816
|
+
const state59 = states.get(api);
|
|
15817
|
+
if (!state59) return;
|
|
15818
|
+
if (state59.hookUnregister) {
|
|
15677
15819
|
try {
|
|
15678
|
-
|
|
15820
|
+
state59.hookUnregister();
|
|
15679
15821
|
} catch {
|
|
15680
15822
|
}
|
|
15681
|
-
|
|
15823
|
+
state59.hookUnregister = null;
|
|
15682
15824
|
}
|
|
15683
15825
|
const final = {
|
|
15684
|
-
invocations:
|
|
15685
|
-
blocks:
|
|
15686
|
-
warns:
|
|
15687
|
-
redosTimeouts:
|
|
15826
|
+
invocations: state59.invocations,
|
|
15827
|
+
blocks: state59.blocks,
|
|
15828
|
+
warns: state59.warns,
|
|
15829
|
+
redosTimeouts: state59.redosTimeouts
|
|
15688
15830
|
};
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15831
|
+
state59.invocations = 0;
|
|
15832
|
+
state59.blocks = 0;
|
|
15833
|
+
state59.warns = 0;
|
|
15834
|
+
state59.redosTimeouts = 0;
|
|
15835
|
+
state59.lastBlock = null;
|
|
15694
15836
|
states.delete(api);
|
|
15695
15837
|
api.log.info("path-guard: teardown complete", { final });
|
|
15696
15838
|
},
|
|
15697
15839
|
async health() {
|
|
15698
|
-
const
|
|
15840
|
+
const state59 = latestState;
|
|
15699
15841
|
return {
|
|
15700
15842
|
ok: true,
|
|
15701
|
-
message:
|
|
15843
|
+
message: state59.lastBlock === null ? `path-guard: ${state59.invocations} invocation(s), ${state59.blocks} block(s), ${state59.warns} warn(s)` : `path-guard: last block on "${state59.lastBlock.path}" (${state59.lastBlock.tool}) at ${state59.lastBlock.when}`,
|
|
15702
15844
|
counters: {
|
|
15703
|
-
invocations:
|
|
15704
|
-
blocks:
|
|
15705
|
-
warns:
|
|
15706
|
-
redosTimeouts:
|
|
15845
|
+
invocations: state59.invocations,
|
|
15846
|
+
blocks: state59.blocks,
|
|
15847
|
+
warns: state59.warns,
|
|
15848
|
+
redosTimeouts: state59.redosTimeouts
|
|
15707
15849
|
}
|
|
15708
15850
|
};
|
|
15709
15851
|
}
|
|
@@ -15713,6 +15855,7 @@ var path_guard_default = plugin39;
|
|
|
15713
15855
|
// src/performance-regression-gate/index.ts
|
|
15714
15856
|
import { existsSync as existsSync7, readFileSync as readFileSync11 } from "node:fs";
|
|
15715
15857
|
import { isAbsolute as isAbsolute16, relative as relative16, resolve as resolve18 } from "node:path";
|
|
15858
|
+
import { ToolValidationError as ToolValidationError19 } from "@wrongstack/core/types";
|
|
15716
15859
|
var API_VERSION26 = "^0.1.10";
|
|
15717
15860
|
var state36 = {
|
|
15718
15861
|
invocationCount: 0,
|
|
@@ -15779,10 +15922,11 @@ function flattenResults(results) {
|
|
|
15779
15922
|
function loadResults(path) {
|
|
15780
15923
|
if (!path || !existsSync7(path)) return null;
|
|
15781
15924
|
try {
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15925
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
15926
|
+
} catch (err) {
|
|
15927
|
+
throw new Error(`Could not read benchmark results at ${path}: ${String(err)}`, {
|
|
15928
|
+
cause: err
|
|
15929
|
+
});
|
|
15786
15930
|
}
|
|
15787
15931
|
}
|
|
15788
15932
|
function stripVariantSuffix(name) {
|
|
@@ -15909,7 +16053,7 @@ var plugin40 = {
|
|
|
15909
16053
|
mutating: false,
|
|
15910
16054
|
async execute(input) {
|
|
15911
16055
|
if (!cfg.enabled) {
|
|
15912
|
-
|
|
16056
|
+
throw new Error("performance-regression-gate is disabled");
|
|
15913
16057
|
}
|
|
15914
16058
|
state36.invocationCount += 1;
|
|
15915
16059
|
const raw = input ?? {};
|
|
@@ -15920,11 +16064,23 @@ var plugin40 = {
|
|
|
15920
16064
|
const resultsPath = resolveProjectPath6(resultsPathStr) ?? "";
|
|
15921
16065
|
if (!resultsPath) {
|
|
15922
16066
|
state36.errorCount += 1;
|
|
15923
|
-
|
|
16067
|
+
throw new ToolValidationError19({
|
|
16068
|
+
message: "invalid results path (must be inside project)",
|
|
16069
|
+
field: "resultsPath"
|
|
16070
|
+
});
|
|
16071
|
+
}
|
|
16072
|
+
let results;
|
|
16073
|
+
try {
|
|
16074
|
+
results = loadResults(resultsPath);
|
|
16075
|
+
} catch (err) {
|
|
16076
|
+
state36.errorCount += 1;
|
|
16077
|
+
throw err;
|
|
15924
16078
|
}
|
|
15925
|
-
const results = loadResults(resultsPath);
|
|
15926
16079
|
if (!results) {
|
|
15927
16080
|
state36.missingResultsCount += 1;
|
|
16081
|
+
if (resultsPathStr !== "bench-results.json" || rawResultsPath !== "bench-results.json") {
|
|
16082
|
+
throw new Error(`No benchmark results found at ${resultsPathStr}.`);
|
|
16083
|
+
}
|
|
15928
16084
|
return {
|
|
15929
16085
|
ok: true,
|
|
15930
16086
|
hasResults: false,
|
|
@@ -15953,15 +16109,21 @@ var plugin40 = {
|
|
|
15953
16109
|
const baselineResolved = resolveProjectPath6(baselinePathStr) ?? "";
|
|
15954
16110
|
if (!baselineResolved) {
|
|
15955
16111
|
state36.errorCount += 1;
|
|
15956
|
-
|
|
16112
|
+
throw new ToolValidationError19({
|
|
16113
|
+
message: "invalid baseline path (must be inside project)",
|
|
16114
|
+
field: "baselinePath"
|
|
16115
|
+
});
|
|
16116
|
+
}
|
|
16117
|
+
let baselineResults;
|
|
16118
|
+
try {
|
|
16119
|
+
baselineResults = loadResults(baselineResolved);
|
|
16120
|
+
} catch (err) {
|
|
16121
|
+
state36.errorCount += 1;
|
|
16122
|
+
throw err;
|
|
15957
16123
|
}
|
|
15958
|
-
const baselineResults = loadResults(baselineResolved);
|
|
15959
16124
|
if (!baselineResults) {
|
|
15960
16125
|
state36.errorCount += 1;
|
|
15961
|
-
|
|
15962
|
-
ok: false,
|
|
15963
|
-
error: `Could not read baseline results at ${baselinePathStr}.`
|
|
15964
|
-
};
|
|
16126
|
+
throw new Error(`Could not read baseline results at ${baselinePathStr}.`);
|
|
15965
16127
|
}
|
|
15966
16128
|
const baseline = flattenResults(baselineResults);
|
|
15967
16129
|
pairs = pairCrossFile(baseline, current);
|
|
@@ -16176,6 +16338,7 @@ var plugin_stack_observer_default = PLUGIN;
|
|
|
16176
16338
|
import { execFile as execFile9 } from "node:child_process";
|
|
16177
16339
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
|
|
16178
16340
|
import { dirname as dirname7, isAbsolute as isAbsolute17, relative as relative17, resolve as resolve19 } from "node:path";
|
|
16341
|
+
import { ToolValidationError as ToolValidationError20 } from "@wrongstack/core/types";
|
|
16179
16342
|
var API_VERSION27 = "^0.1.10";
|
|
16180
16343
|
var state38 = {
|
|
16181
16344
|
commits: [],
|
|
@@ -16188,8 +16351,23 @@ var state38 = {
|
|
|
16188
16351
|
draftErrors: 0,
|
|
16189
16352
|
stopInvocations: 0,
|
|
16190
16353
|
stopHookUnregister: null,
|
|
16354
|
+
postHookUnregister: null,
|
|
16191
16355
|
eventUnsubscribers: []
|
|
16192
16356
|
};
|
|
16357
|
+
var TRACKED_FILE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "write_to_file", "replace_file_content"]);
|
|
16358
|
+
function parseAutocommitResult(content) {
|
|
16359
|
+
const hash = /"hash":\s*"([0-9a-f]{7,64})"/.exec(content)?.[1];
|
|
16360
|
+
if (!hash) return null;
|
|
16361
|
+
const rawMessage = /"message":\s*("(?:[^"\\]|\\.)*")/.exec(content)?.[1];
|
|
16362
|
+
let message = "commit";
|
|
16363
|
+
if (rawMessage) {
|
|
16364
|
+
try {
|
|
16365
|
+
message = JSON.parse(rawMessage);
|
|
16366
|
+
} catch {
|
|
16367
|
+
}
|
|
16368
|
+
}
|
|
16369
|
+
return { hash, message };
|
|
16370
|
+
}
|
|
16193
16371
|
var DEFAULTS36 = {
|
|
16194
16372
|
enabled: true,
|
|
16195
16373
|
outputPath: ".wrongstack/PR_DRAFT.md",
|
|
@@ -16372,6 +16550,7 @@ var plugin41 = {
|
|
|
16372
16550
|
state38.draftErrors = 0;
|
|
16373
16551
|
state38.stopInvocations = 0;
|
|
16374
16552
|
state38.stopHookUnregister = (0, runtime_exports.releaseHandle)(state38.stopHookUnregister);
|
|
16553
|
+
state38.postHookUnregister = (0, runtime_exports.releaseHandle)(state38.postHookUnregister);
|
|
16375
16554
|
for (const off of state38.eventUnsubscribers) {
|
|
16376
16555
|
try {
|
|
16377
16556
|
off();
|
|
@@ -16381,22 +16560,24 @@ var plugin41 = {
|
|
|
16381
16560
|
state38.eventUnsubscribers = [];
|
|
16382
16561
|
const cfg = readConfig37(api.config.extensions?.["pr-drafter"]);
|
|
16383
16562
|
if (api.onPattern) {
|
|
16384
|
-
const offTool = api.onPattern("tool.completed", (
|
|
16385
|
-
const p = payload;
|
|
16386
|
-
const toolName = p?.tool;
|
|
16563
|
+
const offTool = api.onPattern("tool.completed", () => {
|
|
16387
16564
|
state38.toolCalls += 1;
|
|
16388
|
-
if (toolName === "git_autocommit" && p?.result?.committed) {
|
|
16389
|
-
const msg = p.result.commitMessage ?? p.input?.message ?? "commit";
|
|
16390
|
-
state38.commits.push(msg);
|
|
16391
|
-
}
|
|
16392
|
-
const rawInput = p?.input ?? {};
|
|
16393
|
-
const filePath = typeof rawInput["path"] === "string" && rawInput["path"] || typeof rawInput["filePath"] === "string" && rawInput["filePath"] || typeof rawInput["file_path"] === "string" && rawInput["file_path"] || typeof rawInput["TargetFile"] === "string" && rawInput["TargetFile"] || typeof rawInput["targetFile"] === "string" && rawInput["targetFile"] || typeof rawInput["file"] === "string" && rawInput["file"];
|
|
16394
|
-
if ((toolName === "write" || toolName === "edit" || toolName === "write_to_file" || toolName === "replace_file_content") && filePath) {
|
|
16395
|
-
state38.files.add(filePath);
|
|
16396
|
-
}
|
|
16397
16565
|
});
|
|
16398
16566
|
state38.eventUnsubscribers.push(offTool);
|
|
16399
16567
|
}
|
|
16568
|
+
const postHook = (input) => {
|
|
16569
|
+
if (!input.toolResult || input.toolResult.isError) return;
|
|
16570
|
+
const toolName = input.toolName;
|
|
16571
|
+
if (toolName === "git_autocommit") {
|
|
16572
|
+
const commit = parseAutocommitResult(String(input.toolResult.content ?? ""));
|
|
16573
|
+
if (commit) state38.commits.push(commit.message);
|
|
16574
|
+
return;
|
|
16575
|
+
}
|
|
16576
|
+
if (!toolName || !TRACKED_FILE_TOOLS.has(toolName)) return;
|
|
16577
|
+
const rawInput = input.toolInput ?? {};
|
|
16578
|
+
const filePath = ["path", "filePath", "file_path", "TargetFile", "targetFile", "file"].map((key) => rawInput[key]).find((value) => typeof value === "string" && value.length > 0);
|
|
16579
|
+
if (filePath) state38.files.add(filePath);
|
|
16580
|
+
};
|
|
16400
16581
|
if (api.onEvent) {
|
|
16401
16582
|
const offUsage = api.onEvent("provider.response", (payload) => {
|
|
16402
16583
|
const p = payload;
|
|
@@ -16415,6 +16596,11 @@ var plugin41 = {
|
|
|
16415
16596
|
await writeDraft(cfg, api.llm);
|
|
16416
16597
|
};
|
|
16417
16598
|
state38.stopHookUnregister = api.registerHook("Stop", void 0, stopHook);
|
|
16599
|
+
state38.postHookUnregister = api.registerHook(
|
|
16600
|
+
"PostToolUse",
|
|
16601
|
+
"git_autocommit|write|edit|write_to_file|replace_file_content",
|
|
16602
|
+
postHook
|
|
16603
|
+
);
|
|
16418
16604
|
api.tools.register({
|
|
16419
16605
|
name: "pr_draft",
|
|
16420
16606
|
description: "Generate or refresh the pull-request draft for the current session. Writes the markdown file and returns its path + title.",
|
|
@@ -16437,7 +16623,7 @@ var plugin41 = {
|
|
|
16437
16623
|
mutating: true,
|
|
16438
16624
|
capabilities: ["fs.write"],
|
|
16439
16625
|
async execute(input = {}) {
|
|
16440
|
-
if (!cfg.enabled)
|
|
16626
|
+
if (!cfg.enabled) throw new Error("pr-drafter is disabled");
|
|
16441
16627
|
const raw = input ?? {};
|
|
16442
16628
|
const preview = Boolean(
|
|
16443
16629
|
input?.preview ?? raw["dryRun"] ?? raw["dry_run"] ?? raw["dry"] ?? raw["previewOnly"]
|
|
@@ -16449,24 +16635,29 @@ var plugin41 = {
|
|
|
16449
16635
|
return { ok: true, preview: true, title: draft.title, body: draft.body };
|
|
16450
16636
|
}
|
|
16451
16637
|
const resolved = resolveProjectPath7(outputPathStr);
|
|
16452
|
-
if (!resolved)
|
|
16638
|
+
if (!resolved) {
|
|
16639
|
+
throw new ToolValidationError20({
|
|
16640
|
+
message: "outputPath resolves outside project",
|
|
16641
|
+
field: "outputPath"
|
|
16642
|
+
});
|
|
16643
|
+
}
|
|
16453
16644
|
try {
|
|
16454
16645
|
await mkdir2(dirname7(resolved), { recursive: true });
|
|
16455
16646
|
await writeFile4(resolved, draft.body);
|
|
16456
|
-
state38.draftsWritten += 1;
|
|
16457
|
-
return {
|
|
16458
|
-
ok: true,
|
|
16459
|
-
path: outputPathStr,
|
|
16460
|
-
resolvedPath: resolved,
|
|
16461
|
-
title: draft.title
|
|
16462
|
-
};
|
|
16463
16647
|
} catch (err) {
|
|
16464
16648
|
state38.draftErrors += 1;
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16649
|
+
throw new Error(
|
|
16650
|
+
`Could not write PR draft to ${outputPathStr}: ${err instanceof Error ? err.message : String(err)}`,
|
|
16651
|
+
{ cause: err }
|
|
16652
|
+
);
|
|
16469
16653
|
}
|
|
16654
|
+
state38.draftsWritten += 1;
|
|
16655
|
+
return {
|
|
16656
|
+
ok: true,
|
|
16657
|
+
path: outputPathStr,
|
|
16658
|
+
resolvedPath: resolved,
|
|
16659
|
+
title: draft.title
|
|
16660
|
+
};
|
|
16470
16661
|
}
|
|
16471
16662
|
});
|
|
16472
16663
|
api.log.info("pr-drafter plugin loaded", {
|
|
@@ -16483,6 +16674,7 @@ var plugin41 = {
|
|
|
16483
16674
|
}
|
|
16484
16675
|
state38.stopHookUnregister = null;
|
|
16485
16676
|
}
|
|
16677
|
+
state38.postHookUnregister = (0, runtime_exports.releaseHandle)(state38.postHookUnregister);
|
|
16486
16678
|
for (const off of state38.eventUnsubscribers) {
|
|
16487
16679
|
try {
|
|
16488
16680
|
off();
|
|
@@ -17194,8 +17386,9 @@ var plugin43 = {
|
|
|
17194
17386
|
var prompt_firewall_default = plugin43;
|
|
17195
17387
|
|
|
17196
17388
|
// src/refactor-suggester/index.ts
|
|
17197
|
-
import { readFile as readFile12 } from "node:fs/promises";
|
|
17389
|
+
import { readFile as readFile12, stat as stat9 } from "node:fs/promises";
|
|
17198
17390
|
import { isAbsolute as isAbsolute18, relative as relative18, resolve as resolve20 } from "node:path";
|
|
17391
|
+
import { ToolValidationError as ToolValidationError21 } from "@wrongstack/core/types";
|
|
17199
17392
|
var API_VERSION28 = "^0.1.10";
|
|
17200
17393
|
var HOOK_WARNING_COOLDOWN_MS2 = 6e4;
|
|
17201
17394
|
var state41 = {
|
|
@@ -17485,11 +17678,23 @@ var plugin44 = {
|
|
|
17485
17678
|
category: "Diagnostics",
|
|
17486
17679
|
mutating: false,
|
|
17487
17680
|
async execute(input) {
|
|
17488
|
-
if (!cfg.enabled)
|
|
17681
|
+
if (!cfg.enabled) throw new Error("refactor-suggester is disabled");
|
|
17489
17682
|
const raw = input ?? {};
|
|
17490
17683
|
const rawPath = (typeof input.path === "string" && input.path.trim().length > 0 ? input.path.trim() : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? ".";
|
|
17491
17684
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
17492
|
-
|
|
17685
|
+
throw new ToolValidationError21({
|
|
17686
|
+
message: "path is outside the project root",
|
|
17687
|
+
field: "path"
|
|
17688
|
+
});
|
|
17689
|
+
}
|
|
17690
|
+
try {
|
|
17691
|
+
await stat9(resolve20(process.cwd(), rawPath));
|
|
17692
|
+
} catch (err) {
|
|
17693
|
+
throw new ToolValidationError21({
|
|
17694
|
+
message: `path does not exist or cannot be read: ${rawPath}`,
|
|
17695
|
+
field: "path",
|
|
17696
|
+
cause: err
|
|
17697
|
+
});
|
|
17493
17698
|
}
|
|
17494
17699
|
state41.scanCount += 1;
|
|
17495
17700
|
let result;
|
|
@@ -17497,7 +17702,7 @@ var plugin44 = {
|
|
|
17497
17702
|
result = await scanPath4(rawPath, cfg);
|
|
17498
17703
|
} catch (err) {
|
|
17499
17704
|
state41.errorCount += 1;
|
|
17500
|
-
|
|
17705
|
+
throw new Error(`refactor scan failed: ${String(err)}`, { cause: err });
|
|
17501
17706
|
}
|
|
17502
17707
|
state41.suggestionCount += result.suggestions.length;
|
|
17503
17708
|
return {
|
|
@@ -17858,7 +18063,7 @@ var plugin45 = {
|
|
|
17858
18063
|
category: "Development",
|
|
17859
18064
|
mutating: false,
|
|
17860
18065
|
async execute(input, _ctx, execOpts) {
|
|
17861
|
-
if (!cfg.enabled)
|
|
18066
|
+
if (!cfg.enabled) throw new Error("release-notes-generator is disabled");
|
|
17862
18067
|
execOpts?.signal?.throwIfAborted();
|
|
17863
18068
|
const raw = input ?? {};
|
|
17864
18069
|
const rawTo = input.to ?? raw["to_ref"] ?? raw["toRef"] ?? raw["until"] ?? raw["end"];
|
|
@@ -17873,7 +18078,7 @@ var plugin45 = {
|
|
|
17873
18078
|
commits = await getCommits(fromRef, toRef, execOpts?.signal);
|
|
17874
18079
|
} catch (err) {
|
|
17875
18080
|
state42.errorCount += 1;
|
|
17876
|
-
|
|
18081
|
+
throw new Error(`Could not read git history: ${String(err)}`, { cause: err });
|
|
17877
18082
|
}
|
|
17878
18083
|
state42.commitCount += commits.length;
|
|
17879
18084
|
execOpts?.signal?.throwIfAborted();
|
|
@@ -18288,6 +18493,7 @@ ${body}${suffix}`;
|
|
|
18288
18493
|
var schema_evolution_guard_default = plugin46;
|
|
18289
18494
|
|
|
18290
18495
|
// src/secret-scanner/index.ts
|
|
18496
|
+
import { ToolValidationError as ToolValidationError22 } from "@wrongstack/core/types";
|
|
18291
18497
|
var BASE_PATTERNS = cloneCredentialPatterns();
|
|
18292
18498
|
var PATTERNS3 = [...BASE_PATTERNS];
|
|
18293
18499
|
var GROUP_INDEX_OF_PATTERN = [];
|
|
@@ -18496,7 +18702,7 @@ function readConfig43(raw) {
|
|
|
18496
18702
|
function buildHook(cfg, log, runtime) {
|
|
18497
18703
|
return (input) => {
|
|
18498
18704
|
activateRuntime(runtime);
|
|
18499
|
-
const { state:
|
|
18705
|
+
const { state: state59 } = runtime;
|
|
18500
18706
|
if (!cfg.enabled) return;
|
|
18501
18707
|
const toolName = input.toolName ?? "unknown";
|
|
18502
18708
|
let matched;
|
|
@@ -18504,7 +18710,7 @@ function buildHook(cfg, log, runtime) {
|
|
|
18504
18710
|
matched = scanInput(input.toolInput);
|
|
18505
18711
|
} catch (err) {
|
|
18506
18712
|
if (String(err).includes("ReDoS")) {
|
|
18507
|
-
|
|
18713
|
+
state59.timeoutCount += 1;
|
|
18508
18714
|
return {
|
|
18509
18715
|
decision: "block",
|
|
18510
18716
|
reason: "secret-scanner: ReDoS timeout \u2014 regex scan exceeded the wall-clock budget. Fail-closed: treated as a block."
|
|
@@ -18516,8 +18722,8 @@ function buildHook(cfg, log, runtime) {
|
|
|
18516
18722
|
const summary = matched.join(", ");
|
|
18517
18723
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
18518
18724
|
if (cfg.mode === "block") {
|
|
18519
|
-
|
|
18520
|
-
|
|
18725
|
+
state59.blockCount += 1;
|
|
18726
|
+
state59.lastBlock = { toolName, matchedTypes: matched, when };
|
|
18521
18727
|
log.warn(`[secret-scanner] blocked ${toolName} \u2014 matched: ${summary}`);
|
|
18522
18728
|
return {
|
|
18523
18729
|
decision: "block",
|
|
@@ -18527,7 +18733,7 @@ function buildHook(cfg, log, runtime) {
|
|
|
18527
18733
|
if (cfg.mode === "redact") {
|
|
18528
18734
|
const redacted = redactInput(input.toolInput);
|
|
18529
18735
|
if (redacted.ok && redacted.value !== null && typeof redacted.value === "object" && !Array.isArray(redacted.value)) {
|
|
18530
|
-
|
|
18736
|
+
state59.redactCount += 1;
|
|
18531
18737
|
log.info(`[secret-scanner] redacted ${toolName} \u2014 matched: ${summary}`);
|
|
18532
18738
|
return {
|
|
18533
18739
|
decision: "allow",
|
|
@@ -18535,15 +18741,15 @@ function buildHook(cfg, log, runtime) {
|
|
|
18535
18741
|
additionalContext: `secret-scanner: redacted ${matched.length} credential pattern(s) from the ${toolName} arguments before execution.`
|
|
18536
18742
|
};
|
|
18537
18743
|
}
|
|
18538
|
-
|
|
18539
|
-
|
|
18744
|
+
state59.blockCount += 1;
|
|
18745
|
+
state59.lastBlock = { toolName, matchedTypes: matched, when };
|
|
18540
18746
|
const detail = !redacted.ok ? redacted.reason === "oversized_input" ? "an input field exceeds the safe scan limit" : "the input exceeds the safe nesting depth" : "the input has a non-object shape";
|
|
18541
18747
|
return {
|
|
18542
18748
|
decision: "block",
|
|
18543
18749
|
reason: `secret-scanner: cannot safely redact '${toolName}' because ${detail}; refusing to run.`
|
|
18544
18750
|
};
|
|
18545
18751
|
}
|
|
18546
|
-
|
|
18752
|
+
state59.allowCount += 1;
|
|
18547
18753
|
log.warn(
|
|
18548
18754
|
`[secret-scanner] allow-mode: ${toolName} matched ${summary} but mode='allow' lets it through.`
|
|
18549
18755
|
);
|
|
@@ -18553,7 +18759,7 @@ function buildHook(cfg, log, runtime) {
|
|
|
18553
18759
|
function buildPostHook(cfg, log, runtime) {
|
|
18554
18760
|
return (input) => {
|
|
18555
18761
|
activateRuntime(runtime);
|
|
18556
|
-
const { state:
|
|
18762
|
+
const { state: state59 } = runtime;
|
|
18557
18763
|
if (!cfg.enabled) return;
|
|
18558
18764
|
const result = input.toolResult;
|
|
18559
18765
|
if (!result || typeof result.content !== "string") return;
|
|
@@ -18573,8 +18779,8 @@ function buildPostHook(cfg, log, runtime) {
|
|
|
18573
18779
|
}
|
|
18574
18780
|
const summary = credentialMatches.join(", ");
|
|
18575
18781
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
18576
|
-
|
|
18577
|
-
|
|
18782
|
+
state59.leakCount += 1;
|
|
18783
|
+
state59.lastLeak = { toolName, matchedTypes: credentialMatches, when };
|
|
18578
18784
|
log.warn(`[secret-scanner] POST-TOOL LEAK: ${toolName} output matched ${summary}`);
|
|
18579
18785
|
return {
|
|
18580
18786
|
additionalContext: `
|
|
@@ -18652,13 +18858,13 @@ var plugin47 = {
|
|
|
18652
18858
|
};
|
|
18653
18859
|
runtimes.set(api, runtime);
|
|
18654
18860
|
latestRuntime = runtime;
|
|
18655
|
-
const { state:
|
|
18861
|
+
const { state: state59 } = runtime;
|
|
18656
18862
|
const log = {
|
|
18657
18863
|
warn: (msg, ...rest) => api.log.warn(msg, ...rest),
|
|
18658
18864
|
info: (msg, ...rest) => api.log.info(msg, ...rest)
|
|
18659
18865
|
};
|
|
18660
18866
|
const hook = buildHook(cfg, log, runtime);
|
|
18661
|
-
|
|
18867
|
+
state59.hookUnregister = api.registerHook("PreToolUse", cfg.matcher, hook, {
|
|
18662
18868
|
name: "secret-scanner",
|
|
18663
18869
|
// Redaction rewrites arguments; block/allow modes must inspect the final
|
|
18664
18870
|
// result after every mutator has run so a later rewrite cannot smuggle a
|
|
@@ -18668,7 +18874,7 @@ var plugin47 = {
|
|
|
18668
18874
|
policy: true
|
|
18669
18875
|
});
|
|
18670
18876
|
const postHook = buildPostHook(cfg, log, runtime);
|
|
18671
|
-
|
|
18877
|
+
state59.postHookUnregister = api.registerHook("PostToolUse", cfg.postToolUseMatcher, postHook);
|
|
18672
18878
|
api.tools.register({
|
|
18673
18879
|
name: "secret_scanner_status",
|
|
18674
18880
|
description: "Reports the current secret-scanner state: pattern count, last block (if any), and per-mode invocation counters.",
|
|
@@ -18686,14 +18892,14 @@ var plugin47 = {
|
|
|
18686
18892
|
patternCount: PATTERNS3.length,
|
|
18687
18893
|
patternTypes: PATTERNS3.map((p) => p.type),
|
|
18688
18894
|
counters: {
|
|
18689
|
-
block:
|
|
18690
|
-
redact:
|
|
18691
|
-
allow:
|
|
18692
|
-
leak:
|
|
18693
|
-
timeoutCount:
|
|
18895
|
+
block: state59.blockCount,
|
|
18896
|
+
redact: state59.redactCount,
|
|
18897
|
+
allow: state59.allowCount,
|
|
18898
|
+
leak: state59.leakCount,
|
|
18899
|
+
timeoutCount: state59.timeoutCount
|
|
18694
18900
|
},
|
|
18695
|
-
lastBlock:
|
|
18696
|
-
lastLeak:
|
|
18901
|
+
lastBlock: state59.lastBlock,
|
|
18902
|
+
lastLeak: state59.lastLeak
|
|
18697
18903
|
};
|
|
18698
18904
|
}
|
|
18699
18905
|
});
|
|
@@ -18711,9 +18917,14 @@ var plugin47 = {
|
|
|
18711
18917
|
mutating: false,
|
|
18712
18918
|
async execute(input) {
|
|
18713
18919
|
activateRuntime(runtime);
|
|
18714
|
-
const rawText = input["text"] ?? input["content"] ?? input["string"] ?? input["input"] ?? input["code"] ?? input["value"]
|
|
18715
|
-
|
|
18716
|
-
|
|
18920
|
+
const rawText = input["text"] ?? input["content"] ?? input["string"] ?? input["input"] ?? input["code"] ?? input["value"];
|
|
18921
|
+
if (typeof rawText !== "string") {
|
|
18922
|
+
throw new ToolValidationError22({
|
|
18923
|
+
message: "text is required and must be a string",
|
|
18924
|
+
field: "text"
|
|
18925
|
+
});
|
|
18926
|
+
}
|
|
18927
|
+
const matched = findMatches(rawText);
|
|
18717
18928
|
return {
|
|
18718
18929
|
ok: true,
|
|
18719
18930
|
matched,
|
|
@@ -18731,57 +18942,58 @@ var plugin47 = {
|
|
|
18731
18942
|
teardown(api) {
|
|
18732
18943
|
const runtime = runtimes.get(api);
|
|
18733
18944
|
if (!runtime) return;
|
|
18734
|
-
const { state:
|
|
18735
|
-
if (
|
|
18945
|
+
const { state: state59 } = runtime;
|
|
18946
|
+
if (state59.hookUnregister) {
|
|
18736
18947
|
try {
|
|
18737
|
-
|
|
18948
|
+
state59.hookUnregister();
|
|
18738
18949
|
} catch {
|
|
18739
18950
|
}
|
|
18740
|
-
|
|
18951
|
+
state59.hookUnregister = null;
|
|
18741
18952
|
}
|
|
18742
|
-
if (
|
|
18953
|
+
if (state59.postHookUnregister) {
|
|
18743
18954
|
try {
|
|
18744
|
-
|
|
18955
|
+
state59.postHookUnregister();
|
|
18745
18956
|
} catch {
|
|
18746
18957
|
}
|
|
18747
|
-
|
|
18958
|
+
state59.postHookUnregister = null;
|
|
18748
18959
|
}
|
|
18749
18960
|
const finalCounters = {
|
|
18750
|
-
block:
|
|
18751
|
-
redact:
|
|
18752
|
-
allow:
|
|
18753
|
-
leak:
|
|
18961
|
+
block: state59.blockCount,
|
|
18962
|
+
redact: state59.redactCount,
|
|
18963
|
+
allow: state59.allowCount,
|
|
18964
|
+
leak: state59.leakCount
|
|
18754
18965
|
};
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18966
|
+
state59.blockCount = 0;
|
|
18967
|
+
state59.redactCount = 0;
|
|
18968
|
+
state59.allowCount = 0;
|
|
18969
|
+
state59.leakCount = 0;
|
|
18970
|
+
state59.lastBlock = null;
|
|
18971
|
+
state59.lastLeak = null;
|
|
18761
18972
|
runtimes.delete(api);
|
|
18762
18973
|
api.log.info("secret-scanner: teardown complete", { counters: finalCounters });
|
|
18763
18974
|
},
|
|
18764
18975
|
async health() {
|
|
18765
|
-
const
|
|
18976
|
+
const state59 = latestRuntime?.state ?? createState2();
|
|
18766
18977
|
return {
|
|
18767
18978
|
ok: true,
|
|
18768
|
-
message:
|
|
18979
|
+
message: state59.lastLeak !== null ? `secret-scanner: last leak at ${state59.lastLeak.when} on ${state59.lastLeak.toolName} (${state59.lastLeak.matchedTypes.join(", ")})` : state59.lastBlock !== null ? `secret-scanner: last block at ${state59.lastBlock.when} on ${state59.lastBlock.toolName} (${state59.lastBlock.matchedTypes.join(", ")})` : `secret-scanner: ${state59.blockCount + state59.redactCount + state59.allowCount + state59.leakCount} invocations, no blocks or leaks`,
|
|
18769
18980
|
counters: {
|
|
18770
|
-
block:
|
|
18771
|
-
redact:
|
|
18772
|
-
allow:
|
|
18773
|
-
leak:
|
|
18981
|
+
block: state59.blockCount,
|
|
18982
|
+
redact: state59.redactCount,
|
|
18983
|
+
allow: state59.allowCount,
|
|
18984
|
+
leak: state59.leakCount
|
|
18774
18985
|
},
|
|
18775
|
-
lastBlock:
|
|
18776
|
-
lastLeak:
|
|
18986
|
+
lastBlock: state59.lastBlock,
|
|
18987
|
+
lastLeak: state59.lastLeak
|
|
18777
18988
|
};
|
|
18778
18989
|
}
|
|
18779
18990
|
};
|
|
18780
18991
|
var secret_scanner_default = plugin47;
|
|
18781
18992
|
|
|
18782
18993
|
// src/security-hotspot-scanner/index.ts
|
|
18783
|
-
import { readdir as readdir2, readFile as readFile13, stat as
|
|
18994
|
+
import { readdir as readdir2, readFile as readFile13, stat as stat10 } from "node:fs/promises";
|
|
18784
18995
|
import { extname as extname5, isAbsolute as isAbsolute19, relative as relative19, resolve as resolve21 } from "node:path";
|
|
18996
|
+
import { ToolValidationError as ToolValidationError23 } from "@wrongstack/core/types";
|
|
18785
18997
|
var API_VERSION31 = "^0.1.10";
|
|
18786
18998
|
var state44 = {
|
|
18787
18999
|
scanCount: 0,
|
|
@@ -18929,7 +19141,7 @@ async function scanPath5(inputPath, cfg) {
|
|
|
18929
19141
|
if (allFindings.length >= cfg.maxFindings) return;
|
|
18930
19142
|
const full = resolve21(dir, entry);
|
|
18931
19143
|
try {
|
|
18932
|
-
const st = await
|
|
19144
|
+
const st = await stat10(full);
|
|
18933
19145
|
if (st.isDirectory()) {
|
|
18934
19146
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "coverage") {
|
|
18935
19147
|
continue;
|
|
@@ -18943,7 +19155,7 @@ async function scanPath5(inputPath, cfg) {
|
|
|
18943
19155
|
}
|
|
18944
19156
|
};
|
|
18945
19157
|
try {
|
|
18946
|
-
const st = await
|
|
19158
|
+
const st = await stat10(resolved);
|
|
18947
19159
|
if (st.isDirectory()) {
|
|
18948
19160
|
await walk(resolved);
|
|
18949
19161
|
} else if (st.isFile()) {
|
|
@@ -19095,7 +19307,7 @@ Review or remove the risky pattern(s).`;
|
|
|
19095
19307
|
category: "Security",
|
|
19096
19308
|
mutating: false,
|
|
19097
19309
|
async execute(input) {
|
|
19098
|
-
if (!cfg.enabled)
|
|
19310
|
+
if (!cfg.enabled) throw new Error("security-hotspot-scanner is disabled");
|
|
19099
19311
|
const raw = input;
|
|
19100
19312
|
const targetPath = (typeof input.path === "string" ? input.path : void 0) ?? (typeof raw["directory"] === "string" ? raw["directory"] : void 0) ?? (typeof raw["dir"] === "string" ? raw["dir"] : void 0) ?? (typeof raw["SearchDirectory"] === "string" ? raw["SearchDirectory"] : void 0) ?? (typeof raw["TargetFile"] === "string" ? raw["TargetFile"] : void 0) ?? (typeof raw["filePath"] === "string" ? raw["filePath"] : void 0) ?? (typeof raw["targetFile"] === "string" ? raw["targetFile"] : void 0) ?? ".";
|
|
19101
19313
|
const result = await scanPath5(targetPath, cfg);
|
|
@@ -19103,7 +19315,10 @@ Review or remove the risky pattern(s).`;
|
|
|
19103
19315
|
state44.fileScanCount += result.filesScanned;
|
|
19104
19316
|
state44.findingCount += result.findings.length;
|
|
19105
19317
|
if (!result.scanned) {
|
|
19106
|
-
|
|
19318
|
+
throw new ToolValidationError23({
|
|
19319
|
+
message: `cannot scan ${targetPath}: ${result.error ?? "unknown error"}`,
|
|
19320
|
+
field: "path"
|
|
19321
|
+
});
|
|
19107
19322
|
}
|
|
19108
19323
|
state44.lastResult = {
|
|
19109
19324
|
path: result.path,
|
|
@@ -19201,6 +19416,7 @@ var security_hotspot_scanner_default = plugin48;
|
|
|
19201
19416
|
// src/semantic-search-indexer/index.ts
|
|
19202
19417
|
import * as fs2 from "node:fs/promises";
|
|
19203
19418
|
import { isAbsolute as isAbsolute20, relative as relative20, resolve as resolve22 } from "node:path";
|
|
19419
|
+
import { ToolValidationError as ToolValidationError24 } from "@wrongstack/core/types";
|
|
19204
19420
|
import { DEFAULT_WALK_IGNORE_DIRS } from "@wrongstack/core/utils";
|
|
19205
19421
|
var API_VERSION32 = "^0.1.10";
|
|
19206
19422
|
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -19710,16 +19926,31 @@ var plugin49 = {
|
|
|
19710
19926
|
icon: "search",
|
|
19711
19927
|
async execute(input) {
|
|
19712
19928
|
if (!cfg.enabled) {
|
|
19713
|
-
|
|
19929
|
+
throw new Error("semantic-search-indexer is disabled");
|
|
19714
19930
|
}
|
|
19715
19931
|
const rawPath = input.path ?? input["directory"] ?? input["dir"] ?? input["SearchDirectory"] ?? input["SearchPath"] ?? input["TargetFile"] ?? input["targetFile"] ?? input["filePath"] ?? input["file"];
|
|
19716
19932
|
const resolved = resolveProjectPath8(typeof rawPath === "string" ? rawPath : void 0);
|
|
19717
19933
|
if (!resolved) {
|
|
19718
|
-
|
|
19934
|
+
throw new ToolValidationError24({ message: "path outside project root", field: "path" });
|
|
19719
19935
|
}
|
|
19720
|
-
await ensureIndex(resolved, cfg);
|
|
19721
19936
|
const rawQuery = input.query ?? input["q"] ?? input["text"] ?? input["keyword"] ?? input["keywords"] ?? input["search"] ?? "";
|
|
19722
19937
|
const query = String(rawQuery);
|
|
19938
|
+
if (tokenize(query, cfg.minTokenLength).length === 0) {
|
|
19939
|
+
throw new ToolValidationError24({
|
|
19940
|
+
message: `query must contain at least one keyword of ${cfg.minTokenLength}+ characters`,
|
|
19941
|
+
field: "query"
|
|
19942
|
+
});
|
|
19943
|
+
}
|
|
19944
|
+
try {
|
|
19945
|
+
await fs2.stat(resolved);
|
|
19946
|
+
} catch (err) {
|
|
19947
|
+
throw new ToolValidationError24({
|
|
19948
|
+
message: `path does not exist or cannot be read: ${typeof rawPath === "string" ? rawPath : resolved}`,
|
|
19949
|
+
field: "path",
|
|
19950
|
+
cause: err
|
|
19951
|
+
});
|
|
19952
|
+
}
|
|
19953
|
+
await ensureIndex(resolved, cfg);
|
|
19723
19954
|
const limit = typeof input.limit === "number" && input.limit >= 1 ? Math.floor(input.limit) : cfg.defaultLimit;
|
|
19724
19955
|
const results = runQuery(query, limit, cfg);
|
|
19725
19956
|
const queryTokens = [...new Set(tokenize(query, cfg.minTokenLength))];
|
|
@@ -19843,10 +20074,26 @@ var semantic_search_indexer_default = plugin49;
|
|
|
19843
20074
|
// src/semver-bump/index.ts
|
|
19844
20075
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
19845
20076
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
20077
|
+
import { ToolValidationError as ToolValidationError25 } from "@wrongstack/core/types";
|
|
19846
20078
|
import { execFile as execFile11 } from "node:child_process";
|
|
19847
20079
|
import { access as access4, readFile as readFile15, readdir as readdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
19848
20080
|
import { isAbsolute as isAbsolute21, join as join5, relative as relative21, resolve as resolve23 } from "node:path";
|
|
19849
20081
|
var API_VERSION33 = "^0.1.10";
|
|
20082
|
+
function requireProjectRoot(rawCwd) {
|
|
20083
|
+
const safeCwd = resolveProjectRoot(rawCwd);
|
|
20084
|
+
if (!safeCwd) {
|
|
20085
|
+
throw new ToolValidationError25({
|
|
20086
|
+
message: "cwd must stay within the current project directory",
|
|
20087
|
+
field: "cwd"
|
|
20088
|
+
});
|
|
20089
|
+
}
|
|
20090
|
+
return safeCwd;
|
|
20091
|
+
}
|
|
20092
|
+
function requireGitRef(field, ref) {
|
|
20093
|
+
if (ref !== void 0 && (typeof ref !== "string" || ref.startsWith("-"))) {
|
|
20094
|
+
throw new ToolValidationError25({ message: `${field} is not a valid git ref`, field });
|
|
20095
|
+
}
|
|
20096
|
+
}
|
|
19850
20097
|
function resolveProjectRoot(rawCwd, root = process.cwd()) {
|
|
19851
20098
|
if (typeof rawCwd !== "string" || rawCwd.length === 0) return root;
|
|
19852
20099
|
const base = resolve23(root);
|
|
@@ -20092,14 +20339,10 @@ var plugin50 = {
|
|
|
20092
20339
|
defaultPart = readDefaultPart(next);
|
|
20093
20340
|
});
|
|
20094
20341
|
async function performBump(part, dryRun, cwd) {
|
|
20095
|
-
|
|
20096
|
-
if (!safeCwd) {
|
|
20097
|
-
return { ok: false, error: "cwd must stay within the current project directory" };
|
|
20098
|
-
}
|
|
20099
|
-
cwd = safeCwd;
|
|
20342
|
+
cwd = requireProjectRoot(cwd);
|
|
20100
20343
|
const pkg = await getPackageJson(cwd);
|
|
20101
20344
|
if (!pkg) {
|
|
20102
|
-
|
|
20345
|
+
throw new Error("No package.json found");
|
|
20103
20346
|
}
|
|
20104
20347
|
const currentVersion = pkg.version;
|
|
20105
20348
|
let bumpPart = part;
|
|
@@ -20114,8 +20357,7 @@ var plugin50 = {
|
|
|
20114
20357
|
try {
|
|
20115
20358
|
commits = await getRecentCommits(lastTag, cwd);
|
|
20116
20359
|
} catch (err) {
|
|
20117
|
-
|
|
20118
|
-
return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
|
|
20360
|
+
throw new Error(`Git error: ${toErrorMessage(err)}`, { cause: err });
|
|
20119
20361
|
}
|
|
20120
20362
|
bumpPart = determineBump(commits);
|
|
20121
20363
|
} else {
|
|
@@ -20147,8 +20389,7 @@ var plugin50 = {
|
|
|
20147
20389
|
try {
|
|
20148
20390
|
await runCommand3(process.execPath, [bumpScript, "set", newVersion], root);
|
|
20149
20391
|
} catch (err) {
|
|
20150
|
-
|
|
20151
|
-
return { ok: false, error: `bump script failed: ${msg}` };
|
|
20392
|
+
throw new Error(`bump script failed: ${toErrorMessage(err)}`, { cause: err });
|
|
20152
20393
|
}
|
|
20153
20394
|
for (const rel of ["package.json", "package-lock.json", "src/lib/utils.ts", "index.html"]) {
|
|
20154
20395
|
const p = join5(root, "website", rel);
|
|
@@ -20165,16 +20406,15 @@ var plugin50 = {
|
|
|
20165
20406
|
try {
|
|
20166
20407
|
pkgData = JSON.parse(await readFile15(manifest, "utf-8"));
|
|
20167
20408
|
} catch (err) {
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20409
|
+
throw new Error(
|
|
20410
|
+
`cannot bump: ${manifest} is not readable as JSON (${toErrorMessage(err)}). No manifests were modified.`,
|
|
20411
|
+
{ cause: err }
|
|
20412
|
+
);
|
|
20172
20413
|
}
|
|
20173
20414
|
if (!pkgData || typeof pkgData !== "object") {
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
};
|
|
20415
|
+
throw new Error(
|
|
20416
|
+
`cannot bump: ${manifest} does not contain a JSON object. No manifests were modified.`
|
|
20417
|
+
);
|
|
20178
20418
|
}
|
|
20179
20419
|
pkgData.version = newVersion;
|
|
20180
20420
|
pending.push({ path: manifest, contents: `${JSON.stringify(pkgData, null, 2)}
|
|
@@ -20184,16 +20424,20 @@ var plugin50 = {
|
|
|
20184
20424
|
await writeFile5(path, contents, "utf-8");
|
|
20185
20425
|
}
|
|
20186
20426
|
}
|
|
20427
|
+
let commitError;
|
|
20187
20428
|
try {
|
|
20188
20429
|
await runGit6(["add", "--", ...changed], cwd);
|
|
20189
20430
|
await runGit6(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
|
|
20190
|
-
} catch {
|
|
20431
|
+
} catch (err) {
|
|
20432
|
+
commitError = toErrorMessage(err);
|
|
20191
20433
|
}
|
|
20434
|
+
let tagError;
|
|
20192
20435
|
if (autoTag) {
|
|
20193
20436
|
try {
|
|
20194
20437
|
const msg = tagMessage.replace("{{version}}", newVersion);
|
|
20195
20438
|
await runGit6(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", msg], cwd);
|
|
20196
|
-
} catch {
|
|
20439
|
+
} catch (err) {
|
|
20440
|
+
tagError = toErrorMessage(err);
|
|
20197
20441
|
}
|
|
20198
20442
|
}
|
|
20199
20443
|
api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
|
|
@@ -20213,13 +20457,22 @@ var plugin50 = {
|
|
|
20213
20457
|
commitCount: commits.length,
|
|
20214
20458
|
breakingCount: commits.filter((c) => c.breaking).length
|
|
20215
20459
|
};
|
|
20460
|
+
const tagged = autoTag && tagError === void 0;
|
|
20461
|
+
const warnings = [
|
|
20462
|
+
...commitError ? [`commit failed: ${commitError}`] : [],
|
|
20463
|
+
...tagError ? [`tag failed: ${tagError}`] : []
|
|
20464
|
+
];
|
|
20216
20465
|
return {
|
|
20217
20466
|
ok: true,
|
|
20218
20467
|
currentVersion,
|
|
20219
20468
|
newVersion,
|
|
20220
20469
|
bump: bumpPart,
|
|
20221
|
-
tag
|
|
20222
|
-
|
|
20470
|
+
// Only name the tag when it was actually created.
|
|
20471
|
+
tag: tagged ? `${tagPrefix}${newVersion}` : null,
|
|
20472
|
+
committed: commitError === void 0,
|
|
20473
|
+
tagged,
|
|
20474
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
20475
|
+
message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})` + (warnings.length > 0 ? ` \u2014 ${warnings.join("; ")}` : "")
|
|
20223
20476
|
};
|
|
20224
20477
|
}
|
|
20225
20478
|
api.tools.register({
|
|
@@ -20305,9 +20558,13 @@ var plugin50 = {
|
|
|
20305
20558
|
if (!safeCwd) {
|
|
20306
20559
|
return { message: "cwd must stay within the current project directory" };
|
|
20307
20560
|
}
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20561
|
+
try {
|
|
20562
|
+
const result = await performBump(mode, dry, safeCwd);
|
|
20563
|
+
return { message: String(result["message"] ?? JSON.stringify(result)) };
|
|
20564
|
+
} catch (err) {
|
|
20565
|
+
return { message: toErrorMessage(err) };
|
|
20566
|
+
}
|
|
20567
|
+
}
|
|
20311
20568
|
});
|
|
20312
20569
|
api.tools.register({
|
|
20313
20570
|
name: "semver_current",
|
|
@@ -20323,11 +20580,7 @@ var plugin50 = {
|
|
|
20323
20580
|
async execute(input) {
|
|
20324
20581
|
state46.invocationCount += 1;
|
|
20325
20582
|
state46.perTool["semver_current"] = (state46.perTool["semver_current"] ?? 0) + 1;
|
|
20326
|
-
const
|
|
20327
|
-
const safeCwd = resolveProjectRoot(cwdInput);
|
|
20328
|
-
if (!safeCwd) {
|
|
20329
|
-
return { ok: false, error: "cwd must stay within the current project directory" };
|
|
20330
|
-
}
|
|
20583
|
+
const safeCwd = requireProjectRoot(input["cwd"]);
|
|
20331
20584
|
const pkg = await getPackageJson(safeCwd);
|
|
20332
20585
|
const currentVersion = pkg?.version ?? "unknown";
|
|
20333
20586
|
let latestTag = null;
|
|
@@ -20373,22 +20626,20 @@ var plugin50 = {
|
|
|
20373
20626
|
state46.perTool["semver_changelog"] = (state46.perTool["semver_changelog"] ?? 0) + 1;
|
|
20374
20627
|
const from = input["from"];
|
|
20375
20628
|
const to = input["to"] ?? "HEAD";
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
return { ok: false, error: "cwd must stay within the current project directory" };
|
|
20380
|
-
}
|
|
20629
|
+
requireGitRef("from", from);
|
|
20630
|
+
requireGitRef("to", to);
|
|
20631
|
+
const safeCwd = requireProjectRoot(input["cwd"]);
|
|
20381
20632
|
const format = input["format"] ?? "markdown";
|
|
20382
|
-
const
|
|
20633
|
+
const rangeArgs = from ? [`${from}..${to}`] : ["-30", to];
|
|
20383
20634
|
let commits;
|
|
20384
20635
|
try {
|
|
20385
20636
|
const output = await runGit6(
|
|
20386
|
-
["log",
|
|
20637
|
+
["log", ...rangeArgs, "--format=%H%x1f%s%x1f%b%x1e"],
|
|
20387
20638
|
safeCwd
|
|
20388
20639
|
);
|
|
20389
20640
|
commits = parseGitLogOutput(output);
|
|
20390
20641
|
} catch (err) {
|
|
20391
|
-
|
|
20642
|
+
throw new Error(`Failed to get git log: ${toErrorMessage(err)}`, { cause: err });
|
|
20392
20643
|
}
|
|
20393
20644
|
if (format === "json") {
|
|
20394
20645
|
return {
|
|
@@ -20656,8 +20907,10 @@ var plugin51 = {
|
|
|
20656
20907
|
const offTool = api.onPattern("tool.*", (eventName, payload) => {
|
|
20657
20908
|
touchActivity();
|
|
20658
20909
|
const p = payload;
|
|
20659
|
-
const
|
|
20660
|
-
|
|
20910
|
+
const rawTool = p?.tool;
|
|
20911
|
+
const nameOf = (v) => typeof v === "string" ? v : v && typeof v === "object" && typeof v.name === "string" ? v.name : void 0;
|
|
20912
|
+
const toolName = nameOf(rawTool) ?? nameOf(p?.name) ?? eventName;
|
|
20913
|
+
bumpToolCount(toolName);
|
|
20661
20914
|
if (toolName === "git_autocommit" || toolName.startsWith("git ")) {
|
|
20662
20915
|
}
|
|
20663
20916
|
});
|
|
@@ -20903,6 +21156,7 @@ Tokens: ${recap.tokens.total.input} in / ${recap.tokens.total.output} out
|
|
|
20903
21156
|
var session_recap_default = plugin51;
|
|
20904
21157
|
|
|
20905
21158
|
// src/shell-check/index.ts
|
|
21159
|
+
import { ToolValidationError as ToolValidationError26 } from "@wrongstack/core/types";
|
|
20906
21160
|
import { execFile as execFile12 } from "node:child_process";
|
|
20907
21161
|
import { readdir as readdir5 } from "node:fs/promises";
|
|
20908
21162
|
import { isAbsolute as isAbsolute22, join as join6, relative as relative22, resolve as resolve24 } from "node:path";
|
|
@@ -20976,8 +21230,11 @@ async function runShellCheck(files, severity, cwd) {
|
|
|
20976
21230
|
}
|
|
20977
21231
|
);
|
|
20978
21232
|
});
|
|
20979
|
-
} catch {
|
|
20980
|
-
|
|
21233
|
+
} catch (err) {
|
|
21234
|
+
throw new Error(
|
|
21235
|
+
`shellcheck failed without output: ${err instanceof Error ? err.message : String(err)}`,
|
|
21236
|
+
{ cause: err }
|
|
21237
|
+
);
|
|
20981
21238
|
}
|
|
20982
21239
|
if (!raw.trim()) return [];
|
|
20983
21240
|
try {
|
|
@@ -20990,22 +21247,31 @@ async function runShellCheck(files, severity, cwd) {
|
|
|
20990
21247
|
code: item.code,
|
|
20991
21248
|
message: item.message
|
|
20992
21249
|
}));
|
|
20993
|
-
} catch {
|
|
20994
|
-
|
|
21250
|
+
} catch (err) {
|
|
21251
|
+
throw new Error(`shellcheck returned unparseable output: ${raw.trim().slice(0, 500)}`, {
|
|
21252
|
+
cause: err
|
|
21253
|
+
});
|
|
20995
21254
|
}
|
|
20996
21255
|
}
|
|
20997
|
-
async function findShellFiles(dir, pattern) {
|
|
21256
|
+
async function findShellFiles(dir, pattern, isRoot = true) {
|
|
20998
21257
|
const results = [];
|
|
20999
21258
|
let entries;
|
|
21000
21259
|
try {
|
|
21001
21260
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
21002
|
-
} catch {
|
|
21261
|
+
} catch (err) {
|
|
21262
|
+
if (isRoot) {
|
|
21263
|
+
throw new ToolValidationError26({
|
|
21264
|
+
message: `directory does not exist or cannot be read: ${dir}`,
|
|
21265
|
+
field: "directory",
|
|
21266
|
+
cause: err
|
|
21267
|
+
});
|
|
21268
|
+
}
|
|
21003
21269
|
return results;
|
|
21004
21270
|
}
|
|
21005
21271
|
for (const entry of entries) {
|
|
21006
21272
|
const full = join6(dir, entry.name);
|
|
21007
21273
|
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") {
|
|
21008
|
-
results.push(...await findShellFiles(full, pattern));
|
|
21274
|
+
results.push(...await findShellFiles(full, pattern, false));
|
|
21009
21275
|
} else if (entry.isFile() && (entry.name.endsWith(".sh") || entry.name.endsWith(".bash") || entry.name.endsWith(".zsh") || entry.name === "Dockerfile" || entry.name === ".bashrc" || entry.name === ".zshrc")) {
|
|
21010
21276
|
if (!pattern || entry.name.includes(pattern)) {
|
|
21011
21277
|
results.push(full);
|
|
@@ -21075,12 +21341,8 @@ var plugin52 = {
|
|
|
21075
21341
|
enum: ["error", "warning", "info", "style"],
|
|
21076
21342
|
default: "warning",
|
|
21077
21343
|
description: "Minimum severity level to report"
|
|
21078
|
-
},
|
|
21079
|
-
fix: {
|
|
21080
|
-
type: "boolean",
|
|
21081
|
-
default: false,
|
|
21082
|
-
description: "Apply safe automatic fixes where possible"
|
|
21083
21344
|
}
|
|
21345
|
+
// `fix` was declared ("apply safe automatic fixes") but never implemented.
|
|
21084
21346
|
}
|
|
21085
21347
|
},
|
|
21086
21348
|
permission: "auto",
|
|
@@ -21106,22 +21368,16 @@ var plugin52 = {
|
|
|
21106
21368
|
state48.invocationCount += 1;
|
|
21107
21369
|
const pathIsSafe = (p) => typeof p === "string" && p.length > 0 && p.length <= MAX_PATH_LEN && withinProject22(p);
|
|
21108
21370
|
if (!pathIsSafe(directory)) {
|
|
21109
|
-
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
filesScanned: 0,
|
|
21114
|
-
rejectedOutsideProject: true
|
|
21115
|
-
};
|
|
21371
|
+
throw new ToolValidationError26({
|
|
21372
|
+
message: `directory path is outside the project root: ${directory}`,
|
|
21373
|
+
field: "directory"
|
|
21374
|
+
});
|
|
21116
21375
|
}
|
|
21117
21376
|
if (files?.some((f) => !pathIsSafe(f))) {
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
filesScanned: 0,
|
|
21123
|
-
rejectedOutsideProject: true
|
|
21124
|
-
};
|
|
21377
|
+
throw new ToolValidationError26({
|
|
21378
|
+
message: "one or more file paths are outside the project root",
|
|
21379
|
+
field: "files"
|
|
21380
|
+
});
|
|
21125
21381
|
}
|
|
21126
21382
|
let checkFiles;
|
|
21127
21383
|
let scannedDirectories = false;
|
|
@@ -21147,19 +21403,7 @@ var plugin52 = {
|
|
|
21147
21403
|
mode: scannedDirectories ? "directory" : "files"
|
|
21148
21404
|
};
|
|
21149
21405
|
}
|
|
21150
|
-
|
|
21151
|
-
try {
|
|
21152
|
-
issues = await runShellCheck(checkFiles, severity);
|
|
21153
|
-
} catch (err) {
|
|
21154
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
21155
|
-
return {
|
|
21156
|
-
ok: false,
|
|
21157
|
-
error: msg,
|
|
21158
|
-
issues: [],
|
|
21159
|
-
filesScanned: 0,
|
|
21160
|
-
mode: scannedDirectories ? "directory" : "files"
|
|
21161
|
-
};
|
|
21162
|
-
}
|
|
21406
|
+
const issues = await runShellCheck(checkFiles, severity);
|
|
21163
21407
|
const byFile = {};
|
|
21164
21408
|
for (const issue of issues) {
|
|
21165
21409
|
if (byFile[issue.file] === void 0) {
|
|
@@ -21227,6 +21471,7 @@ var shell_check_default = plugin52;
|
|
|
21227
21471
|
// src/smart-rename/index.ts
|
|
21228
21472
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
|
|
21229
21473
|
import { extname as extname6, isAbsolute as isAbsolute23, relative as relative23, resolve as resolve25 } from "node:path";
|
|
21474
|
+
import { ToolValidationError as ToolValidationError27 } from "@wrongstack/core/types";
|
|
21230
21475
|
var NEW_API_VERSION = "^0.1.10";
|
|
21231
21476
|
var state49 = {
|
|
21232
21477
|
renameCount: 0,
|
|
@@ -21332,32 +21577,44 @@ var plugin53 = {
|
|
|
21332
21577
|
mutating: true,
|
|
21333
21578
|
capabilities: ["fs.write"],
|
|
21334
21579
|
async execute(input) {
|
|
21335
|
-
if (!cfg.enabled)
|
|
21580
|
+
if (!cfg.enabled) throw new Error("smart-rename is disabled");
|
|
21336
21581
|
const inp = input ?? {};
|
|
21337
21582
|
const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
|
|
21338
21583
|
const oldName = inp["oldName"] ?? inp["old_name"] ?? inp["from"];
|
|
21339
21584
|
const newName = inp["newName"] ?? inp["new_name"] ?? inp["to"];
|
|
21340
21585
|
if (!rawPath || typeof rawPath !== "string") {
|
|
21341
|
-
|
|
21586
|
+
throw new ToolValidationError27({ message: "path is required", field: "path" });
|
|
21342
21587
|
}
|
|
21343
21588
|
if (!oldName || typeof oldName !== "string" || oldName.length === 0) {
|
|
21344
|
-
|
|
21589
|
+
throw new ToolValidationError27({ message: "oldName is required", field: "oldName" });
|
|
21345
21590
|
}
|
|
21346
21591
|
if (!newName || typeof newName !== "string" || newName.length === 0) {
|
|
21347
|
-
|
|
21592
|
+
throw new ToolValidationError27({ message: "newName is required", field: "newName" });
|
|
21348
21593
|
}
|
|
21349
21594
|
if (!isIdentifier(oldName)) {
|
|
21350
|
-
|
|
21595
|
+
throw new ToolValidationError27({
|
|
21596
|
+
message: `oldName "${oldName}" is not a valid identifier`,
|
|
21597
|
+
field: "oldName"
|
|
21598
|
+
});
|
|
21351
21599
|
}
|
|
21352
21600
|
if (!isIdentifier(newName)) {
|
|
21353
|
-
|
|
21601
|
+
throw new ToolValidationError27({
|
|
21602
|
+
message: `newName "${newName}" is not a valid identifier`,
|
|
21603
|
+
field: "newName"
|
|
21604
|
+
});
|
|
21354
21605
|
}
|
|
21355
21606
|
if (!withinProject23(rawPath)) {
|
|
21356
|
-
|
|
21607
|
+
throw new ToolValidationError27({
|
|
21608
|
+
message: "path is outside the project root",
|
|
21609
|
+
field: "path"
|
|
21610
|
+
});
|
|
21357
21611
|
}
|
|
21358
21612
|
const ext = extname6(rawPath).toLowerCase();
|
|
21359
21613
|
if (!cfg.extensions.includes(ext)) {
|
|
21360
|
-
|
|
21614
|
+
throw new ToolValidationError27({
|
|
21615
|
+
message: `extension ${ext} is not allowed for rename`,
|
|
21616
|
+
field: "path"
|
|
21617
|
+
});
|
|
21361
21618
|
}
|
|
21362
21619
|
const resolved = resolve25(process.cwd(), rawPath);
|
|
21363
21620
|
let content;
|
|
@@ -21365,7 +21622,7 @@ var plugin53 = {
|
|
|
21365
21622
|
content = readFileSync13(resolved, "utf-8");
|
|
21366
21623
|
} catch (err) {
|
|
21367
21624
|
state49.errorCount += 1;
|
|
21368
|
-
|
|
21625
|
+
throw new Error(`Could not read ${rawPath}: ${String(err)}`, { cause: err });
|
|
21369
21626
|
}
|
|
21370
21627
|
const { preview, replacements } = renameInContent(content, oldName, newName);
|
|
21371
21628
|
state49.renameCount += 1;
|
|
@@ -21378,7 +21635,7 @@ var plugin53 = {
|
|
|
21378
21635
|
writeFileSync2(resolved, preview, "utf-8");
|
|
21379
21636
|
} catch (err) {
|
|
21380
21637
|
state49.errorCount += 1;
|
|
21381
|
-
|
|
21638
|
+
throw new Error(`Could not write ${rawPath}: ${String(err)}`, { cause: err });
|
|
21382
21639
|
}
|
|
21383
21640
|
}
|
|
21384
21641
|
return {
|
|
@@ -21811,8 +22068,8 @@ var plugin54 = {
|
|
|
21811
22068
|
state50.postInvocations += 1;
|
|
21812
22069
|
let content;
|
|
21813
22070
|
try {
|
|
21814
|
-
const
|
|
21815
|
-
if (!
|
|
22071
|
+
const stat13 = await fs3.stat(filePath);
|
|
22072
|
+
if (!stat13.isFile()) return;
|
|
21816
22073
|
content = await fs3.readFile(filePath, "utf-8");
|
|
21817
22074
|
} catch {
|
|
21818
22075
|
state50.readErrorCount += 1;
|
|
@@ -21962,6 +22219,7 @@ var spec_linker_default = plugin54;
|
|
|
21962
22219
|
// src/template-engine/index.ts
|
|
21963
22220
|
import { readFile as readFile17, writeFile as writeFile6 } from "node:fs/promises";
|
|
21964
22221
|
import { isAbsolute as isAbsolute24 } from "node:path";
|
|
22222
|
+
import { ToolValidationError as ToolValidationError28 } from "@wrongstack/core/types";
|
|
21965
22223
|
var API_VERSION35 = "^0.1.10";
|
|
21966
22224
|
var templates = /* @__PURE__ */ new Map();
|
|
21967
22225
|
var MAX_TEMPLATES = 256;
|
|
@@ -22123,24 +22381,26 @@ var plugin55 = {
|
|
|
22123
22381
|
const output_path = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : void 0;
|
|
22124
22382
|
const raw = input["raw"] ?? false;
|
|
22125
22383
|
if (!template || typeof template !== "string") {
|
|
22126
|
-
|
|
22384
|
+
throw new ToolValidationError28({
|
|
22385
|
+
message: "template is required and must be a string",
|
|
22386
|
+
field: "template"
|
|
22387
|
+
});
|
|
22127
22388
|
}
|
|
22128
22389
|
if (!variables || typeof variables !== "object") {
|
|
22129
|
-
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
|
|
22133
|
-
result = raw ? renderTemplateRaw(template, variables) : renderTemplate(template, variables, autoEscapeHtml);
|
|
22134
|
-
} catch (err) {
|
|
22135
|
-
return { ok: false, error: String(err) };
|
|
22390
|
+
throw new ToolValidationError28({
|
|
22391
|
+
message: "variables is required and must be an object",
|
|
22392
|
+
field: "variables"
|
|
22393
|
+
});
|
|
22136
22394
|
}
|
|
22395
|
+
const result = raw ? renderTemplateRaw(template, variables) : renderTemplate(template, variables, autoEscapeHtml);
|
|
22137
22396
|
if (output_path) {
|
|
22138
22397
|
const pathError = validateWritableTemplateTarget("output_path", output_path);
|
|
22139
|
-
if (pathError)
|
|
22398
|
+
if (pathError)
|
|
22399
|
+
throw new ToolValidationError28({ message: pathError, field: "output_path" });
|
|
22140
22400
|
try {
|
|
22141
22401
|
await writeFile6(output_path, result, "utf-8");
|
|
22142
22402
|
} catch (err) {
|
|
22143
|
-
|
|
22403
|
+
throw new Error(`Could not write ${output_path}: ${String(err)}`, { cause: err });
|
|
22144
22404
|
}
|
|
22145
22405
|
return {
|
|
22146
22406
|
ok: true,
|
|
@@ -22191,32 +22451,36 @@ var plugin55 = {
|
|
|
22191
22451
|
const output_path = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : void 0;
|
|
22192
22452
|
const raw = input["raw"] ?? false;
|
|
22193
22453
|
if (!template_path || typeof template_path !== "string") {
|
|
22194
|
-
|
|
22454
|
+
throw new ToolValidationError28({
|
|
22455
|
+
message: "template_path is required and must be a string",
|
|
22456
|
+
field: "template_path"
|
|
22457
|
+
});
|
|
22195
22458
|
}
|
|
22196
22459
|
const templatePathError = validateRelativeTemplatePath("template_path", template_path);
|
|
22197
|
-
if (templatePathError)
|
|
22460
|
+
if (templatePathError) {
|
|
22461
|
+
throw new ToolValidationError28({ message: templatePathError, field: "template_path" });
|
|
22462
|
+
}
|
|
22198
22463
|
if (!variables || typeof variables !== "object") {
|
|
22199
|
-
|
|
22464
|
+
throw new ToolValidationError28({
|
|
22465
|
+
message: "variables is required and must be an object",
|
|
22466
|
+
field: "variables"
|
|
22467
|
+
});
|
|
22200
22468
|
}
|
|
22201
22469
|
let content;
|
|
22202
22470
|
try {
|
|
22203
22471
|
content = await readFile17(template_path, "utf-8");
|
|
22204
22472
|
} catch (err) {
|
|
22205
|
-
|
|
22206
|
-
}
|
|
22207
|
-
let result;
|
|
22208
|
-
try {
|
|
22209
|
-
result = raw ? renderTemplateRaw(content, variables) : renderTemplate(content, variables, autoEscapeHtml);
|
|
22210
|
-
} catch (err) {
|
|
22211
|
-
return { ok: false, error: `Template rendering failed: ${err}` };
|
|
22473
|
+
throw new Error(`Could not read template file: ${String(err)}`, { cause: err });
|
|
22212
22474
|
}
|
|
22475
|
+
const result = raw ? renderTemplateRaw(content, variables) : renderTemplate(content, variables, autoEscapeHtml);
|
|
22213
22476
|
if (output_path) {
|
|
22214
22477
|
const pathError = validateWritableTemplateTarget("output_path", output_path);
|
|
22215
|
-
if (pathError)
|
|
22478
|
+
if (pathError)
|
|
22479
|
+
throw new ToolValidationError28({ message: pathError, field: "output_path" });
|
|
22216
22480
|
try {
|
|
22217
22481
|
await writeFile6(output_path, result, "utf-8");
|
|
22218
22482
|
} catch (err) {
|
|
22219
|
-
|
|
22483
|
+
throw new Error(`Could not write ${output_path}: ${String(err)}`, { cause: err });
|
|
22220
22484
|
}
|
|
22221
22485
|
return {
|
|
22222
22486
|
ok: true,
|
|
@@ -22267,30 +22531,39 @@ var plugin55 = {
|
|
|
22267
22531
|
const rawDesc = input["description"] ?? input["desc"] ?? input["summary"];
|
|
22268
22532
|
const description = typeof rawDesc === "string" ? rawDesc : void 0;
|
|
22269
22533
|
if (!name || typeof name !== "string" || name.trim() === "") {
|
|
22270
|
-
|
|
22534
|
+
throw new ToolValidationError28({
|
|
22535
|
+
message: "name is required and must be a non-empty string",
|
|
22536
|
+
field: "name"
|
|
22537
|
+
});
|
|
22271
22538
|
}
|
|
22272
22539
|
if (!content || typeof content !== "string") {
|
|
22273
|
-
|
|
22540
|
+
throw new ToolValidationError28({
|
|
22541
|
+
message: "content is required and must be a string",
|
|
22542
|
+
field: "content"
|
|
22543
|
+
});
|
|
22274
22544
|
}
|
|
22275
22545
|
if (name.length > MAX_TEMPLATE_NAME_CHARS) {
|
|
22276
|
-
|
|
22546
|
+
throw new ToolValidationError28({
|
|
22547
|
+
message: `name exceeds ${MAX_TEMPLATE_NAME_CHARS} characters`,
|
|
22548
|
+
field: "name"
|
|
22549
|
+
});
|
|
22277
22550
|
}
|
|
22278
22551
|
if (content.length > MAX_TEMPLATE_CONTENT_CHARS) {
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
};
|
|
22552
|
+
throw new ToolValidationError28({
|
|
22553
|
+
message: `content exceeds ${MAX_TEMPLATE_CONTENT_CHARS} characters`,
|
|
22554
|
+
field: "content"
|
|
22555
|
+
});
|
|
22283
22556
|
}
|
|
22284
22557
|
if (description && description.length > MAX_TEMPLATE_DESCRIPTION_CHARS) {
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
};
|
|
22558
|
+
throw new ToolValidationError28({
|
|
22559
|
+
message: `description exceeds ${MAX_TEMPLATE_DESCRIPTION_CHARS} characters`,
|
|
22560
|
+
field: "description"
|
|
22561
|
+
});
|
|
22289
22562
|
}
|
|
22290
22563
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22291
22564
|
const existing = templates.get(name);
|
|
22292
22565
|
if (!existing && templates.size >= MAX_TEMPLATES) {
|
|
22293
|
-
|
|
22566
|
+
throw new Error(`template limit reached (${MAX_TEMPLATES})`);
|
|
22294
22567
|
}
|
|
22295
22568
|
const tmpl = {
|
|
22296
22569
|
name,
|
|
@@ -22303,10 +22576,7 @@ var plugin55 = {
|
|
|
22303
22576
|
for (const stored of templates.values()) retainedChars += templateChars(stored);
|
|
22304
22577
|
const nextChars = retainedChars - (existing ? templateChars(existing) : 0) + templateChars(tmpl);
|
|
22305
22578
|
if (nextChars > MAX_TOTAL_TEMPLATE_CHARS) {
|
|
22306
|
-
|
|
22307
|
-
ok: false,
|
|
22308
|
-
error: `template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`
|
|
22309
|
-
};
|
|
22579
|
+
throw new Error(`template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`);
|
|
22310
22580
|
}
|
|
22311
22581
|
templates.set(name, tmpl);
|
|
22312
22582
|
api.metrics.gauge("template_count", templates.size);
|
|
@@ -22635,6 +22905,7 @@ import { execFile as execFile13 } from "node:child_process";
|
|
|
22635
22905
|
import { readFileSync as readFileSync15 } from "node:fs";
|
|
22636
22906
|
import { createRequire as createRequire2 } from "node:module";
|
|
22637
22907
|
import { dirname as dirname8, isAbsolute as isAbsolute25, relative as relative24, resolve as resolve26 } from "node:path";
|
|
22908
|
+
import { ToolValidationError as ToolValidationError29 } from "@wrongstack/core/types";
|
|
22638
22909
|
var API_VERSION37 = "^0.1.10";
|
|
22639
22910
|
var state52 = {
|
|
22640
22911
|
invocationCount: 0,
|
|
@@ -22885,7 +23156,7 @@ var plugin57 = {
|
|
|
22885
23156
|
mutating: false,
|
|
22886
23157
|
async execute(input) {
|
|
22887
23158
|
if (!cfg.enabled) {
|
|
22888
|
-
|
|
23159
|
+
throw new Error("test-flake-detector is disabled");
|
|
22889
23160
|
}
|
|
22890
23161
|
const raw = input;
|
|
22891
23162
|
const rawPattern = input.testPattern ?? raw["pattern"] ?? raw["path"] ?? raw["file"] ?? raw["filePath"] ?? raw["TargetFile"] ?? raw["targetFile"];
|
|
@@ -22896,10 +23167,10 @@ var plugin57 = {
|
|
|
22896
23167
|
const requestedRuns = typeof rawRuns === "number" && rawRuns >= 1 ? Math.min(Math.floor(rawRuns), cfg.maxRuns) : 5;
|
|
22897
23168
|
const command = resolveTestCommand(commandString, testPattern);
|
|
22898
23169
|
if (!command) {
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
};
|
|
23170
|
+
throw new ToolValidationError29({
|
|
23171
|
+
message: "Unsupported test command or unsafe testPattern. Use vitest, jest, or mocha through a supported package runner, and keep patterns inside the project.",
|
|
23172
|
+
field: "command"
|
|
23173
|
+
});
|
|
22903
23174
|
}
|
|
22904
23175
|
state52.invocationCount += 1;
|
|
22905
23176
|
const start = Date.now();
|
|
@@ -22927,6 +23198,11 @@ var plugin57 = {
|
|
|
22927
23198
|
}
|
|
22928
23199
|
}
|
|
22929
23200
|
const all = Array.from(records.values());
|
|
23201
|
+
if (all.length === 0 && runErrors.length === requestedRuns) {
|
|
23202
|
+
throw new Error(
|
|
23203
|
+
`test command produced no test results in ${requestedRuns} run(s): ${runErrors.slice(0, 3).join("; ")}`
|
|
23204
|
+
);
|
|
23205
|
+
}
|
|
22930
23206
|
const flakyTests = all.filter((r) => r.passCount > 0 && r.failCount > 0);
|
|
22931
23207
|
const alwaysFailing = all.filter((r) => r.passCount === 0 && r.failCount > 0);
|
|
22932
23208
|
const alwaysPassing = all.filter((r) => r.passCount > 0 && r.failCount === 0);
|
|
@@ -23014,6 +23290,7 @@ var test_flake_detector_default = plugin57;
|
|
|
23014
23290
|
// src/test-generator/index.ts
|
|
23015
23291
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
23016
23292
|
import { isAbsolute as isAbsolute26, relative as relative25, resolve as resolve27 } from "node:path";
|
|
23293
|
+
import { ToolValidationError as ToolValidationError30 } from "@wrongstack/core/types";
|
|
23017
23294
|
var API_VERSION38 = "^0.1.10";
|
|
23018
23295
|
var state53 = {
|
|
23019
23296
|
generateCount: 0,
|
|
@@ -23302,7 +23579,7 @@ var plugin58 = {
|
|
|
23302
23579
|
category: "Development",
|
|
23303
23580
|
mutating: false,
|
|
23304
23581
|
async execute(input, _ctx, execOpts) {
|
|
23305
|
-
if (!cfg.enabled)
|
|
23582
|
+
if (!cfg.enabled) throw new Error("test-generator is disabled");
|
|
23306
23583
|
execOpts?.signal?.throwIfAborted();
|
|
23307
23584
|
const inp = input ?? {};
|
|
23308
23585
|
const rawFramework = inp["framework"];
|
|
@@ -23313,16 +23590,19 @@ var plugin58 = {
|
|
|
23313
23590
|
};
|
|
23314
23591
|
const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
|
|
23315
23592
|
if (!rawPath || typeof rawPath !== "string") {
|
|
23316
|
-
|
|
23593
|
+
throw new ToolValidationError30({ message: "path is required", field: "path" });
|
|
23317
23594
|
}
|
|
23318
23595
|
if (!withinProject27(rawPath)) {
|
|
23319
|
-
|
|
23596
|
+
throw new ToolValidationError30({
|
|
23597
|
+
message: "path is outside the project root",
|
|
23598
|
+
field: "path"
|
|
23599
|
+
});
|
|
23320
23600
|
}
|
|
23321
23601
|
if (!SOURCE_EXTENSIONS.some((ext) => rawPath.toLowerCase().endsWith(ext))) {
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
23325
|
-
};
|
|
23602
|
+
throw new ToolValidationError30({
|
|
23603
|
+
message: `test generation only reads source files (${SOURCE_EXTENSIONS.join(", ")}); refusing "${rawPath}"`,
|
|
23604
|
+
field: "path"
|
|
23605
|
+
});
|
|
23326
23606
|
}
|
|
23327
23607
|
const resolved = resolve27(process.cwd(), rawPath);
|
|
23328
23608
|
state53.generateCount += 1;
|
|
@@ -23331,7 +23611,7 @@ var plugin58 = {
|
|
|
23331
23611
|
result = generateForFile(resolved, effectiveCfg);
|
|
23332
23612
|
} catch (err) {
|
|
23333
23613
|
state53.errorCount += 1;
|
|
23334
|
-
|
|
23614
|
+
throw new Error(`Could not read ${rawPath}: ${String(err)}`, { cause: err });
|
|
23335
23615
|
}
|
|
23336
23616
|
state53.exportCount += result.exports.length;
|
|
23337
23617
|
const raw = input ?? {};
|
|
@@ -24152,451 +24432,632 @@ var todo_listener_default = plugin60;
|
|
|
24152
24432
|
// src/todo-tracker/index.ts
|
|
24153
24433
|
import { randomUUID } from "node:crypto";
|
|
24154
24434
|
import * as fsp from "node:fs/promises";
|
|
24155
|
-
import { dirname as dirname10 } from "node:path";
|
|
24156
|
-
import {
|
|
24435
|
+
import { basename as basename7, dirname as dirname10, extname as extname8 } from "node:path";
|
|
24436
|
+
import { ToolValidationError as ToolValidationError31 } from "@wrongstack/core/types";
|
|
24437
|
+
import { atomicWrite as atomicWrite3, ensureDir as ensureDir3, withFileLock } from "@wrongstack/core/utils";
|
|
24157
24438
|
import { nowIso } from "@wrongstack/primitives";
|
|
24439
|
+
var STATUSES = ["pending", "in_progress", "completed", "dropped"];
|
|
24440
|
+
var PRIORITIES = ["low", "normal", "high"];
|
|
24441
|
+
var defaultDeps = {
|
|
24442
|
+
readFile: (p) => fsp.readFile(p, "utf8"),
|
|
24443
|
+
rename: (from, to) => fsp.rename(from, to),
|
|
24444
|
+
atomicWrite: (p, content, opts) => atomicWrite3(p, content, opts),
|
|
24445
|
+
withFileLock: (p, fn) => withFileLock(p, fn),
|
|
24446
|
+
ensureDir: (dir) => ensureDir3(dir)
|
|
24447
|
+
};
|
|
24448
|
+
function deriveProjectSlug(filePath) {
|
|
24449
|
+
const base = basename7(filePath.replace(/[\\/]+$/, "").replace(/\\/g, "/"));
|
|
24450
|
+
const ext = extname8(base);
|
|
24451
|
+
const stem = ext && ext !== base ? base.slice(0, -ext.length) : base;
|
|
24452
|
+
return stem || "tracker";
|
|
24453
|
+
}
|
|
24158
24454
|
function deriveFilePath(api) {
|
|
24159
24455
|
const raw = api.config.extensions?.["todo-tracker"];
|
|
24160
24456
|
const rawPath = raw?.["filePath"] ?? raw?.["file_path"] ?? raw?.["path"] ?? raw?.["file"] ?? raw?.["targetFile"];
|
|
24161
24457
|
const explicit = typeof rawPath === "string" && rawPath.trim().length > 0 ? rawPath.trim() : null;
|
|
24162
24458
|
if (explicit) {
|
|
24163
|
-
|
|
24164
|
-
return { filePath: explicit, projectSlug: base };
|
|
24459
|
+
return { filePath: explicit, projectSlug: deriveProjectSlug(explicit) };
|
|
24165
24460
|
}
|
|
24166
24461
|
return { filePath: null, projectSlug: null };
|
|
24167
24462
|
}
|
|
24168
24463
|
var FILE_VERSION = 1;
|
|
24169
|
-
|
|
24170
|
-
|
|
24464
|
+
var isStr = (v) => typeof v === "string";
|
|
24465
|
+
var isOptStr = (v) => v === void 0 || v === null || typeof v === "string";
|
|
24466
|
+
function validateItem(raw, index) {
|
|
24467
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
24468
|
+
return `items[${index}] is not an object`;
|
|
24469
|
+
}
|
|
24470
|
+
const it = raw;
|
|
24471
|
+
if (!isStr(it["id"]) || it["id"].length === 0) return `items[${index}].id is not a string`;
|
|
24472
|
+
if (!isStr(it["content"])) return `items[${index}].content is not a string`;
|
|
24473
|
+
if (!STATUSES.includes(it["status"])) {
|
|
24474
|
+
return `items[${index}].status is not one of ${STATUSES.join("|")}`;
|
|
24475
|
+
}
|
|
24476
|
+
if (!PRIORITIES.includes(it["priority"])) {
|
|
24477
|
+
return `items[${index}].priority is not one of ${PRIORITIES.join("|")}`;
|
|
24478
|
+
}
|
|
24479
|
+
if (!Array.isArray(it["tags"]) || !it["tags"].every(isStr)) {
|
|
24480
|
+
return `items[${index}].tags is not a string[]`;
|
|
24481
|
+
}
|
|
24482
|
+
if (!isStr(it["createdAt"]) || !isStr(it["updatedAt"])) {
|
|
24483
|
+
return `items[${index}] timestamps are not strings`;
|
|
24484
|
+
}
|
|
24485
|
+
if (!isOptStr(it["completedAt"]) || !isOptStr(it["sourceSessionId"]) || !isOptStr(it["notes"])) {
|
|
24486
|
+
return `items[${index}] optional fields are not string|null`;
|
|
24487
|
+
}
|
|
24488
|
+
return {
|
|
24489
|
+
id: it["id"],
|
|
24490
|
+
content: it["content"],
|
|
24491
|
+
status: it["status"],
|
|
24492
|
+
priority: it["priority"],
|
|
24493
|
+
tags: [...it["tags"]],
|
|
24494
|
+
createdAt: it["createdAt"],
|
|
24495
|
+
updatedAt: it["updatedAt"],
|
|
24496
|
+
completedAt: it["completedAt"] ?? null,
|
|
24497
|
+
sourceSessionId: it["sourceSessionId"] ?? null,
|
|
24498
|
+
notes: it["notes"] ?? null
|
|
24499
|
+
};
|
|
24500
|
+
}
|
|
24501
|
+
function parseTrackerFile(rawText) {
|
|
24502
|
+
const text = rawText.charCodeAt(0) === 65279 ? rawText.slice(1) : rawText;
|
|
24503
|
+
let parsed;
|
|
24171
24504
|
try {
|
|
24172
|
-
|
|
24505
|
+
parsed = JSON.parse(text);
|
|
24173
24506
|
} catch (err) {
|
|
24174
|
-
|
|
24175
|
-
throw err;
|
|
24507
|
+
return { kind: "corrupt", reason: `invalid JSON: ${err.message}` };
|
|
24176
24508
|
}
|
|
24177
|
-
|
|
24178
|
-
|
|
24179
|
-
if (parsed.version !== FILE_VERSION || !Array.isArray(parsed.items)) {
|
|
24180
|
-
return null;
|
|
24181
|
-
}
|
|
24182
|
-
return parsed;
|
|
24183
|
-
} catch {
|
|
24184
|
-
return null;
|
|
24509
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
24510
|
+
return { kind: "corrupt", reason: "top-level value is not an object" };
|
|
24185
24511
|
}
|
|
24186
|
-
|
|
24187
|
-
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24191
|
-
|
|
24192
|
-
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24198
|
-
|
|
24199
|
-
|
|
24200
|
-
|
|
24201
|
-
|
|
24202
|
-
|
|
24203
|
-
|
|
24204
|
-
|
|
24205
|
-
state56.file = {
|
|
24512
|
+
const obj = parsed;
|
|
24513
|
+
if (typeof obj["version"] !== "number") {
|
|
24514
|
+
return { kind: "corrupt", reason: "missing or non-numeric version" };
|
|
24515
|
+
}
|
|
24516
|
+
if (obj["version"] !== FILE_VERSION) {
|
|
24517
|
+
return { kind: "unsupportedVersion", version: obj["version"] };
|
|
24518
|
+
}
|
|
24519
|
+
if (!Array.isArray(obj["items"])) {
|
|
24520
|
+
return { kind: "invalidItems", reason: "items is not an array" };
|
|
24521
|
+
}
|
|
24522
|
+
const items = [];
|
|
24523
|
+
for (const [i, rawItem] of obj["items"].entries()) {
|
|
24524
|
+
const v = validateItem(rawItem, i);
|
|
24525
|
+
if (typeof v === "string") return { kind: "invalidItems", reason: v };
|
|
24526
|
+
items.push(v);
|
|
24527
|
+
}
|
|
24528
|
+
return {
|
|
24529
|
+
kind: "ok",
|
|
24530
|
+
file: {
|
|
24206
24531
|
version: FILE_VERSION,
|
|
24207
|
-
|
|
24208
|
-
|
|
24209
|
-
|
|
24210
|
-
|
|
24532
|
+
// Any stored slug is accepted (older versions stored the basename
|
|
24533
|
+
// including the extension); the current slug is written on next save.
|
|
24534
|
+
projectSlug: isStr(obj["projectSlug"]) ? obj["projectSlug"] : "",
|
|
24535
|
+
updatedAt: isStr(obj["updatedAt"]) ? obj["updatedAt"] : "",
|
|
24536
|
+
items
|
|
24537
|
+
}
|
|
24538
|
+
};
|
|
24539
|
+
}
|
|
24540
|
+
async function loadFile(deps, filePath) {
|
|
24541
|
+
let raw;
|
|
24542
|
+
try {
|
|
24543
|
+
raw = await deps.readFile(filePath);
|
|
24544
|
+
} catch (err) {
|
|
24545
|
+
if (err.code === "ENOENT") return { kind: "missing" };
|
|
24546
|
+
throw err;
|
|
24211
24547
|
}
|
|
24212
|
-
return
|
|
24548
|
+
return parseTrackerFile(raw);
|
|
24213
24549
|
}
|
|
24214
|
-
function
|
|
24215
|
-
|
|
24216
|
-
if (op === "add") state56.addCount += 1;
|
|
24217
|
-
else if (op === "complete") state56.completeCount += 1;
|
|
24218
|
-
else if (op === "drop") state56.dropCount += 1;
|
|
24219
|
-
else if (op === "remove") state56.removeCount += 1;
|
|
24220
|
-
else if (op === "pull") state56.pullCount += 1;
|
|
24550
|
+
function quarantineSuffix() {
|
|
24551
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
24221
24552
|
}
|
|
24222
|
-
function
|
|
24223
|
-
return
|
|
24553
|
+
function emptyFile(slug) {
|
|
24554
|
+
return { version: FILE_VERSION, projectSlug: slug, updatedAt: nowIso(), items: [] };
|
|
24224
24555
|
}
|
|
24225
|
-
function
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
24229
|
-
|
|
24556
|
+
function requireItemId(input) {
|
|
24557
|
+
const rawId = input["id"] ?? input["itemId"] ?? input["taskId"] ?? input["todoId"];
|
|
24558
|
+
const id = typeof rawId === "string" ? rawId.trim() : "";
|
|
24559
|
+
if (!id) throw new ToolValidationError31({ message: "id is required", field: "id" });
|
|
24560
|
+
return id;
|
|
24230
24561
|
}
|
|
24231
|
-
|
|
24232
|
-
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24240
|
-
|
|
24241
|
-
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
|
|
24562
|
+
function commonPrefixLength(a, b) {
|
|
24563
|
+
const n = Math.min(a.length, b.length);
|
|
24564
|
+
let i = 0;
|
|
24565
|
+
while (i < n && a[i] === b[i]) i++;
|
|
24566
|
+
return i;
|
|
24567
|
+
}
|
|
24568
|
+
function requireItemIndex(file, id) {
|
|
24569
|
+
const idx = file.items.findIndex((it) => it.id === id);
|
|
24570
|
+
if (idx !== -1) return idx;
|
|
24571
|
+
const open = file.items.filter((it) => it.status === "pending" || it.status === "in_progress");
|
|
24572
|
+
const pool = open.length > 0 ? open : file.items;
|
|
24573
|
+
const candidates = [...pool].sort((a, b) => commonPrefixLength(b.id, id) - commonPrefixLength(a.id, id)).slice(0, 5).map((it) => `${it.id} ("${it.content.slice(0, 40)}")`);
|
|
24574
|
+
const hint = candidates.length > 0 ? ` Known ${open.length > 0 ? "open " : ""}ids: ${candidates.join(", ")}` : " The tracker is empty.";
|
|
24575
|
+
throw new ToolValidationError31({ message: `no item with id ${id}.${hint}`, field: "id" });
|
|
24576
|
+
}
|
|
24577
|
+
function createTodoTrackerPlugin(overrides = {}) {
|
|
24578
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
24579
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
24580
|
+
let latest = null;
|
|
24581
|
+
function unregisterTools(inst) {
|
|
24582
|
+
const unregister = inst.api.tools.unregister;
|
|
24583
|
+
for (const name of inst.registeredTools.splice(0)) {
|
|
24584
|
+
if (typeof unregister !== "function") continue;
|
|
24585
|
+
try {
|
|
24586
|
+
unregister.call(inst.api.tools, name);
|
|
24587
|
+
} catch (err) {
|
|
24588
|
+
inst.api.log.warn("todo-tracker: failed to unregister tool", { name, err });
|
|
24246
24589
|
}
|
|
24247
24590
|
}
|
|
24248
|
-
}
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24591
|
+
}
|
|
24592
|
+
function assertWritable(inst) {
|
|
24593
|
+
if (inst.readOnlyReason !== null) {
|
|
24594
|
+
throw new Error(`todo-tracker: store is read-only \u2014 ${inst.readOnlyReason}`);
|
|
24595
|
+
}
|
|
24596
|
+
}
|
|
24597
|
+
function serialize(inst, fn) {
|
|
24598
|
+
const run = inst.queue.then(fn, fn);
|
|
24599
|
+
inst.queue = run.catch(() => void 0);
|
|
24600
|
+
return run;
|
|
24601
|
+
}
|
|
24602
|
+
async function quarantineLocked(inst, reason) {
|
|
24603
|
+
const target = `${inst.filePath}.corrupt-${quarantineSuffix()}`;
|
|
24604
|
+
try {
|
|
24605
|
+
await deps.rename(inst.filePath, target);
|
|
24606
|
+
} catch (err) {
|
|
24607
|
+
inst.readOnlyReason = `${inst.filePath} is unreadable (${reason}) and could not be moved aside (${err.message}); fix or remove the file, then reload the plugin`;
|
|
24608
|
+
inst.api.log.error(
|
|
24609
|
+
"todo-tracker: corrupt file could not be quarantined; store is read-only",
|
|
24610
|
+
{
|
|
24611
|
+
filePath: inst.filePath,
|
|
24612
|
+
reason,
|
|
24613
|
+
err
|
|
24614
|
+
}
|
|
24261
24615
|
);
|
|
24262
|
-
return;
|
|
24616
|
+
return false;
|
|
24263
24617
|
}
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24618
|
+
inst.degradedReason = `${inst.filePath} was unreadable (${reason}); original moved to ${target}`;
|
|
24619
|
+
inst.api.log.error(`todo-tracker: corrupt file quarantined to ${target}`, {
|
|
24620
|
+
filePath: inst.filePath,
|
|
24621
|
+
quarantinedTo: target,
|
|
24622
|
+
reason
|
|
24623
|
+
});
|
|
24624
|
+
return true;
|
|
24625
|
+
}
|
|
24626
|
+
async function resolveLoad(inst, res, opts) {
|
|
24627
|
+
switch (res.kind) {
|
|
24628
|
+
case "ok":
|
|
24629
|
+
return res.file;
|
|
24630
|
+
case "missing":
|
|
24631
|
+
return emptyFile(inst.projectSlug);
|
|
24632
|
+
case "unsupportedVersion": {
|
|
24633
|
+
inst.readOnlyReason = `${inst.filePath} has format version ${JSON.stringify(res.version)}, this plugin only writes version ${FILE_VERSION}; refusing to modify it`;
|
|
24634
|
+
inst.api.log.error("todo-tracker: unsupported file version; store is read-only", {
|
|
24635
|
+
filePath: inst.filePath,
|
|
24636
|
+
version: res.version
|
|
24637
|
+
});
|
|
24638
|
+
if (opts.strict) assertWritable(inst);
|
|
24639
|
+
return emptyFile(inst.projectSlug);
|
|
24640
|
+
}
|
|
24641
|
+
case "corrupt":
|
|
24642
|
+
case "invalidItems": {
|
|
24643
|
+
const quarantine = async () => {
|
|
24644
|
+
const again = opts.locked ? res : await loadFile(deps, inst.filePath);
|
|
24645
|
+
if (again.kind === "ok") return again.file;
|
|
24646
|
+
if (again.kind === "missing") return emptyFile(inst.projectSlug);
|
|
24647
|
+
if (again.kind === "unsupportedVersion") {
|
|
24648
|
+
return resolveLoad(inst, again, { locked: true, strict: opts.strict });
|
|
24649
|
+
}
|
|
24650
|
+
const moved = await quarantineLocked(inst, again.reason);
|
|
24651
|
+
if (!moved && opts.strict) assertWritable(inst);
|
|
24652
|
+
return emptyFile(inst.projectSlug);
|
|
24653
|
+
};
|
|
24654
|
+
return opts.locked ? quarantine() : deps.withFileLock(inst.filePath, quarantine);
|
|
24655
|
+
}
|
|
24274
24656
|
}
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
|
|
24286
|
-
|
|
24287
|
-
|
|
24288
|
-
|
|
24657
|
+
}
|
|
24658
|
+
async function refresh(inst) {
|
|
24659
|
+
if (inst.readOnlyReason !== null) return inst.file;
|
|
24660
|
+
const res = await loadFile(deps, inst.filePath);
|
|
24661
|
+
const file = await resolveLoad(inst, res, { locked: false, strict: false });
|
|
24662
|
+
inst.file = file;
|
|
24663
|
+
return file;
|
|
24664
|
+
}
|
|
24665
|
+
function mutate(inst, apply) {
|
|
24666
|
+
return serialize(
|
|
24667
|
+
inst,
|
|
24668
|
+
() => deps.withFileLock(inst.filePath, async () => {
|
|
24669
|
+
assertWritable(inst);
|
|
24670
|
+
const res = await loadFile(deps, inst.filePath);
|
|
24671
|
+
const current = await resolveLoad(inst, res, { locked: true, strict: true });
|
|
24672
|
+
const draft = structuredClone(current);
|
|
24673
|
+
const now = nowIso();
|
|
24674
|
+
const { changed, result } = apply(draft, now);
|
|
24675
|
+
if (changed) {
|
|
24676
|
+
draft.version = FILE_VERSION;
|
|
24677
|
+
draft.projectSlug = inst.projectSlug;
|
|
24678
|
+
draft.updatedAt = now;
|
|
24679
|
+
await deps.ensureDir(dirname10(inst.filePath));
|
|
24680
|
+
await deps.atomicWrite(inst.filePath, JSON.stringify(draft, null, 2), { mode: 384 });
|
|
24681
|
+
inst.file = draft;
|
|
24682
|
+
} else {
|
|
24683
|
+
inst.file = current;
|
|
24289
24684
|
}
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
|
|
24295
|
-
|
|
24296
|
-
|
|
24297
|
-
|
|
24298
|
-
|
|
24299
|
-
|
|
24300
|
-
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
24306
|
-
|
|
24307
|
-
|
|
24685
|
+
return result;
|
|
24686
|
+
})
|
|
24687
|
+
);
|
|
24688
|
+
}
|
|
24689
|
+
function recordMutation(inst, op, itemId) {
|
|
24690
|
+
inst.lastMutation = { op, itemId, when: nowIso() };
|
|
24691
|
+
if (op === "add") inst.addCount += 1;
|
|
24692
|
+
else if (op === "complete") inst.completeCount += 1;
|
|
24693
|
+
else if (op === "drop") inst.dropCount += 1;
|
|
24694
|
+
else if (op === "remove") inst.removeCount += 1;
|
|
24695
|
+
}
|
|
24696
|
+
function sessionCounts(inst) {
|
|
24697
|
+
return {
|
|
24698
|
+
add: inst.addCount,
|
|
24699
|
+
complete: inst.completeCount,
|
|
24700
|
+
drop: inst.dropCount,
|
|
24701
|
+
remove: inst.removeCount,
|
|
24702
|
+
pull: inst.pullCount
|
|
24703
|
+
};
|
|
24704
|
+
}
|
|
24705
|
+
function disposeInstance(api) {
|
|
24706
|
+
const inst = instances.get(api);
|
|
24707
|
+
if (!inst) return void 0;
|
|
24708
|
+
unregisterTools(inst);
|
|
24709
|
+
instances.delete(api);
|
|
24710
|
+
if (latest === inst) latest = null;
|
|
24711
|
+
return inst;
|
|
24712
|
+
}
|
|
24713
|
+
const plugin65 = {
|
|
24714
|
+
name: "todo-tracker",
|
|
24715
|
+
version: "0.1.0",
|
|
24716
|
+
description: "Persistent, project-scoped todo backlog that survives across sessions",
|
|
24717
|
+
apiVersion: "^0.1.10",
|
|
24718
|
+
capabilities: { tools: true },
|
|
24719
|
+
defaultConfig: {
|
|
24720
|
+
filePath: ""
|
|
24721
|
+
},
|
|
24722
|
+
configSchema: {
|
|
24723
|
+
type: "object",
|
|
24724
|
+
properties: {
|
|
24725
|
+
filePath: {
|
|
24726
|
+
type: "string",
|
|
24727
|
+
description: "Override the auto-derived per-project path. Defaults to <projectDir>/todo-tracker.json when `paths.projectDir` is provided by the host."
|
|
24728
|
+
}
|
|
24729
|
+
}
|
|
24730
|
+
},
|
|
24731
|
+
async setup(api) {
|
|
24732
|
+
disposeInstance(api);
|
|
24733
|
+
const derived = deriveFilePath(api);
|
|
24734
|
+
if (derived.filePath === null) {
|
|
24735
|
+
latest = null;
|
|
24736
|
+
api.log.warn(
|
|
24737
|
+
'todo-tracker: no file path configured (set `config.extensions["todo-tracker"].filePath` or wire `paths.projectDir` through PluginAPI) \u2014 tools will report a clear error'
|
|
24738
|
+
);
|
|
24739
|
+
return;
|
|
24740
|
+
}
|
|
24741
|
+
const inst = {
|
|
24742
|
+
api,
|
|
24743
|
+
filePath: derived.filePath,
|
|
24744
|
+
projectSlug: derived.projectSlug ?? "tracker",
|
|
24745
|
+
file: emptyFile(derived.projectSlug ?? "tracker"),
|
|
24746
|
+
readOnlyReason: null,
|
|
24747
|
+
degradedReason: null,
|
|
24748
|
+
queue: Promise.resolve(),
|
|
24749
|
+
registeredTools: [],
|
|
24750
|
+
addCount: 0,
|
|
24751
|
+
completeCount: 0,
|
|
24752
|
+
dropCount: 0,
|
|
24753
|
+
removeCount: 0,
|
|
24754
|
+
pullCount: 0,
|
|
24755
|
+
lastMutation: null
|
|
24756
|
+
};
|
|
24757
|
+
instances.set(api, inst);
|
|
24758
|
+
latest = inst;
|
|
24759
|
+
await refresh(inst);
|
|
24760
|
+
const register = (tool) => {
|
|
24761
|
+
api.tools.register(tool);
|
|
24762
|
+
inst.registeredTools.push(tool.name);
|
|
24763
|
+
};
|
|
24764
|
+
register({
|
|
24765
|
+
name: "todo_tracker_list",
|
|
24766
|
+
description: "List persistent todo-tracker items. Filterable by status, priority, and tag. By default only pending + in_progress items are shown.",
|
|
24767
|
+
inputSchema: {
|
|
24768
|
+
type: "object",
|
|
24769
|
+
properties: {
|
|
24770
|
+
status: {
|
|
24771
|
+
type: "string",
|
|
24772
|
+
enum: ["pending", "in_progress", "completed", "dropped", "all"],
|
|
24773
|
+
description: "Filter by status. 'all' returns every item; default is pending+in_progress."
|
|
24774
|
+
},
|
|
24775
|
+
priority: { type: "string", enum: ["low", "normal", "high"] },
|
|
24776
|
+
tag: { type: "string", description: "Filter by exact tag match" },
|
|
24777
|
+
limit: { type: "number", description: "Max items to return (default 50, max 200)" }
|
|
24778
|
+
}
|
|
24779
|
+
},
|
|
24780
|
+
permission: "auto",
|
|
24781
|
+
mutating: false,
|
|
24782
|
+
async execute(input) {
|
|
24783
|
+
const rawStatus = typeof input["status"] === "string" ? input["status"].trim().toLowerCase() : void 0;
|
|
24784
|
+
const status = rawStatus ?? "active";
|
|
24785
|
+
const priority = typeof input["priority"] === "string" ? input["priority"].trim().toLowerCase() : void 0;
|
|
24786
|
+
const tag = typeof input["tag"] === "string" ? input["tag"] : void 0;
|
|
24787
|
+
const limit = Math.min(Math.max(Number(input["limit"] ?? 50) || 50, 1), 200);
|
|
24788
|
+
const file = await refresh(inst);
|
|
24789
|
+
let items = file.items;
|
|
24790
|
+
if (status !== "all") {
|
|
24791
|
+
if (status === "active") {
|
|
24792
|
+
items = items.filter((it) => it.status === "pending" || it.status === "in_progress");
|
|
24793
|
+
} else {
|
|
24794
|
+
items = items.filter((it) => it.status === status);
|
|
24795
|
+
}
|
|
24308
24796
|
}
|
|
24797
|
+
if (priority) items = items.filter((it) => it.priority === priority);
|
|
24798
|
+
if (tag) items = items.filter((it) => it.tags.includes(tag));
|
|
24799
|
+
const total = items.length;
|
|
24800
|
+
const truncated = items.slice(0, limit);
|
|
24801
|
+
return {
|
|
24802
|
+
ok: true,
|
|
24803
|
+
total,
|
|
24804
|
+
returned: truncated.length,
|
|
24805
|
+
truncated: total > truncated.length,
|
|
24806
|
+
items: truncated,
|
|
24807
|
+
...inst.readOnlyReason ? { readOnly: inst.readOnlyReason } : {}
|
|
24808
|
+
};
|
|
24309
24809
|
}
|
|
24310
|
-
|
|
24311
|
-
|
|
24312
|
-
|
|
24313
|
-
|
|
24314
|
-
|
|
24315
|
-
|
|
24316
|
-
|
|
24317
|
-
|
|
24318
|
-
|
|
24319
|
-
|
|
24320
|
-
|
|
24321
|
-
|
|
24322
|
-
|
|
24323
|
-
|
|
24324
|
-
|
|
24325
|
-
|
|
24326
|
-
inputSchema: {
|
|
24327
|
-
type: "object",
|
|
24328
|
-
properties: {
|
|
24329
|
-
content: { type: "string", description: "What needs doing (required)" },
|
|
24330
|
-
priority: { type: "string", enum: ["low", "normal", "high"], default: "normal" },
|
|
24331
|
-
tags: {
|
|
24332
|
-
type: "array",
|
|
24333
|
-
items: { type: "string" },
|
|
24334
|
-
description: "Optional tags for filtering"
|
|
24810
|
+
});
|
|
24811
|
+
register({
|
|
24812
|
+
name: "todo_tracker_add",
|
|
24813
|
+
description: "Append a new item to the persistent todo-tracker backlog.",
|
|
24814
|
+
inputSchema: {
|
|
24815
|
+
type: "object",
|
|
24816
|
+
properties: {
|
|
24817
|
+
content: { type: "string", description: "What needs doing (required)" },
|
|
24818
|
+
priority: { type: "string", enum: ["low", "normal", "high"], default: "normal" },
|
|
24819
|
+
tags: {
|
|
24820
|
+
type: "array",
|
|
24821
|
+
items: { type: "string" },
|
|
24822
|
+
description: "Optional tags for filtering"
|
|
24823
|
+
},
|
|
24824
|
+
sourceSessionId: { type: "string", description: "Session that created this item" },
|
|
24825
|
+
notes: { type: "string", description: "Optional free-form notes" }
|
|
24335
24826
|
},
|
|
24336
|
-
|
|
24337
|
-
notes: { type: "string", description: "Optional free-form notes" }
|
|
24827
|
+
required: ["content"]
|
|
24338
24828
|
},
|
|
24339
|
-
|
|
24340
|
-
|
|
24341
|
-
|
|
24342
|
-
|
|
24343
|
-
|
|
24344
|
-
|
|
24345
|
-
|
|
24346
|
-
|
|
24347
|
-
|
|
24348
|
-
|
|
24349
|
-
|
|
24350
|
-
|
|
24351
|
-
|
|
24352
|
-
|
|
24353
|
-
|
|
24354
|
-
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
24364
|
-
|
|
24365
|
-
|
|
24366
|
-
|
|
24367
|
-
|
|
24368
|
-
|
|
24369
|
-
|
|
24370
|
-
file.updatedAt = now;
|
|
24371
|
-
await saveFile(state56.filePath, file);
|
|
24372
|
-
recordMutation("add", item.id);
|
|
24373
|
-
api.log.info("todo-tracker: added item", { id: item.id, content });
|
|
24374
|
-
try {
|
|
24375
|
-
await api.session?.append?.({
|
|
24376
|
-
type: "todo-tracker:add",
|
|
24377
|
-
ts: now,
|
|
24378
|
-
id: item.id,
|
|
24379
|
-
content,
|
|
24380
|
-
priority,
|
|
24381
|
-
tags
|
|
24829
|
+
permission: "auto",
|
|
24830
|
+
mutating: true,
|
|
24831
|
+
async execute(input) {
|
|
24832
|
+
const rawContent = input["content"] ?? input["text"] ?? input["task"] ?? input["title"] ?? input["todo"] ?? input["message"] ?? input["item"];
|
|
24833
|
+
const content = typeof rawContent === "string" ? rawContent.trim() : "";
|
|
24834
|
+
if (!content) {
|
|
24835
|
+
throw new ToolValidationError31({
|
|
24836
|
+
message: "content is required and must be a non-empty string",
|
|
24837
|
+
field: "content"
|
|
24838
|
+
});
|
|
24839
|
+
}
|
|
24840
|
+
const rawPri = typeof input["priority"] === "string" ? input["priority"].trim().toLowerCase() : "";
|
|
24841
|
+
const priority = rawPri === "low" || rawPri === "high" ? rawPri : "normal";
|
|
24842
|
+
const tags = Array.isArray(input["tags"]) ? input["tags"].filter((t) => typeof t === "string") : [];
|
|
24843
|
+
const sourceSessionId = typeof input["sourceSessionId"] === "string" ? input["sourceSessionId"] : null;
|
|
24844
|
+
const notes = typeof input["notes"] === "string" ? input["notes"] : null;
|
|
24845
|
+
const item = await mutate(inst, (draft, now) => {
|
|
24846
|
+
const created = {
|
|
24847
|
+
id: randomUUID(),
|
|
24848
|
+
content,
|
|
24849
|
+
status: "pending",
|
|
24850
|
+
priority,
|
|
24851
|
+
tags,
|
|
24852
|
+
createdAt: now,
|
|
24853
|
+
updatedAt: now,
|
|
24854
|
+
completedAt: null,
|
|
24855
|
+
sourceSessionId,
|
|
24856
|
+
notes
|
|
24857
|
+
};
|
|
24858
|
+
draft.items.push(created);
|
|
24859
|
+
return { changed: true, result: created };
|
|
24382
24860
|
});
|
|
24383
|
-
|
|
24861
|
+
recordMutation(inst, "add", item.id);
|
|
24862
|
+
api.log.info("todo-tracker: added item", { id: item.id, content });
|
|
24863
|
+
try {
|
|
24864
|
+
await api.session?.append?.({
|
|
24865
|
+
type: "todo-tracker:add",
|
|
24866
|
+
ts: item.createdAt,
|
|
24867
|
+
id: item.id,
|
|
24868
|
+
content,
|
|
24869
|
+
priority,
|
|
24870
|
+
tags
|
|
24871
|
+
});
|
|
24872
|
+
} catch (err) {
|
|
24873
|
+
api.log.warn("todo-tracker: session.append failed (item was saved)", {
|
|
24874
|
+
id: item.id,
|
|
24875
|
+
err
|
|
24876
|
+
});
|
|
24877
|
+
}
|
|
24878
|
+
return { ok: true, item };
|
|
24384
24879
|
}
|
|
24385
|
-
|
|
24386
|
-
|
|
24387
|
-
|
|
24388
|
-
|
|
24389
|
-
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24393
|
-
|
|
24394
|
-
|
|
24880
|
+
});
|
|
24881
|
+
const setTerminalStatus = async (input, target) => {
|
|
24882
|
+
const id = requireItemId(input);
|
|
24883
|
+
return mutate(inst, (draft, now) => {
|
|
24884
|
+
const item = draft.items[requireItemIndex(draft, id)];
|
|
24885
|
+
if (item.status === target) {
|
|
24886
|
+
return {
|
|
24887
|
+
changed: false,
|
|
24888
|
+
result: { ok: true, item, message: `already ${target} (idempotent)` }
|
|
24889
|
+
};
|
|
24890
|
+
}
|
|
24891
|
+
item.status = target;
|
|
24892
|
+
item.updatedAt = now;
|
|
24893
|
+
item.completedAt = now;
|
|
24894
|
+
return { changed: true, result: { ok: true, item } };
|
|
24895
|
+
});
|
|
24896
|
+
};
|
|
24897
|
+
register({
|
|
24898
|
+
name: "todo_tracker_complete",
|
|
24899
|
+
description: "Mark a tracked item as completed. Idempotent.",
|
|
24900
|
+
inputSchema: {
|
|
24901
|
+
type: "object",
|
|
24902
|
+
properties: {
|
|
24903
|
+
id: { type: "string", description: "Item id" }
|
|
24904
|
+
},
|
|
24905
|
+
required: ["id"]
|
|
24395
24906
|
},
|
|
24396
|
-
|
|
24397
|
-
|
|
24398
|
-
|
|
24399
|
-
|
|
24400
|
-
|
|
24401
|
-
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
|
|
24405
|
-
const idx = findItemIndex(id);
|
|
24406
|
-
if (idx === -1) return { ok: false, error: `no item with id ${id}` };
|
|
24407
|
-
const file = ensureFile();
|
|
24408
|
-
const item = file.items[idx];
|
|
24409
|
-
if (item.status === "completed") {
|
|
24410
|
-
return { ok: true, item, message: "already completed (idempotent)" };
|
|
24907
|
+
permission: "auto",
|
|
24908
|
+
mutating: true,
|
|
24909
|
+
async execute(input) {
|
|
24910
|
+
const result = await setTerminalStatus(input, "completed");
|
|
24911
|
+
if (!result.message) {
|
|
24912
|
+
recordMutation(inst, "complete", result.item.id);
|
|
24913
|
+
api.log.info("todo-tracker: completed item", { id: result.item.id });
|
|
24914
|
+
}
|
|
24915
|
+
return result;
|
|
24411
24916
|
}
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
|
|
24415
|
-
item.
|
|
24416
|
-
|
|
24417
|
-
|
|
24418
|
-
|
|
24419
|
-
|
|
24420
|
-
|
|
24421
|
-
|
|
24422
|
-
});
|
|
24423
|
-
api.tools.register({
|
|
24424
|
-
name: "todo_tracker_drop",
|
|
24425
|
-
description: "Mark a tracked item as dropped (skipped/obsolete). The row is kept for audit. Idempotent.",
|
|
24426
|
-
inputSchema: {
|
|
24427
|
-
type: "object",
|
|
24428
|
-
properties: {
|
|
24429
|
-
id: { type: "string", description: "Item id" }
|
|
24917
|
+
});
|
|
24918
|
+
register({
|
|
24919
|
+
name: "todo_tracker_drop",
|
|
24920
|
+
description: "Mark a tracked item as dropped (skipped/obsolete). The row is kept for audit. Idempotent.",
|
|
24921
|
+
inputSchema: {
|
|
24922
|
+
type: "object",
|
|
24923
|
+
properties: {
|
|
24924
|
+
id: { type: "string", description: "Item id" }
|
|
24925
|
+
},
|
|
24926
|
+
required: ["id"]
|
|
24430
24927
|
},
|
|
24431
|
-
|
|
24432
|
-
|
|
24433
|
-
|
|
24434
|
-
|
|
24435
|
-
|
|
24436
|
-
|
|
24437
|
-
const rawId = input["id"] ?? input["itemId"] ?? input["taskId"] ?? input["todoId"];
|
|
24438
|
-
const id = typeof rawId === "string" ? rawId.trim() : "";
|
|
24439
|
-
if (!id) return { ok: false, error: "id is required" };
|
|
24440
|
-
const idx = findItemIndex(id);
|
|
24441
|
-
if (idx === -1) return { ok: false, error: `no item with id ${id}` };
|
|
24442
|
-
const file = ensureFile();
|
|
24443
|
-
const item = file.items[idx];
|
|
24444
|
-
if (item.status === "dropped") {
|
|
24445
|
-
return { ok: true, item, message: "already dropped (idempotent)" };
|
|
24928
|
+
permission: "auto",
|
|
24929
|
+
mutating: true,
|
|
24930
|
+
async execute(input) {
|
|
24931
|
+
const result = await setTerminalStatus(input, "dropped");
|
|
24932
|
+
if (!result.message) recordMutation(inst, "drop", result.item.id);
|
|
24933
|
+
return result;
|
|
24446
24934
|
}
|
|
24447
|
-
|
|
24448
|
-
|
|
24449
|
-
|
|
24450
|
-
item.
|
|
24451
|
-
|
|
24452
|
-
|
|
24453
|
-
|
|
24454
|
-
|
|
24455
|
-
|
|
24456
|
-
|
|
24457
|
-
api.tools.register({
|
|
24458
|
-
name: "todo_tracker_remove",
|
|
24459
|
-
description: "Permanently delete a tracked item by id. Use todo_tracker_drop instead if you want to keep the audit row.",
|
|
24460
|
-
inputSchema: {
|
|
24461
|
-
type: "object",
|
|
24462
|
-
properties: {
|
|
24463
|
-
id: { type: "string", description: "Item id" }
|
|
24935
|
+
});
|
|
24936
|
+
register({
|
|
24937
|
+
name: "todo_tracker_remove",
|
|
24938
|
+
description: "Permanently delete a tracked item by id. Use todo_tracker_drop instead if you want to keep the audit row.",
|
|
24939
|
+
inputSchema: {
|
|
24940
|
+
type: "object",
|
|
24941
|
+
properties: {
|
|
24942
|
+
id: { type: "string", description: "Item id" }
|
|
24943
|
+
},
|
|
24944
|
+
required: ["id"]
|
|
24464
24945
|
},
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24472
|
-
|
|
24473
|
-
|
|
24474
|
-
|
|
24475
|
-
if (idx === -1) return { ok: false, error: `no item with id ${id}` };
|
|
24476
|
-
const file = ensureFile();
|
|
24477
|
-
const [removed] = file.items.splice(idx, 1);
|
|
24478
|
-
file.updatedAt = nowIso();
|
|
24479
|
-
await saveFile(state56.filePath, file);
|
|
24480
|
-
recordMutation("remove", id);
|
|
24481
|
-
return { ok: true, removed };
|
|
24482
|
-
}
|
|
24483
|
-
});
|
|
24484
|
-
api.tools.register({
|
|
24485
|
-
name: "todo_tracker_pull",
|
|
24486
|
-
description: "Return all pending + in_progress items. The LLM is expected to take this list and re-register each entry with the session-local `todo` tool (which mutates ctx.todos). After pull, the LLM may also choose to call todo_tracker_complete on items it finishes mid-session.",
|
|
24487
|
-
inputSchema: {
|
|
24488
|
-
type: "object",
|
|
24489
|
-
properties: {
|
|
24490
|
-
limit: { type: "number", description: "Max items to return (default 50, max 200)" }
|
|
24946
|
+
permission: "confirm",
|
|
24947
|
+
mutating: true,
|
|
24948
|
+
async execute(input) {
|
|
24949
|
+
const id = requireItemId(input);
|
|
24950
|
+
const removed = await mutate(inst, (draft) => {
|
|
24951
|
+
const [gone] = draft.items.splice(requireItemIndex(draft, id), 1);
|
|
24952
|
+
return { changed: true, result: gone };
|
|
24953
|
+
});
|
|
24954
|
+
recordMutation(inst, "remove", id);
|
|
24955
|
+
return { ok: true, removed };
|
|
24491
24956
|
}
|
|
24492
|
-
}
|
|
24493
|
-
|
|
24494
|
-
|
|
24495
|
-
|
|
24496
|
-
|
|
24497
|
-
|
|
24498
|
-
|
|
24499
|
-
|
|
24500
|
-
|
|
24501
|
-
|
|
24957
|
+
});
|
|
24958
|
+
register({
|
|
24959
|
+
name: "todo_tracker_pull",
|
|
24960
|
+
description: "Return all pending + in_progress items. The LLM is expected to take this list and re-register each entry with the session-local `todo` tool (which mutates ctx.todos). After pull, the LLM may also choose to call todo_tracker_complete on items it finishes mid-session.",
|
|
24961
|
+
inputSchema: {
|
|
24962
|
+
type: "object",
|
|
24963
|
+
properties: {
|
|
24964
|
+
limit: { type: "number", description: "Max items to return (default 50, max 200)" }
|
|
24965
|
+
}
|
|
24966
|
+
},
|
|
24967
|
+
permission: "auto",
|
|
24968
|
+
mutating: false,
|
|
24969
|
+
async execute(input) {
|
|
24970
|
+
const limit = Math.min(Math.max(Number(input["limit"] ?? 50) || 50, 1), 200);
|
|
24971
|
+
const file = await refresh(inst);
|
|
24972
|
+
const open = file.items.filter(
|
|
24973
|
+
(it) => it.status === "pending" || it.status === "in_progress"
|
|
24974
|
+
);
|
|
24975
|
+
const items = open.slice(0, limit);
|
|
24976
|
+
if (items.length > 0) inst.pullCount += 1;
|
|
24977
|
+
return {
|
|
24978
|
+
ok: true,
|
|
24979
|
+
total: open.length,
|
|
24980
|
+
returned: items.length,
|
|
24981
|
+
truncated: open.length > items.length,
|
|
24982
|
+
items,
|
|
24983
|
+
hint: "These are persistent items. To work on them this session, register each one with the built-in `todo` tool. Mark them `completed` via todo_tracker_complete when done."
|
|
24984
|
+
};
|
|
24502
24985
|
}
|
|
24986
|
+
});
|
|
24987
|
+
register({
|
|
24988
|
+
name: "todo_tracker_status",
|
|
24989
|
+
description: "Report todo-tracker counters (per-status totals) + the file path + last update timestamp.",
|
|
24990
|
+
inputSchema: { type: "object", properties: {} },
|
|
24991
|
+
permission: "auto",
|
|
24992
|
+
mutating: false,
|
|
24993
|
+
async execute() {
|
|
24994
|
+
const file = await refresh(inst);
|
|
24995
|
+
const byStatus = {
|
|
24996
|
+
pending: 0,
|
|
24997
|
+
in_progress: 0,
|
|
24998
|
+
completed: 0,
|
|
24999
|
+
dropped: 0
|
|
25000
|
+
};
|
|
25001
|
+
for (const it of file.items) {
|
|
25002
|
+
if (STATUSES.includes(it.status)) byStatus[it.status] += 1;
|
|
25003
|
+
}
|
|
25004
|
+
return {
|
|
25005
|
+
ok: true,
|
|
25006
|
+
filePath: inst.filePath,
|
|
25007
|
+
projectSlug: inst.projectSlug,
|
|
25008
|
+
updatedAt: file.updatedAt,
|
|
25009
|
+
counters: byStatus,
|
|
25010
|
+
total: file.items.length,
|
|
25011
|
+
session: sessionCounts(inst),
|
|
25012
|
+
lastMutation: inst.lastMutation,
|
|
25013
|
+
readOnly: inst.readOnlyReason,
|
|
25014
|
+
degraded: inst.degradedReason
|
|
25015
|
+
};
|
|
25016
|
+
}
|
|
25017
|
+
});
|
|
25018
|
+
api.log.info("todo-tracker plugin loaded", {
|
|
25019
|
+
filePath: inst.filePath,
|
|
25020
|
+
projectSlug: inst.projectSlug,
|
|
25021
|
+
initialItemCount: inst.file.items.length,
|
|
25022
|
+
readOnly: inst.readOnlyReason,
|
|
25023
|
+
degraded: inst.degradedReason
|
|
25024
|
+
});
|
|
25025
|
+
},
|
|
25026
|
+
teardown(api) {
|
|
25027
|
+
const inst = disposeInstance(api);
|
|
25028
|
+
if (!inst) return;
|
|
25029
|
+
api.log.info("todo-tracker: teardown complete", { sessionCounts: sessionCounts(inst) });
|
|
25030
|
+
},
|
|
25031
|
+
async health() {
|
|
25032
|
+
const inst = latest;
|
|
25033
|
+
if (inst === null) {
|
|
24503
25034
|
return {
|
|
24504
|
-
ok:
|
|
24505
|
-
|
|
24506
|
-
items,
|
|
24507
|
-
hint: "These are persistent items. To work on them this session, register each one with the built-in `todo` tool. Mark them `completed` via todo_tracker_complete when done."
|
|
24508
|
-
};
|
|
24509
|
-
}
|
|
24510
|
-
});
|
|
24511
|
-
api.tools.register({
|
|
24512
|
-
name: "todo_tracker_status",
|
|
24513
|
-
description: "Report todo-tracker counters (per-status totals) + the file path + last update timestamp.",
|
|
24514
|
-
inputSchema: { type: "object", properties: {} },
|
|
24515
|
-
permission: "auto",
|
|
24516
|
-
mutating: false,
|
|
24517
|
-
async execute() {
|
|
24518
|
-
if (state56.filePath === null) return notConfiguredError();
|
|
24519
|
-
const file = ensureFile();
|
|
24520
|
-
const byStatus = {
|
|
24521
|
-
pending: 0,
|
|
24522
|
-
in_progress: 0,
|
|
24523
|
-
completed: 0,
|
|
24524
|
-
dropped: 0
|
|
24525
|
-
};
|
|
24526
|
-
for (const it of file.items) byStatus[it.status] += 1;
|
|
24527
|
-
return {
|
|
24528
|
-
ok: true,
|
|
24529
|
-
filePath: state56.filePath,
|
|
24530
|
-
projectSlug: state56.projectSlug,
|
|
24531
|
-
updatedAt: file.updatedAt,
|
|
24532
|
-
counters: byStatus,
|
|
24533
|
-
total: file.items.length,
|
|
24534
|
-
session: {
|
|
24535
|
-
add: state56.addCount,
|
|
24536
|
-
complete: state56.completeCount,
|
|
24537
|
-
drop: state56.dropCount,
|
|
24538
|
-
remove: state56.removeCount,
|
|
24539
|
-
pull: state56.pullCount
|
|
24540
|
-
},
|
|
24541
|
-
lastMutation: state56.lastMutation
|
|
25035
|
+
ok: false,
|
|
25036
|
+
message: "todo-tracker: no file path configured \u2014 tools will error"
|
|
24542
25037
|
};
|
|
24543
25038
|
}
|
|
24544
|
-
|
|
24545
|
-
api.log.info("todo-tracker plugin loaded", {
|
|
24546
|
-
filePath: state56.filePath,
|
|
24547
|
-
projectSlug: state56.projectSlug,
|
|
24548
|
-
initialItemCount: state56.file.items.length
|
|
24549
|
-
});
|
|
24550
|
-
},
|
|
24551
|
-
teardown(api) {
|
|
24552
|
-
const finalCounts = {
|
|
24553
|
-
add: state56.addCount,
|
|
24554
|
-
complete: state56.completeCount,
|
|
24555
|
-
drop: state56.dropCount,
|
|
24556
|
-
remove: state56.removeCount,
|
|
24557
|
-
pull: state56.pullCount
|
|
24558
|
-
};
|
|
24559
|
-
state56.addCount = 0;
|
|
24560
|
-
state56.completeCount = 0;
|
|
24561
|
-
state56.dropCount = 0;
|
|
24562
|
-
state56.removeCount = 0;
|
|
24563
|
-
state56.pullCount = 0;
|
|
24564
|
-
state56.lastMutation = null;
|
|
24565
|
-
state56.file = null;
|
|
24566
|
-
state56.filePath = null;
|
|
24567
|
-
state56.projectSlug = null;
|
|
24568
|
-
api.log.info("todo-tracker: teardown complete", { sessionCounts: finalCounts });
|
|
24569
|
-
},
|
|
24570
|
-
async health() {
|
|
24571
|
-
if (state56.filePath === null) {
|
|
25039
|
+
const reason = inst.readOnlyReason ?? inst.degradedReason;
|
|
24572
25040
|
return {
|
|
24573
|
-
ok:
|
|
24574
|
-
message:
|
|
25041
|
+
ok: reason === null,
|
|
25042
|
+
message: reason === null ? `todo-tracker: ${inst.file.items.length} item(s) at ${inst.filePath}` : `todo-tracker: ${inst.readOnlyReason ? "read-only" : "degraded"} \u2014 ${reason}`,
|
|
25043
|
+
filePath: inst.filePath,
|
|
25044
|
+
projectSlug: inst.projectSlug,
|
|
25045
|
+
total: inst.file.items.length,
|
|
25046
|
+
readOnly: inst.readOnlyReason,
|
|
25047
|
+
degraded: inst.degradedReason,
|
|
25048
|
+
sessionCounts: sessionCounts(inst),
|
|
25049
|
+
lastMutation: inst.lastMutation
|
|
24575
25050
|
};
|
|
24576
25051
|
}
|
|
24577
|
-
|
|
24578
|
-
|
|
24579
|
-
|
|
24580
|
-
|
|
24581
|
-
filePath: state56.filePath,
|
|
24582
|
-
projectSlug: state56.projectSlug,
|
|
24583
|
-
total: file.items.length,
|
|
24584
|
-
sessionCounts: {
|
|
24585
|
-
add: state56.addCount,
|
|
24586
|
-
complete: state56.completeCount,
|
|
24587
|
-
drop: state56.dropCount,
|
|
24588
|
-
remove: state56.removeCount,
|
|
24589
|
-
pull: state56.pullCount
|
|
24590
|
-
},
|
|
24591
|
-
lastMutation: state56.lastMutation
|
|
24592
|
-
};
|
|
24593
|
-
}
|
|
24594
|
-
};
|
|
25052
|
+
};
|
|
25053
|
+
return plugin65;
|
|
25054
|
+
}
|
|
25055
|
+
var plugin61 = createTodoTrackerPlugin();
|
|
24595
25056
|
var todo_tracker_default = plugin61;
|
|
24596
25057
|
|
|
24597
25058
|
// src/token-budget/index.ts
|
|
24598
25059
|
var API_VERSION40 = "^0.1.10";
|
|
24599
|
-
var
|
|
25060
|
+
var state56 = {
|
|
24600
25061
|
totalTokens: 0,
|
|
24601
25062
|
totalPromptTokens: 0,
|
|
24602
25063
|
totalCompletionTokens: 0,
|
|
@@ -24654,13 +25115,13 @@ function readConfig54(raw) {
|
|
|
24654
25115
|
}
|
|
24655
25116
|
function clearRegistrations3() {
|
|
24656
25117
|
for (const key of ["hookUnregister", "postHookUnregister"]) {
|
|
24657
|
-
const off =
|
|
25118
|
+
const off = state56[key];
|
|
24658
25119
|
if (!off) continue;
|
|
24659
25120
|
try {
|
|
24660
25121
|
off();
|
|
24661
25122
|
} catch {
|
|
24662
25123
|
}
|
|
24663
|
-
|
|
25124
|
+
state56[key] = null;
|
|
24664
25125
|
}
|
|
24665
25126
|
}
|
|
24666
25127
|
var plugin62 = {
|
|
@@ -24701,15 +25162,15 @@ var plugin62 = {
|
|
|
24701
25162
|
}
|
|
24702
25163
|
},
|
|
24703
25164
|
setup(api) {
|
|
24704
|
-
|
|
24705
|
-
|
|
24706
|
-
|
|
24707
|
-
|
|
24708
|
-
|
|
24709
|
-
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
|
|
25165
|
+
state56.totalTokens = 0;
|
|
25166
|
+
state56.totalPromptTokens = 0;
|
|
25167
|
+
state56.totalCompletionTokens = 0;
|
|
25168
|
+
state56.requestCount = 0;
|
|
25169
|
+
state56.warningFired = false;
|
|
25170
|
+
state56.stopFired = false;
|
|
25171
|
+
state56.warnContextInjected = false;
|
|
25172
|
+
state56.stopContextInjected = false;
|
|
25173
|
+
state56.lastRequest = null;
|
|
24713
25174
|
clearRegistrations3();
|
|
24714
25175
|
const cfg = readConfig54(api.config.extensions?.["token-budget"]);
|
|
24715
25176
|
api.onEvent("provider.response", (payload) => {
|
|
@@ -24722,24 +25183,26 @@ var plugin62 = {
|
|
|
24722
25183
|
if (!modelMatches(cfg.model, modelName)) return;
|
|
24723
25184
|
}
|
|
24724
25185
|
const rawUsage = usage;
|
|
24725
|
-
const
|
|
24726
|
-
const
|
|
25186
|
+
const promptTokensRaw = (typeof rawUsage["input"] === "number" ? rawUsage["input"] : void 0) ?? (typeof rawUsage["prompt_tokens"] === "number" ? rawUsage["prompt_tokens"] : void 0) ?? (typeof rawUsage["input_tokens"] === "number" ? rawUsage["input_tokens"] : void 0) ?? (typeof rawUsage["promptTokens"] === "number" ? rawUsage["promptTokens"] : 0);
|
|
25187
|
+
const promptTokens = Number.isFinite(promptTokensRaw) ? promptTokensRaw : 0;
|
|
25188
|
+
const completionTokensRaw = (typeof rawUsage["output"] === "number" ? rawUsage["output"] : void 0) ?? (typeof rawUsage["completion_tokens"] === "number" ? rawUsage["completion_tokens"] : void 0) ?? (typeof rawUsage["output_tokens"] === "number" ? rawUsage["output_tokens"] : void 0) ?? (typeof rawUsage["completionTokens"] === "number" ? rawUsage["completionTokens"] : 0);
|
|
25189
|
+
const completionTokens = Number.isFinite(completionTokensRaw) ? completionTokensRaw : 0;
|
|
24727
25190
|
const total = promptTokens + completionTokens;
|
|
24728
|
-
|
|
24729
|
-
|
|
24730
|
-
|
|
24731
|
-
|
|
24732
|
-
|
|
25191
|
+
state56.totalPromptTokens += promptTokens;
|
|
25192
|
+
state56.totalCompletionTokens += completionTokens;
|
|
25193
|
+
state56.totalTokens += total;
|
|
25194
|
+
state56.requestCount += 1;
|
|
25195
|
+
state56.lastRequest = {
|
|
24733
25196
|
model: modelName,
|
|
24734
25197
|
prompt: promptTokens,
|
|
24735
25198
|
completion: completionTokens,
|
|
24736
25199
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
24737
25200
|
};
|
|
24738
25201
|
if (cfg.limit <= 0) return;
|
|
24739
|
-
const percent =
|
|
24740
|
-
if (!
|
|
24741
|
-
|
|
24742
|
-
const remaining = cfg.limit -
|
|
25202
|
+
const percent = state56.totalTokens / cfg.limit * 100;
|
|
25203
|
+
if (!state56.warningFired && percent >= cfg.warnPercent) {
|
|
25204
|
+
state56.warningFired = true;
|
|
25205
|
+
const remaining = Math.max(cfg.limit - state56.totalTokens, 0);
|
|
24743
25206
|
api.log.info("token-budget: warning threshold reached", {
|
|
24744
25207
|
percent: Math.round(percent),
|
|
24745
25208
|
remaining
|
|
@@ -24747,45 +25210,46 @@ var plugin62 = {
|
|
|
24747
25210
|
api.emitCustom("token-budget:warning", {
|
|
24748
25211
|
percent: Math.round(percent),
|
|
24749
25212
|
remaining,
|
|
24750
|
-
total:
|
|
25213
|
+
total: state56.totalTokens,
|
|
24751
25214
|
limit: cfg.limit
|
|
24752
25215
|
});
|
|
24753
25216
|
}
|
|
24754
|
-
if (!
|
|
24755
|
-
|
|
25217
|
+
if (!state56.stopFired && percent >= cfg.stopPercent) {
|
|
25218
|
+
state56.stopFired = true;
|
|
24756
25219
|
api.log.warn("token-budget: hard limit reached \u2014 agent loop will be stopped", {
|
|
24757
|
-
total:
|
|
25220
|
+
total: state56.totalTokens,
|
|
24758
25221
|
limit: cfg.limit
|
|
24759
25222
|
});
|
|
24760
25223
|
api.emitCustom("token-budget:limit_reached", {
|
|
24761
|
-
total:
|
|
25224
|
+
total: state56.totalTokens,
|
|
24762
25225
|
limit: cfg.limit
|
|
24763
25226
|
});
|
|
24764
25227
|
}
|
|
24765
25228
|
});
|
|
24766
|
-
|
|
24767
|
-
if (cfg.limit <= 0 || !
|
|
25229
|
+
state56.hookUnregister = api.registerHook("Stop", void 0, () => {
|
|
25230
|
+
if (cfg.limit <= 0 || !state56.stopFired) return;
|
|
24768
25231
|
return {
|
|
24769
25232
|
decision: "block",
|
|
24770
|
-
reason: `token-budget: session token limit reached (${
|
|
25233
|
+
reason: `token-budget: session token limit reached (${state56.totalTokens.toLocaleString()} / ${cfg.limit.toLocaleString()} tokens). The budget is exhausted \u2014 wrap up the current task and summarize what was accomplished.`
|
|
24771
25234
|
};
|
|
24772
25235
|
});
|
|
24773
|
-
|
|
25236
|
+
state56.postHookUnregister = api.registerHook("PostToolUse", "*", () => {
|
|
24774
25237
|
if (cfg.limit <= 0) return;
|
|
24775
|
-
const percent = Math.round(
|
|
24776
|
-
const remaining = Math.max(cfg.limit -
|
|
24777
|
-
if (
|
|
24778
|
-
|
|
25238
|
+
const percent = Math.round(state56.totalTokens / cfg.limit * 100);
|
|
25239
|
+
const remaining = Math.max(cfg.limit - state56.totalTokens, 0);
|
|
25240
|
+
if (state56.stopFired && !state56.stopContextInjected) {
|
|
25241
|
+
state56.stopContextInjected = true;
|
|
25242
|
+
state56.warnContextInjected = true;
|
|
24779
25243
|
return {
|
|
24780
25244
|
additionalContext: `
|
|
24781
|
-
\u{1F6D1} token-budget: HARD LIMIT REACHED \u2014 ${
|
|
25245
|
+
\u{1F6D1} token-budget: HARD LIMIT REACHED \u2014 ${state56.totalTokens.toLocaleString()} / ${cfg.limit.toLocaleString()} tokens (${percent}%). You must stop here. Do NOT start any new task. Summarize what was accomplished and list any remaining work.`
|
|
24782
25246
|
};
|
|
24783
25247
|
}
|
|
24784
|
-
if (
|
|
24785
|
-
|
|
25248
|
+
if (state56.warningFired && !state56.warnContextInjected) {
|
|
25249
|
+
state56.warnContextInjected = true;
|
|
24786
25250
|
return {
|
|
24787
25251
|
additionalContext: `
|
|
24788
|
-
\u26A0\uFE0F token-budget: ${percent}% of budget used (${
|
|
25252
|
+
\u26A0\uFE0F token-budget: ${percent}% of budget used (${state56.totalTokens.toLocaleString()} / ${cfg.limit.toLocaleString()} tokens, ${remaining.toLocaleString()} remaining). Start wrapping up \u2014 prioritize finishing the current task over starting new ones.`
|
|
24789
25253
|
};
|
|
24790
25254
|
}
|
|
24791
25255
|
return;
|
|
@@ -24798,7 +25262,7 @@ var plugin62 = {
|
|
|
24798
25262
|
category: "Meta",
|
|
24799
25263
|
mutating: false,
|
|
24800
25264
|
async execute() {
|
|
24801
|
-
const consumed =
|
|
25265
|
+
const consumed = state56.totalTokens;
|
|
24802
25266
|
const limit = cfg.limit;
|
|
24803
25267
|
const percent = limit > 0 ? Math.round(consumed / limit * 100) : 0;
|
|
24804
25268
|
const remaining = limit > 0 ? Math.max(limit - consumed, 0) : Infinity;
|
|
@@ -24808,7 +25272,7 @@ var plugin62 = {
|
|
|
24808
25272
|
consumed,
|
|
24809
25273
|
remaining,
|
|
24810
25274
|
percent,
|
|
24811
|
-
requestCount:
|
|
25275
|
+
requestCount: state56.requestCount,
|
|
24812
25276
|
// The EFFECTIVE thresholds, after out-of-range values fall back to
|
|
24813
25277
|
// the defaults and warn/stop are ordered. Without these the user
|
|
24814
25278
|
// cannot tell that a rejected or clamped setting is not in force.
|
|
@@ -24816,12 +25280,12 @@ var plugin62 = {
|
|
|
24816
25280
|
stopPercent: cfg.stopPercent,
|
|
24817
25281
|
model: cfg.model === "" ? null : cfg.model,
|
|
24818
25282
|
breakdown: {
|
|
24819
|
-
prompt:
|
|
24820
|
-
completion:
|
|
25283
|
+
prompt: state56.totalPromptTokens,
|
|
25284
|
+
completion: state56.totalCompletionTokens
|
|
24821
25285
|
},
|
|
24822
|
-
warningFired:
|
|
24823
|
-
stopFired:
|
|
24824
|
-
lastRequest:
|
|
25286
|
+
warningFired: state56.warningFired,
|
|
25287
|
+
stopFired: state56.stopFired,
|
|
25288
|
+
lastRequest: state56.lastRequest
|
|
24825
25289
|
};
|
|
24826
25290
|
}
|
|
24827
25291
|
});
|
|
@@ -24835,30 +25299,30 @@ var plugin62 = {
|
|
|
24835
25299
|
teardown(api) {
|
|
24836
25300
|
clearRegistrations3();
|
|
24837
25301
|
const final = {
|
|
24838
|
-
totalTokens:
|
|
24839
|
-
requestCount:
|
|
24840
|
-
warningFired:
|
|
24841
|
-
stopFired:
|
|
25302
|
+
totalTokens: state56.totalTokens,
|
|
25303
|
+
requestCount: state56.requestCount,
|
|
25304
|
+
warningFired: state56.warningFired,
|
|
25305
|
+
stopFired: state56.stopFired
|
|
24842
25306
|
};
|
|
24843
|
-
|
|
24844
|
-
|
|
24845
|
-
|
|
24846
|
-
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
25307
|
+
state56.totalTokens = 0;
|
|
25308
|
+
state56.totalPromptTokens = 0;
|
|
25309
|
+
state56.totalCompletionTokens = 0;
|
|
25310
|
+
state56.requestCount = 0;
|
|
25311
|
+
state56.warningFired = false;
|
|
25312
|
+
state56.stopFired = false;
|
|
25313
|
+
state56.warnContextInjected = false;
|
|
25314
|
+
state56.stopContextInjected = false;
|
|
25315
|
+
state56.lastRequest = null;
|
|
24852
25316
|
api.log.info("token-budget: teardown complete", { final });
|
|
24853
25317
|
},
|
|
24854
25318
|
async health() {
|
|
24855
25319
|
return {
|
|
24856
25320
|
ok: true,
|
|
24857
|
-
message:
|
|
24858
|
-
totalTokens:
|
|
24859
|
-
requestCount:
|
|
24860
|
-
warningFired:
|
|
24861
|
-
stopFired:
|
|
25321
|
+
message: state56.lastRequest === null ? `token-budget: ${state56.totalTokens.toLocaleString()} tokens across ${state56.requestCount} request(s)` : `token-budget: ${state56.totalTokens.toLocaleString()} tokens, last ${state56.lastRequest.model} at ${state56.lastRequest.when}`,
|
|
25322
|
+
totalTokens: state56.totalTokens,
|
|
25323
|
+
requestCount: state56.requestCount,
|
|
25324
|
+
warningFired: state56.warningFired,
|
|
25325
|
+
stopFired: state56.stopFired
|
|
24862
25326
|
};
|
|
24863
25327
|
}
|
|
24864
25328
|
};
|
|
@@ -24905,7 +25369,7 @@ function computeThrottleDelay(entries, now, limit, projected) {
|
|
|
24905
25369
|
const newest = sorted[sorted.length - 1];
|
|
24906
25370
|
return newest ? Math.max(0, newest.at + WINDOW_MS - now) : 0;
|
|
24907
25371
|
}
|
|
24908
|
-
var
|
|
25372
|
+
var state57 = {
|
|
24909
25373
|
window: [],
|
|
24910
25374
|
invocations: 0,
|
|
24911
25375
|
throttled: 0,
|
|
@@ -24978,16 +25442,16 @@ var plugin63 = {
|
|
|
24978
25442
|
}
|
|
24979
25443
|
},
|
|
24980
25444
|
setup(api) {
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
|
|
24984
|
-
|
|
24985
|
-
if (
|
|
25445
|
+
state57.window = [];
|
|
25446
|
+
state57.invocations = 0;
|
|
25447
|
+
state57.throttled = 0;
|
|
25448
|
+
state57.totalDelayMs = 0;
|
|
25449
|
+
if (state57.extensionUnregister) {
|
|
24986
25450
|
try {
|
|
24987
|
-
|
|
25451
|
+
state57.extensionUnregister();
|
|
24988
25452
|
} catch {
|
|
24989
25453
|
}
|
|
24990
|
-
|
|
25454
|
+
state57.extensionUnregister = null;
|
|
24991
25455
|
}
|
|
24992
25456
|
const cfg = readConfig55(api.config.extensions?.["token-throttle"]);
|
|
24993
25457
|
if (cfg.enabled) {
|
|
@@ -24996,21 +25460,21 @@ var plugin63 = {
|
|
|
24996
25460
|
kind: "throttle",
|
|
24997
25461
|
wraps: ["request"]
|
|
24998
25462
|
});
|
|
24999
|
-
|
|
25463
|
+
state57.extensionUnregister = api.extensions.register({
|
|
25000
25464
|
name: "token-throttle",
|
|
25001
25465
|
owner: "token-throttle",
|
|
25002
25466
|
async wrapProviderRunner(_ctx, request, inner) {
|
|
25003
25467
|
const signal = _ctx?.signal;
|
|
25004
25468
|
const req = request ?? {};
|
|
25005
|
-
|
|
25469
|
+
state57.invocations += 1;
|
|
25006
25470
|
const now = Date.now();
|
|
25007
|
-
|
|
25471
|
+
state57.window = pruneWindow(state57.window, now);
|
|
25008
25472
|
const projected = estimateRequestTokens(req, cfg.charsPerToken);
|
|
25009
|
-
const rawDelay = computeThrottleDelay(
|
|
25473
|
+
const rawDelay = computeThrottleDelay(state57.window, now, cfg.tokensPerMinute, projected);
|
|
25010
25474
|
const delay = Math.min(rawDelay, cfg.maxDelayMs);
|
|
25011
25475
|
if (delay > 0) {
|
|
25012
|
-
|
|
25013
|
-
|
|
25476
|
+
state57.throttled += 1;
|
|
25477
|
+
state57.totalDelayMs += delay;
|
|
25014
25478
|
api.metrics.counter("throttled");
|
|
25015
25479
|
api.metrics.histogram("delay_ms", delay);
|
|
25016
25480
|
api.log.info("token-throttle: delaying provider call", { delayMs: delay, projected });
|
|
@@ -25018,11 +25482,14 @@ var plugin63 = {
|
|
|
25018
25482
|
}
|
|
25019
25483
|
const response = await inner(_ctx, request);
|
|
25020
25484
|
const rawUsage = response?.usage;
|
|
25021
|
-
const
|
|
25022
|
-
const
|
|
25023
|
-
const
|
|
25485
|
+
const inputTokensRaw = (typeof rawUsage?.["input"] === "number" ? rawUsage["input"] : void 0) ?? (typeof rawUsage?.["prompt_tokens"] === "number" ? rawUsage["prompt_tokens"] : void 0) ?? (typeof rawUsage?.["input_tokens"] === "number" ? rawUsage["input_tokens"] : void 0) ?? (typeof rawUsage?.["promptTokens"] === "number" ? rawUsage["promptTokens"] : void 0) ?? (typeof rawUsage?.["inputTokens"] === "number" ? rawUsage["inputTokens"] : 0);
|
|
25486
|
+
const inputTokens = Number.isFinite(inputTokensRaw) ? inputTokensRaw : 0;
|
|
25487
|
+
const outputTokensRaw = (typeof rawUsage?.["output"] === "number" ? rawUsage["output"] : void 0) ?? (typeof rawUsage?.["completion_tokens"] === "number" ? rawUsage["completion_tokens"] : void 0) ?? (typeof rawUsage?.["output_tokens"] === "number" ? rawUsage["output_tokens"] : void 0) ?? (typeof rawUsage?.["completionTokens"] === "number" ? rawUsage["completionTokens"] : void 0) ?? (typeof rawUsage?.["outputTokens"] === "number" ? rawUsage["outputTokens"] : 0);
|
|
25488
|
+
const outputTokens = Number.isFinite(outputTokensRaw) ? outputTokensRaw : 0;
|
|
25489
|
+
const totalTokensRaw = (typeof rawUsage?.["total_tokens"] === "number" ? rawUsage["total_tokens"] : void 0) ?? (typeof rawUsage?.["totalTokens"] === "number" ? rawUsage["totalTokens"] : void 0) ?? (typeof rawUsage?.["total"] === "number" ? rawUsage["total"] : void 0) ?? inputTokens + outputTokens;
|
|
25490
|
+
const totalTokens = Number.isFinite(totalTokensRaw) ? totalTokensRaw : 0;
|
|
25024
25491
|
const used = totalTokens > 0 ? totalTokens : projected;
|
|
25025
|
-
|
|
25492
|
+
state57.window.push({ at: Date.now(), tokens: used });
|
|
25026
25493
|
return response;
|
|
25027
25494
|
}
|
|
25028
25495
|
});
|
|
@@ -25036,7 +25503,7 @@ var plugin63 = {
|
|
|
25036
25503
|
mutating: false,
|
|
25037
25504
|
async execute() {
|
|
25038
25505
|
const now = Date.now();
|
|
25039
|
-
const live = pruneWindow(
|
|
25506
|
+
const live = pruneWindow(state57.window, now);
|
|
25040
25507
|
return {
|
|
25041
25508
|
ok: true,
|
|
25042
25509
|
enabled: cfg.enabled,
|
|
@@ -25045,9 +25512,9 @@ var plugin63 = {
|
|
|
25045
25512
|
windowSpend: windowSpend(live),
|
|
25046
25513
|
windowEntries: live.length,
|
|
25047
25514
|
counters: {
|
|
25048
|
-
invocations:
|
|
25049
|
-
throttled:
|
|
25050
|
-
totalDelayMs:
|
|
25515
|
+
invocations: state57.invocations,
|
|
25516
|
+
throttled: state57.throttled,
|
|
25517
|
+
totalDelayMs: state57.totalDelayMs
|
|
25051
25518
|
}
|
|
25052
25519
|
};
|
|
25053
25520
|
}
|
|
@@ -25059,32 +25526,32 @@ var plugin63 = {
|
|
|
25059
25526
|
});
|
|
25060
25527
|
},
|
|
25061
25528
|
teardown(api) {
|
|
25062
|
-
if (
|
|
25529
|
+
if (state57.extensionUnregister) {
|
|
25063
25530
|
try {
|
|
25064
|
-
|
|
25531
|
+
state57.extensionUnregister();
|
|
25065
25532
|
} catch {
|
|
25066
25533
|
}
|
|
25067
|
-
|
|
25534
|
+
state57.extensionUnregister = null;
|
|
25068
25535
|
}
|
|
25069
25536
|
const final = {
|
|
25070
|
-
invocations:
|
|
25071
|
-
throttled:
|
|
25072
|
-
totalDelayMs:
|
|
25537
|
+
invocations: state57.invocations,
|
|
25538
|
+
throttled: state57.throttled,
|
|
25539
|
+
totalDelayMs: state57.totalDelayMs
|
|
25073
25540
|
};
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
|
|
25077
|
-
|
|
25541
|
+
state57.window = [];
|
|
25542
|
+
state57.invocations = 0;
|
|
25543
|
+
state57.throttled = 0;
|
|
25544
|
+
state57.totalDelayMs = 0;
|
|
25078
25545
|
api.log.info("token-throttle: teardown complete", { final });
|
|
25079
25546
|
},
|
|
25080
25547
|
async health() {
|
|
25081
25548
|
return {
|
|
25082
25549
|
ok: true,
|
|
25083
|
-
message: `token-throttle: ${
|
|
25550
|
+
message: `token-throttle: ${state57.throttled} throttle(s) of ${state57.invocations} call(s), ${state57.totalDelayMs}ms total delay`,
|
|
25084
25551
|
counters: {
|
|
25085
|
-
invocations:
|
|
25086
|
-
throttled:
|
|
25087
|
-
totalDelayMs:
|
|
25552
|
+
invocations: state57.invocations,
|
|
25553
|
+
throttled: state57.throttled,
|
|
25554
|
+
totalDelayMs: state57.totalDelayMs
|
|
25088
25555
|
}
|
|
25089
25556
|
};
|
|
25090
25557
|
}
|
|
@@ -25094,7 +25561,7 @@ var token_throttle_default = plugin63;
|
|
|
25094
25561
|
// src/type-gate/index.ts
|
|
25095
25562
|
import { existsSync as existsSync8 } from "node:fs";
|
|
25096
25563
|
var API_VERSION41 = "^0.1.10";
|
|
25097
|
-
var
|
|
25564
|
+
var state58 = {
|
|
25098
25565
|
invocationCount: 0,
|
|
25099
25566
|
runCount: 0,
|
|
25100
25567
|
passCount: 0,
|
|
@@ -25260,14 +25727,14 @@ var plugin64 = {
|
|
|
25260
25727
|
}
|
|
25261
25728
|
},
|
|
25262
25729
|
setup(api) {
|
|
25263
|
-
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
25268
|
-
|
|
25269
|
-
|
|
25270
|
-
|
|
25730
|
+
state58.invocationCount = 0;
|
|
25731
|
+
state58.runCount = 0;
|
|
25732
|
+
state58.passCount = 0;
|
|
25733
|
+
state58.failCount = 0;
|
|
25734
|
+
state58.errorCount = 0;
|
|
25735
|
+
state58.skippedCount = 0;
|
|
25736
|
+
state58.lastResult = null;
|
|
25737
|
+
state58.hookUnregister = (0, runtime_exports.releaseHandle)(state58.hookUnregister);
|
|
25271
25738
|
const cfg = readConfig56(api.config.extensions?.["type-gate"]);
|
|
25272
25739
|
const hook = async (input) => {
|
|
25273
25740
|
if (!cfg.enabled) return;
|
|
@@ -25279,27 +25746,27 @@ var plugin64 = {
|
|
|
25279
25746
|
if (!(0, runtime_exports.withinProject)(sourcePath)) return;
|
|
25280
25747
|
const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
|
|
25281
25748
|
if (!runOnChangeSet2.has(ext)) {
|
|
25282
|
-
|
|
25749
|
+
state58.skippedCount += 1;
|
|
25283
25750
|
return;
|
|
25284
25751
|
}
|
|
25285
|
-
|
|
25752
|
+
state58.invocationCount += 1;
|
|
25286
25753
|
const result = await runTypeCheck(cfg);
|
|
25287
25754
|
if (!result) {
|
|
25288
|
-
|
|
25755
|
+
state58.errorCount += 1;
|
|
25289
25756
|
return;
|
|
25290
25757
|
}
|
|
25291
|
-
|
|
25292
|
-
|
|
25758
|
+
state58.runCount += 1;
|
|
25759
|
+
state58.lastResult = {
|
|
25293
25760
|
passed: result.passed,
|
|
25294
25761
|
errorCount: result.errorCount,
|
|
25295
25762
|
durationMs: result.durationMs,
|
|
25296
25763
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
25297
25764
|
};
|
|
25298
25765
|
if (result.passed) {
|
|
25299
|
-
|
|
25766
|
+
state58.passCount += 1;
|
|
25300
25767
|
return;
|
|
25301
25768
|
}
|
|
25302
|
-
|
|
25769
|
+
state58.failCount += 1;
|
|
25303
25770
|
const errorList = result.errors.map((e) => ` \u274C ${e}`).join("\n");
|
|
25304
25771
|
const message = `
|
|
25305
25772
|
\u274C type-gate: Type check failed after editing ${sourcePath} (${result.durationMs}ms).
|
|
@@ -25314,7 +25781,7 @@ Fix the type error(s) or adjust the change.`;
|
|
|
25314
25781
|
}
|
|
25315
25782
|
return { additionalContext: message };
|
|
25316
25783
|
};
|
|
25317
|
-
|
|
25784
|
+
state58.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
|
|
25318
25785
|
background: true
|
|
25319
25786
|
});
|
|
25320
25787
|
api.tools.register({
|
|
@@ -25334,14 +25801,14 @@ Fix the type error(s) or adjust the change.`;
|
|
|
25334
25801
|
maxErrors: cfg.maxErrors,
|
|
25335
25802
|
runOnChange: cfg.runOnChange,
|
|
25336
25803
|
counters: {
|
|
25337
|
-
invocations:
|
|
25338
|
-
runs:
|
|
25339
|
-
passed:
|
|
25340
|
-
failed:
|
|
25341
|
-
errors:
|
|
25342
|
-
skipped:
|
|
25804
|
+
invocations: state58.invocationCount,
|
|
25805
|
+
runs: state58.runCount,
|
|
25806
|
+
passed: state58.passCount,
|
|
25807
|
+
failed: state58.failCount,
|
|
25808
|
+
errors: state58.errorCount,
|
|
25809
|
+
skipped: state58.skippedCount
|
|
25343
25810
|
},
|
|
25344
|
-
lastResult:
|
|
25811
|
+
lastResult: state58.lastResult
|
|
25345
25812
|
};
|
|
25346
25813
|
}
|
|
25347
25814
|
});
|
|
@@ -25353,43 +25820,43 @@ Fix the type error(s) or adjust the change.`;
|
|
|
25353
25820
|
});
|
|
25354
25821
|
},
|
|
25355
25822
|
teardown(api) {
|
|
25356
|
-
if (
|
|
25823
|
+
if (state58.hookUnregister) {
|
|
25357
25824
|
try {
|
|
25358
|
-
|
|
25825
|
+
state58.hookUnregister();
|
|
25359
25826
|
} catch {
|
|
25360
25827
|
}
|
|
25361
|
-
|
|
25828
|
+
state58.hookUnregister = null;
|
|
25362
25829
|
}
|
|
25363
25830
|
const final = {
|
|
25364
|
-
invocations:
|
|
25365
|
-
runs:
|
|
25366
|
-
passed:
|
|
25367
|
-
failed:
|
|
25368
|
-
errors:
|
|
25369
|
-
skipped:
|
|
25831
|
+
invocations: state58.invocationCount,
|
|
25832
|
+
runs: state58.runCount,
|
|
25833
|
+
passed: state58.passCount,
|
|
25834
|
+
failed: state58.failCount,
|
|
25835
|
+
errors: state58.errorCount,
|
|
25836
|
+
skipped: state58.skippedCount
|
|
25370
25837
|
};
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25838
|
+
state58.invocationCount = 0;
|
|
25839
|
+
state58.runCount = 0;
|
|
25840
|
+
state58.passCount = 0;
|
|
25841
|
+
state58.failCount = 0;
|
|
25842
|
+
state58.errorCount = 0;
|
|
25843
|
+
state58.skippedCount = 0;
|
|
25844
|
+
state58.lastResult = null;
|
|
25378
25845
|
api.log.info("type-gate: teardown complete", { final });
|
|
25379
25846
|
},
|
|
25380
25847
|
async health() {
|
|
25381
25848
|
return {
|
|
25382
25849
|
ok: true,
|
|
25383
|
-
message:
|
|
25850
|
+
message: state58.lastResult ? `type-gate: ${state58.runCount} run(s), last ${state58.lastResult.passed ? "PASSED" : "FAILED"} (${state58.lastResult.errorCount} errors)` : `type-gate: ${state58.invocationCount} invocation(s), ${state58.runCount} run(s)`,
|
|
25384
25851
|
counters: {
|
|
25385
|
-
invocations:
|
|
25386
|
-
runs:
|
|
25387
|
-
passed:
|
|
25388
|
-
failed:
|
|
25389
|
-
errors:
|
|
25390
|
-
skipped:
|
|
25391
|
-
},
|
|
25392
|
-
lastResult:
|
|
25852
|
+
invocations: state58.invocationCount,
|
|
25853
|
+
runs: state58.runCount,
|
|
25854
|
+
passed: state58.passCount,
|
|
25855
|
+
failed: state58.failCount,
|
|
25856
|
+
errors: state58.errorCount,
|
|
25857
|
+
skipped: state58.skippedCount
|
|
25858
|
+
},
|
|
25859
|
+
lastResult: state58.lastResult
|
|
25393
25860
|
};
|
|
25394
25861
|
}
|
|
25395
25862
|
};
|