@wrongstack/plugins 1.0.7 → 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
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @public
|
|
26
26
|
*/
|
|
27
|
-
import type
|
|
27
|
+
import { type Plugin } from '@wrongstack/core/types';
|
|
28
28
|
export type A11yRule = 'missing-alt' | 'missing-input-label' | 'low-contrast-placeholder' | 'missing-button-text' | 'duplicate-id';
|
|
29
29
|
export interface A11yFinding {
|
|
30
30
|
file: string;
|
|
@@ -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;
|
package/dist/agent-handoff.js
CHANGED
|
@@ -238,8 +238,8 @@ var plugin = {
|
|
|
238
238
|
category: "Coordination",
|
|
239
239
|
mutating: false,
|
|
240
240
|
async execute(input) {
|
|
241
|
-
if (!cfg.enabled)
|
|
242
|
-
if (!mailbox)
|
|
241
|
+
if (!cfg.enabled) throw new Error("agent-handoff is disabled");
|
|
242
|
+
if (!mailbox) throw new Error("mailbox not available");
|
|
243
243
|
const raw = input;
|
|
244
244
|
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);
|
|
245
245
|
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);
|
|
@@ -266,10 +266,10 @@ var plugin = {
|
|
|
266
266
|
return { ok: true, messageId: result.id, to: recipient };
|
|
267
267
|
} catch (err) {
|
|
268
268
|
state.errorCount += 1;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
throw new Error(
|
|
270
|
+
`handoff_note: mailbox send to "${recipient}" failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
271
|
+
{ cause: err }
|
|
272
|
+
);
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
});
|
package/dist/auto-doc/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* the session default. Best-effort: any LLM failure (or a bad response)
|
|
15
15
|
* falls back to the template for that entity — a doc comment always lands.
|
|
16
16
|
*/
|
|
17
|
-
import type
|
|
17
|
+
import { type Plugin } from '@wrongstack/core/types';
|
|
18
18
|
declare const plugin: Plugin;
|
|
19
19
|
export default plugin;
|
|
20
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auto-doc.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/auto-doc/index.ts
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
3
4
|
var AUTO_DOC_API_VERSION = "^0.1.10";
|
|
4
5
|
function resolveProjectPath(rawPath, cwd = process.cwd()) {
|
|
5
6
|
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
@@ -163,41 +164,37 @@ function injectDocComment(content, entity, doc) {
|
|
|
163
164
|
async function runAutoDoc(input, api) {
|
|
164
165
|
const rawInput = input;
|
|
165
166
|
if (rawInput["files"] !== void 0 && !Array.isArray(rawInput["files"])) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
changes: []
|
|
171
|
-
};
|
|
167
|
+
throw new ToolValidationError({
|
|
168
|
+
message: "input.files must be an array of file paths",
|
|
169
|
+
field: "files"
|
|
170
|
+
});
|
|
172
171
|
}
|
|
173
172
|
const rawFiles = rawInput["files"] ?? rawInput["file"] ?? rawInput["path"] ?? rawInput["filePath"] ?? rawInput["TargetFile"] ?? rawInput["targetFile"];
|
|
174
173
|
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;
|
|
175
174
|
if (!files || !Array.isArray(files)) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
changes: []
|
|
181
|
-
};
|
|
175
|
+
throw new ToolValidationError({
|
|
176
|
+
message: "input.files must be an array of file paths",
|
|
177
|
+
field: "files"
|
|
178
|
+
});
|
|
182
179
|
}
|
|
183
180
|
if (files.length === 0) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
changes: []
|
|
189
|
-
};
|
|
181
|
+
throw new ToolValidationError({
|
|
182
|
+
message: "input.files is empty \u2014 provide at least one file path",
|
|
183
|
+
field: "files"
|
|
184
|
+
});
|
|
190
185
|
}
|
|
191
186
|
const extConfig = api.config.extensions?.["auto-doc"] ?? {};
|
|
192
187
|
const includeTypes = extConfig["includeTypes"] ?? false;
|
|
193
188
|
const useLlm = (input.use_llm ?? rawInput["useLlm"] ?? extConfig["useLlm"] ?? false) === true && Boolean(api.llm);
|
|
194
189
|
const maxLlmEntities = typeof extConfig["maxLlmEntities"] === "number" && extConfig["maxLlmEntities"] >= 0 ? extConfig["maxLlmEntities"] : 25;
|
|
195
190
|
const results = [];
|
|
191
|
+
const skipped = [];
|
|
196
192
|
let llmBudget = maxLlmEntities;
|
|
197
193
|
for (const rawFile of files) {
|
|
198
194
|
const safeFile = resolveProjectPath(rawFile);
|
|
199
195
|
if (!safeFile) {
|
|
200
196
|
api.log.warn(`auto-doc: skipped file outside project directory: ${rawFile}`);
|
|
197
|
+
skipped.push({ file: rawFile, reason: "outside project directory" });
|
|
201
198
|
continue;
|
|
202
199
|
}
|
|
203
200
|
try {
|
|
@@ -205,8 +202,12 @@ async function runAutoDoc(input, api) {
|
|
|
205
202
|
let content;
|
|
206
203
|
try {
|
|
207
204
|
content = readFileSync(safeFile, "utf-8");
|
|
208
|
-
} catch {
|
|
205
|
+
} catch (err) {
|
|
209
206
|
api.log.warn(`auto-doc: could not read file ${safeFile}`);
|
|
207
|
+
skipped.push({
|
|
208
|
+
file: safeFile,
|
|
209
|
+
reason: `could not read: ${err instanceof Error ? err.message : String(err)}`
|
|
210
|
+
});
|
|
210
211
|
continue;
|
|
211
212
|
}
|
|
212
213
|
const entities = parseSource(content);
|
|
@@ -246,11 +247,21 @@ async function runAutoDoc(input, api) {
|
|
|
246
247
|
}
|
|
247
248
|
} catch (err) {
|
|
248
249
|
api.log.error(`auto-doc: error processing ${safeFile}: ${err}`);
|
|
250
|
+
skipped.push({
|
|
251
|
+
file: safeFile,
|
|
252
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
253
|
+
});
|
|
249
254
|
}
|
|
250
255
|
}
|
|
256
|
+
if (skipped.length === files.length) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`auto_doc processed no files: ${skipped.map((s) => `${s.file} (${s.reason})`).join("; ")}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
251
261
|
return {
|
|
252
262
|
ok: true,
|
|
253
|
-
filesProcessed: files.length,
|
|
263
|
+
filesProcessed: files.length - skipped.length,
|
|
264
|
+
...skipped.length > 0 ? { skipped } : {},
|
|
254
265
|
changes: results,
|
|
255
266
|
llm: useLlm ? { docs: state.llmDocs, fallbacks: state.llmFallbacks } : void 0
|
|
256
267
|
};
|
|
@@ -14,6 +14,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
|
|
|
14
14
|
|
|
15
15
|
// src/auto-i18n-extractor/index.ts
|
|
16
16
|
import { readFileSync } from "node:fs";
|
|
17
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
17
18
|
|
|
18
19
|
// src/runtime/index.ts
|
|
19
20
|
var runtime_exports = {};
|
|
@@ -244,28 +245,31 @@ ${lines.join("\n")}${more}`;
|
|
|
244
245
|
category: "Diagnostics",
|
|
245
246
|
mutating: false,
|
|
246
247
|
async execute(input) {
|
|
247
|
-
if (!cfg.enabled)
|
|
248
|
+
if (!cfg.enabled) throw new Error("auto-i18n-extractor is disabled");
|
|
248
249
|
const raw = input;
|
|
249
250
|
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);
|
|
250
251
|
const filePath = typeof rawPath === "string" ? rawPath.trim() : "";
|
|
251
252
|
if (!filePath) {
|
|
252
|
-
|
|
253
|
+
throw new ToolValidationError({ message: "path is required", field: "path" });
|
|
253
254
|
}
|
|
254
255
|
if (!(0, runtime_exports.withinProject)(filePath)) {
|
|
255
|
-
|
|
256
|
+
throw new ToolValidationError({
|
|
257
|
+
message: "path must be inside the project",
|
|
258
|
+
field: "path"
|
|
259
|
+
});
|
|
256
260
|
}
|
|
257
261
|
const ext = fileExtension(filePath);
|
|
258
262
|
if (!cfg.fileExtensions.includes(ext)) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
};
|
|
263
|
+
throw new ToolValidationError({
|
|
264
|
+
message: `unsupported extension "${ext}"; allowed: ${cfg.fileExtensions.join(", ")}`,
|
|
265
|
+
field: "path"
|
|
266
|
+
});
|
|
263
267
|
}
|
|
264
268
|
state.filesScanned += 1;
|
|
265
269
|
const content = readSourceFile(filePath);
|
|
266
270
|
if (content === null) {
|
|
267
271
|
state.readErrors += 1;
|
|
268
|
-
|
|
272
|
+
throw new Error(`could not read ${filePath}`);
|
|
269
273
|
}
|
|
270
274
|
const extracted = extractStrings(content, cfg);
|
|
271
275
|
state.stringsFound += extracted.length;
|
package/dist/branch-guard.js
CHANGED
|
@@ -114,13 +114,13 @@ async function detectUncommittedChanges(cwd, signal) {
|
|
|
114
114
|
}
|
|
115
115
|
function detectGitCommand(command) {
|
|
116
116
|
const cmd = command.trim();
|
|
117
|
-
if (/\bgit\s+commit
|
|
117
|
+
if (/\bgit\s+commit(?![a-zA-Z0-9_-])/.test(cmd)) {
|
|
118
118
|
return { type: "commit", snippet: cmd.slice(0, 120) };
|
|
119
119
|
}
|
|
120
|
-
if (/\bgit\s+push
|
|
120
|
+
if (/\bgit\s+push(?![a-zA-Z0-9_-])/.test(cmd)) {
|
|
121
121
|
return { type: "push", snippet: cmd.slice(0, 120) };
|
|
122
122
|
}
|
|
123
|
-
if (/\bgit\s+merge
|
|
123
|
+
if (/\bgit\s+merge(?![a-zA-Z0-9_-])/.test(cmd)) {
|
|
124
124
|
return { type: "merge", snippet: cmd.slice(0, 120) };
|
|
125
125
|
}
|
|
126
126
|
return null;
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
*
|
|
44
44
|
* @public
|
|
45
45
|
*/
|
|
46
|
-
import type
|
|
46
|
+
import { type Plugin } from '@wrongstack/core/types';
|
|
47
47
|
type Section = 'Added' | 'Changed' | 'Fixed' | 'Removed' | 'Security' | 'Documentation' | 'Maintenance';
|
|
48
48
|
export interface ChangelogEntry {
|
|
49
49
|
section: Section;
|
package/dist/changelog-writer.js
CHANGED
|
@@ -15,6 +15,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
|
|
|
15
15
|
// src/changelog-writer/index.ts
|
|
16
16
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
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 = {};
|
|
@@ -318,11 +319,13 @@ var plugin = {
|
|
|
318
319
|
category: "Docs",
|
|
319
320
|
mutating: false,
|
|
320
321
|
async execute(input) {
|
|
321
|
-
if (!cfg.enabled)
|
|
322
|
+
if (!cfg.enabled) throw new Error("changelog-writer is disabled");
|
|
322
323
|
const raw = input ?? {};
|
|
323
324
|
const rawText = input.text ?? raw["message"] ?? raw["entry"] ?? raw["content"] ?? raw["description"] ?? raw["desc"] ?? raw["summary"] ?? raw["body"] ?? raw["note"] ?? raw["item"];
|
|
324
325
|
const text = String(rawText ?? "").trim();
|
|
325
|
-
if (!text)
|
|
326
|
+
if (!text) {
|
|
327
|
+
throw new ToolValidationError({ message: "entry text must not be empty", field: "text" });
|
|
328
|
+
}
|
|
326
329
|
const section = SECTION_ORDER.includes(input.section) ? input.section : "Changed";
|
|
327
330
|
addEntry({ section, text, origin: "manual", when: (/* @__PURE__ */ new Date()).toISOString() });
|
|
328
331
|
return { ok: true, pendingEntries: state.entries.length };
|
|
@@ -377,12 +380,12 @@ var plugin = {
|
|
|
377
380
|
category: "Docs",
|
|
378
381
|
mutating: true,
|
|
379
382
|
async execute(input) {
|
|
380
|
-
if (!cfg.enabled)
|
|
383
|
+
if (!cfg.enabled) throw new Error("changelog-writer is disabled");
|
|
381
384
|
if (!cfg.filePath) {
|
|
382
|
-
|
|
385
|
+
throw new Error("filePath must stay within the current project directory");
|
|
383
386
|
}
|
|
384
387
|
if (state.entries.length === 0) {
|
|
385
|
-
|
|
388
|
+
throw new Error("no pending entries \u2014 add some with changelog_add first");
|
|
386
389
|
}
|
|
387
390
|
const rawInput = input ?? {};
|
|
388
391
|
const shouldPolish = input.polish === true || rawInput["use_llm"] === true || rawInput["useLlm"] === true || rawInput["ai"] === true || rawInput["use_ai"] === true;
|
|
@@ -390,16 +393,22 @@ var plugin = {
|
|
|
390
393
|
let existing = null;
|
|
391
394
|
try {
|
|
392
395
|
existing = readFileSync(cfg.filePath, "utf-8");
|
|
393
|
-
} catch {
|
|
396
|
+
} catch (err) {
|
|
397
|
+
if (err.code !== "ENOENT") {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`failed to read ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
400
|
+
{ cause: err }
|
|
401
|
+
);
|
|
402
|
+
}
|
|
394
403
|
existing = null;
|
|
395
404
|
}
|
|
396
405
|
try {
|
|
397
406
|
writeFileSync(cfg.filePath, mergeIntoChangelog(existing, block));
|
|
398
407
|
} catch (err) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
408
|
+
throw new Error(
|
|
409
|
+
`failed to write ${cfg.filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
410
|
+
{ cause: err }
|
|
411
|
+
);
|
|
403
412
|
}
|
|
404
413
|
const written = state.entries.length;
|
|
405
414
|
state.entries = [];
|
package/dist/checkpoint.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/checkpoint/index.ts
|
|
2
2
|
import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
4
5
|
var state = {
|
|
5
6
|
snapshots: [],
|
|
6
7
|
nextId: 1,
|
|
@@ -83,7 +84,13 @@ async function captureFile(path, maxBytes) {
|
|
|
83
84
|
if (st.size > maxBytes) return "too-large";
|
|
84
85
|
const content = await readFile(path, "utf-8");
|
|
85
86
|
return { path, content, bytes: st.size };
|
|
86
|
-
} catch {
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err.code !== "ENOENT") {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`could not snapshot ${path}: ${err instanceof Error ? err.message : String(err)}`,
|
|
91
|
+
{ cause: err }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
87
94
|
return { path, content: null, bytes: 0 };
|
|
88
95
|
}
|
|
89
96
|
}
|
|
@@ -225,7 +232,7 @@ var plugin = {
|
|
|
225
232
|
category: "Safety",
|
|
226
233
|
mutating: false,
|
|
227
234
|
async execute(input) {
|
|
228
|
-
if (!cfg.enabled)
|
|
235
|
+
if (!cfg.enabled) throw new Error("checkpoint is disabled");
|
|
229
236
|
let paths = [];
|
|
230
237
|
const rawInput = input;
|
|
231
238
|
const raw = rawInput["paths"] ?? rawInput["path"] ?? rawInput["files"] ?? rawInput["file"] ?? rawInput["filePath"] ?? rawInput["file_path"] ?? rawInput["TargetFile"] ?? rawInput["targetFile"];
|
|
@@ -234,16 +241,25 @@ var plugin = {
|
|
|
234
241
|
} else if (Array.isArray(raw)) {
|
|
235
242
|
paths = raw.filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
236
243
|
}
|
|
237
|
-
if (paths.length === 0)
|
|
238
|
-
|
|
244
|
+
if (paths.length === 0) {
|
|
245
|
+
throw new ToolValidationError({ message: "paths must not be empty", field: "paths" });
|
|
246
|
+
}
|
|
239
247
|
const rejectedOutsideProject = [];
|
|
240
|
-
|
|
248
|
+
const safePaths = [];
|
|
241
249
|
for (const p of paths) {
|
|
242
250
|
const safePath = await resolveProjectPath(p);
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
if (safePath) safePaths.push(safePath);
|
|
252
|
+
else rejectedOutsideProject.push(p);
|
|
253
|
+
}
|
|
254
|
+
if (rejectedOutsideProject.length > 0) {
|
|
255
|
+
throw new ToolValidationError({
|
|
256
|
+
message: `paths must stay within the current project directory: ${rejectedOutsideProject.join(", ")}`,
|
|
257
|
+
field: "paths"
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const files = [];
|
|
261
|
+
let skipped = 0;
|
|
262
|
+
for (const safePath of safePaths) {
|
|
247
263
|
const captured = await captureFile(safePath, cfg.maxFileBytes);
|
|
248
264
|
if (captured === "too-large") {
|
|
249
265
|
skipped += 1;
|
|
@@ -252,15 +268,10 @@ var plugin = {
|
|
|
252
268
|
}
|
|
253
269
|
files.push(captured);
|
|
254
270
|
}
|
|
255
|
-
if (rejectedOutsideProject.length > 0) {
|
|
256
|
-
return {
|
|
257
|
-
ok: false,
|
|
258
|
-
error: "paths must stay within the current project directory",
|
|
259
|
-
rejectedOutsideProject
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
271
|
if (files.length === 0) {
|
|
263
|
-
|
|
272
|
+
throw new Error(
|
|
273
|
+
`all files were skipped (larger than maxFileBytes=${cfg.maxFileBytes}); nothing was snapshotted`
|
|
274
|
+
);
|
|
264
275
|
}
|
|
265
276
|
const snapshot = {
|
|
266
277
|
id: `cp-${state.nextId++}`,
|
|
@@ -335,21 +346,18 @@ var plugin = {
|
|
|
335
346
|
category: "Safety",
|
|
336
347
|
mutating: true,
|
|
337
348
|
async execute(input) {
|
|
338
|
-
if (!cfg.enabled)
|
|
349
|
+
if (!cfg.enabled) throw new Error("checkpoint is disabled");
|
|
339
350
|
const raw = input ?? {};
|
|
340
351
|
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);
|
|
341
352
|
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);
|
|
342
353
|
const snapshot = rawId ? state.snapshots.find((s) => s.id === rawId) : state.snapshots[state.snapshots.length - 1];
|
|
343
354
|
if (!snapshot) {
|
|
344
|
-
|
|
345
|
-
ok: false,
|
|
346
|
-
error: rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet"
|
|
347
|
-
};
|
|
355
|
+
throw new Error(rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet");
|
|
348
356
|
}
|
|
349
357
|
const targetPath = rawPath ? await resolveProjectPath(rawPath) ?? rawPath : null;
|
|
350
358
|
const targets = targetPath ? snapshot.files.filter((f) => f.path === targetPath || f.path === rawPath) : snapshot.files;
|
|
351
359
|
if (targets.length === 0) {
|
|
352
|
-
|
|
360
|
+
throw new Error(`snapshot ${snapshot.id} has no entry for "${rawPath}"`);
|
|
353
361
|
}
|
|
354
362
|
const restored = [];
|
|
355
363
|
const createdByTool = [];
|
|
@@ -371,8 +379,14 @@ var plugin = {
|
|
|
371
379
|
state.restores += 1;
|
|
372
380
|
api.metrics.counter("restores");
|
|
373
381
|
}
|
|
382
|
+
if (errors.length > 0) {
|
|
383
|
+
const failed = errors.map((e) => `${e.path} (${e.error})`).join("; ");
|
|
384
|
+
throw new Error(
|
|
385
|
+
`checkpoint_restore ${snapshot.id}: ${errors.length} file(s) failed to restore: ${failed}` + (restored.length > 0 ? `. Restored: ${restored.join(", ")}` : "")
|
|
386
|
+
);
|
|
387
|
+
}
|
|
374
388
|
return {
|
|
375
|
-
ok:
|
|
389
|
+
ok: true,
|
|
376
390
|
snapshotId: snapshot.id,
|
|
377
391
|
restored,
|
|
378
392
|
notRestoredFileDidNotExist: createdByTool,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* @public
|
|
25
25
|
*/
|
|
26
|
-
import type
|
|
26
|
+
import { type Plugin } from '@wrongstack/core/types';
|
|
27
27
|
/**
|
|
28
28
|
* Heuristic cyclomatic-complexity formula (per file, not per function):
|
|
29
29
|
* control keywords (if | else if | for | while | switch | catch)
|
package/dist/code-metrics.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/code-metrics/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 = {};
|
|
@@ -257,19 +258,26 @@ var plugin = {
|
|
|
257
258
|
category: "Diagnostics",
|
|
258
259
|
mutating: false,
|
|
259
260
|
async execute(input) {
|
|
260
|
-
if (!cfg.enabled)
|
|
261
|
+
if (!cfg.enabled) throw new Error("code-metrics is disabled");
|
|
261
262
|
const raw = input ?? {};
|
|
262
263
|
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) ?? ".";
|
|
263
264
|
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
264
|
-
|
|
265
|
+
throw new ToolValidationError({
|
|
266
|
+
message: "path is outside the project root",
|
|
267
|
+
field: "path"
|
|
268
|
+
});
|
|
265
269
|
}
|
|
266
270
|
state.measureCount += 1;
|
|
267
271
|
let result;
|
|
268
272
|
try {
|
|
273
|
+
await stat(resolve(process.cwd(), rawPath));
|
|
269
274
|
result = await measurePath(rawPath, cfg);
|
|
270
275
|
} catch (err) {
|
|
271
276
|
state.errorCount += 1;
|
|
272
|
-
|
|
277
|
+
throw new Error(
|
|
278
|
+
`measure_code_metrics failed for ${rawPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
279
|
+
{ cause: err }
|
|
280
|
+
);
|
|
273
281
|
}
|
|
274
282
|
state.fileCount += result.files.length;
|
|
275
283
|
return {
|
package/dist/commit-validator.js
CHANGED
|
@@ -144,11 +144,11 @@ function parseCommitMessage(message, cfg) {
|
|
|
144
144
|
}
|
|
145
145
|
var GIT_MESSAGE_FLAG_RE = new RegExp(
|
|
146
146
|
[
|
|
147
|
-
// -m "…" | -m '…' | -m=… ; --message "…" | --message='…' | --message=…
|
|
148
|
-
String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)"([^"]*)"`,
|
|
149
|
-
String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)'([^']*)'`,
|
|
147
|
+
// -m "…" | -am "…" | -m '…' | -m=… ; --message "…" | --message='…' | --message=…
|
|
148
|
+
String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)"([^"]*)"`,
|
|
149
|
+
String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)'([^']*)'`,
|
|
150
150
|
// Bare value: stops at whitespace or a shell separator.
|
|
151
|
-
String.raw`(?:^|\s)(?:-m|--message)(?:\s+|=)([^\s;&|"']+)`
|
|
151
|
+
String.raw`(?:^|\s)(?:-[a-zA-Z]*m|--message)(?:\s+|=)([^\s;&|"']+)`
|
|
152
152
|
].join("|"),
|
|
153
153
|
"g"
|
|
154
154
|
);
|
|
@@ -258,7 +258,7 @@ var plugin = {
|
|
|
258
258
|
} else if (toolName === "bash") {
|
|
259
259
|
const command = inp["command"] ?? inp["CommandLine"] ?? inp["cmd"] ?? inp["script"] ?? inp["input"];
|
|
260
260
|
if (typeof command !== "string") return;
|
|
261
|
-
if (!/\bgit\s+commit
|
|
261
|
+
if (!/\bgit\s+commit(?![a-zA-Z0-9_-])/.test(command)) return;
|
|
262
262
|
message = extractMessageFromBash(command);
|
|
263
263
|
if (!message) return;
|
|
264
264
|
} else {
|
package/dist/context-pins.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/context-pins/index.ts
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
4
5
|
import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
|
|
5
6
|
var state = {
|
|
6
7
|
pins: [],
|
|
@@ -141,16 +142,17 @@ var plugin = {
|
|
|
141
142
|
category: "Memory",
|
|
142
143
|
mutating: true,
|
|
143
144
|
async execute(input) {
|
|
144
|
-
if (!cfg.enabled)
|
|
145
|
+
if (!cfg.enabled) throw new Error("context-pins is disabled");
|
|
145
146
|
const raw = input ?? {};
|
|
146
147
|
const rawText = input.text ?? raw["pin"] ?? raw["content"] ?? raw["message"] ?? raw["note"] ?? raw["fact"] ?? raw["data"];
|
|
147
148
|
const text = String(rawText ?? "").trim();
|
|
148
|
-
if (!text)
|
|
149
|
+
if (!text) {
|
|
150
|
+
throw new ToolValidationError({ message: "pin text must not be empty", field: "text" });
|
|
151
|
+
}
|
|
149
152
|
if (state.pins.length >= cfg.maxPins) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
};
|
|
153
|
+
throw new Error(
|
|
154
|
+
`pin limit reached (${cfg.maxPins}). Remove a pin first with pin_remove.`
|
|
155
|
+
);
|
|
154
156
|
}
|
|
155
157
|
const pin = {
|
|
156
158
|
id: `pin-${state.nextId++}`,
|
|
@@ -179,16 +181,17 @@ var plugin = {
|
|
|
179
181
|
category: "Memory",
|
|
180
182
|
mutating: true,
|
|
181
183
|
async execute(input) {
|
|
182
|
-
if (!cfg.enabled)
|
|
184
|
+
if (!cfg.enabled) throw new Error("context-pins is disabled");
|
|
183
185
|
const raw = input ?? {};
|
|
184
186
|
const key = String(
|
|
185
187
|
input.id ?? input.label ?? raw["pinId"] ?? raw["pin_id"] ?? raw["name"] ?? raw["key"] ?? ""
|
|
186
188
|
).trim();
|
|
187
|
-
if (!key)
|
|
189
|
+
if (!key)
|
|
190
|
+
throw new ToolValidationError({ message: "id or label is required", field: "id" });
|
|
188
191
|
const before = state.pins.length;
|
|
189
192
|
state.pins = state.pins.filter((p) => p.id !== key && p.label !== key);
|
|
190
193
|
const removed = before - state.pins.length;
|
|
191
|
-
if (removed === 0)
|
|
194
|
+
if (removed === 0) throw new Error(`no pin matches "${key}"`);
|
|
192
195
|
state.removals += removed;
|
|
193
196
|
api.metrics.counter("removals", removed);
|
|
194
197
|
const persisted = await persistPins(cfg.filePath);
|