@wrongstack/plugins 1.0.8 → 1.0.10
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 +1534 -1080
- 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/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 +12 -11
- 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);
|
|
@@ -12839,6 +12963,7 @@ var loop_breaker_default = plugin35;
|
|
|
12839
12963
|
|
|
12840
12964
|
// src/migration-planner/index.ts
|
|
12841
12965
|
import { existsSync as existsSync5, readFileSync as readFileSync10 } from "node:fs";
|
|
12966
|
+
import { ToolValidationError as ToolValidationError17 } from "@wrongstack/core/types";
|
|
12842
12967
|
|
|
12843
12968
|
// src/runtime/llm.ts
|
|
12844
12969
|
import {
|
|
@@ -13107,14 +13232,14 @@ var plugin36 = {
|
|
|
13107
13232
|
const rawPath = inp["path"] ?? inp["TargetFile"] ?? inp["filePath"] ?? inp["targetFile"] ?? inp["file_path"] ?? inp["file"];
|
|
13108
13233
|
const path = typeof rawPath === "string" ? rawPath : void 0;
|
|
13109
13234
|
if (!path) return;
|
|
13110
|
-
const
|
|
13235
|
+
const basename8 = path.split(/[/\\]/).pop() ?? "";
|
|
13111
13236
|
if (!/^(package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?)$/i.test(
|
|
13112
|
-
|
|
13237
|
+
basename8
|
|
13113
13238
|
)) {
|
|
13114
13239
|
return;
|
|
13115
13240
|
}
|
|
13116
13241
|
return {
|
|
13117
|
-
additionalContext: `Manifest file ${
|
|
13242
|
+
additionalContext: `Manifest file ${basename8} changed. Consider running migration_plan if a dependency version was updated.`
|
|
13118
13243
|
};
|
|
13119
13244
|
};
|
|
13120
13245
|
state33.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
|
|
@@ -13212,7 +13337,7 @@ var plugin36 = {
|
|
|
13212
13337
|
category: "Planning",
|
|
13213
13338
|
mutating: false,
|
|
13214
13339
|
async execute(input, _ctx, execOpts) {
|
|
13215
|
-
if (!cfg.enabled)
|
|
13340
|
+
if (!cfg.enabled) throw new Error("migration-planner is disabled");
|
|
13216
13341
|
execOpts?.signal?.throwIfAborted();
|
|
13217
13342
|
const raw = input ?? {};
|
|
13218
13343
|
const rawPackage = input.packageName || raw["package"] || raw["pkg"] || raw["name"] || raw["package_name"] || raw["packageName"] || raw["dependency"] || raw["dep"] || raw["module"];
|
|
@@ -13223,7 +13348,10 @@ var plugin36 = {
|
|
|
13223
13348
|
const fromVersion = String(rawFrom ?? "").trim();
|
|
13224
13349
|
const toVersion = String(rawTo ?? "").trim();
|
|
13225
13350
|
if (!packageName || !fromVersion || !toVersion) {
|
|
13226
|
-
|
|
13351
|
+
throw new ToolValidationError17({
|
|
13352
|
+
message: "packageName, fromVersion, and toVersion are required",
|
|
13353
|
+
field: !packageName ? "packageName" : !fromVersion ? "fromVersion" : "toVersion"
|
|
13354
|
+
});
|
|
13227
13355
|
}
|
|
13228
13356
|
const changelog = readChangelog(packageName, cfg);
|
|
13229
13357
|
let breakingChanges;
|
|
@@ -13593,6 +13721,7 @@ var model_router_default = plugin37;
|
|
|
13593
13721
|
|
|
13594
13722
|
// src/notify-hub/index.ts
|
|
13595
13723
|
import { lookup } from "node:dns/promises";
|
|
13724
|
+
import { ToolValidationError as ToolValidationError18 } from "@wrongstack/core/types";
|
|
13596
13725
|
|
|
13597
13726
|
// src/notify-hub/webhook-channel.ts
|
|
13598
13727
|
function freshCircuit() {
|
|
@@ -13995,18 +14124,19 @@ var plugin38 = {
|
|
|
13995
14124
|
category: "Notifications",
|
|
13996
14125
|
mutating: true,
|
|
13997
14126
|
async execute(input) {
|
|
13998
|
-
if (!cfg.enabled)
|
|
14127
|
+
if (!cfg.enabled) throw new Error("notify-hub is disabled");
|
|
13999
14128
|
const ch = state35.channel;
|
|
14000
14129
|
if (!ch) {
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
};
|
|
14130
|
+
throw new Error(
|
|
14131
|
+
'no webhookUrl configured \u2014 set config.extensions["notify-hub"].webhookUrl to enable deliveries'
|
|
14132
|
+
);
|
|
14005
14133
|
}
|
|
14006
14134
|
const inp = input ?? {};
|
|
14007
14135
|
const rawMsg = inp["message"] ?? inp["body"] ?? inp["text"] ?? inp["content"] ?? inp["msg"];
|
|
14008
14136
|
const message = typeof rawMsg === "string" && rawMsg.trim().length > 0 ? rawMsg.trim() : "";
|
|
14009
|
-
if (!message)
|
|
14137
|
+
if (!message) {
|
|
14138
|
+
throw new ToolValidationError18({ message: "message is required", field: "message" });
|
|
14139
|
+
}
|
|
14010
14140
|
const rawTitle = inp["title"] ?? inp["subject"] ?? inp["header"];
|
|
14011
14141
|
const title = typeof rawTitle === "string" && rawTitle.trim() ? rawTitle.trim() : "WrongStack notification";
|
|
14012
14142
|
const result = await deliverViaChannel(ch, "manual", {
|
|
@@ -14015,11 +14145,10 @@ var plugin38 = {
|
|
|
14015
14145
|
level: input.level === "warning" || input.level === "critical" ? input.level : "info",
|
|
14016
14146
|
source: "manual"
|
|
14017
14147
|
});
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
};
|
|
14148
|
+
if (!result.ok) {
|
|
14149
|
+
throw new Error(`notification delivery failed: ${result.error ?? "unknown error"}`);
|
|
14150
|
+
}
|
|
14151
|
+
return { ok: true, circuitOpen: ch.circuitStatus().open };
|
|
14023
14152
|
}
|
|
14024
14153
|
});
|
|
14025
14154
|
api.tools.register({
|
|
@@ -14238,8 +14367,8 @@ function isRootPathScope(path) {
|
|
|
14238
14367
|
function isDirectoryAmbiguousPath(path) {
|
|
14239
14368
|
const normalized = normalizePath2(path).replace(/\/$/, "");
|
|
14240
14369
|
if (isRootPathScope(normalized)) return true;
|
|
14241
|
-
const
|
|
14242
|
-
return path.endsWith("/") ||
|
|
14370
|
+
const basename8 = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
14371
|
+
return path.endsWith("/") || basename8.length > 0 && !basename8.includes(".");
|
|
14243
14372
|
}
|
|
14244
14373
|
function hasConfiguredProtectedDescendant(path, patterns) {
|
|
14245
14374
|
const normalized = normalizePath2(path).replace(/\/$/, "").toLowerCase();
|
|
@@ -15516,9 +15645,9 @@ var plugin39 = {
|
|
|
15516
15645
|
} catch {
|
|
15517
15646
|
}
|
|
15518
15647
|
}
|
|
15519
|
-
const
|
|
15520
|
-
states.set(api,
|
|
15521
|
-
latestState =
|
|
15648
|
+
const state59 = createState();
|
|
15649
|
+
states.set(api, state59);
|
|
15650
|
+
latestState = state59;
|
|
15522
15651
|
const cfg = readConfig34(api.config.extensions?.["path-guard"]);
|
|
15523
15652
|
const protectRes = cfg.protect.map(compilePathGlob);
|
|
15524
15653
|
const allowRes = cfg.allow.map(compilePathGlob);
|
|
@@ -15526,15 +15655,15 @@ var plugin39 = {
|
|
|
15526
15655
|
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
15656
|
const matchContext = isScope ? 'its unresolved scope overlaps config.extensions["path-guard"].protect' : 'matched by config.extensions["path-guard"].protect';
|
|
15528
15657
|
if (cfg.mode === "block") {
|
|
15529
|
-
|
|
15530
|
-
|
|
15658
|
+
state59.blocks += 1;
|
|
15659
|
+
state59.lastBlock = { path, tool, when: (/* @__PURE__ */ new Date()).toISOString() };
|
|
15531
15660
|
api.metrics.counter("blocks");
|
|
15532
15661
|
return {
|
|
15533
15662
|
decision: "block",
|
|
15534
15663
|
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
15664
|
};
|
|
15536
15665
|
}
|
|
15537
|
-
|
|
15666
|
+
state59.warns += 1;
|
|
15538
15667
|
api.metrics.counter("warns");
|
|
15539
15668
|
return {
|
|
15540
15669
|
decision: "allow",
|
|
@@ -15542,12 +15671,12 @@ var plugin39 = {
|
|
|
15542
15671
|
};
|
|
15543
15672
|
};
|
|
15544
15673
|
const bumpRedos = () => {
|
|
15545
|
-
|
|
15674
|
+
state59.redosTimeouts += 1;
|
|
15546
15675
|
api.metrics.counter("redos_timeouts");
|
|
15547
15676
|
};
|
|
15548
15677
|
const hook = async (input) => {
|
|
15549
15678
|
if (!cfg.enabled) return;
|
|
15550
|
-
|
|
15679
|
+
state59.invocations += 1;
|
|
15551
15680
|
const toolName = input.toolName ?? "";
|
|
15552
15681
|
const ti = input.toolInput ?? {};
|
|
15553
15682
|
const command = typeof ti["command"] === "string" ? ti["command"] : "";
|
|
@@ -15633,7 +15762,7 @@ var plugin39 = {
|
|
|
15633
15762
|
}
|
|
15634
15763
|
return;
|
|
15635
15764
|
};
|
|
15636
|
-
|
|
15765
|
+
state59.hookUnregister = api.registerHook("PreToolUse", "*", hook, {
|
|
15637
15766
|
name: "path-guard",
|
|
15638
15767
|
stage: "validate",
|
|
15639
15768
|
failurePolicy: "closed",
|
|
@@ -15654,12 +15783,12 @@ var plugin39 = {
|
|
|
15654
15783
|
protect: cfg.protect,
|
|
15655
15784
|
allow: cfg.allow,
|
|
15656
15785
|
counters: {
|
|
15657
|
-
invocations:
|
|
15658
|
-
blocks:
|
|
15659
|
-
warns:
|
|
15660
|
-
redosTimeouts:
|
|
15786
|
+
invocations: state59.invocations,
|
|
15787
|
+
blocks: state59.blocks,
|
|
15788
|
+
warns: state59.warns,
|
|
15789
|
+
redosTimeouts: state59.redosTimeouts
|
|
15661
15790
|
},
|
|
15662
|
-
lastBlock:
|
|
15791
|
+
lastBlock: state59.lastBlock
|
|
15663
15792
|
};
|
|
15664
15793
|
}
|
|
15665
15794
|
});
|
|
@@ -15671,39 +15800,39 @@ var plugin39 = {
|
|
|
15671
15800
|
});
|
|
15672
15801
|
},
|
|
15673
15802
|
teardown(api) {
|
|
15674
|
-
const
|
|
15675
|
-
if (!
|
|
15676
|
-
if (
|
|
15803
|
+
const state59 = states.get(api);
|
|
15804
|
+
if (!state59) return;
|
|
15805
|
+
if (state59.hookUnregister) {
|
|
15677
15806
|
try {
|
|
15678
|
-
|
|
15807
|
+
state59.hookUnregister();
|
|
15679
15808
|
} catch {
|
|
15680
15809
|
}
|
|
15681
|
-
|
|
15810
|
+
state59.hookUnregister = null;
|
|
15682
15811
|
}
|
|
15683
15812
|
const final = {
|
|
15684
|
-
invocations:
|
|
15685
|
-
blocks:
|
|
15686
|
-
warns:
|
|
15687
|
-
redosTimeouts:
|
|
15813
|
+
invocations: state59.invocations,
|
|
15814
|
+
blocks: state59.blocks,
|
|
15815
|
+
warns: state59.warns,
|
|
15816
|
+
redosTimeouts: state59.redosTimeouts
|
|
15688
15817
|
};
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15818
|
+
state59.invocations = 0;
|
|
15819
|
+
state59.blocks = 0;
|
|
15820
|
+
state59.warns = 0;
|
|
15821
|
+
state59.redosTimeouts = 0;
|
|
15822
|
+
state59.lastBlock = null;
|
|
15694
15823
|
states.delete(api);
|
|
15695
15824
|
api.log.info("path-guard: teardown complete", { final });
|
|
15696
15825
|
},
|
|
15697
15826
|
async health() {
|
|
15698
|
-
const
|
|
15827
|
+
const state59 = latestState;
|
|
15699
15828
|
return {
|
|
15700
15829
|
ok: true,
|
|
15701
|
-
message:
|
|
15830
|
+
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
15831
|
counters: {
|
|
15703
|
-
invocations:
|
|
15704
|
-
blocks:
|
|
15705
|
-
warns:
|
|
15706
|
-
redosTimeouts:
|
|
15832
|
+
invocations: state59.invocations,
|
|
15833
|
+
blocks: state59.blocks,
|
|
15834
|
+
warns: state59.warns,
|
|
15835
|
+
redosTimeouts: state59.redosTimeouts
|
|
15707
15836
|
}
|
|
15708
15837
|
};
|
|
15709
15838
|
}
|
|
@@ -15713,6 +15842,7 @@ var path_guard_default = plugin39;
|
|
|
15713
15842
|
// src/performance-regression-gate/index.ts
|
|
15714
15843
|
import { existsSync as existsSync7, readFileSync as readFileSync11 } from "node:fs";
|
|
15715
15844
|
import { isAbsolute as isAbsolute16, relative as relative16, resolve as resolve18 } from "node:path";
|
|
15845
|
+
import { ToolValidationError as ToolValidationError19 } from "@wrongstack/core/types";
|
|
15716
15846
|
var API_VERSION26 = "^0.1.10";
|
|
15717
15847
|
var state36 = {
|
|
15718
15848
|
invocationCount: 0,
|
|
@@ -15779,10 +15909,11 @@ function flattenResults(results) {
|
|
|
15779
15909
|
function loadResults(path) {
|
|
15780
15910
|
if (!path || !existsSync7(path)) return null;
|
|
15781
15911
|
try {
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15912
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
15913
|
+
} catch (err) {
|
|
15914
|
+
throw new Error(`Could not read benchmark results at ${path}: ${String(err)}`, {
|
|
15915
|
+
cause: err
|
|
15916
|
+
});
|
|
15786
15917
|
}
|
|
15787
15918
|
}
|
|
15788
15919
|
function stripVariantSuffix(name) {
|
|
@@ -15909,7 +16040,7 @@ var plugin40 = {
|
|
|
15909
16040
|
mutating: false,
|
|
15910
16041
|
async execute(input) {
|
|
15911
16042
|
if (!cfg.enabled) {
|
|
15912
|
-
|
|
16043
|
+
throw new Error("performance-regression-gate is disabled");
|
|
15913
16044
|
}
|
|
15914
16045
|
state36.invocationCount += 1;
|
|
15915
16046
|
const raw = input ?? {};
|
|
@@ -15920,11 +16051,23 @@ var plugin40 = {
|
|
|
15920
16051
|
const resultsPath = resolveProjectPath6(resultsPathStr) ?? "";
|
|
15921
16052
|
if (!resultsPath) {
|
|
15922
16053
|
state36.errorCount += 1;
|
|
15923
|
-
|
|
16054
|
+
throw new ToolValidationError19({
|
|
16055
|
+
message: "invalid results path (must be inside project)",
|
|
16056
|
+
field: "resultsPath"
|
|
16057
|
+
});
|
|
16058
|
+
}
|
|
16059
|
+
let results;
|
|
16060
|
+
try {
|
|
16061
|
+
results = loadResults(resultsPath);
|
|
16062
|
+
} catch (err) {
|
|
16063
|
+
state36.errorCount += 1;
|
|
16064
|
+
throw err;
|
|
15924
16065
|
}
|
|
15925
|
-
const results = loadResults(resultsPath);
|
|
15926
16066
|
if (!results) {
|
|
15927
16067
|
state36.missingResultsCount += 1;
|
|
16068
|
+
if (resultsPathStr !== "bench-results.json" || rawResultsPath !== "bench-results.json") {
|
|
16069
|
+
throw new Error(`No benchmark results found at ${resultsPathStr}.`);
|
|
16070
|
+
}
|
|
15928
16071
|
return {
|
|
15929
16072
|
ok: true,
|
|
15930
16073
|
hasResults: false,
|
|
@@ -15953,15 +16096,21 @@ var plugin40 = {
|
|
|
15953
16096
|
const baselineResolved = resolveProjectPath6(baselinePathStr) ?? "";
|
|
15954
16097
|
if (!baselineResolved) {
|
|
15955
16098
|
state36.errorCount += 1;
|
|
15956
|
-
|
|
16099
|
+
throw new ToolValidationError19({
|
|
16100
|
+
message: "invalid baseline path (must be inside project)",
|
|
16101
|
+
field: "baselinePath"
|
|
16102
|
+
});
|
|
16103
|
+
}
|
|
16104
|
+
let baselineResults;
|
|
16105
|
+
try {
|
|
16106
|
+
baselineResults = loadResults(baselineResolved);
|
|
16107
|
+
} catch (err) {
|
|
16108
|
+
state36.errorCount += 1;
|
|
16109
|
+
throw err;
|
|
15957
16110
|
}
|
|
15958
|
-
const baselineResults = loadResults(baselineResolved);
|
|
15959
16111
|
if (!baselineResults) {
|
|
15960
16112
|
state36.errorCount += 1;
|
|
15961
|
-
|
|
15962
|
-
ok: false,
|
|
15963
|
-
error: `Could not read baseline results at ${baselinePathStr}.`
|
|
15964
|
-
};
|
|
16113
|
+
throw new Error(`Could not read baseline results at ${baselinePathStr}.`);
|
|
15965
16114
|
}
|
|
15966
16115
|
const baseline = flattenResults(baselineResults);
|
|
15967
16116
|
pairs = pairCrossFile(baseline, current);
|
|
@@ -16176,6 +16325,7 @@ var plugin_stack_observer_default = PLUGIN;
|
|
|
16176
16325
|
import { execFile as execFile9 } from "node:child_process";
|
|
16177
16326
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
|
|
16178
16327
|
import { dirname as dirname7, isAbsolute as isAbsolute17, relative as relative17, resolve as resolve19 } from "node:path";
|
|
16328
|
+
import { ToolValidationError as ToolValidationError20 } from "@wrongstack/core/types";
|
|
16179
16329
|
var API_VERSION27 = "^0.1.10";
|
|
16180
16330
|
var state38 = {
|
|
16181
16331
|
commits: [],
|
|
@@ -16188,8 +16338,23 @@ var state38 = {
|
|
|
16188
16338
|
draftErrors: 0,
|
|
16189
16339
|
stopInvocations: 0,
|
|
16190
16340
|
stopHookUnregister: null,
|
|
16341
|
+
postHookUnregister: null,
|
|
16191
16342
|
eventUnsubscribers: []
|
|
16192
16343
|
};
|
|
16344
|
+
var TRACKED_FILE_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "write_to_file", "replace_file_content"]);
|
|
16345
|
+
function parseAutocommitResult(content) {
|
|
16346
|
+
const hash = /"hash":\s*"([0-9a-f]{7,64})"/.exec(content)?.[1];
|
|
16347
|
+
if (!hash) return null;
|
|
16348
|
+
const rawMessage = /"message":\s*("(?:[^"\\]|\\.)*")/.exec(content)?.[1];
|
|
16349
|
+
let message = "commit";
|
|
16350
|
+
if (rawMessage) {
|
|
16351
|
+
try {
|
|
16352
|
+
message = JSON.parse(rawMessage);
|
|
16353
|
+
} catch {
|
|
16354
|
+
}
|
|
16355
|
+
}
|
|
16356
|
+
return { hash, message };
|
|
16357
|
+
}
|
|
16193
16358
|
var DEFAULTS36 = {
|
|
16194
16359
|
enabled: true,
|
|
16195
16360
|
outputPath: ".wrongstack/PR_DRAFT.md",
|
|
@@ -16372,6 +16537,7 @@ var plugin41 = {
|
|
|
16372
16537
|
state38.draftErrors = 0;
|
|
16373
16538
|
state38.stopInvocations = 0;
|
|
16374
16539
|
state38.stopHookUnregister = (0, runtime_exports.releaseHandle)(state38.stopHookUnregister);
|
|
16540
|
+
state38.postHookUnregister = (0, runtime_exports.releaseHandle)(state38.postHookUnregister);
|
|
16375
16541
|
for (const off of state38.eventUnsubscribers) {
|
|
16376
16542
|
try {
|
|
16377
16543
|
off();
|
|
@@ -16381,22 +16547,24 @@ var plugin41 = {
|
|
|
16381
16547
|
state38.eventUnsubscribers = [];
|
|
16382
16548
|
const cfg = readConfig37(api.config.extensions?.["pr-drafter"]);
|
|
16383
16549
|
if (api.onPattern) {
|
|
16384
|
-
const offTool = api.onPattern("tool.completed", (
|
|
16385
|
-
const p = payload;
|
|
16386
|
-
const toolName = p?.tool;
|
|
16550
|
+
const offTool = api.onPattern("tool.completed", () => {
|
|
16387
16551
|
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
16552
|
});
|
|
16398
16553
|
state38.eventUnsubscribers.push(offTool);
|
|
16399
16554
|
}
|
|
16555
|
+
const postHook = (input) => {
|
|
16556
|
+
if (!input.toolResult || input.toolResult.isError) return;
|
|
16557
|
+
const toolName = input.toolName;
|
|
16558
|
+
if (toolName === "git_autocommit") {
|
|
16559
|
+
const commit = parseAutocommitResult(String(input.toolResult.content ?? ""));
|
|
16560
|
+
if (commit) state38.commits.push(commit.message);
|
|
16561
|
+
return;
|
|
16562
|
+
}
|
|
16563
|
+
if (!toolName || !TRACKED_FILE_TOOLS.has(toolName)) return;
|
|
16564
|
+
const rawInput = input.toolInput ?? {};
|
|
16565
|
+
const filePath = ["path", "filePath", "file_path", "TargetFile", "targetFile", "file"].map((key) => rawInput[key]).find((value) => typeof value === "string" && value.length > 0);
|
|
16566
|
+
if (filePath) state38.files.add(filePath);
|
|
16567
|
+
};
|
|
16400
16568
|
if (api.onEvent) {
|
|
16401
16569
|
const offUsage = api.onEvent("provider.response", (payload) => {
|
|
16402
16570
|
const p = payload;
|
|
@@ -16415,6 +16583,11 @@ var plugin41 = {
|
|
|
16415
16583
|
await writeDraft(cfg, api.llm);
|
|
16416
16584
|
};
|
|
16417
16585
|
state38.stopHookUnregister = api.registerHook("Stop", void 0, stopHook);
|
|
16586
|
+
state38.postHookUnregister = api.registerHook(
|
|
16587
|
+
"PostToolUse",
|
|
16588
|
+
"git_autocommit|write|edit|write_to_file|replace_file_content",
|
|
16589
|
+
postHook
|
|
16590
|
+
);
|
|
16418
16591
|
api.tools.register({
|
|
16419
16592
|
name: "pr_draft",
|
|
16420
16593
|
description: "Generate or refresh the pull-request draft for the current session. Writes the markdown file and returns its path + title.",
|
|
@@ -16437,7 +16610,7 @@ var plugin41 = {
|
|
|
16437
16610
|
mutating: true,
|
|
16438
16611
|
capabilities: ["fs.write"],
|
|
16439
16612
|
async execute(input = {}) {
|
|
16440
|
-
if (!cfg.enabled)
|
|
16613
|
+
if (!cfg.enabled) throw new Error("pr-drafter is disabled");
|
|
16441
16614
|
const raw = input ?? {};
|
|
16442
16615
|
const preview = Boolean(
|
|
16443
16616
|
input?.preview ?? raw["dryRun"] ?? raw["dry_run"] ?? raw["dry"] ?? raw["previewOnly"]
|
|
@@ -16449,24 +16622,29 @@ var plugin41 = {
|
|
|
16449
16622
|
return { ok: true, preview: true, title: draft.title, body: draft.body };
|
|
16450
16623
|
}
|
|
16451
16624
|
const resolved = resolveProjectPath7(outputPathStr);
|
|
16452
|
-
if (!resolved)
|
|
16625
|
+
if (!resolved) {
|
|
16626
|
+
throw new ToolValidationError20({
|
|
16627
|
+
message: "outputPath resolves outside project",
|
|
16628
|
+
field: "outputPath"
|
|
16629
|
+
});
|
|
16630
|
+
}
|
|
16453
16631
|
try {
|
|
16454
16632
|
await mkdir2(dirname7(resolved), { recursive: true });
|
|
16455
16633
|
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
16634
|
} catch (err) {
|
|
16464
16635
|
state38.draftErrors += 1;
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16636
|
+
throw new Error(
|
|
16637
|
+
`Could not write PR draft to ${outputPathStr}: ${err instanceof Error ? err.message : String(err)}`,
|
|
16638
|
+
{ cause: err }
|
|
16639
|
+
);
|
|
16469
16640
|
}
|
|
16641
|
+
state38.draftsWritten += 1;
|
|
16642
|
+
return {
|
|
16643
|
+
ok: true,
|
|
16644
|
+
path: outputPathStr,
|
|
16645
|
+
resolvedPath: resolved,
|
|
16646
|
+
title: draft.title
|
|
16647
|
+
};
|
|
16470
16648
|
}
|
|
16471
16649
|
});
|
|
16472
16650
|
api.log.info("pr-drafter plugin loaded", {
|
|
@@ -16483,6 +16661,7 @@ var plugin41 = {
|
|
|
16483
16661
|
}
|
|
16484
16662
|
state38.stopHookUnregister = null;
|
|
16485
16663
|
}
|
|
16664
|
+
state38.postHookUnregister = (0, runtime_exports.releaseHandle)(state38.postHookUnregister);
|
|
16486
16665
|
for (const off of state38.eventUnsubscribers) {
|
|
16487
16666
|
try {
|
|
16488
16667
|
off();
|
|
@@ -17194,8 +17373,9 @@ var plugin43 = {
|
|
|
17194
17373
|
var prompt_firewall_default = plugin43;
|
|
17195
17374
|
|
|
17196
17375
|
// src/refactor-suggester/index.ts
|
|
17197
|
-
import { readFile as readFile12 } from "node:fs/promises";
|
|
17376
|
+
import { readFile as readFile12, stat as stat9 } from "node:fs/promises";
|
|
17198
17377
|
import { isAbsolute as isAbsolute18, relative as relative18, resolve as resolve20 } from "node:path";
|
|
17378
|
+
import { ToolValidationError as ToolValidationError21 } from "@wrongstack/core/types";
|
|
17199
17379
|
var API_VERSION28 = "^0.1.10";
|
|
17200
17380
|
var HOOK_WARNING_COOLDOWN_MS2 = 6e4;
|
|
17201
17381
|
var state41 = {
|
|
@@ -17485,11 +17665,23 @@ var plugin44 = {
|
|
|
17485
17665
|
category: "Diagnostics",
|
|
17486
17666
|
mutating: false,
|
|
17487
17667
|
async execute(input) {
|
|
17488
|
-
if (!cfg.enabled)
|
|
17668
|
+
if (!cfg.enabled) throw new Error("refactor-suggester is disabled");
|
|
17489
17669
|
const raw = input ?? {};
|
|
17490
17670
|
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
17671
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
17492
|
-
|
|
17672
|
+
throw new ToolValidationError21({
|
|
17673
|
+
message: "path is outside the project root",
|
|
17674
|
+
field: "path"
|
|
17675
|
+
});
|
|
17676
|
+
}
|
|
17677
|
+
try {
|
|
17678
|
+
await stat9(resolve20(process.cwd(), rawPath));
|
|
17679
|
+
} catch (err) {
|
|
17680
|
+
throw new ToolValidationError21({
|
|
17681
|
+
message: `path does not exist or cannot be read: ${rawPath}`,
|
|
17682
|
+
field: "path",
|
|
17683
|
+
cause: err
|
|
17684
|
+
});
|
|
17493
17685
|
}
|
|
17494
17686
|
state41.scanCount += 1;
|
|
17495
17687
|
let result;
|
|
@@ -17497,7 +17689,7 @@ var plugin44 = {
|
|
|
17497
17689
|
result = await scanPath4(rawPath, cfg);
|
|
17498
17690
|
} catch (err) {
|
|
17499
17691
|
state41.errorCount += 1;
|
|
17500
|
-
|
|
17692
|
+
throw new Error(`refactor scan failed: ${String(err)}`, { cause: err });
|
|
17501
17693
|
}
|
|
17502
17694
|
state41.suggestionCount += result.suggestions.length;
|
|
17503
17695
|
return {
|
|
@@ -17858,7 +18050,7 @@ var plugin45 = {
|
|
|
17858
18050
|
category: "Development",
|
|
17859
18051
|
mutating: false,
|
|
17860
18052
|
async execute(input, _ctx, execOpts) {
|
|
17861
|
-
if (!cfg.enabled)
|
|
18053
|
+
if (!cfg.enabled) throw new Error("release-notes-generator is disabled");
|
|
17862
18054
|
execOpts?.signal?.throwIfAborted();
|
|
17863
18055
|
const raw = input ?? {};
|
|
17864
18056
|
const rawTo = input.to ?? raw["to_ref"] ?? raw["toRef"] ?? raw["until"] ?? raw["end"];
|
|
@@ -17873,7 +18065,7 @@ var plugin45 = {
|
|
|
17873
18065
|
commits = await getCommits(fromRef, toRef, execOpts?.signal);
|
|
17874
18066
|
} catch (err) {
|
|
17875
18067
|
state42.errorCount += 1;
|
|
17876
|
-
|
|
18068
|
+
throw new Error(`Could not read git history: ${String(err)}`, { cause: err });
|
|
17877
18069
|
}
|
|
17878
18070
|
state42.commitCount += commits.length;
|
|
17879
18071
|
execOpts?.signal?.throwIfAborted();
|
|
@@ -18288,6 +18480,7 @@ ${body}${suffix}`;
|
|
|
18288
18480
|
var schema_evolution_guard_default = plugin46;
|
|
18289
18481
|
|
|
18290
18482
|
// src/secret-scanner/index.ts
|
|
18483
|
+
import { ToolValidationError as ToolValidationError22 } from "@wrongstack/core/types";
|
|
18291
18484
|
var BASE_PATTERNS = cloneCredentialPatterns();
|
|
18292
18485
|
var PATTERNS3 = [...BASE_PATTERNS];
|
|
18293
18486
|
var GROUP_INDEX_OF_PATTERN = [];
|
|
@@ -18496,7 +18689,7 @@ function readConfig43(raw) {
|
|
|
18496
18689
|
function buildHook(cfg, log, runtime) {
|
|
18497
18690
|
return (input) => {
|
|
18498
18691
|
activateRuntime(runtime);
|
|
18499
|
-
const { state:
|
|
18692
|
+
const { state: state59 } = runtime;
|
|
18500
18693
|
if (!cfg.enabled) return;
|
|
18501
18694
|
const toolName = input.toolName ?? "unknown";
|
|
18502
18695
|
let matched;
|
|
@@ -18504,7 +18697,7 @@ function buildHook(cfg, log, runtime) {
|
|
|
18504
18697
|
matched = scanInput(input.toolInput);
|
|
18505
18698
|
} catch (err) {
|
|
18506
18699
|
if (String(err).includes("ReDoS")) {
|
|
18507
|
-
|
|
18700
|
+
state59.timeoutCount += 1;
|
|
18508
18701
|
return {
|
|
18509
18702
|
decision: "block",
|
|
18510
18703
|
reason: "secret-scanner: ReDoS timeout \u2014 regex scan exceeded the wall-clock budget. Fail-closed: treated as a block."
|
|
@@ -18516,8 +18709,8 @@ function buildHook(cfg, log, runtime) {
|
|
|
18516
18709
|
const summary = matched.join(", ");
|
|
18517
18710
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
18518
18711
|
if (cfg.mode === "block") {
|
|
18519
|
-
|
|
18520
|
-
|
|
18712
|
+
state59.blockCount += 1;
|
|
18713
|
+
state59.lastBlock = { toolName, matchedTypes: matched, when };
|
|
18521
18714
|
log.warn(`[secret-scanner] blocked ${toolName} \u2014 matched: ${summary}`);
|
|
18522
18715
|
return {
|
|
18523
18716
|
decision: "block",
|
|
@@ -18527,7 +18720,7 @@ function buildHook(cfg, log, runtime) {
|
|
|
18527
18720
|
if (cfg.mode === "redact") {
|
|
18528
18721
|
const redacted = redactInput(input.toolInput);
|
|
18529
18722
|
if (redacted.ok && redacted.value !== null && typeof redacted.value === "object" && !Array.isArray(redacted.value)) {
|
|
18530
|
-
|
|
18723
|
+
state59.redactCount += 1;
|
|
18531
18724
|
log.info(`[secret-scanner] redacted ${toolName} \u2014 matched: ${summary}`);
|
|
18532
18725
|
return {
|
|
18533
18726
|
decision: "allow",
|
|
@@ -18535,15 +18728,15 @@ function buildHook(cfg, log, runtime) {
|
|
|
18535
18728
|
additionalContext: `secret-scanner: redacted ${matched.length} credential pattern(s) from the ${toolName} arguments before execution.`
|
|
18536
18729
|
};
|
|
18537
18730
|
}
|
|
18538
|
-
|
|
18539
|
-
|
|
18731
|
+
state59.blockCount += 1;
|
|
18732
|
+
state59.lastBlock = { toolName, matchedTypes: matched, when };
|
|
18540
18733
|
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
18734
|
return {
|
|
18542
18735
|
decision: "block",
|
|
18543
18736
|
reason: `secret-scanner: cannot safely redact '${toolName}' because ${detail}; refusing to run.`
|
|
18544
18737
|
};
|
|
18545
18738
|
}
|
|
18546
|
-
|
|
18739
|
+
state59.allowCount += 1;
|
|
18547
18740
|
log.warn(
|
|
18548
18741
|
`[secret-scanner] allow-mode: ${toolName} matched ${summary} but mode='allow' lets it through.`
|
|
18549
18742
|
);
|
|
@@ -18553,7 +18746,7 @@ function buildHook(cfg, log, runtime) {
|
|
|
18553
18746
|
function buildPostHook(cfg, log, runtime) {
|
|
18554
18747
|
return (input) => {
|
|
18555
18748
|
activateRuntime(runtime);
|
|
18556
|
-
const { state:
|
|
18749
|
+
const { state: state59 } = runtime;
|
|
18557
18750
|
if (!cfg.enabled) return;
|
|
18558
18751
|
const result = input.toolResult;
|
|
18559
18752
|
if (!result || typeof result.content !== "string") return;
|
|
@@ -18573,8 +18766,8 @@ function buildPostHook(cfg, log, runtime) {
|
|
|
18573
18766
|
}
|
|
18574
18767
|
const summary = credentialMatches.join(", ");
|
|
18575
18768
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
18576
|
-
|
|
18577
|
-
|
|
18769
|
+
state59.leakCount += 1;
|
|
18770
|
+
state59.lastLeak = { toolName, matchedTypes: credentialMatches, when };
|
|
18578
18771
|
log.warn(`[secret-scanner] POST-TOOL LEAK: ${toolName} output matched ${summary}`);
|
|
18579
18772
|
return {
|
|
18580
18773
|
additionalContext: `
|
|
@@ -18652,13 +18845,13 @@ var plugin47 = {
|
|
|
18652
18845
|
};
|
|
18653
18846
|
runtimes.set(api, runtime);
|
|
18654
18847
|
latestRuntime = runtime;
|
|
18655
|
-
const { state:
|
|
18848
|
+
const { state: state59 } = runtime;
|
|
18656
18849
|
const log = {
|
|
18657
18850
|
warn: (msg, ...rest) => api.log.warn(msg, ...rest),
|
|
18658
18851
|
info: (msg, ...rest) => api.log.info(msg, ...rest)
|
|
18659
18852
|
};
|
|
18660
18853
|
const hook = buildHook(cfg, log, runtime);
|
|
18661
|
-
|
|
18854
|
+
state59.hookUnregister = api.registerHook("PreToolUse", cfg.matcher, hook, {
|
|
18662
18855
|
name: "secret-scanner",
|
|
18663
18856
|
// Redaction rewrites arguments; block/allow modes must inspect the final
|
|
18664
18857
|
// result after every mutator has run so a later rewrite cannot smuggle a
|
|
@@ -18668,7 +18861,7 @@ var plugin47 = {
|
|
|
18668
18861
|
policy: true
|
|
18669
18862
|
});
|
|
18670
18863
|
const postHook = buildPostHook(cfg, log, runtime);
|
|
18671
|
-
|
|
18864
|
+
state59.postHookUnregister = api.registerHook("PostToolUse", cfg.postToolUseMatcher, postHook);
|
|
18672
18865
|
api.tools.register({
|
|
18673
18866
|
name: "secret_scanner_status",
|
|
18674
18867
|
description: "Reports the current secret-scanner state: pattern count, last block (if any), and per-mode invocation counters.",
|
|
@@ -18686,14 +18879,14 @@ var plugin47 = {
|
|
|
18686
18879
|
patternCount: PATTERNS3.length,
|
|
18687
18880
|
patternTypes: PATTERNS3.map((p) => p.type),
|
|
18688
18881
|
counters: {
|
|
18689
|
-
block:
|
|
18690
|
-
redact:
|
|
18691
|
-
allow:
|
|
18692
|
-
leak:
|
|
18693
|
-
timeoutCount:
|
|
18882
|
+
block: state59.blockCount,
|
|
18883
|
+
redact: state59.redactCount,
|
|
18884
|
+
allow: state59.allowCount,
|
|
18885
|
+
leak: state59.leakCount,
|
|
18886
|
+
timeoutCount: state59.timeoutCount
|
|
18694
18887
|
},
|
|
18695
|
-
lastBlock:
|
|
18696
|
-
lastLeak:
|
|
18888
|
+
lastBlock: state59.lastBlock,
|
|
18889
|
+
lastLeak: state59.lastLeak
|
|
18697
18890
|
};
|
|
18698
18891
|
}
|
|
18699
18892
|
});
|
|
@@ -18711,9 +18904,14 @@ var plugin47 = {
|
|
|
18711
18904
|
mutating: false,
|
|
18712
18905
|
async execute(input) {
|
|
18713
18906
|
activateRuntime(runtime);
|
|
18714
|
-
const rawText = input["text"] ?? input["content"] ?? input["string"] ?? input["input"] ?? input["code"] ?? input["value"]
|
|
18715
|
-
|
|
18716
|
-
|
|
18907
|
+
const rawText = input["text"] ?? input["content"] ?? input["string"] ?? input["input"] ?? input["code"] ?? input["value"];
|
|
18908
|
+
if (typeof rawText !== "string") {
|
|
18909
|
+
throw new ToolValidationError22({
|
|
18910
|
+
message: "text is required and must be a string",
|
|
18911
|
+
field: "text"
|
|
18912
|
+
});
|
|
18913
|
+
}
|
|
18914
|
+
const matched = findMatches(rawText);
|
|
18717
18915
|
return {
|
|
18718
18916
|
ok: true,
|
|
18719
18917
|
matched,
|
|
@@ -18731,57 +18929,58 @@ var plugin47 = {
|
|
|
18731
18929
|
teardown(api) {
|
|
18732
18930
|
const runtime = runtimes.get(api);
|
|
18733
18931
|
if (!runtime) return;
|
|
18734
|
-
const { state:
|
|
18735
|
-
if (
|
|
18932
|
+
const { state: state59 } = runtime;
|
|
18933
|
+
if (state59.hookUnregister) {
|
|
18736
18934
|
try {
|
|
18737
|
-
|
|
18935
|
+
state59.hookUnregister();
|
|
18738
18936
|
} catch {
|
|
18739
18937
|
}
|
|
18740
|
-
|
|
18938
|
+
state59.hookUnregister = null;
|
|
18741
18939
|
}
|
|
18742
|
-
if (
|
|
18940
|
+
if (state59.postHookUnregister) {
|
|
18743
18941
|
try {
|
|
18744
|
-
|
|
18942
|
+
state59.postHookUnregister();
|
|
18745
18943
|
} catch {
|
|
18746
18944
|
}
|
|
18747
|
-
|
|
18945
|
+
state59.postHookUnregister = null;
|
|
18748
18946
|
}
|
|
18749
18947
|
const finalCounters = {
|
|
18750
|
-
block:
|
|
18751
|
-
redact:
|
|
18752
|
-
allow:
|
|
18753
|
-
leak:
|
|
18948
|
+
block: state59.blockCount,
|
|
18949
|
+
redact: state59.redactCount,
|
|
18950
|
+
allow: state59.allowCount,
|
|
18951
|
+
leak: state59.leakCount
|
|
18754
18952
|
};
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18953
|
+
state59.blockCount = 0;
|
|
18954
|
+
state59.redactCount = 0;
|
|
18955
|
+
state59.allowCount = 0;
|
|
18956
|
+
state59.leakCount = 0;
|
|
18957
|
+
state59.lastBlock = null;
|
|
18958
|
+
state59.lastLeak = null;
|
|
18761
18959
|
runtimes.delete(api);
|
|
18762
18960
|
api.log.info("secret-scanner: teardown complete", { counters: finalCounters });
|
|
18763
18961
|
},
|
|
18764
18962
|
async health() {
|
|
18765
|
-
const
|
|
18963
|
+
const state59 = latestRuntime?.state ?? createState2();
|
|
18766
18964
|
return {
|
|
18767
18965
|
ok: true,
|
|
18768
|
-
message:
|
|
18966
|
+
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
18967
|
counters: {
|
|
18770
|
-
block:
|
|
18771
|
-
redact:
|
|
18772
|
-
allow:
|
|
18773
|
-
leak:
|
|
18968
|
+
block: state59.blockCount,
|
|
18969
|
+
redact: state59.redactCount,
|
|
18970
|
+
allow: state59.allowCount,
|
|
18971
|
+
leak: state59.leakCount
|
|
18774
18972
|
},
|
|
18775
|
-
lastBlock:
|
|
18776
|
-
lastLeak:
|
|
18973
|
+
lastBlock: state59.lastBlock,
|
|
18974
|
+
lastLeak: state59.lastLeak
|
|
18777
18975
|
};
|
|
18778
18976
|
}
|
|
18779
18977
|
};
|
|
18780
18978
|
var secret_scanner_default = plugin47;
|
|
18781
18979
|
|
|
18782
18980
|
// src/security-hotspot-scanner/index.ts
|
|
18783
|
-
import { readdir as readdir2, readFile as readFile13, stat as
|
|
18981
|
+
import { readdir as readdir2, readFile as readFile13, stat as stat10 } from "node:fs/promises";
|
|
18784
18982
|
import { extname as extname5, isAbsolute as isAbsolute19, relative as relative19, resolve as resolve21 } from "node:path";
|
|
18983
|
+
import { ToolValidationError as ToolValidationError23 } from "@wrongstack/core/types";
|
|
18785
18984
|
var API_VERSION31 = "^0.1.10";
|
|
18786
18985
|
var state44 = {
|
|
18787
18986
|
scanCount: 0,
|
|
@@ -18929,7 +19128,7 @@ async function scanPath5(inputPath, cfg) {
|
|
|
18929
19128
|
if (allFindings.length >= cfg.maxFindings) return;
|
|
18930
19129
|
const full = resolve21(dir, entry);
|
|
18931
19130
|
try {
|
|
18932
|
-
const st = await
|
|
19131
|
+
const st = await stat10(full);
|
|
18933
19132
|
if (st.isDirectory()) {
|
|
18934
19133
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "coverage") {
|
|
18935
19134
|
continue;
|
|
@@ -18943,7 +19142,7 @@ async function scanPath5(inputPath, cfg) {
|
|
|
18943
19142
|
}
|
|
18944
19143
|
};
|
|
18945
19144
|
try {
|
|
18946
|
-
const st = await
|
|
19145
|
+
const st = await stat10(resolved);
|
|
18947
19146
|
if (st.isDirectory()) {
|
|
18948
19147
|
await walk(resolved);
|
|
18949
19148
|
} else if (st.isFile()) {
|
|
@@ -19095,7 +19294,7 @@ Review or remove the risky pattern(s).`;
|
|
|
19095
19294
|
category: "Security",
|
|
19096
19295
|
mutating: false,
|
|
19097
19296
|
async execute(input) {
|
|
19098
|
-
if (!cfg.enabled)
|
|
19297
|
+
if (!cfg.enabled) throw new Error("security-hotspot-scanner is disabled");
|
|
19099
19298
|
const raw = input;
|
|
19100
19299
|
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
19300
|
const result = await scanPath5(targetPath, cfg);
|
|
@@ -19103,7 +19302,10 @@ Review or remove the risky pattern(s).`;
|
|
|
19103
19302
|
state44.fileScanCount += result.filesScanned;
|
|
19104
19303
|
state44.findingCount += result.findings.length;
|
|
19105
19304
|
if (!result.scanned) {
|
|
19106
|
-
|
|
19305
|
+
throw new ToolValidationError23({
|
|
19306
|
+
message: `cannot scan ${targetPath}: ${result.error ?? "unknown error"}`,
|
|
19307
|
+
field: "path"
|
|
19308
|
+
});
|
|
19107
19309
|
}
|
|
19108
19310
|
state44.lastResult = {
|
|
19109
19311
|
path: result.path,
|
|
@@ -19201,6 +19403,7 @@ var security_hotspot_scanner_default = plugin48;
|
|
|
19201
19403
|
// src/semantic-search-indexer/index.ts
|
|
19202
19404
|
import * as fs2 from "node:fs/promises";
|
|
19203
19405
|
import { isAbsolute as isAbsolute20, relative as relative20, resolve as resolve22 } from "node:path";
|
|
19406
|
+
import { ToolValidationError as ToolValidationError24 } from "@wrongstack/core/types";
|
|
19204
19407
|
import { DEFAULT_WALK_IGNORE_DIRS } from "@wrongstack/core/utils";
|
|
19205
19408
|
var API_VERSION32 = "^0.1.10";
|
|
19206
19409
|
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -19710,16 +19913,31 @@ var plugin49 = {
|
|
|
19710
19913
|
icon: "search",
|
|
19711
19914
|
async execute(input) {
|
|
19712
19915
|
if (!cfg.enabled) {
|
|
19713
|
-
|
|
19916
|
+
throw new Error("semantic-search-indexer is disabled");
|
|
19714
19917
|
}
|
|
19715
19918
|
const rawPath = input.path ?? input["directory"] ?? input["dir"] ?? input["SearchDirectory"] ?? input["SearchPath"] ?? input["TargetFile"] ?? input["targetFile"] ?? input["filePath"] ?? input["file"];
|
|
19716
19919
|
const resolved = resolveProjectPath8(typeof rawPath === "string" ? rawPath : void 0);
|
|
19717
19920
|
if (!resolved) {
|
|
19718
|
-
|
|
19921
|
+
throw new ToolValidationError24({ message: "path outside project root", field: "path" });
|
|
19719
19922
|
}
|
|
19720
|
-
await ensureIndex(resolved, cfg);
|
|
19721
19923
|
const rawQuery = input.query ?? input["q"] ?? input["text"] ?? input["keyword"] ?? input["keywords"] ?? input["search"] ?? "";
|
|
19722
19924
|
const query = String(rawQuery);
|
|
19925
|
+
if (tokenize(query, cfg.minTokenLength).length === 0) {
|
|
19926
|
+
throw new ToolValidationError24({
|
|
19927
|
+
message: `query must contain at least one keyword of ${cfg.minTokenLength}+ characters`,
|
|
19928
|
+
field: "query"
|
|
19929
|
+
});
|
|
19930
|
+
}
|
|
19931
|
+
try {
|
|
19932
|
+
await fs2.stat(resolved);
|
|
19933
|
+
} catch (err) {
|
|
19934
|
+
throw new ToolValidationError24({
|
|
19935
|
+
message: `path does not exist or cannot be read: ${typeof rawPath === "string" ? rawPath : resolved}`,
|
|
19936
|
+
field: "path",
|
|
19937
|
+
cause: err
|
|
19938
|
+
});
|
|
19939
|
+
}
|
|
19940
|
+
await ensureIndex(resolved, cfg);
|
|
19723
19941
|
const limit = typeof input.limit === "number" && input.limit >= 1 ? Math.floor(input.limit) : cfg.defaultLimit;
|
|
19724
19942
|
const results = runQuery(query, limit, cfg);
|
|
19725
19943
|
const queryTokens = [...new Set(tokenize(query, cfg.minTokenLength))];
|
|
@@ -19843,10 +20061,26 @@ var semantic_search_indexer_default = plugin49;
|
|
|
19843
20061
|
// src/semver-bump/index.ts
|
|
19844
20062
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
19845
20063
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
20064
|
+
import { ToolValidationError as ToolValidationError25 } from "@wrongstack/core/types";
|
|
19846
20065
|
import { execFile as execFile11 } from "node:child_process";
|
|
19847
20066
|
import { access as access4, readFile as readFile15, readdir as readdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
19848
20067
|
import { isAbsolute as isAbsolute21, join as join5, relative as relative21, resolve as resolve23 } from "node:path";
|
|
19849
20068
|
var API_VERSION33 = "^0.1.10";
|
|
20069
|
+
function requireProjectRoot(rawCwd) {
|
|
20070
|
+
const safeCwd = resolveProjectRoot(rawCwd);
|
|
20071
|
+
if (!safeCwd) {
|
|
20072
|
+
throw new ToolValidationError25({
|
|
20073
|
+
message: "cwd must stay within the current project directory",
|
|
20074
|
+
field: "cwd"
|
|
20075
|
+
});
|
|
20076
|
+
}
|
|
20077
|
+
return safeCwd;
|
|
20078
|
+
}
|
|
20079
|
+
function requireGitRef(field, ref) {
|
|
20080
|
+
if (ref !== void 0 && (typeof ref !== "string" || ref.startsWith("-"))) {
|
|
20081
|
+
throw new ToolValidationError25({ message: `${field} is not a valid git ref`, field });
|
|
20082
|
+
}
|
|
20083
|
+
}
|
|
19850
20084
|
function resolveProjectRoot(rawCwd, root = process.cwd()) {
|
|
19851
20085
|
if (typeof rawCwd !== "string" || rawCwd.length === 0) return root;
|
|
19852
20086
|
const base = resolve23(root);
|
|
@@ -20092,14 +20326,10 @@ var plugin50 = {
|
|
|
20092
20326
|
defaultPart = readDefaultPart(next);
|
|
20093
20327
|
});
|
|
20094
20328
|
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;
|
|
20329
|
+
cwd = requireProjectRoot(cwd);
|
|
20100
20330
|
const pkg = await getPackageJson(cwd);
|
|
20101
20331
|
if (!pkg) {
|
|
20102
|
-
|
|
20332
|
+
throw new Error("No package.json found");
|
|
20103
20333
|
}
|
|
20104
20334
|
const currentVersion = pkg.version;
|
|
20105
20335
|
let bumpPart = part;
|
|
@@ -20114,8 +20344,7 @@ var plugin50 = {
|
|
|
20114
20344
|
try {
|
|
20115
20345
|
commits = await getRecentCommits(lastTag, cwd);
|
|
20116
20346
|
} catch (err) {
|
|
20117
|
-
|
|
20118
|
-
return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
|
|
20347
|
+
throw new Error(`Git error: ${toErrorMessage(err)}`, { cause: err });
|
|
20119
20348
|
}
|
|
20120
20349
|
bumpPart = determineBump(commits);
|
|
20121
20350
|
} else {
|
|
@@ -20147,8 +20376,7 @@ var plugin50 = {
|
|
|
20147
20376
|
try {
|
|
20148
20377
|
await runCommand3(process.execPath, [bumpScript, "set", newVersion], root);
|
|
20149
20378
|
} catch (err) {
|
|
20150
|
-
|
|
20151
|
-
return { ok: false, error: `bump script failed: ${msg}` };
|
|
20379
|
+
throw new Error(`bump script failed: ${toErrorMessage(err)}`, { cause: err });
|
|
20152
20380
|
}
|
|
20153
20381
|
for (const rel of ["package.json", "package-lock.json", "src/lib/utils.ts", "index.html"]) {
|
|
20154
20382
|
const p = join5(root, "website", rel);
|
|
@@ -20165,16 +20393,15 @@ var plugin50 = {
|
|
|
20165
20393
|
try {
|
|
20166
20394
|
pkgData = JSON.parse(await readFile15(manifest, "utf-8"));
|
|
20167
20395
|
} catch (err) {
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20396
|
+
throw new Error(
|
|
20397
|
+
`cannot bump: ${manifest} is not readable as JSON (${toErrorMessage(err)}). No manifests were modified.`,
|
|
20398
|
+
{ cause: err }
|
|
20399
|
+
);
|
|
20172
20400
|
}
|
|
20173
20401
|
if (!pkgData || typeof pkgData !== "object") {
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
};
|
|
20402
|
+
throw new Error(
|
|
20403
|
+
`cannot bump: ${manifest} does not contain a JSON object. No manifests were modified.`
|
|
20404
|
+
);
|
|
20178
20405
|
}
|
|
20179
20406
|
pkgData.version = newVersion;
|
|
20180
20407
|
pending.push({ path: manifest, contents: `${JSON.stringify(pkgData, null, 2)}
|
|
@@ -20184,16 +20411,20 @@ var plugin50 = {
|
|
|
20184
20411
|
await writeFile5(path, contents, "utf-8");
|
|
20185
20412
|
}
|
|
20186
20413
|
}
|
|
20414
|
+
let commitError;
|
|
20187
20415
|
try {
|
|
20188
20416
|
await runGit6(["add", "--", ...changed], cwd);
|
|
20189
20417
|
await runGit6(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
|
|
20190
|
-
} catch {
|
|
20418
|
+
} catch (err) {
|
|
20419
|
+
commitError = toErrorMessage(err);
|
|
20191
20420
|
}
|
|
20421
|
+
let tagError;
|
|
20192
20422
|
if (autoTag) {
|
|
20193
20423
|
try {
|
|
20194
20424
|
const msg = tagMessage.replace("{{version}}", newVersion);
|
|
20195
20425
|
await runGit6(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", msg], cwd);
|
|
20196
|
-
} catch {
|
|
20426
|
+
} catch (err) {
|
|
20427
|
+
tagError = toErrorMessage(err);
|
|
20197
20428
|
}
|
|
20198
20429
|
}
|
|
20199
20430
|
api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
|
|
@@ -20213,13 +20444,22 @@ var plugin50 = {
|
|
|
20213
20444
|
commitCount: commits.length,
|
|
20214
20445
|
breakingCount: commits.filter((c) => c.breaking).length
|
|
20215
20446
|
};
|
|
20447
|
+
const tagged = autoTag && tagError === void 0;
|
|
20448
|
+
const warnings = [
|
|
20449
|
+
...commitError ? [`commit failed: ${commitError}`] : [],
|
|
20450
|
+
...tagError ? [`tag failed: ${tagError}`] : []
|
|
20451
|
+
];
|
|
20216
20452
|
return {
|
|
20217
20453
|
ok: true,
|
|
20218
20454
|
currentVersion,
|
|
20219
20455
|
newVersion,
|
|
20220
20456
|
bump: bumpPart,
|
|
20221
|
-
tag
|
|
20222
|
-
|
|
20457
|
+
// Only name the tag when it was actually created.
|
|
20458
|
+
tag: tagged ? `${tagPrefix}${newVersion}` : null,
|
|
20459
|
+
committed: commitError === void 0,
|
|
20460
|
+
tagged,
|
|
20461
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
20462
|
+
message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})` + (warnings.length > 0 ? ` \u2014 ${warnings.join("; ")}` : "")
|
|
20223
20463
|
};
|
|
20224
20464
|
}
|
|
20225
20465
|
api.tools.register({
|
|
@@ -20305,8 +20545,12 @@ var plugin50 = {
|
|
|
20305
20545
|
if (!safeCwd) {
|
|
20306
20546
|
return { message: "cwd must stay within the current project directory" };
|
|
20307
20547
|
}
|
|
20308
|
-
|
|
20309
|
-
|
|
20548
|
+
try {
|
|
20549
|
+
const result = await performBump(mode, dry, safeCwd);
|
|
20550
|
+
return { message: String(result["message"] ?? JSON.stringify(result)) };
|
|
20551
|
+
} catch (err) {
|
|
20552
|
+
return { message: toErrorMessage(err) };
|
|
20553
|
+
}
|
|
20310
20554
|
}
|
|
20311
20555
|
});
|
|
20312
20556
|
api.tools.register({
|
|
@@ -20323,11 +20567,7 @@ var plugin50 = {
|
|
|
20323
20567
|
async execute(input) {
|
|
20324
20568
|
state46.invocationCount += 1;
|
|
20325
20569
|
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
|
-
}
|
|
20570
|
+
const safeCwd = requireProjectRoot(input["cwd"]);
|
|
20331
20571
|
const pkg = await getPackageJson(safeCwd);
|
|
20332
20572
|
const currentVersion = pkg?.version ?? "unknown";
|
|
20333
20573
|
let latestTag = null;
|
|
@@ -20373,22 +20613,20 @@ var plugin50 = {
|
|
|
20373
20613
|
state46.perTool["semver_changelog"] = (state46.perTool["semver_changelog"] ?? 0) + 1;
|
|
20374
20614
|
const from = input["from"];
|
|
20375
20615
|
const to = input["to"] ?? "HEAD";
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
return { ok: false, error: "cwd must stay within the current project directory" };
|
|
20380
|
-
}
|
|
20616
|
+
requireGitRef("from", from);
|
|
20617
|
+
requireGitRef("to", to);
|
|
20618
|
+
const safeCwd = requireProjectRoot(input["cwd"]);
|
|
20381
20619
|
const format = input["format"] ?? "markdown";
|
|
20382
|
-
const
|
|
20620
|
+
const rangeArgs = from ? [`${from}..${to}`] : ["-30", to];
|
|
20383
20621
|
let commits;
|
|
20384
20622
|
try {
|
|
20385
20623
|
const output = await runGit6(
|
|
20386
|
-
["log",
|
|
20624
|
+
["log", ...rangeArgs, "--format=%H%x1f%s%x1f%b%x1e"],
|
|
20387
20625
|
safeCwd
|
|
20388
20626
|
);
|
|
20389
20627
|
commits = parseGitLogOutput(output);
|
|
20390
20628
|
} catch (err) {
|
|
20391
|
-
|
|
20629
|
+
throw new Error(`Failed to get git log: ${toErrorMessage(err)}`, { cause: err });
|
|
20392
20630
|
}
|
|
20393
20631
|
if (format === "json") {
|
|
20394
20632
|
return {
|
|
@@ -20656,8 +20894,10 @@ var plugin51 = {
|
|
|
20656
20894
|
const offTool = api.onPattern("tool.*", (eventName, payload) => {
|
|
20657
20895
|
touchActivity();
|
|
20658
20896
|
const p = payload;
|
|
20659
|
-
const
|
|
20660
|
-
|
|
20897
|
+
const rawTool = p?.tool;
|
|
20898
|
+
const nameOf = (v) => typeof v === "string" ? v : v && typeof v === "object" && typeof v.name === "string" ? v.name : void 0;
|
|
20899
|
+
const toolName = nameOf(rawTool) ?? nameOf(p?.name) ?? eventName;
|
|
20900
|
+
bumpToolCount(toolName);
|
|
20661
20901
|
if (toolName === "git_autocommit" || toolName.startsWith("git ")) {
|
|
20662
20902
|
}
|
|
20663
20903
|
});
|
|
@@ -20903,6 +21143,7 @@ Tokens: ${recap.tokens.total.input} in / ${recap.tokens.total.output} out
|
|
|
20903
21143
|
var session_recap_default = plugin51;
|
|
20904
21144
|
|
|
20905
21145
|
// src/shell-check/index.ts
|
|
21146
|
+
import { ToolValidationError as ToolValidationError26 } from "@wrongstack/core/types";
|
|
20906
21147
|
import { execFile as execFile12 } from "node:child_process";
|
|
20907
21148
|
import { readdir as readdir5 } from "node:fs/promises";
|
|
20908
21149
|
import { isAbsolute as isAbsolute22, join as join6, relative as relative22, resolve as resolve24 } from "node:path";
|
|
@@ -20976,8 +21217,11 @@ async function runShellCheck(files, severity, cwd) {
|
|
|
20976
21217
|
}
|
|
20977
21218
|
);
|
|
20978
21219
|
});
|
|
20979
|
-
} catch {
|
|
20980
|
-
|
|
21220
|
+
} catch (err) {
|
|
21221
|
+
throw new Error(
|
|
21222
|
+
`shellcheck failed without output: ${err instanceof Error ? err.message : String(err)}`,
|
|
21223
|
+
{ cause: err }
|
|
21224
|
+
);
|
|
20981
21225
|
}
|
|
20982
21226
|
if (!raw.trim()) return [];
|
|
20983
21227
|
try {
|
|
@@ -20990,22 +21234,31 @@ async function runShellCheck(files, severity, cwd) {
|
|
|
20990
21234
|
code: item.code,
|
|
20991
21235
|
message: item.message
|
|
20992
21236
|
}));
|
|
20993
|
-
} catch {
|
|
20994
|
-
|
|
21237
|
+
} catch (err) {
|
|
21238
|
+
throw new Error(`shellcheck returned unparseable output: ${raw.trim().slice(0, 500)}`, {
|
|
21239
|
+
cause: err
|
|
21240
|
+
});
|
|
20995
21241
|
}
|
|
20996
21242
|
}
|
|
20997
|
-
async function findShellFiles(dir, pattern) {
|
|
21243
|
+
async function findShellFiles(dir, pattern, isRoot = true) {
|
|
20998
21244
|
const results = [];
|
|
20999
21245
|
let entries;
|
|
21000
21246
|
try {
|
|
21001
21247
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
21002
|
-
} catch {
|
|
21248
|
+
} catch (err) {
|
|
21249
|
+
if (isRoot) {
|
|
21250
|
+
throw new ToolValidationError26({
|
|
21251
|
+
message: `directory does not exist or cannot be read: ${dir}`,
|
|
21252
|
+
field: "directory",
|
|
21253
|
+
cause: err
|
|
21254
|
+
});
|
|
21255
|
+
}
|
|
21003
21256
|
return results;
|
|
21004
21257
|
}
|
|
21005
21258
|
for (const entry of entries) {
|
|
21006
21259
|
const full = join6(dir, entry.name);
|
|
21007
21260
|
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") {
|
|
21008
|
-
results.push(...await findShellFiles(full, pattern));
|
|
21261
|
+
results.push(...await findShellFiles(full, pattern, false));
|
|
21009
21262
|
} 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
21263
|
if (!pattern || entry.name.includes(pattern)) {
|
|
21011
21264
|
results.push(full);
|
|
@@ -21075,12 +21328,8 @@ var plugin52 = {
|
|
|
21075
21328
|
enum: ["error", "warning", "info", "style"],
|
|
21076
21329
|
default: "warning",
|
|
21077
21330
|
description: "Minimum severity level to report"
|
|
21078
|
-
},
|
|
21079
|
-
fix: {
|
|
21080
|
-
type: "boolean",
|
|
21081
|
-
default: false,
|
|
21082
|
-
description: "Apply safe automatic fixes where possible"
|
|
21083
21331
|
}
|
|
21332
|
+
// `fix` was declared ("apply safe automatic fixes") but never implemented.
|
|
21084
21333
|
}
|
|
21085
21334
|
},
|
|
21086
21335
|
permission: "auto",
|
|
@@ -21106,22 +21355,16 @@ var plugin52 = {
|
|
|
21106
21355
|
state48.invocationCount += 1;
|
|
21107
21356
|
const pathIsSafe = (p) => typeof p === "string" && p.length > 0 && p.length <= MAX_PATH_LEN && withinProject22(p);
|
|
21108
21357
|
if (!pathIsSafe(directory)) {
|
|
21109
|
-
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
filesScanned: 0,
|
|
21114
|
-
rejectedOutsideProject: true
|
|
21115
|
-
};
|
|
21358
|
+
throw new ToolValidationError26({
|
|
21359
|
+
message: `directory path is outside the project root: ${directory}`,
|
|
21360
|
+
field: "directory"
|
|
21361
|
+
});
|
|
21116
21362
|
}
|
|
21117
21363
|
if (files?.some((f) => !pathIsSafe(f))) {
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
filesScanned: 0,
|
|
21123
|
-
rejectedOutsideProject: true
|
|
21124
|
-
};
|
|
21364
|
+
throw new ToolValidationError26({
|
|
21365
|
+
message: "one or more file paths are outside the project root",
|
|
21366
|
+
field: "files"
|
|
21367
|
+
});
|
|
21125
21368
|
}
|
|
21126
21369
|
let checkFiles;
|
|
21127
21370
|
let scannedDirectories = false;
|
|
@@ -21147,19 +21390,7 @@ var plugin52 = {
|
|
|
21147
21390
|
mode: scannedDirectories ? "directory" : "files"
|
|
21148
21391
|
};
|
|
21149
21392
|
}
|
|
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
|
-
}
|
|
21393
|
+
const issues = await runShellCheck(checkFiles, severity);
|
|
21163
21394
|
const byFile = {};
|
|
21164
21395
|
for (const issue of issues) {
|
|
21165
21396
|
if (byFile[issue.file] === void 0) {
|
|
@@ -21227,6 +21458,7 @@ var shell_check_default = plugin52;
|
|
|
21227
21458
|
// src/smart-rename/index.ts
|
|
21228
21459
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
|
|
21229
21460
|
import { extname as extname6, isAbsolute as isAbsolute23, relative as relative23, resolve as resolve25 } from "node:path";
|
|
21461
|
+
import { ToolValidationError as ToolValidationError27 } from "@wrongstack/core/types";
|
|
21230
21462
|
var NEW_API_VERSION = "^0.1.10";
|
|
21231
21463
|
var state49 = {
|
|
21232
21464
|
renameCount: 0,
|
|
@@ -21332,32 +21564,44 @@ var plugin53 = {
|
|
|
21332
21564
|
mutating: true,
|
|
21333
21565
|
capabilities: ["fs.write"],
|
|
21334
21566
|
async execute(input) {
|
|
21335
|
-
if (!cfg.enabled)
|
|
21567
|
+
if (!cfg.enabled) throw new Error("smart-rename is disabled");
|
|
21336
21568
|
const inp = input ?? {};
|
|
21337
21569
|
const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
|
|
21338
21570
|
const oldName = inp["oldName"] ?? inp["old_name"] ?? inp["from"];
|
|
21339
21571
|
const newName = inp["newName"] ?? inp["new_name"] ?? inp["to"];
|
|
21340
21572
|
if (!rawPath || typeof rawPath !== "string") {
|
|
21341
|
-
|
|
21573
|
+
throw new ToolValidationError27({ message: "path is required", field: "path" });
|
|
21342
21574
|
}
|
|
21343
21575
|
if (!oldName || typeof oldName !== "string" || oldName.length === 0) {
|
|
21344
|
-
|
|
21576
|
+
throw new ToolValidationError27({ message: "oldName is required", field: "oldName" });
|
|
21345
21577
|
}
|
|
21346
21578
|
if (!newName || typeof newName !== "string" || newName.length === 0) {
|
|
21347
|
-
|
|
21579
|
+
throw new ToolValidationError27({ message: "newName is required", field: "newName" });
|
|
21348
21580
|
}
|
|
21349
21581
|
if (!isIdentifier(oldName)) {
|
|
21350
|
-
|
|
21582
|
+
throw new ToolValidationError27({
|
|
21583
|
+
message: `oldName "${oldName}" is not a valid identifier`,
|
|
21584
|
+
field: "oldName"
|
|
21585
|
+
});
|
|
21351
21586
|
}
|
|
21352
21587
|
if (!isIdentifier(newName)) {
|
|
21353
|
-
|
|
21588
|
+
throw new ToolValidationError27({
|
|
21589
|
+
message: `newName "${newName}" is not a valid identifier`,
|
|
21590
|
+
field: "newName"
|
|
21591
|
+
});
|
|
21354
21592
|
}
|
|
21355
21593
|
if (!withinProject23(rawPath)) {
|
|
21356
|
-
|
|
21594
|
+
throw new ToolValidationError27({
|
|
21595
|
+
message: "path is outside the project root",
|
|
21596
|
+
field: "path"
|
|
21597
|
+
});
|
|
21357
21598
|
}
|
|
21358
21599
|
const ext = extname6(rawPath).toLowerCase();
|
|
21359
21600
|
if (!cfg.extensions.includes(ext)) {
|
|
21360
|
-
|
|
21601
|
+
throw new ToolValidationError27({
|
|
21602
|
+
message: `extension ${ext} is not allowed for rename`,
|
|
21603
|
+
field: "path"
|
|
21604
|
+
});
|
|
21361
21605
|
}
|
|
21362
21606
|
const resolved = resolve25(process.cwd(), rawPath);
|
|
21363
21607
|
let content;
|
|
@@ -21365,7 +21609,7 @@ var plugin53 = {
|
|
|
21365
21609
|
content = readFileSync13(resolved, "utf-8");
|
|
21366
21610
|
} catch (err) {
|
|
21367
21611
|
state49.errorCount += 1;
|
|
21368
|
-
|
|
21612
|
+
throw new Error(`Could not read ${rawPath}: ${String(err)}`, { cause: err });
|
|
21369
21613
|
}
|
|
21370
21614
|
const { preview, replacements } = renameInContent(content, oldName, newName);
|
|
21371
21615
|
state49.renameCount += 1;
|
|
@@ -21378,7 +21622,7 @@ var plugin53 = {
|
|
|
21378
21622
|
writeFileSync2(resolved, preview, "utf-8");
|
|
21379
21623
|
} catch (err) {
|
|
21380
21624
|
state49.errorCount += 1;
|
|
21381
|
-
|
|
21625
|
+
throw new Error(`Could not write ${rawPath}: ${String(err)}`, { cause: err });
|
|
21382
21626
|
}
|
|
21383
21627
|
}
|
|
21384
21628
|
return {
|
|
@@ -21811,8 +22055,8 @@ var plugin54 = {
|
|
|
21811
22055
|
state50.postInvocations += 1;
|
|
21812
22056
|
let content;
|
|
21813
22057
|
try {
|
|
21814
|
-
const
|
|
21815
|
-
if (!
|
|
22058
|
+
const stat13 = await fs3.stat(filePath);
|
|
22059
|
+
if (!stat13.isFile()) return;
|
|
21816
22060
|
content = await fs3.readFile(filePath, "utf-8");
|
|
21817
22061
|
} catch {
|
|
21818
22062
|
state50.readErrorCount += 1;
|
|
@@ -21962,6 +22206,7 @@ var spec_linker_default = plugin54;
|
|
|
21962
22206
|
// src/template-engine/index.ts
|
|
21963
22207
|
import { readFile as readFile17, writeFile as writeFile6 } from "node:fs/promises";
|
|
21964
22208
|
import { isAbsolute as isAbsolute24 } from "node:path";
|
|
22209
|
+
import { ToolValidationError as ToolValidationError28 } from "@wrongstack/core/types";
|
|
21965
22210
|
var API_VERSION35 = "^0.1.10";
|
|
21966
22211
|
var templates = /* @__PURE__ */ new Map();
|
|
21967
22212
|
var MAX_TEMPLATES = 256;
|
|
@@ -22123,24 +22368,26 @@ var plugin55 = {
|
|
|
22123
22368
|
const output_path = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : void 0;
|
|
22124
22369
|
const raw = input["raw"] ?? false;
|
|
22125
22370
|
if (!template || typeof template !== "string") {
|
|
22126
|
-
|
|
22371
|
+
throw new ToolValidationError28({
|
|
22372
|
+
message: "template is required and must be a string",
|
|
22373
|
+
field: "template"
|
|
22374
|
+
});
|
|
22127
22375
|
}
|
|
22128
22376
|
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) };
|
|
22377
|
+
throw new ToolValidationError28({
|
|
22378
|
+
message: "variables is required and must be an object",
|
|
22379
|
+
field: "variables"
|
|
22380
|
+
});
|
|
22136
22381
|
}
|
|
22382
|
+
const result = raw ? renderTemplateRaw(template, variables) : renderTemplate(template, variables, autoEscapeHtml);
|
|
22137
22383
|
if (output_path) {
|
|
22138
22384
|
const pathError = validateWritableTemplateTarget("output_path", output_path);
|
|
22139
|
-
if (pathError)
|
|
22385
|
+
if (pathError)
|
|
22386
|
+
throw new ToolValidationError28({ message: pathError, field: "output_path" });
|
|
22140
22387
|
try {
|
|
22141
22388
|
await writeFile6(output_path, result, "utf-8");
|
|
22142
22389
|
} catch (err) {
|
|
22143
|
-
|
|
22390
|
+
throw new Error(`Could not write ${output_path}: ${String(err)}`, { cause: err });
|
|
22144
22391
|
}
|
|
22145
22392
|
return {
|
|
22146
22393
|
ok: true,
|
|
@@ -22191,32 +22438,36 @@ var plugin55 = {
|
|
|
22191
22438
|
const output_path = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : void 0;
|
|
22192
22439
|
const raw = input["raw"] ?? false;
|
|
22193
22440
|
if (!template_path || typeof template_path !== "string") {
|
|
22194
|
-
|
|
22441
|
+
throw new ToolValidationError28({
|
|
22442
|
+
message: "template_path is required and must be a string",
|
|
22443
|
+
field: "template_path"
|
|
22444
|
+
});
|
|
22195
22445
|
}
|
|
22196
22446
|
const templatePathError = validateRelativeTemplatePath("template_path", template_path);
|
|
22197
|
-
if (templatePathError)
|
|
22447
|
+
if (templatePathError) {
|
|
22448
|
+
throw new ToolValidationError28({ message: templatePathError, field: "template_path" });
|
|
22449
|
+
}
|
|
22198
22450
|
if (!variables || typeof variables !== "object") {
|
|
22199
|
-
|
|
22451
|
+
throw new ToolValidationError28({
|
|
22452
|
+
message: "variables is required and must be an object",
|
|
22453
|
+
field: "variables"
|
|
22454
|
+
});
|
|
22200
22455
|
}
|
|
22201
22456
|
let content;
|
|
22202
22457
|
try {
|
|
22203
22458
|
content = await readFile17(template_path, "utf-8");
|
|
22204
22459
|
} 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}` };
|
|
22460
|
+
throw new Error(`Could not read template file: ${String(err)}`, { cause: err });
|
|
22212
22461
|
}
|
|
22462
|
+
const result = raw ? renderTemplateRaw(content, variables) : renderTemplate(content, variables, autoEscapeHtml);
|
|
22213
22463
|
if (output_path) {
|
|
22214
22464
|
const pathError = validateWritableTemplateTarget("output_path", output_path);
|
|
22215
|
-
if (pathError)
|
|
22465
|
+
if (pathError)
|
|
22466
|
+
throw new ToolValidationError28({ message: pathError, field: "output_path" });
|
|
22216
22467
|
try {
|
|
22217
22468
|
await writeFile6(output_path, result, "utf-8");
|
|
22218
22469
|
} catch (err) {
|
|
22219
|
-
|
|
22470
|
+
throw new Error(`Could not write ${output_path}: ${String(err)}`, { cause: err });
|
|
22220
22471
|
}
|
|
22221
22472
|
return {
|
|
22222
22473
|
ok: true,
|
|
@@ -22267,30 +22518,39 @@ var plugin55 = {
|
|
|
22267
22518
|
const rawDesc = input["description"] ?? input["desc"] ?? input["summary"];
|
|
22268
22519
|
const description = typeof rawDesc === "string" ? rawDesc : void 0;
|
|
22269
22520
|
if (!name || typeof name !== "string" || name.trim() === "") {
|
|
22270
|
-
|
|
22521
|
+
throw new ToolValidationError28({
|
|
22522
|
+
message: "name is required and must be a non-empty string",
|
|
22523
|
+
field: "name"
|
|
22524
|
+
});
|
|
22271
22525
|
}
|
|
22272
22526
|
if (!content || typeof content !== "string") {
|
|
22273
|
-
|
|
22527
|
+
throw new ToolValidationError28({
|
|
22528
|
+
message: "content is required and must be a string",
|
|
22529
|
+
field: "content"
|
|
22530
|
+
});
|
|
22274
22531
|
}
|
|
22275
22532
|
if (name.length > MAX_TEMPLATE_NAME_CHARS) {
|
|
22276
|
-
|
|
22533
|
+
throw new ToolValidationError28({
|
|
22534
|
+
message: `name exceeds ${MAX_TEMPLATE_NAME_CHARS} characters`,
|
|
22535
|
+
field: "name"
|
|
22536
|
+
});
|
|
22277
22537
|
}
|
|
22278
22538
|
if (content.length > MAX_TEMPLATE_CONTENT_CHARS) {
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
};
|
|
22539
|
+
throw new ToolValidationError28({
|
|
22540
|
+
message: `content exceeds ${MAX_TEMPLATE_CONTENT_CHARS} characters`,
|
|
22541
|
+
field: "content"
|
|
22542
|
+
});
|
|
22283
22543
|
}
|
|
22284
22544
|
if (description && description.length > MAX_TEMPLATE_DESCRIPTION_CHARS) {
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
};
|
|
22545
|
+
throw new ToolValidationError28({
|
|
22546
|
+
message: `description exceeds ${MAX_TEMPLATE_DESCRIPTION_CHARS} characters`,
|
|
22547
|
+
field: "description"
|
|
22548
|
+
});
|
|
22289
22549
|
}
|
|
22290
22550
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22291
22551
|
const existing = templates.get(name);
|
|
22292
22552
|
if (!existing && templates.size >= MAX_TEMPLATES) {
|
|
22293
|
-
|
|
22553
|
+
throw new Error(`template limit reached (${MAX_TEMPLATES})`);
|
|
22294
22554
|
}
|
|
22295
22555
|
const tmpl = {
|
|
22296
22556
|
name,
|
|
@@ -22303,10 +22563,7 @@ var plugin55 = {
|
|
|
22303
22563
|
for (const stored of templates.values()) retainedChars += templateChars(stored);
|
|
22304
22564
|
const nextChars = retainedChars - (existing ? templateChars(existing) : 0) + templateChars(tmpl);
|
|
22305
22565
|
if (nextChars > MAX_TOTAL_TEMPLATE_CHARS) {
|
|
22306
|
-
|
|
22307
|
-
ok: false,
|
|
22308
|
-
error: `template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`
|
|
22309
|
-
};
|
|
22566
|
+
throw new Error(`template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`);
|
|
22310
22567
|
}
|
|
22311
22568
|
templates.set(name, tmpl);
|
|
22312
22569
|
api.metrics.gauge("template_count", templates.size);
|
|
@@ -22635,6 +22892,7 @@ import { execFile as execFile13 } from "node:child_process";
|
|
|
22635
22892
|
import { readFileSync as readFileSync15 } from "node:fs";
|
|
22636
22893
|
import { createRequire as createRequire2 } from "node:module";
|
|
22637
22894
|
import { dirname as dirname8, isAbsolute as isAbsolute25, relative as relative24, resolve as resolve26 } from "node:path";
|
|
22895
|
+
import { ToolValidationError as ToolValidationError29 } from "@wrongstack/core/types";
|
|
22638
22896
|
var API_VERSION37 = "^0.1.10";
|
|
22639
22897
|
var state52 = {
|
|
22640
22898
|
invocationCount: 0,
|
|
@@ -22885,7 +23143,7 @@ var plugin57 = {
|
|
|
22885
23143
|
mutating: false,
|
|
22886
23144
|
async execute(input) {
|
|
22887
23145
|
if (!cfg.enabled) {
|
|
22888
|
-
|
|
23146
|
+
throw new Error("test-flake-detector is disabled");
|
|
22889
23147
|
}
|
|
22890
23148
|
const raw = input;
|
|
22891
23149
|
const rawPattern = input.testPattern ?? raw["pattern"] ?? raw["path"] ?? raw["file"] ?? raw["filePath"] ?? raw["TargetFile"] ?? raw["targetFile"];
|
|
@@ -22896,10 +23154,10 @@ var plugin57 = {
|
|
|
22896
23154
|
const requestedRuns = typeof rawRuns === "number" && rawRuns >= 1 ? Math.min(Math.floor(rawRuns), cfg.maxRuns) : 5;
|
|
22897
23155
|
const command = resolveTestCommand(commandString, testPattern);
|
|
22898
23156
|
if (!command) {
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
};
|
|
23157
|
+
throw new ToolValidationError29({
|
|
23158
|
+
message: "Unsupported test command or unsafe testPattern. Use vitest, jest, or mocha through a supported package runner, and keep patterns inside the project.",
|
|
23159
|
+
field: "command"
|
|
23160
|
+
});
|
|
22903
23161
|
}
|
|
22904
23162
|
state52.invocationCount += 1;
|
|
22905
23163
|
const start = Date.now();
|
|
@@ -22927,6 +23185,11 @@ var plugin57 = {
|
|
|
22927
23185
|
}
|
|
22928
23186
|
}
|
|
22929
23187
|
const all = Array.from(records.values());
|
|
23188
|
+
if (all.length === 0 && runErrors.length === requestedRuns) {
|
|
23189
|
+
throw new Error(
|
|
23190
|
+
`test command produced no test results in ${requestedRuns} run(s): ${runErrors.slice(0, 3).join("; ")}`
|
|
23191
|
+
);
|
|
23192
|
+
}
|
|
22930
23193
|
const flakyTests = all.filter((r) => r.passCount > 0 && r.failCount > 0);
|
|
22931
23194
|
const alwaysFailing = all.filter((r) => r.passCount === 0 && r.failCount > 0);
|
|
22932
23195
|
const alwaysPassing = all.filter((r) => r.passCount > 0 && r.failCount === 0);
|
|
@@ -23014,6 +23277,7 @@ var test_flake_detector_default = plugin57;
|
|
|
23014
23277
|
// src/test-generator/index.ts
|
|
23015
23278
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
23016
23279
|
import { isAbsolute as isAbsolute26, relative as relative25, resolve as resolve27 } from "node:path";
|
|
23280
|
+
import { ToolValidationError as ToolValidationError30 } from "@wrongstack/core/types";
|
|
23017
23281
|
var API_VERSION38 = "^0.1.10";
|
|
23018
23282
|
var state53 = {
|
|
23019
23283
|
generateCount: 0,
|
|
@@ -23302,7 +23566,7 @@ var plugin58 = {
|
|
|
23302
23566
|
category: "Development",
|
|
23303
23567
|
mutating: false,
|
|
23304
23568
|
async execute(input, _ctx, execOpts) {
|
|
23305
|
-
if (!cfg.enabled)
|
|
23569
|
+
if (!cfg.enabled) throw new Error("test-generator is disabled");
|
|
23306
23570
|
execOpts?.signal?.throwIfAborted();
|
|
23307
23571
|
const inp = input ?? {};
|
|
23308
23572
|
const rawFramework = inp["framework"];
|
|
@@ -23313,16 +23577,19 @@ var plugin58 = {
|
|
|
23313
23577
|
};
|
|
23314
23578
|
const rawPath = inp["path"] ?? inp["filePath"] ?? inp["file_path"] ?? inp["TargetFile"] ?? inp["targetFile"] ?? inp["file"];
|
|
23315
23579
|
if (!rawPath || typeof rawPath !== "string") {
|
|
23316
|
-
|
|
23580
|
+
throw new ToolValidationError30({ message: "path is required", field: "path" });
|
|
23317
23581
|
}
|
|
23318
23582
|
if (!withinProject27(rawPath)) {
|
|
23319
|
-
|
|
23583
|
+
throw new ToolValidationError30({
|
|
23584
|
+
message: "path is outside the project root",
|
|
23585
|
+
field: "path"
|
|
23586
|
+
});
|
|
23320
23587
|
}
|
|
23321
23588
|
if (!SOURCE_EXTENSIONS.some((ext) => rawPath.toLowerCase().endsWith(ext))) {
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
23325
|
-
};
|
|
23589
|
+
throw new ToolValidationError30({
|
|
23590
|
+
message: `test generation only reads source files (${SOURCE_EXTENSIONS.join(", ")}); refusing "${rawPath}"`,
|
|
23591
|
+
field: "path"
|
|
23592
|
+
});
|
|
23326
23593
|
}
|
|
23327
23594
|
const resolved = resolve27(process.cwd(), rawPath);
|
|
23328
23595
|
state53.generateCount += 1;
|
|
@@ -23331,7 +23598,7 @@ var plugin58 = {
|
|
|
23331
23598
|
result = generateForFile(resolved, effectiveCfg);
|
|
23332
23599
|
} catch (err) {
|
|
23333
23600
|
state53.errorCount += 1;
|
|
23334
|
-
|
|
23601
|
+
throw new Error(`Could not read ${rawPath}: ${String(err)}`, { cause: err });
|
|
23335
23602
|
}
|
|
23336
23603
|
state53.exportCount += result.exports.length;
|
|
23337
23604
|
const raw = input ?? {};
|
|
@@ -24152,451 +24419,632 @@ var todo_listener_default = plugin60;
|
|
|
24152
24419
|
// src/todo-tracker/index.ts
|
|
24153
24420
|
import { randomUUID } from "node:crypto";
|
|
24154
24421
|
import * as fsp from "node:fs/promises";
|
|
24155
|
-
import { dirname as dirname10 } from "node:path";
|
|
24156
|
-
import {
|
|
24422
|
+
import { basename as basename7, dirname as dirname10, extname as extname8 } from "node:path";
|
|
24423
|
+
import { ToolValidationError as ToolValidationError31 } from "@wrongstack/core/types";
|
|
24424
|
+
import { atomicWrite as atomicWrite3, ensureDir as ensureDir3, withFileLock } from "@wrongstack/core/utils";
|
|
24157
24425
|
import { nowIso } from "@wrongstack/primitives";
|
|
24426
|
+
var STATUSES = ["pending", "in_progress", "completed", "dropped"];
|
|
24427
|
+
var PRIORITIES = ["low", "normal", "high"];
|
|
24428
|
+
var defaultDeps = {
|
|
24429
|
+
readFile: (p) => fsp.readFile(p, "utf8"),
|
|
24430
|
+
rename: (from, to) => fsp.rename(from, to),
|
|
24431
|
+
atomicWrite: (p, content, opts) => atomicWrite3(p, content, opts),
|
|
24432
|
+
withFileLock: (p, fn) => withFileLock(p, fn),
|
|
24433
|
+
ensureDir: (dir) => ensureDir3(dir)
|
|
24434
|
+
};
|
|
24435
|
+
function deriveProjectSlug(filePath) {
|
|
24436
|
+
const base = basename7(filePath.replace(/[\\/]+$/, "").replace(/\\/g, "/"));
|
|
24437
|
+
const ext = extname8(base);
|
|
24438
|
+
const stem = ext && ext !== base ? base.slice(0, -ext.length) : base;
|
|
24439
|
+
return stem || "tracker";
|
|
24440
|
+
}
|
|
24158
24441
|
function deriveFilePath(api) {
|
|
24159
24442
|
const raw = api.config.extensions?.["todo-tracker"];
|
|
24160
24443
|
const rawPath = raw?.["filePath"] ?? raw?.["file_path"] ?? raw?.["path"] ?? raw?.["file"] ?? raw?.["targetFile"];
|
|
24161
24444
|
const explicit = typeof rawPath === "string" && rawPath.trim().length > 0 ? rawPath.trim() : null;
|
|
24162
24445
|
if (explicit) {
|
|
24163
|
-
|
|
24164
|
-
return { filePath: explicit, projectSlug: base };
|
|
24446
|
+
return { filePath: explicit, projectSlug: deriveProjectSlug(explicit) };
|
|
24165
24447
|
}
|
|
24166
24448
|
return { filePath: null, projectSlug: null };
|
|
24167
24449
|
}
|
|
24168
24450
|
var FILE_VERSION = 1;
|
|
24169
|
-
|
|
24170
|
-
|
|
24451
|
+
var isStr = (v) => typeof v === "string";
|
|
24452
|
+
var isOptStr = (v) => v === void 0 || v === null || typeof v === "string";
|
|
24453
|
+
function validateItem(raw, index) {
|
|
24454
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
24455
|
+
return `items[${index}] is not an object`;
|
|
24456
|
+
}
|
|
24457
|
+
const it = raw;
|
|
24458
|
+
if (!isStr(it["id"]) || it["id"].length === 0) return `items[${index}].id is not a string`;
|
|
24459
|
+
if (!isStr(it["content"])) return `items[${index}].content is not a string`;
|
|
24460
|
+
if (!STATUSES.includes(it["status"])) {
|
|
24461
|
+
return `items[${index}].status is not one of ${STATUSES.join("|")}`;
|
|
24462
|
+
}
|
|
24463
|
+
if (!PRIORITIES.includes(it["priority"])) {
|
|
24464
|
+
return `items[${index}].priority is not one of ${PRIORITIES.join("|")}`;
|
|
24465
|
+
}
|
|
24466
|
+
if (!Array.isArray(it["tags"]) || !it["tags"].every(isStr)) {
|
|
24467
|
+
return `items[${index}].tags is not a string[]`;
|
|
24468
|
+
}
|
|
24469
|
+
if (!isStr(it["createdAt"]) || !isStr(it["updatedAt"])) {
|
|
24470
|
+
return `items[${index}] timestamps are not strings`;
|
|
24471
|
+
}
|
|
24472
|
+
if (!isOptStr(it["completedAt"]) || !isOptStr(it["sourceSessionId"]) || !isOptStr(it["notes"])) {
|
|
24473
|
+
return `items[${index}] optional fields are not string|null`;
|
|
24474
|
+
}
|
|
24475
|
+
return {
|
|
24476
|
+
id: it["id"],
|
|
24477
|
+
content: it["content"],
|
|
24478
|
+
status: it["status"],
|
|
24479
|
+
priority: it["priority"],
|
|
24480
|
+
tags: [...it["tags"]],
|
|
24481
|
+
createdAt: it["createdAt"],
|
|
24482
|
+
updatedAt: it["updatedAt"],
|
|
24483
|
+
completedAt: it["completedAt"] ?? null,
|
|
24484
|
+
sourceSessionId: it["sourceSessionId"] ?? null,
|
|
24485
|
+
notes: it["notes"] ?? null
|
|
24486
|
+
};
|
|
24487
|
+
}
|
|
24488
|
+
function parseTrackerFile(rawText) {
|
|
24489
|
+
const text = rawText.charCodeAt(0) === 65279 ? rawText.slice(1) : rawText;
|
|
24490
|
+
let parsed;
|
|
24171
24491
|
try {
|
|
24172
|
-
|
|
24492
|
+
parsed = JSON.parse(text);
|
|
24173
24493
|
} catch (err) {
|
|
24174
|
-
|
|
24175
|
-
throw err;
|
|
24494
|
+
return { kind: "corrupt", reason: `invalid JSON: ${err.message}` };
|
|
24176
24495
|
}
|
|
24177
|
-
|
|
24178
|
-
|
|
24179
|
-
if (parsed.version !== FILE_VERSION || !Array.isArray(parsed.items)) {
|
|
24180
|
-
return null;
|
|
24181
|
-
}
|
|
24182
|
-
return parsed;
|
|
24183
|
-
} catch {
|
|
24184
|
-
return null;
|
|
24496
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
24497
|
+
return { kind: "corrupt", reason: "top-level value is not an object" };
|
|
24185
24498
|
}
|
|
24186
|
-
|
|
24187
|
-
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24191
|
-
|
|
24192
|
-
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24198
|
-
|
|
24199
|
-
|
|
24200
|
-
|
|
24201
|
-
|
|
24202
|
-
|
|
24203
|
-
|
|
24204
|
-
|
|
24205
|
-
state56.file = {
|
|
24499
|
+
const obj = parsed;
|
|
24500
|
+
if (typeof obj["version"] !== "number") {
|
|
24501
|
+
return { kind: "corrupt", reason: "missing or non-numeric version" };
|
|
24502
|
+
}
|
|
24503
|
+
if (obj["version"] !== FILE_VERSION) {
|
|
24504
|
+
return { kind: "unsupportedVersion", version: obj["version"] };
|
|
24505
|
+
}
|
|
24506
|
+
if (!Array.isArray(obj["items"])) {
|
|
24507
|
+
return { kind: "invalidItems", reason: "items is not an array" };
|
|
24508
|
+
}
|
|
24509
|
+
const items = [];
|
|
24510
|
+
for (const [i, rawItem] of obj["items"].entries()) {
|
|
24511
|
+
const v = validateItem(rawItem, i);
|
|
24512
|
+
if (typeof v === "string") return { kind: "invalidItems", reason: v };
|
|
24513
|
+
items.push(v);
|
|
24514
|
+
}
|
|
24515
|
+
return {
|
|
24516
|
+
kind: "ok",
|
|
24517
|
+
file: {
|
|
24206
24518
|
version: FILE_VERSION,
|
|
24207
|
-
|
|
24208
|
-
|
|
24209
|
-
|
|
24210
|
-
|
|
24519
|
+
// Any stored slug is accepted (older versions stored the basename
|
|
24520
|
+
// including the extension); the current slug is written on next save.
|
|
24521
|
+
projectSlug: isStr(obj["projectSlug"]) ? obj["projectSlug"] : "",
|
|
24522
|
+
updatedAt: isStr(obj["updatedAt"]) ? obj["updatedAt"] : "",
|
|
24523
|
+
items
|
|
24524
|
+
}
|
|
24525
|
+
};
|
|
24526
|
+
}
|
|
24527
|
+
async function loadFile(deps, filePath) {
|
|
24528
|
+
let raw;
|
|
24529
|
+
try {
|
|
24530
|
+
raw = await deps.readFile(filePath);
|
|
24531
|
+
} catch (err) {
|
|
24532
|
+
if (err.code === "ENOENT") return { kind: "missing" };
|
|
24533
|
+
throw err;
|
|
24211
24534
|
}
|
|
24212
|
-
return
|
|
24535
|
+
return parseTrackerFile(raw);
|
|
24213
24536
|
}
|
|
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;
|
|
24537
|
+
function quarantineSuffix() {
|
|
24538
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
24221
24539
|
}
|
|
24222
|
-
function
|
|
24223
|
-
return
|
|
24540
|
+
function emptyFile(slug) {
|
|
24541
|
+
return { version: FILE_VERSION, projectSlug: slug, updatedAt: nowIso(), items: [] };
|
|
24224
24542
|
}
|
|
24225
|
-
function
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
24229
|
-
|
|
24543
|
+
function requireItemId(input) {
|
|
24544
|
+
const rawId = input["id"] ?? input["itemId"] ?? input["taskId"] ?? input["todoId"];
|
|
24545
|
+
const id = typeof rawId === "string" ? rawId.trim() : "";
|
|
24546
|
+
if (!id) throw new ToolValidationError31({ message: "id is required", field: "id" });
|
|
24547
|
+
return id;
|
|
24230
24548
|
}
|
|
24231
|
-
|
|
24232
|
-
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24240
|
-
|
|
24241
|
-
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
|
|
24549
|
+
function commonPrefixLength(a, b) {
|
|
24550
|
+
const n = Math.min(a.length, b.length);
|
|
24551
|
+
let i = 0;
|
|
24552
|
+
while (i < n && a[i] === b[i]) i++;
|
|
24553
|
+
return i;
|
|
24554
|
+
}
|
|
24555
|
+
function requireItemIndex(file, id) {
|
|
24556
|
+
const idx = file.items.findIndex((it) => it.id === id);
|
|
24557
|
+
if (idx !== -1) return idx;
|
|
24558
|
+
const open = file.items.filter((it) => it.status === "pending" || it.status === "in_progress");
|
|
24559
|
+
const pool = open.length > 0 ? open : file.items;
|
|
24560
|
+
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)}")`);
|
|
24561
|
+
const hint = candidates.length > 0 ? ` Known ${open.length > 0 ? "open " : ""}ids: ${candidates.join(", ")}` : " The tracker is empty.";
|
|
24562
|
+
throw new ToolValidationError31({ message: `no item with id ${id}.${hint}`, field: "id" });
|
|
24563
|
+
}
|
|
24564
|
+
function createTodoTrackerPlugin(overrides = {}) {
|
|
24565
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
24566
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
24567
|
+
let latest = null;
|
|
24568
|
+
function unregisterTools(inst) {
|
|
24569
|
+
const unregister = inst.api.tools.unregister;
|
|
24570
|
+
for (const name of inst.registeredTools.splice(0)) {
|
|
24571
|
+
if (typeof unregister !== "function") continue;
|
|
24572
|
+
try {
|
|
24573
|
+
unregister.call(inst.api.tools, name);
|
|
24574
|
+
} catch (err) {
|
|
24575
|
+
inst.api.log.warn("todo-tracker: failed to unregister tool", { name, err });
|
|
24246
24576
|
}
|
|
24247
24577
|
}
|
|
24248
|
-
}
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24578
|
+
}
|
|
24579
|
+
function assertWritable(inst) {
|
|
24580
|
+
if (inst.readOnlyReason !== null) {
|
|
24581
|
+
throw new Error(`todo-tracker: store is read-only \u2014 ${inst.readOnlyReason}`);
|
|
24582
|
+
}
|
|
24583
|
+
}
|
|
24584
|
+
function serialize(inst, fn) {
|
|
24585
|
+
const run = inst.queue.then(fn, fn);
|
|
24586
|
+
inst.queue = run.catch(() => void 0);
|
|
24587
|
+
return run;
|
|
24588
|
+
}
|
|
24589
|
+
async function quarantineLocked(inst, reason) {
|
|
24590
|
+
const target = `${inst.filePath}.corrupt-${quarantineSuffix()}`;
|
|
24591
|
+
try {
|
|
24592
|
+
await deps.rename(inst.filePath, target);
|
|
24593
|
+
} catch (err) {
|
|
24594
|
+
inst.readOnlyReason = `${inst.filePath} is unreadable (${reason}) and could not be moved aside (${err.message}); fix or remove the file, then reload the plugin`;
|
|
24595
|
+
inst.api.log.error(
|
|
24596
|
+
"todo-tracker: corrupt file could not be quarantined; store is read-only",
|
|
24597
|
+
{
|
|
24598
|
+
filePath: inst.filePath,
|
|
24599
|
+
reason,
|
|
24600
|
+
err
|
|
24601
|
+
}
|
|
24261
24602
|
);
|
|
24262
|
-
return;
|
|
24603
|
+
return false;
|
|
24263
24604
|
}
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24605
|
+
inst.degradedReason = `${inst.filePath} was unreadable (${reason}); original moved to ${target}`;
|
|
24606
|
+
inst.api.log.error(`todo-tracker: corrupt file quarantined to ${target}`, {
|
|
24607
|
+
filePath: inst.filePath,
|
|
24608
|
+
quarantinedTo: target,
|
|
24609
|
+
reason
|
|
24610
|
+
});
|
|
24611
|
+
return true;
|
|
24612
|
+
}
|
|
24613
|
+
async function resolveLoad(inst, res, opts) {
|
|
24614
|
+
switch (res.kind) {
|
|
24615
|
+
case "ok":
|
|
24616
|
+
return res.file;
|
|
24617
|
+
case "missing":
|
|
24618
|
+
return emptyFile(inst.projectSlug);
|
|
24619
|
+
case "unsupportedVersion": {
|
|
24620
|
+
inst.readOnlyReason = `${inst.filePath} has format version ${JSON.stringify(res.version)}, this plugin only writes version ${FILE_VERSION}; refusing to modify it`;
|
|
24621
|
+
inst.api.log.error("todo-tracker: unsupported file version; store is read-only", {
|
|
24622
|
+
filePath: inst.filePath,
|
|
24623
|
+
version: res.version
|
|
24624
|
+
});
|
|
24625
|
+
if (opts.strict) assertWritable(inst);
|
|
24626
|
+
return emptyFile(inst.projectSlug);
|
|
24627
|
+
}
|
|
24628
|
+
case "corrupt":
|
|
24629
|
+
case "invalidItems": {
|
|
24630
|
+
const quarantine = async () => {
|
|
24631
|
+
const again = opts.locked ? res : await loadFile(deps, inst.filePath);
|
|
24632
|
+
if (again.kind === "ok") return again.file;
|
|
24633
|
+
if (again.kind === "missing") return emptyFile(inst.projectSlug);
|
|
24634
|
+
if (again.kind === "unsupportedVersion") {
|
|
24635
|
+
return resolveLoad(inst, again, { locked: true, strict: opts.strict });
|
|
24636
|
+
}
|
|
24637
|
+
const moved = await quarantineLocked(inst, again.reason);
|
|
24638
|
+
if (!moved && opts.strict) assertWritable(inst);
|
|
24639
|
+
return emptyFile(inst.projectSlug);
|
|
24640
|
+
};
|
|
24641
|
+
return opts.locked ? quarantine() : deps.withFileLock(inst.filePath, quarantine);
|
|
24642
|
+
}
|
|
24274
24643
|
}
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
|
|
24286
|
-
|
|
24287
|
-
|
|
24288
|
-
|
|
24644
|
+
}
|
|
24645
|
+
async function refresh(inst) {
|
|
24646
|
+
if (inst.readOnlyReason !== null) return inst.file;
|
|
24647
|
+
const res = await loadFile(deps, inst.filePath);
|
|
24648
|
+
const file = await resolveLoad(inst, res, { locked: false, strict: false });
|
|
24649
|
+
inst.file = file;
|
|
24650
|
+
return file;
|
|
24651
|
+
}
|
|
24652
|
+
function mutate(inst, apply) {
|
|
24653
|
+
return serialize(
|
|
24654
|
+
inst,
|
|
24655
|
+
() => deps.withFileLock(inst.filePath, async () => {
|
|
24656
|
+
assertWritable(inst);
|
|
24657
|
+
const res = await loadFile(deps, inst.filePath);
|
|
24658
|
+
const current = await resolveLoad(inst, res, { locked: true, strict: true });
|
|
24659
|
+
const draft = structuredClone(current);
|
|
24660
|
+
const now = nowIso();
|
|
24661
|
+
const { changed, result } = apply(draft, now);
|
|
24662
|
+
if (changed) {
|
|
24663
|
+
draft.version = FILE_VERSION;
|
|
24664
|
+
draft.projectSlug = inst.projectSlug;
|
|
24665
|
+
draft.updatedAt = now;
|
|
24666
|
+
await deps.ensureDir(dirname10(inst.filePath));
|
|
24667
|
+
await deps.atomicWrite(inst.filePath, JSON.stringify(draft, null, 2), { mode: 384 });
|
|
24668
|
+
inst.file = draft;
|
|
24669
|
+
} else {
|
|
24670
|
+
inst.file = current;
|
|
24289
24671
|
}
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
|
|
24295
|
-
|
|
24296
|
-
|
|
24297
|
-
|
|
24298
|
-
|
|
24299
|
-
|
|
24300
|
-
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
24306
|
-
|
|
24307
|
-
|
|
24672
|
+
return result;
|
|
24673
|
+
})
|
|
24674
|
+
);
|
|
24675
|
+
}
|
|
24676
|
+
function recordMutation(inst, op, itemId) {
|
|
24677
|
+
inst.lastMutation = { op, itemId, when: nowIso() };
|
|
24678
|
+
if (op === "add") inst.addCount += 1;
|
|
24679
|
+
else if (op === "complete") inst.completeCount += 1;
|
|
24680
|
+
else if (op === "drop") inst.dropCount += 1;
|
|
24681
|
+
else if (op === "remove") inst.removeCount += 1;
|
|
24682
|
+
}
|
|
24683
|
+
function sessionCounts(inst) {
|
|
24684
|
+
return {
|
|
24685
|
+
add: inst.addCount,
|
|
24686
|
+
complete: inst.completeCount,
|
|
24687
|
+
drop: inst.dropCount,
|
|
24688
|
+
remove: inst.removeCount,
|
|
24689
|
+
pull: inst.pullCount
|
|
24690
|
+
};
|
|
24691
|
+
}
|
|
24692
|
+
function disposeInstance(api) {
|
|
24693
|
+
const inst = instances.get(api);
|
|
24694
|
+
if (!inst) return void 0;
|
|
24695
|
+
unregisterTools(inst);
|
|
24696
|
+
instances.delete(api);
|
|
24697
|
+
if (latest === inst) latest = null;
|
|
24698
|
+
return inst;
|
|
24699
|
+
}
|
|
24700
|
+
const plugin65 = {
|
|
24701
|
+
name: "todo-tracker",
|
|
24702
|
+
version: "0.1.0",
|
|
24703
|
+
description: "Persistent, project-scoped todo backlog that survives across sessions",
|
|
24704
|
+
apiVersion: "^0.1.10",
|
|
24705
|
+
capabilities: { tools: true },
|
|
24706
|
+
defaultConfig: {
|
|
24707
|
+
filePath: ""
|
|
24708
|
+
},
|
|
24709
|
+
configSchema: {
|
|
24710
|
+
type: "object",
|
|
24711
|
+
properties: {
|
|
24712
|
+
filePath: {
|
|
24713
|
+
type: "string",
|
|
24714
|
+
description: "Override the auto-derived per-project path. Defaults to <projectDir>/todo-tracker.json when `paths.projectDir` is provided by the host."
|
|
24715
|
+
}
|
|
24716
|
+
}
|
|
24717
|
+
},
|
|
24718
|
+
async setup(api) {
|
|
24719
|
+
disposeInstance(api);
|
|
24720
|
+
const derived = deriveFilePath(api);
|
|
24721
|
+
if (derived.filePath === null) {
|
|
24722
|
+
latest = null;
|
|
24723
|
+
api.log.warn(
|
|
24724
|
+
'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'
|
|
24725
|
+
);
|
|
24726
|
+
return;
|
|
24727
|
+
}
|
|
24728
|
+
const inst = {
|
|
24729
|
+
api,
|
|
24730
|
+
filePath: derived.filePath,
|
|
24731
|
+
projectSlug: derived.projectSlug ?? "tracker",
|
|
24732
|
+
file: emptyFile(derived.projectSlug ?? "tracker"),
|
|
24733
|
+
readOnlyReason: null,
|
|
24734
|
+
degradedReason: null,
|
|
24735
|
+
queue: Promise.resolve(),
|
|
24736
|
+
registeredTools: [],
|
|
24737
|
+
addCount: 0,
|
|
24738
|
+
completeCount: 0,
|
|
24739
|
+
dropCount: 0,
|
|
24740
|
+
removeCount: 0,
|
|
24741
|
+
pullCount: 0,
|
|
24742
|
+
lastMutation: null
|
|
24743
|
+
};
|
|
24744
|
+
instances.set(api, inst);
|
|
24745
|
+
latest = inst;
|
|
24746
|
+
await refresh(inst);
|
|
24747
|
+
const register = (tool) => {
|
|
24748
|
+
api.tools.register(tool);
|
|
24749
|
+
inst.registeredTools.push(tool.name);
|
|
24750
|
+
};
|
|
24751
|
+
register({
|
|
24752
|
+
name: "todo_tracker_list",
|
|
24753
|
+
description: "List persistent todo-tracker items. Filterable by status, priority, and tag. By default only pending + in_progress items are shown.",
|
|
24754
|
+
inputSchema: {
|
|
24755
|
+
type: "object",
|
|
24756
|
+
properties: {
|
|
24757
|
+
status: {
|
|
24758
|
+
type: "string",
|
|
24759
|
+
enum: ["pending", "in_progress", "completed", "dropped", "all"],
|
|
24760
|
+
description: "Filter by status. 'all' returns every item; default is pending+in_progress."
|
|
24761
|
+
},
|
|
24762
|
+
priority: { type: "string", enum: ["low", "normal", "high"] },
|
|
24763
|
+
tag: { type: "string", description: "Filter by exact tag match" },
|
|
24764
|
+
limit: { type: "number", description: "Max items to return (default 50, max 200)" }
|
|
24765
|
+
}
|
|
24766
|
+
},
|
|
24767
|
+
permission: "auto",
|
|
24768
|
+
mutating: false,
|
|
24769
|
+
async execute(input) {
|
|
24770
|
+
const rawStatus = typeof input["status"] === "string" ? input["status"].trim().toLowerCase() : void 0;
|
|
24771
|
+
const status = rawStatus ?? "active";
|
|
24772
|
+
const priority = typeof input["priority"] === "string" ? input["priority"].trim().toLowerCase() : void 0;
|
|
24773
|
+
const tag = typeof input["tag"] === "string" ? input["tag"] : void 0;
|
|
24774
|
+
const limit = Math.min(Math.max(Number(input["limit"] ?? 50) || 50, 1), 200);
|
|
24775
|
+
const file = await refresh(inst);
|
|
24776
|
+
let items = file.items;
|
|
24777
|
+
if (status !== "all") {
|
|
24778
|
+
if (status === "active") {
|
|
24779
|
+
items = items.filter((it) => it.status === "pending" || it.status === "in_progress");
|
|
24780
|
+
} else {
|
|
24781
|
+
items = items.filter((it) => it.status === status);
|
|
24782
|
+
}
|
|
24308
24783
|
}
|
|
24784
|
+
if (priority) items = items.filter((it) => it.priority === priority);
|
|
24785
|
+
if (tag) items = items.filter((it) => it.tags.includes(tag));
|
|
24786
|
+
const total = items.length;
|
|
24787
|
+
const truncated = items.slice(0, limit);
|
|
24788
|
+
return {
|
|
24789
|
+
ok: true,
|
|
24790
|
+
total,
|
|
24791
|
+
returned: truncated.length,
|
|
24792
|
+
truncated: total > truncated.length,
|
|
24793
|
+
items: truncated,
|
|
24794
|
+
...inst.readOnlyReason ? { readOnly: inst.readOnlyReason } : {}
|
|
24795
|
+
};
|
|
24309
24796
|
}
|
|
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"
|
|
24797
|
+
});
|
|
24798
|
+
register({
|
|
24799
|
+
name: "todo_tracker_add",
|
|
24800
|
+
description: "Append a new item to the persistent todo-tracker backlog.",
|
|
24801
|
+
inputSchema: {
|
|
24802
|
+
type: "object",
|
|
24803
|
+
properties: {
|
|
24804
|
+
content: { type: "string", description: "What needs doing (required)" },
|
|
24805
|
+
priority: { type: "string", enum: ["low", "normal", "high"], default: "normal" },
|
|
24806
|
+
tags: {
|
|
24807
|
+
type: "array",
|
|
24808
|
+
items: { type: "string" },
|
|
24809
|
+
description: "Optional tags for filtering"
|
|
24810
|
+
},
|
|
24811
|
+
sourceSessionId: { type: "string", description: "Session that created this item" },
|
|
24812
|
+
notes: { type: "string", description: "Optional free-form notes" }
|
|
24335
24813
|
},
|
|
24336
|
-
|
|
24337
|
-
notes: { type: "string", description: "Optional free-form notes" }
|
|
24814
|
+
required: ["content"]
|
|
24338
24815
|
},
|
|
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
|
|
24816
|
+
permission: "auto",
|
|
24817
|
+
mutating: true,
|
|
24818
|
+
async execute(input) {
|
|
24819
|
+
const rawContent = input["content"] ?? input["text"] ?? input["task"] ?? input["title"] ?? input["todo"] ?? input["message"] ?? input["item"];
|
|
24820
|
+
const content = typeof rawContent === "string" ? rawContent.trim() : "";
|
|
24821
|
+
if (!content) {
|
|
24822
|
+
throw new ToolValidationError31({
|
|
24823
|
+
message: "content is required and must be a non-empty string",
|
|
24824
|
+
field: "content"
|
|
24825
|
+
});
|
|
24826
|
+
}
|
|
24827
|
+
const rawPri = typeof input["priority"] === "string" ? input["priority"].trim().toLowerCase() : "";
|
|
24828
|
+
const priority = rawPri === "low" || rawPri === "high" ? rawPri : "normal";
|
|
24829
|
+
const tags = Array.isArray(input["tags"]) ? input["tags"].filter((t) => typeof t === "string") : [];
|
|
24830
|
+
const sourceSessionId = typeof input["sourceSessionId"] === "string" ? input["sourceSessionId"] : null;
|
|
24831
|
+
const notes = typeof input["notes"] === "string" ? input["notes"] : null;
|
|
24832
|
+
const item = await mutate(inst, (draft, now) => {
|
|
24833
|
+
const created = {
|
|
24834
|
+
id: randomUUID(),
|
|
24835
|
+
content,
|
|
24836
|
+
status: "pending",
|
|
24837
|
+
priority,
|
|
24838
|
+
tags,
|
|
24839
|
+
createdAt: now,
|
|
24840
|
+
updatedAt: now,
|
|
24841
|
+
completedAt: null,
|
|
24842
|
+
sourceSessionId,
|
|
24843
|
+
notes
|
|
24844
|
+
};
|
|
24845
|
+
draft.items.push(created);
|
|
24846
|
+
return { changed: true, result: created };
|
|
24382
24847
|
});
|
|
24383
|
-
|
|
24848
|
+
recordMutation(inst, "add", item.id);
|
|
24849
|
+
api.log.info("todo-tracker: added item", { id: item.id, content });
|
|
24850
|
+
try {
|
|
24851
|
+
await api.session?.append?.({
|
|
24852
|
+
type: "todo-tracker:add",
|
|
24853
|
+
ts: item.createdAt,
|
|
24854
|
+
id: item.id,
|
|
24855
|
+
content,
|
|
24856
|
+
priority,
|
|
24857
|
+
tags
|
|
24858
|
+
});
|
|
24859
|
+
} catch (err) {
|
|
24860
|
+
api.log.warn("todo-tracker: session.append failed (item was saved)", {
|
|
24861
|
+
id: item.id,
|
|
24862
|
+
err
|
|
24863
|
+
});
|
|
24864
|
+
}
|
|
24865
|
+
return { ok: true, item };
|
|
24384
24866
|
}
|
|
24385
|
-
|
|
24386
|
-
|
|
24387
|
-
|
|
24388
|
-
|
|
24389
|
-
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24393
|
-
|
|
24394
|
-
|
|
24867
|
+
});
|
|
24868
|
+
const setTerminalStatus = async (input, target) => {
|
|
24869
|
+
const id = requireItemId(input);
|
|
24870
|
+
return mutate(inst, (draft, now) => {
|
|
24871
|
+
const item = draft.items[requireItemIndex(draft, id)];
|
|
24872
|
+
if (item.status === target) {
|
|
24873
|
+
return {
|
|
24874
|
+
changed: false,
|
|
24875
|
+
result: { ok: true, item, message: `already ${target} (idempotent)` }
|
|
24876
|
+
};
|
|
24877
|
+
}
|
|
24878
|
+
item.status = target;
|
|
24879
|
+
item.updatedAt = now;
|
|
24880
|
+
item.completedAt = now;
|
|
24881
|
+
return { changed: true, result: { ok: true, item } };
|
|
24882
|
+
});
|
|
24883
|
+
};
|
|
24884
|
+
register({
|
|
24885
|
+
name: "todo_tracker_complete",
|
|
24886
|
+
description: "Mark a tracked item as completed. Idempotent.",
|
|
24887
|
+
inputSchema: {
|
|
24888
|
+
type: "object",
|
|
24889
|
+
properties: {
|
|
24890
|
+
id: { type: "string", description: "Item id" }
|
|
24891
|
+
},
|
|
24892
|
+
required: ["id"]
|
|
24395
24893
|
},
|
|
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)" };
|
|
24894
|
+
permission: "auto",
|
|
24895
|
+
mutating: true,
|
|
24896
|
+
async execute(input) {
|
|
24897
|
+
const result = await setTerminalStatus(input, "completed");
|
|
24898
|
+
if (!result.message) {
|
|
24899
|
+
recordMutation(inst, "complete", result.item.id);
|
|
24900
|
+
api.log.info("todo-tracker: completed item", { id: result.item.id });
|
|
24901
|
+
}
|
|
24902
|
+
return result;
|
|
24411
24903
|
}
|
|
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" }
|
|
24904
|
+
});
|
|
24905
|
+
register({
|
|
24906
|
+
name: "todo_tracker_drop",
|
|
24907
|
+
description: "Mark a tracked item as dropped (skipped/obsolete). The row is kept for audit. Idempotent.",
|
|
24908
|
+
inputSchema: {
|
|
24909
|
+
type: "object",
|
|
24910
|
+
properties: {
|
|
24911
|
+
id: { type: "string", description: "Item id" }
|
|
24912
|
+
},
|
|
24913
|
+
required: ["id"]
|
|
24430
24914
|
},
|
|
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)" };
|
|
24915
|
+
permission: "auto",
|
|
24916
|
+
mutating: true,
|
|
24917
|
+
async execute(input) {
|
|
24918
|
+
const result = await setTerminalStatus(input, "dropped");
|
|
24919
|
+
if (!result.message) recordMutation(inst, "drop", result.item.id);
|
|
24920
|
+
return result;
|
|
24446
24921
|
}
|
|
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" }
|
|
24922
|
+
});
|
|
24923
|
+
register({
|
|
24924
|
+
name: "todo_tracker_remove",
|
|
24925
|
+
description: "Permanently delete a tracked item by id. Use todo_tracker_drop instead if you want to keep the audit row.",
|
|
24926
|
+
inputSchema: {
|
|
24927
|
+
type: "object",
|
|
24928
|
+
properties: {
|
|
24929
|
+
id: { type: "string", description: "Item id" }
|
|
24930
|
+
},
|
|
24931
|
+
required: ["id"]
|
|
24464
24932
|
},
|
|
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)" }
|
|
24933
|
+
permission: "confirm",
|
|
24934
|
+
mutating: true,
|
|
24935
|
+
async execute(input) {
|
|
24936
|
+
const id = requireItemId(input);
|
|
24937
|
+
const removed = await mutate(inst, (draft) => {
|
|
24938
|
+
const [gone] = draft.items.splice(requireItemIndex(draft, id), 1);
|
|
24939
|
+
return { changed: true, result: gone };
|
|
24940
|
+
});
|
|
24941
|
+
recordMutation(inst, "remove", id);
|
|
24942
|
+
return { ok: true, removed };
|
|
24491
24943
|
}
|
|
24492
|
-
}
|
|
24493
|
-
|
|
24494
|
-
|
|
24495
|
-
|
|
24496
|
-
|
|
24497
|
-
|
|
24498
|
-
|
|
24499
|
-
|
|
24500
|
-
|
|
24501
|
-
|
|
24944
|
+
});
|
|
24945
|
+
register({
|
|
24946
|
+
name: "todo_tracker_pull",
|
|
24947
|
+
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.",
|
|
24948
|
+
inputSchema: {
|
|
24949
|
+
type: "object",
|
|
24950
|
+
properties: {
|
|
24951
|
+
limit: { type: "number", description: "Max items to return (default 50, max 200)" }
|
|
24952
|
+
}
|
|
24953
|
+
},
|
|
24954
|
+
permission: "auto",
|
|
24955
|
+
mutating: false,
|
|
24956
|
+
async execute(input) {
|
|
24957
|
+
const limit = Math.min(Math.max(Number(input["limit"] ?? 50) || 50, 1), 200);
|
|
24958
|
+
const file = await refresh(inst);
|
|
24959
|
+
const open = file.items.filter(
|
|
24960
|
+
(it) => it.status === "pending" || it.status === "in_progress"
|
|
24961
|
+
);
|
|
24962
|
+
const items = open.slice(0, limit);
|
|
24963
|
+
if (items.length > 0) inst.pullCount += 1;
|
|
24964
|
+
return {
|
|
24965
|
+
ok: true,
|
|
24966
|
+
total: open.length,
|
|
24967
|
+
returned: items.length,
|
|
24968
|
+
truncated: open.length > items.length,
|
|
24969
|
+
items,
|
|
24970
|
+
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."
|
|
24971
|
+
};
|
|
24502
24972
|
}
|
|
24973
|
+
});
|
|
24974
|
+
register({
|
|
24975
|
+
name: "todo_tracker_status",
|
|
24976
|
+
description: "Report todo-tracker counters (per-status totals) + the file path + last update timestamp.",
|
|
24977
|
+
inputSchema: { type: "object", properties: {} },
|
|
24978
|
+
permission: "auto",
|
|
24979
|
+
mutating: false,
|
|
24980
|
+
async execute() {
|
|
24981
|
+
const file = await refresh(inst);
|
|
24982
|
+
const byStatus = {
|
|
24983
|
+
pending: 0,
|
|
24984
|
+
in_progress: 0,
|
|
24985
|
+
completed: 0,
|
|
24986
|
+
dropped: 0
|
|
24987
|
+
};
|
|
24988
|
+
for (const it of file.items) {
|
|
24989
|
+
if (STATUSES.includes(it.status)) byStatus[it.status] += 1;
|
|
24990
|
+
}
|
|
24991
|
+
return {
|
|
24992
|
+
ok: true,
|
|
24993
|
+
filePath: inst.filePath,
|
|
24994
|
+
projectSlug: inst.projectSlug,
|
|
24995
|
+
updatedAt: file.updatedAt,
|
|
24996
|
+
counters: byStatus,
|
|
24997
|
+
total: file.items.length,
|
|
24998
|
+
session: sessionCounts(inst),
|
|
24999
|
+
lastMutation: inst.lastMutation,
|
|
25000
|
+
readOnly: inst.readOnlyReason,
|
|
25001
|
+
degraded: inst.degradedReason
|
|
25002
|
+
};
|
|
25003
|
+
}
|
|
25004
|
+
});
|
|
25005
|
+
api.log.info("todo-tracker plugin loaded", {
|
|
25006
|
+
filePath: inst.filePath,
|
|
25007
|
+
projectSlug: inst.projectSlug,
|
|
25008
|
+
initialItemCount: inst.file.items.length,
|
|
25009
|
+
readOnly: inst.readOnlyReason,
|
|
25010
|
+
degraded: inst.degradedReason
|
|
25011
|
+
});
|
|
25012
|
+
},
|
|
25013
|
+
teardown(api) {
|
|
25014
|
+
const inst = disposeInstance(api);
|
|
25015
|
+
if (!inst) return;
|
|
25016
|
+
api.log.info("todo-tracker: teardown complete", { sessionCounts: sessionCounts(inst) });
|
|
25017
|
+
},
|
|
25018
|
+
async health() {
|
|
25019
|
+
const inst = latest;
|
|
25020
|
+
if (inst === null) {
|
|
24503
25021
|
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
|
|
25022
|
+
ok: false,
|
|
25023
|
+
message: "todo-tracker: no file path configured \u2014 tools will error"
|
|
24542
25024
|
};
|
|
24543
25025
|
}
|
|
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) {
|
|
25026
|
+
const reason = inst.readOnlyReason ?? inst.degradedReason;
|
|
24572
25027
|
return {
|
|
24573
|
-
ok:
|
|
24574
|
-
message:
|
|
25028
|
+
ok: reason === null,
|
|
25029
|
+
message: reason === null ? `todo-tracker: ${inst.file.items.length} item(s) at ${inst.filePath}` : `todo-tracker: ${inst.readOnlyReason ? "read-only" : "degraded"} \u2014 ${reason}`,
|
|
25030
|
+
filePath: inst.filePath,
|
|
25031
|
+
projectSlug: inst.projectSlug,
|
|
25032
|
+
total: inst.file.items.length,
|
|
25033
|
+
readOnly: inst.readOnlyReason,
|
|
25034
|
+
degraded: inst.degradedReason,
|
|
25035
|
+
sessionCounts: sessionCounts(inst),
|
|
25036
|
+
lastMutation: inst.lastMutation
|
|
24575
25037
|
};
|
|
24576
25038
|
}
|
|
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
|
-
};
|
|
25039
|
+
};
|
|
25040
|
+
return plugin65;
|
|
25041
|
+
}
|
|
25042
|
+
var plugin61 = createTodoTrackerPlugin();
|
|
24595
25043
|
var todo_tracker_default = plugin61;
|
|
24596
25044
|
|
|
24597
25045
|
// src/token-budget/index.ts
|
|
24598
25046
|
var API_VERSION40 = "^0.1.10";
|
|
24599
|
-
var
|
|
25047
|
+
var state56 = {
|
|
24600
25048
|
totalTokens: 0,
|
|
24601
25049
|
totalPromptTokens: 0,
|
|
24602
25050
|
totalCompletionTokens: 0,
|
|
@@ -24654,13 +25102,13 @@ function readConfig54(raw) {
|
|
|
24654
25102
|
}
|
|
24655
25103
|
function clearRegistrations3() {
|
|
24656
25104
|
for (const key of ["hookUnregister", "postHookUnregister"]) {
|
|
24657
|
-
const off =
|
|
25105
|
+
const off = state56[key];
|
|
24658
25106
|
if (!off) continue;
|
|
24659
25107
|
try {
|
|
24660
25108
|
off();
|
|
24661
25109
|
} catch {
|
|
24662
25110
|
}
|
|
24663
|
-
|
|
25111
|
+
state56[key] = null;
|
|
24664
25112
|
}
|
|
24665
25113
|
}
|
|
24666
25114
|
var plugin62 = {
|
|
@@ -24701,15 +25149,15 @@ var plugin62 = {
|
|
|
24701
25149
|
}
|
|
24702
25150
|
},
|
|
24703
25151
|
setup(api) {
|
|
24704
|
-
|
|
24705
|
-
|
|
24706
|
-
|
|
24707
|
-
|
|
24708
|
-
|
|
24709
|
-
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
|
|
25152
|
+
state56.totalTokens = 0;
|
|
25153
|
+
state56.totalPromptTokens = 0;
|
|
25154
|
+
state56.totalCompletionTokens = 0;
|
|
25155
|
+
state56.requestCount = 0;
|
|
25156
|
+
state56.warningFired = false;
|
|
25157
|
+
state56.stopFired = false;
|
|
25158
|
+
state56.warnContextInjected = false;
|
|
25159
|
+
state56.stopContextInjected = false;
|
|
25160
|
+
state56.lastRequest = null;
|
|
24713
25161
|
clearRegistrations3();
|
|
24714
25162
|
const cfg = readConfig54(api.config.extensions?.["token-budget"]);
|
|
24715
25163
|
api.onEvent("provider.response", (payload) => {
|
|
@@ -24722,24 +25170,26 @@ var plugin62 = {
|
|
|
24722
25170
|
if (!modelMatches(cfg.model, modelName)) return;
|
|
24723
25171
|
}
|
|
24724
25172
|
const rawUsage = usage;
|
|
24725
|
-
const
|
|
24726
|
-
const
|
|
25173
|
+
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);
|
|
25174
|
+
const promptTokens = Number.isFinite(promptTokensRaw) ? promptTokensRaw : 0;
|
|
25175
|
+
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);
|
|
25176
|
+
const completionTokens = Number.isFinite(completionTokensRaw) ? completionTokensRaw : 0;
|
|
24727
25177
|
const total = promptTokens + completionTokens;
|
|
24728
|
-
|
|
24729
|
-
|
|
24730
|
-
|
|
24731
|
-
|
|
24732
|
-
|
|
25178
|
+
state56.totalPromptTokens += promptTokens;
|
|
25179
|
+
state56.totalCompletionTokens += completionTokens;
|
|
25180
|
+
state56.totalTokens += total;
|
|
25181
|
+
state56.requestCount += 1;
|
|
25182
|
+
state56.lastRequest = {
|
|
24733
25183
|
model: modelName,
|
|
24734
25184
|
prompt: promptTokens,
|
|
24735
25185
|
completion: completionTokens,
|
|
24736
25186
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
24737
25187
|
};
|
|
24738
25188
|
if (cfg.limit <= 0) return;
|
|
24739
|
-
const percent =
|
|
24740
|
-
if (!
|
|
24741
|
-
|
|
24742
|
-
const remaining = cfg.limit -
|
|
25189
|
+
const percent = state56.totalTokens / cfg.limit * 100;
|
|
25190
|
+
if (!state56.warningFired && percent >= cfg.warnPercent) {
|
|
25191
|
+
state56.warningFired = true;
|
|
25192
|
+
const remaining = Math.max(cfg.limit - state56.totalTokens, 0);
|
|
24743
25193
|
api.log.info("token-budget: warning threshold reached", {
|
|
24744
25194
|
percent: Math.round(percent),
|
|
24745
25195
|
remaining
|
|
@@ -24747,45 +25197,46 @@ var plugin62 = {
|
|
|
24747
25197
|
api.emitCustom("token-budget:warning", {
|
|
24748
25198
|
percent: Math.round(percent),
|
|
24749
25199
|
remaining,
|
|
24750
|
-
total:
|
|
25200
|
+
total: state56.totalTokens,
|
|
24751
25201
|
limit: cfg.limit
|
|
24752
25202
|
});
|
|
24753
25203
|
}
|
|
24754
|
-
if (!
|
|
24755
|
-
|
|
25204
|
+
if (!state56.stopFired && percent >= cfg.stopPercent) {
|
|
25205
|
+
state56.stopFired = true;
|
|
24756
25206
|
api.log.warn("token-budget: hard limit reached \u2014 agent loop will be stopped", {
|
|
24757
|
-
total:
|
|
25207
|
+
total: state56.totalTokens,
|
|
24758
25208
|
limit: cfg.limit
|
|
24759
25209
|
});
|
|
24760
25210
|
api.emitCustom("token-budget:limit_reached", {
|
|
24761
|
-
total:
|
|
25211
|
+
total: state56.totalTokens,
|
|
24762
25212
|
limit: cfg.limit
|
|
24763
25213
|
});
|
|
24764
25214
|
}
|
|
24765
25215
|
});
|
|
24766
|
-
|
|
24767
|
-
if (cfg.limit <= 0 || !
|
|
25216
|
+
state56.hookUnregister = api.registerHook("Stop", void 0, () => {
|
|
25217
|
+
if (cfg.limit <= 0 || !state56.stopFired) return;
|
|
24768
25218
|
return {
|
|
24769
25219
|
decision: "block",
|
|
24770
|
-
reason: `token-budget: session token limit reached (${
|
|
25220
|
+
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
25221
|
};
|
|
24772
25222
|
});
|
|
24773
|
-
|
|
25223
|
+
state56.postHookUnregister = api.registerHook("PostToolUse", "*", () => {
|
|
24774
25224
|
if (cfg.limit <= 0) return;
|
|
24775
|
-
const percent = Math.round(
|
|
24776
|
-
const remaining = Math.max(cfg.limit -
|
|
24777
|
-
if (
|
|
24778
|
-
|
|
25225
|
+
const percent = Math.round(state56.totalTokens / cfg.limit * 100);
|
|
25226
|
+
const remaining = Math.max(cfg.limit - state56.totalTokens, 0);
|
|
25227
|
+
if (state56.stopFired && !state56.stopContextInjected) {
|
|
25228
|
+
state56.stopContextInjected = true;
|
|
25229
|
+
state56.warnContextInjected = true;
|
|
24779
25230
|
return {
|
|
24780
25231
|
additionalContext: `
|
|
24781
|
-
\u{1F6D1} token-budget: HARD LIMIT REACHED \u2014 ${
|
|
25232
|
+
\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
25233
|
};
|
|
24783
25234
|
}
|
|
24784
|
-
if (
|
|
24785
|
-
|
|
25235
|
+
if (state56.warningFired && !state56.warnContextInjected) {
|
|
25236
|
+
state56.warnContextInjected = true;
|
|
24786
25237
|
return {
|
|
24787
25238
|
additionalContext: `
|
|
24788
|
-
\u26A0\uFE0F token-budget: ${percent}% of budget used (${
|
|
25239
|
+
\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
25240
|
};
|
|
24790
25241
|
}
|
|
24791
25242
|
return;
|
|
@@ -24798,7 +25249,7 @@ var plugin62 = {
|
|
|
24798
25249
|
category: "Meta",
|
|
24799
25250
|
mutating: false,
|
|
24800
25251
|
async execute() {
|
|
24801
|
-
const consumed =
|
|
25252
|
+
const consumed = state56.totalTokens;
|
|
24802
25253
|
const limit = cfg.limit;
|
|
24803
25254
|
const percent = limit > 0 ? Math.round(consumed / limit * 100) : 0;
|
|
24804
25255
|
const remaining = limit > 0 ? Math.max(limit - consumed, 0) : Infinity;
|
|
@@ -24808,7 +25259,7 @@ var plugin62 = {
|
|
|
24808
25259
|
consumed,
|
|
24809
25260
|
remaining,
|
|
24810
25261
|
percent,
|
|
24811
|
-
requestCount:
|
|
25262
|
+
requestCount: state56.requestCount,
|
|
24812
25263
|
// The EFFECTIVE thresholds, after out-of-range values fall back to
|
|
24813
25264
|
// the defaults and warn/stop are ordered. Without these the user
|
|
24814
25265
|
// cannot tell that a rejected or clamped setting is not in force.
|
|
@@ -24816,12 +25267,12 @@ var plugin62 = {
|
|
|
24816
25267
|
stopPercent: cfg.stopPercent,
|
|
24817
25268
|
model: cfg.model === "" ? null : cfg.model,
|
|
24818
25269
|
breakdown: {
|
|
24819
|
-
prompt:
|
|
24820
|
-
completion:
|
|
25270
|
+
prompt: state56.totalPromptTokens,
|
|
25271
|
+
completion: state56.totalCompletionTokens
|
|
24821
25272
|
},
|
|
24822
|
-
warningFired:
|
|
24823
|
-
stopFired:
|
|
24824
|
-
lastRequest:
|
|
25273
|
+
warningFired: state56.warningFired,
|
|
25274
|
+
stopFired: state56.stopFired,
|
|
25275
|
+
lastRequest: state56.lastRequest
|
|
24825
25276
|
};
|
|
24826
25277
|
}
|
|
24827
25278
|
});
|
|
@@ -24835,30 +25286,30 @@ var plugin62 = {
|
|
|
24835
25286
|
teardown(api) {
|
|
24836
25287
|
clearRegistrations3();
|
|
24837
25288
|
const final = {
|
|
24838
|
-
totalTokens:
|
|
24839
|
-
requestCount:
|
|
24840
|
-
warningFired:
|
|
24841
|
-
stopFired:
|
|
25289
|
+
totalTokens: state56.totalTokens,
|
|
25290
|
+
requestCount: state56.requestCount,
|
|
25291
|
+
warningFired: state56.warningFired,
|
|
25292
|
+
stopFired: state56.stopFired
|
|
24842
25293
|
};
|
|
24843
|
-
|
|
24844
|
-
|
|
24845
|
-
|
|
24846
|
-
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
25294
|
+
state56.totalTokens = 0;
|
|
25295
|
+
state56.totalPromptTokens = 0;
|
|
25296
|
+
state56.totalCompletionTokens = 0;
|
|
25297
|
+
state56.requestCount = 0;
|
|
25298
|
+
state56.warningFired = false;
|
|
25299
|
+
state56.stopFired = false;
|
|
25300
|
+
state56.warnContextInjected = false;
|
|
25301
|
+
state56.stopContextInjected = false;
|
|
25302
|
+
state56.lastRequest = null;
|
|
24852
25303
|
api.log.info("token-budget: teardown complete", { final });
|
|
24853
25304
|
},
|
|
24854
25305
|
async health() {
|
|
24855
25306
|
return {
|
|
24856
25307
|
ok: true,
|
|
24857
|
-
message:
|
|
24858
|
-
totalTokens:
|
|
24859
|
-
requestCount:
|
|
24860
|
-
warningFired:
|
|
24861
|
-
stopFired:
|
|
25308
|
+
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}`,
|
|
25309
|
+
totalTokens: state56.totalTokens,
|
|
25310
|
+
requestCount: state56.requestCount,
|
|
25311
|
+
warningFired: state56.warningFired,
|
|
25312
|
+
stopFired: state56.stopFired
|
|
24862
25313
|
};
|
|
24863
25314
|
}
|
|
24864
25315
|
};
|
|
@@ -24905,7 +25356,7 @@ function computeThrottleDelay(entries, now, limit, projected) {
|
|
|
24905
25356
|
const newest = sorted[sorted.length - 1];
|
|
24906
25357
|
return newest ? Math.max(0, newest.at + WINDOW_MS - now) : 0;
|
|
24907
25358
|
}
|
|
24908
|
-
var
|
|
25359
|
+
var state57 = {
|
|
24909
25360
|
window: [],
|
|
24910
25361
|
invocations: 0,
|
|
24911
25362
|
throttled: 0,
|
|
@@ -24978,16 +25429,16 @@ var plugin63 = {
|
|
|
24978
25429
|
}
|
|
24979
25430
|
},
|
|
24980
25431
|
setup(api) {
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
|
|
24984
|
-
|
|
24985
|
-
if (
|
|
25432
|
+
state57.window = [];
|
|
25433
|
+
state57.invocations = 0;
|
|
25434
|
+
state57.throttled = 0;
|
|
25435
|
+
state57.totalDelayMs = 0;
|
|
25436
|
+
if (state57.extensionUnregister) {
|
|
24986
25437
|
try {
|
|
24987
|
-
|
|
25438
|
+
state57.extensionUnregister();
|
|
24988
25439
|
} catch {
|
|
24989
25440
|
}
|
|
24990
|
-
|
|
25441
|
+
state57.extensionUnregister = null;
|
|
24991
25442
|
}
|
|
24992
25443
|
const cfg = readConfig55(api.config.extensions?.["token-throttle"]);
|
|
24993
25444
|
if (cfg.enabled) {
|
|
@@ -24996,21 +25447,21 @@ var plugin63 = {
|
|
|
24996
25447
|
kind: "throttle",
|
|
24997
25448
|
wraps: ["request"]
|
|
24998
25449
|
});
|
|
24999
|
-
|
|
25450
|
+
state57.extensionUnregister = api.extensions.register({
|
|
25000
25451
|
name: "token-throttle",
|
|
25001
25452
|
owner: "token-throttle",
|
|
25002
25453
|
async wrapProviderRunner(_ctx, request, inner) {
|
|
25003
25454
|
const signal = _ctx?.signal;
|
|
25004
25455
|
const req = request ?? {};
|
|
25005
|
-
|
|
25456
|
+
state57.invocations += 1;
|
|
25006
25457
|
const now = Date.now();
|
|
25007
|
-
|
|
25458
|
+
state57.window = pruneWindow(state57.window, now);
|
|
25008
25459
|
const projected = estimateRequestTokens(req, cfg.charsPerToken);
|
|
25009
|
-
const rawDelay = computeThrottleDelay(
|
|
25460
|
+
const rawDelay = computeThrottleDelay(state57.window, now, cfg.tokensPerMinute, projected);
|
|
25010
25461
|
const delay = Math.min(rawDelay, cfg.maxDelayMs);
|
|
25011
25462
|
if (delay > 0) {
|
|
25012
|
-
|
|
25013
|
-
|
|
25463
|
+
state57.throttled += 1;
|
|
25464
|
+
state57.totalDelayMs += delay;
|
|
25014
25465
|
api.metrics.counter("throttled");
|
|
25015
25466
|
api.metrics.histogram("delay_ms", delay);
|
|
25016
25467
|
api.log.info("token-throttle: delaying provider call", { delayMs: delay, projected });
|
|
@@ -25018,11 +25469,14 @@ var plugin63 = {
|
|
|
25018
25469
|
}
|
|
25019
25470
|
const response = await inner(_ctx, request);
|
|
25020
25471
|
const rawUsage = response?.usage;
|
|
25021
|
-
const
|
|
25022
|
-
const
|
|
25023
|
-
const
|
|
25472
|
+
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);
|
|
25473
|
+
const inputTokens = Number.isFinite(inputTokensRaw) ? inputTokensRaw : 0;
|
|
25474
|
+
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);
|
|
25475
|
+
const outputTokens = Number.isFinite(outputTokensRaw) ? outputTokensRaw : 0;
|
|
25476
|
+
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;
|
|
25477
|
+
const totalTokens = Number.isFinite(totalTokensRaw) ? totalTokensRaw : 0;
|
|
25024
25478
|
const used = totalTokens > 0 ? totalTokens : projected;
|
|
25025
|
-
|
|
25479
|
+
state57.window.push({ at: Date.now(), tokens: used });
|
|
25026
25480
|
return response;
|
|
25027
25481
|
}
|
|
25028
25482
|
});
|
|
@@ -25036,7 +25490,7 @@ var plugin63 = {
|
|
|
25036
25490
|
mutating: false,
|
|
25037
25491
|
async execute() {
|
|
25038
25492
|
const now = Date.now();
|
|
25039
|
-
const live = pruneWindow(
|
|
25493
|
+
const live = pruneWindow(state57.window, now);
|
|
25040
25494
|
return {
|
|
25041
25495
|
ok: true,
|
|
25042
25496
|
enabled: cfg.enabled,
|
|
@@ -25045,9 +25499,9 @@ var plugin63 = {
|
|
|
25045
25499
|
windowSpend: windowSpend(live),
|
|
25046
25500
|
windowEntries: live.length,
|
|
25047
25501
|
counters: {
|
|
25048
|
-
invocations:
|
|
25049
|
-
throttled:
|
|
25050
|
-
totalDelayMs:
|
|
25502
|
+
invocations: state57.invocations,
|
|
25503
|
+
throttled: state57.throttled,
|
|
25504
|
+
totalDelayMs: state57.totalDelayMs
|
|
25051
25505
|
}
|
|
25052
25506
|
};
|
|
25053
25507
|
}
|
|
@@ -25059,32 +25513,32 @@ var plugin63 = {
|
|
|
25059
25513
|
});
|
|
25060
25514
|
},
|
|
25061
25515
|
teardown(api) {
|
|
25062
|
-
if (
|
|
25516
|
+
if (state57.extensionUnregister) {
|
|
25063
25517
|
try {
|
|
25064
|
-
|
|
25518
|
+
state57.extensionUnregister();
|
|
25065
25519
|
} catch {
|
|
25066
25520
|
}
|
|
25067
|
-
|
|
25521
|
+
state57.extensionUnregister = null;
|
|
25068
25522
|
}
|
|
25069
25523
|
const final = {
|
|
25070
|
-
invocations:
|
|
25071
|
-
throttled:
|
|
25072
|
-
totalDelayMs:
|
|
25524
|
+
invocations: state57.invocations,
|
|
25525
|
+
throttled: state57.throttled,
|
|
25526
|
+
totalDelayMs: state57.totalDelayMs
|
|
25073
25527
|
};
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
|
|
25077
|
-
|
|
25528
|
+
state57.window = [];
|
|
25529
|
+
state57.invocations = 0;
|
|
25530
|
+
state57.throttled = 0;
|
|
25531
|
+
state57.totalDelayMs = 0;
|
|
25078
25532
|
api.log.info("token-throttle: teardown complete", { final });
|
|
25079
25533
|
},
|
|
25080
25534
|
async health() {
|
|
25081
25535
|
return {
|
|
25082
25536
|
ok: true,
|
|
25083
|
-
message: `token-throttle: ${
|
|
25537
|
+
message: `token-throttle: ${state57.throttled} throttle(s) of ${state57.invocations} call(s), ${state57.totalDelayMs}ms total delay`,
|
|
25084
25538
|
counters: {
|
|
25085
|
-
invocations:
|
|
25086
|
-
throttled:
|
|
25087
|
-
totalDelayMs:
|
|
25539
|
+
invocations: state57.invocations,
|
|
25540
|
+
throttled: state57.throttled,
|
|
25541
|
+
totalDelayMs: state57.totalDelayMs
|
|
25088
25542
|
}
|
|
25089
25543
|
};
|
|
25090
25544
|
}
|
|
@@ -25094,7 +25548,7 @@ var token_throttle_default = plugin63;
|
|
|
25094
25548
|
// src/type-gate/index.ts
|
|
25095
25549
|
import { existsSync as existsSync8 } from "node:fs";
|
|
25096
25550
|
var API_VERSION41 = "^0.1.10";
|
|
25097
|
-
var
|
|
25551
|
+
var state58 = {
|
|
25098
25552
|
invocationCount: 0,
|
|
25099
25553
|
runCount: 0,
|
|
25100
25554
|
passCount: 0,
|
|
@@ -25260,14 +25714,14 @@ var plugin64 = {
|
|
|
25260
25714
|
}
|
|
25261
25715
|
},
|
|
25262
25716
|
setup(api) {
|
|
25263
|
-
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
25268
|
-
|
|
25269
|
-
|
|
25270
|
-
|
|
25717
|
+
state58.invocationCount = 0;
|
|
25718
|
+
state58.runCount = 0;
|
|
25719
|
+
state58.passCount = 0;
|
|
25720
|
+
state58.failCount = 0;
|
|
25721
|
+
state58.errorCount = 0;
|
|
25722
|
+
state58.skippedCount = 0;
|
|
25723
|
+
state58.lastResult = null;
|
|
25724
|
+
state58.hookUnregister = (0, runtime_exports.releaseHandle)(state58.hookUnregister);
|
|
25271
25725
|
const cfg = readConfig56(api.config.extensions?.["type-gate"]);
|
|
25272
25726
|
const hook = async (input) => {
|
|
25273
25727
|
if (!cfg.enabled) return;
|
|
@@ -25279,27 +25733,27 @@ var plugin64 = {
|
|
|
25279
25733
|
if (!(0, runtime_exports.withinProject)(sourcePath)) return;
|
|
25280
25734
|
const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
|
|
25281
25735
|
if (!runOnChangeSet2.has(ext)) {
|
|
25282
|
-
|
|
25736
|
+
state58.skippedCount += 1;
|
|
25283
25737
|
return;
|
|
25284
25738
|
}
|
|
25285
|
-
|
|
25739
|
+
state58.invocationCount += 1;
|
|
25286
25740
|
const result = await runTypeCheck(cfg);
|
|
25287
25741
|
if (!result) {
|
|
25288
|
-
|
|
25742
|
+
state58.errorCount += 1;
|
|
25289
25743
|
return;
|
|
25290
25744
|
}
|
|
25291
|
-
|
|
25292
|
-
|
|
25745
|
+
state58.runCount += 1;
|
|
25746
|
+
state58.lastResult = {
|
|
25293
25747
|
passed: result.passed,
|
|
25294
25748
|
errorCount: result.errorCount,
|
|
25295
25749
|
durationMs: result.durationMs,
|
|
25296
25750
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
25297
25751
|
};
|
|
25298
25752
|
if (result.passed) {
|
|
25299
|
-
|
|
25753
|
+
state58.passCount += 1;
|
|
25300
25754
|
return;
|
|
25301
25755
|
}
|
|
25302
|
-
|
|
25756
|
+
state58.failCount += 1;
|
|
25303
25757
|
const errorList = result.errors.map((e) => ` \u274C ${e}`).join("\n");
|
|
25304
25758
|
const message = `
|
|
25305
25759
|
\u274C type-gate: Type check failed after editing ${sourcePath} (${result.durationMs}ms).
|
|
@@ -25314,7 +25768,7 @@ Fix the type error(s) or adjust the change.`;
|
|
|
25314
25768
|
}
|
|
25315
25769
|
return { additionalContext: message };
|
|
25316
25770
|
};
|
|
25317
|
-
|
|
25771
|
+
state58.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
|
|
25318
25772
|
background: true
|
|
25319
25773
|
});
|
|
25320
25774
|
api.tools.register({
|
|
@@ -25334,14 +25788,14 @@ Fix the type error(s) or adjust the change.`;
|
|
|
25334
25788
|
maxErrors: cfg.maxErrors,
|
|
25335
25789
|
runOnChange: cfg.runOnChange,
|
|
25336
25790
|
counters: {
|
|
25337
|
-
invocations:
|
|
25338
|
-
runs:
|
|
25339
|
-
passed:
|
|
25340
|
-
failed:
|
|
25341
|
-
errors:
|
|
25342
|
-
skipped:
|
|
25791
|
+
invocations: state58.invocationCount,
|
|
25792
|
+
runs: state58.runCount,
|
|
25793
|
+
passed: state58.passCount,
|
|
25794
|
+
failed: state58.failCount,
|
|
25795
|
+
errors: state58.errorCount,
|
|
25796
|
+
skipped: state58.skippedCount
|
|
25343
25797
|
},
|
|
25344
|
-
lastResult:
|
|
25798
|
+
lastResult: state58.lastResult
|
|
25345
25799
|
};
|
|
25346
25800
|
}
|
|
25347
25801
|
});
|
|
@@ -25353,43 +25807,43 @@ Fix the type error(s) or adjust the change.`;
|
|
|
25353
25807
|
});
|
|
25354
25808
|
},
|
|
25355
25809
|
teardown(api) {
|
|
25356
|
-
if (
|
|
25810
|
+
if (state58.hookUnregister) {
|
|
25357
25811
|
try {
|
|
25358
|
-
|
|
25812
|
+
state58.hookUnregister();
|
|
25359
25813
|
} catch {
|
|
25360
25814
|
}
|
|
25361
|
-
|
|
25815
|
+
state58.hookUnregister = null;
|
|
25362
25816
|
}
|
|
25363
25817
|
const final = {
|
|
25364
|
-
invocations:
|
|
25365
|
-
runs:
|
|
25366
|
-
passed:
|
|
25367
|
-
failed:
|
|
25368
|
-
errors:
|
|
25369
|
-
skipped:
|
|
25818
|
+
invocations: state58.invocationCount,
|
|
25819
|
+
runs: state58.runCount,
|
|
25820
|
+
passed: state58.passCount,
|
|
25821
|
+
failed: state58.failCount,
|
|
25822
|
+
errors: state58.errorCount,
|
|
25823
|
+
skipped: state58.skippedCount
|
|
25370
25824
|
};
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25825
|
+
state58.invocationCount = 0;
|
|
25826
|
+
state58.runCount = 0;
|
|
25827
|
+
state58.passCount = 0;
|
|
25828
|
+
state58.failCount = 0;
|
|
25829
|
+
state58.errorCount = 0;
|
|
25830
|
+
state58.skippedCount = 0;
|
|
25831
|
+
state58.lastResult = null;
|
|
25378
25832
|
api.log.info("type-gate: teardown complete", { final });
|
|
25379
25833
|
},
|
|
25380
25834
|
async health() {
|
|
25381
25835
|
return {
|
|
25382
25836
|
ok: true,
|
|
25383
|
-
message:
|
|
25837
|
+
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
25838
|
counters: {
|
|
25385
|
-
invocations:
|
|
25386
|
-
runs:
|
|
25387
|
-
passed:
|
|
25388
|
-
failed:
|
|
25389
|
-
errors:
|
|
25390
|
-
skipped:
|
|
25391
|
-
},
|
|
25392
|
-
lastResult:
|
|
25839
|
+
invocations: state58.invocationCount,
|
|
25840
|
+
runs: state58.runCount,
|
|
25841
|
+
passed: state58.passCount,
|
|
25842
|
+
failed: state58.failCount,
|
|
25843
|
+
errors: state58.errorCount,
|
|
25844
|
+
skipped: state58.skippedCount
|
|
25845
|
+
},
|
|
25846
|
+
lastResult: state58.lastResult
|
|
25393
25847
|
};
|
|
25394
25848
|
}
|
|
25395
25849
|
};
|