@wrongstack/plugins 0.277.2 → 0.280.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +838 -0
- package/dist/auto-doc.d.ts +8 -0
- package/dist/auto-doc.js +175 -13
- package/dist/auto-escalate.d.ts +45 -0
- package/dist/auto-escalate.js +190 -0
- package/dist/branch-guard.d.ts +33 -0
- package/dist/branch-guard.js +228 -0
- package/dist/changelog-writer.d.ts +73 -0
- package/dist/changelog-writer.js +369 -0
- package/dist/checkpoint.d.ts +55 -0
- package/dist/checkpoint.js +305 -0
- package/dist/commit-validator.d.ts +33 -0
- package/dist/commit-validator.js +315 -0
- package/dist/config-validator.d.ts +48 -0
- package/dist/config-validator.js +347 -0
- package/dist/context-pins.d.ts +45 -0
- package/dist/context-pins.js +240 -0
- package/dist/cost-tracker.d.ts +40 -1
- package/dist/cost-tracker.js +105 -4
- package/dist/dep-guard.d.ts +65 -0
- package/dist/dep-guard.js +316 -0
- package/dist/diff-summary.d.ts +36 -0
- package/dist/diff-summary.js +235 -0
- package/dist/error-lens.d.ts +67 -0
- package/dist/error-lens.js +280 -0
- package/dist/format-on-save.d.ts +35 -0
- package/dist/format-on-save.js +219 -0
- package/dist/git-autocommit.js +186 -26
- package/dist/import-organizer.d.ts +52 -0
- package/dist/import-organizer.js +274 -0
- package/dist/index.d.ts +32 -6
- package/dist/index.js +10151 -1628
- package/dist/injection-shield.d.ts +49 -0
- package/dist/injection-shield.js +205 -0
- package/dist/lint-gate.d.ts +33 -0
- package/dist/lint-gate.js +394 -0
- package/dist/llm-cache.d.ts +56 -0
- package/dist/llm-cache.js +251 -0
- package/dist/loop-breaker.d.ts +43 -0
- package/dist/loop-breaker.js +241 -0
- package/dist/model-router.d.ts +69 -0
- package/dist/model-router.js +198 -0
- package/dist/notify-hub.d.ts +45 -0
- package/dist/notify-hub.js +304 -0
- package/dist/path-guard.d.ts +54 -0
- package/dist/path-guard.js +235 -0
- package/dist/prompt-firewall.d.ts +57 -0
- package/dist/prompt-firewall.js +290 -0
- package/dist/secret-scanner.d.ts +34 -0
- package/dist/secret-scanner.js +409 -0
- package/dist/semver-bump.js +45 -0
- package/dist/session-recap.d.ts +50 -0
- package/dist/session-recap.js +421 -0
- package/dist/shell-check.js +52 -4
- package/dist/spec-linker.d.ts +51 -0
- package/dist/spec-linker.js +541 -0
- package/dist/template-engine.js +19 -1
- package/dist/test-runner-gate.d.ts +37 -0
- package/dist/test-runner-gate.js +356 -0
- package/dist/todo-listener.d.ts +37 -0
- package/dist/todo-listener.js +216 -0
- package/dist/todo-tracker.d.ts +5 -0
- package/dist/todo-tracker.js +441 -0
- package/dist/token-budget.d.ts +40 -0
- package/dist/token-budget.js +254 -0
- package/dist/token-throttle.d.ts +54 -0
- package/dist/token-throttle.js +203 -0
- package/package.json +116 -12
- package/dist/json-path.d.ts +0 -18
- package/dist/json-path.js +0 -15
- package/dist/web-search.d.ts +0 -19
- package/dist/web-search.js +0 -15
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'fs';
|
|
2
|
+
import * as fsp from 'fs/promises';
|
|
3
|
+
import 'child_process';
|
|
4
|
+
import 'path';
|
|
5
|
+
import '@wrongstack/core';
|
|
6
|
+
import 'os';
|
|
7
|
+
import 'crypto';
|
|
8
|
+
import '@wrongstack/core/utils';
|
|
9
|
+
|
|
10
|
+
// src/spec-linker/index.ts
|
|
11
|
+
var plugin = {
|
|
12
|
+
name: "auto-doc"};
|
|
13
|
+
var auto_doc_default = plugin;
|
|
14
|
+
var plugin2 = {
|
|
15
|
+
name: "auto-escalate"};
|
|
16
|
+
var auto_escalate_default = plugin2;
|
|
17
|
+
var plugin3 = {
|
|
18
|
+
name: "branch-guard"};
|
|
19
|
+
var branch_guard_default = plugin3;
|
|
20
|
+
var plugin4 = {
|
|
21
|
+
name: "changelog-writer"};
|
|
22
|
+
var changelog_writer_default = plugin4;
|
|
23
|
+
var plugin5 = {
|
|
24
|
+
name: "checkpoint"};
|
|
25
|
+
var checkpoint_default = plugin5;
|
|
26
|
+
var plugin6 = {
|
|
27
|
+
name: "commit-validator"};
|
|
28
|
+
var commit_validator_default = plugin6;
|
|
29
|
+
var plugin7 = {
|
|
30
|
+
name: "config-validator"};
|
|
31
|
+
var config_validator_default = plugin7;
|
|
32
|
+
var plugin8 = {
|
|
33
|
+
name: "context-pins"};
|
|
34
|
+
var context_pins_default = plugin8;
|
|
35
|
+
var plugin9 = {
|
|
36
|
+
name: "cost-tracker"};
|
|
37
|
+
var cost_tracker_default = plugin9;
|
|
38
|
+
({
|
|
39
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40
|
+
});
|
|
41
|
+
var plugin10 = {
|
|
42
|
+
name: "cron"};
|
|
43
|
+
var cron_default = plugin10;
|
|
44
|
+
var plugin11 = {
|
|
45
|
+
name: "dep-guard"};
|
|
46
|
+
var dep_guard_default = plugin11;
|
|
47
|
+
var plugin12 = {
|
|
48
|
+
name: "diff-summary"};
|
|
49
|
+
var diff_summary_default = plugin12;
|
|
50
|
+
var plugin13 = {
|
|
51
|
+
name: "error-lens"};
|
|
52
|
+
var error_lens_default = plugin13;
|
|
53
|
+
var plugin14 = {
|
|
54
|
+
name: "file-watcher"};
|
|
55
|
+
var file_watcher_default = plugin14;
|
|
56
|
+
var plugin15 = {
|
|
57
|
+
name: "format-on-save"};
|
|
58
|
+
var format_on_save_default = plugin15;
|
|
59
|
+
var plugin16 = {
|
|
60
|
+
name: "git-autocommit"};
|
|
61
|
+
var git_autocommit_default = plugin16;
|
|
62
|
+
var plugin17 = {
|
|
63
|
+
name: "import-organizer"};
|
|
64
|
+
var import_organizer_default = plugin17;
|
|
65
|
+
var plugin18 = {
|
|
66
|
+
name: "injection-shield"};
|
|
67
|
+
var injection_shield_default = plugin18;
|
|
68
|
+
var plugin19 = {
|
|
69
|
+
name: "lint-gate"};
|
|
70
|
+
var lint_gate_default = plugin19;
|
|
71
|
+
var plugin20 = {
|
|
72
|
+
name: "llm-cache"};
|
|
73
|
+
var llm_cache_default = plugin20;
|
|
74
|
+
var plugin21 = {
|
|
75
|
+
name: "loop-breaker"};
|
|
76
|
+
var loop_breaker_default = plugin21;
|
|
77
|
+
var plugin22 = {
|
|
78
|
+
name: "model-router"};
|
|
79
|
+
var model_router_default = plugin22;
|
|
80
|
+
var plugin23 = {
|
|
81
|
+
name: "notify-hub"};
|
|
82
|
+
var notify_hub_default = plugin23;
|
|
83
|
+
var plugin24 = {
|
|
84
|
+
name: "path-guard"};
|
|
85
|
+
var path_guard_default = plugin24;
|
|
86
|
+
var plugin25 = {
|
|
87
|
+
name: "prompt-firewall"};
|
|
88
|
+
var prompt_firewall_default = plugin25;
|
|
89
|
+
|
|
90
|
+
// src/secret-scanner/index.ts
|
|
91
|
+
var BASE_PATTERNS = [
|
|
92
|
+
// LLM provider keys
|
|
93
|
+
{ type: "anthropic_key", regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
94
|
+
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
95
|
+
// GitHub
|
|
96
|
+
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
97
|
+
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
|
|
98
|
+
// AWS
|
|
99
|
+
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
|
|
100
|
+
// GCP
|
|
101
|
+
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
|
|
102
|
+
// Slack
|
|
103
|
+
{ type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
104
|
+
// Stripe
|
|
105
|
+
{ type: "stripe_key", regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g },
|
|
106
|
+
// Twilio
|
|
107
|
+
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
108
|
+
// Telegram
|
|
109
|
+
{
|
|
110
|
+
type: "telegram_bot_token",
|
|
111
|
+
regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
112
|
+
},
|
|
113
|
+
// JWT
|
|
114
|
+
{
|
|
115
|
+
type: "jwt",
|
|
116
|
+
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
117
|
+
},
|
|
118
|
+
// Private keys
|
|
119
|
+
{
|
|
120
|
+
type: "private_key",
|
|
121
|
+
regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----(?!\S)/g
|
|
122
|
+
},
|
|
123
|
+
// AI/ML provider tokens
|
|
124
|
+
{ type: "huggingface_token", regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g },
|
|
125
|
+
{ type: "replicate_token", regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
126
|
+
{ type: "perplexity_key", regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
127
|
+
{ type: "groq_key", regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
128
|
+
// Bearer tokens
|
|
129
|
+
{
|
|
130
|
+
type: "bearer_token",
|
|
131
|
+
regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
|
|
132
|
+
},
|
|
133
|
+
// Database URIs
|
|
134
|
+
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
|
|
135
|
+
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
136
|
+
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
137
|
+
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g }
|
|
138
|
+
];
|
|
139
|
+
var PATTERNS3 = [...BASE_PATTERNS];
|
|
140
|
+
buildCombinedRegex(PATTERNS3);
|
|
141
|
+
function buildCombinedRegex(patterns) {
|
|
142
|
+
return new RegExp(
|
|
143
|
+
patterns.map((p) => `(${p.regex.source})`).join("|"),
|
|
144
|
+
"g"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
var plugin26 = {
|
|
148
|
+
name: "secret-scanner"};
|
|
149
|
+
var secret_scanner_default = plugin26;
|
|
150
|
+
var plugin27 = {
|
|
151
|
+
name: "semver-bump"};
|
|
152
|
+
var semver_bump_default = plugin27;
|
|
153
|
+
var plugin28 = {
|
|
154
|
+
name: "session-recap"};
|
|
155
|
+
var session_recap_default = plugin28;
|
|
156
|
+
var plugin29 = {
|
|
157
|
+
name: "shell-check"};
|
|
158
|
+
var shell_check_default = plugin29;
|
|
159
|
+
var plugin30 = {
|
|
160
|
+
name: "template-engine"};
|
|
161
|
+
var template_engine_default = plugin30;
|
|
162
|
+
var plugin31 = {
|
|
163
|
+
name: "test-runner-gate"};
|
|
164
|
+
var test_runner_gate_default = plugin31;
|
|
165
|
+
var plugin32 = {
|
|
166
|
+
name: "todo-listener"};
|
|
167
|
+
var todo_listener_default = plugin32;
|
|
168
|
+
var plugin33 = {
|
|
169
|
+
name: "todo-tracker"};
|
|
170
|
+
var todo_tracker_default = plugin33;
|
|
171
|
+
var plugin34 = {
|
|
172
|
+
name: "token-budget"};
|
|
173
|
+
var token_budget_default = plugin34;
|
|
174
|
+
var plugin35 = {
|
|
175
|
+
name: "token-throttle"};
|
|
176
|
+
var token_throttle_default = plugin35;
|
|
177
|
+
|
|
178
|
+
// src/catalog.ts
|
|
179
|
+
var ENTRIES = [
|
|
180
|
+
{ name: auto_doc_default.name, path: "./src/auto-doc" },
|
|
181
|
+
{ name: git_autocommit_default.name, path: "./src/git-autocommit" },
|
|
182
|
+
{ name: shell_check_default.name, path: "./src/shell-check" },
|
|
183
|
+
{ name: cost_tracker_default.name, path: "./src/cost-tracker" },
|
|
184
|
+
{ name: file_watcher_default.name, path: "./src/file-watcher" },
|
|
185
|
+
{ name: cron_default.name, path: "./src/cron" },
|
|
186
|
+
{ name: template_engine_default.name, path: "./src/template-engine" },
|
|
187
|
+
{ name: semver_bump_default.name, path: "./src/semver-bump" },
|
|
188
|
+
{ name: secret_scanner_default.name, path: "./src/secret-scanner" },
|
|
189
|
+
{ name: todo_tracker_default.name, path: "./src/todo-tracker" },
|
|
190
|
+
{ name: token_budget_default.name, path: "./src/token-budget" },
|
|
191
|
+
{ name: lint_gate_default.name, path: "./src/lint-gate" },
|
|
192
|
+
{ name: branch_guard_default.name, path: "./src/branch-guard" },
|
|
193
|
+
{ name: diff_summary_default.name, path: "./src/diff-summary" },
|
|
194
|
+
{ name: commit_validator_default.name, path: "./src/commit-validator" },
|
|
195
|
+
{ name: format_on_save_default.name, path: "./src/format-on-save" },
|
|
196
|
+
{ name: test_runner_gate_default.name, path: "./src/test-runner-gate" },
|
|
197
|
+
{ name: import_organizer_default.name, path: "./src/import-organizer" },
|
|
198
|
+
{ name: todo_listener_default.name, path: "./src/todo-listener" },
|
|
199
|
+
{ name: session_recap_default.name, path: "./src/session-recap" },
|
|
200
|
+
{ name: "spec-linker", path: "./src/spec-linker" },
|
|
201
|
+
{ name: loop_breaker_default.name, path: "./src/loop-breaker" },
|
|
202
|
+
{ name: path_guard_default.name, path: "./src/path-guard" },
|
|
203
|
+
{ name: context_pins_default.name, path: "./src/context-pins" },
|
|
204
|
+
{ name: checkpoint_default.name, path: "./src/checkpoint" },
|
|
205
|
+
{ name: error_lens_default.name, path: "./src/error-lens" },
|
|
206
|
+
{ name: dep_guard_default.name, path: "./src/dep-guard" },
|
|
207
|
+
{ name: config_validator_default.name, path: "./src/config-validator" },
|
|
208
|
+
{ name: notify_hub_default.name, path: "./src/notify-hub" },
|
|
209
|
+
{ name: changelog_writer_default.name, path: "./src/changelog-writer" },
|
|
210
|
+
{ name: injection_shield_default.name, path: "./src/injection-shield" },
|
|
211
|
+
{ name: llm_cache_default.name, path: "./src/llm-cache" },
|
|
212
|
+
{ name: model_router_default.name, path: "./src/model-router" },
|
|
213
|
+
{ name: prompt_firewall_default.name, path: "./src/prompt-firewall" },
|
|
214
|
+
{ name: auto_escalate_default.name, path: "./src/auto-escalate" },
|
|
215
|
+
{ name: token_throttle_default.name, path: "./src/token-throttle" }
|
|
216
|
+
];
|
|
217
|
+
function assertValidCatalog(entries) {
|
|
218
|
+
for (const e of entries) {
|
|
219
|
+
if (typeof e.name !== "string" || e.name.length === 0) {
|
|
220
|
+
throw new Error(`plugin catalog: entry has invalid name: ${JSON.stringify(e)}`);
|
|
221
|
+
}
|
|
222
|
+
if (!/^[a-z0-9-]+$/.test(e.name)) {
|
|
223
|
+
throw new Error(`plugin catalog: name "${e.name}" is not kebab-case`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const seen = /* @__PURE__ */ new Set();
|
|
227
|
+
for (const e of entries) {
|
|
228
|
+
if (seen.has(e.name)) {
|
|
229
|
+
throw new Error(`plugin catalog: duplicate entry for "${e.name}"`);
|
|
230
|
+
}
|
|
231
|
+
seen.add(e.name);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
assertValidCatalog(ENTRIES);
|
|
235
|
+
var PLUGIN_CATALOG = (() => {
|
|
236
|
+
const m = /* @__PURE__ */ new Map();
|
|
237
|
+
for (const e of ENTRIES) m.set(e.name, e.path);
|
|
238
|
+
return m;
|
|
239
|
+
})();
|
|
240
|
+
var PLUGIN_CATALOG_ENTRIES = Object.freeze(
|
|
241
|
+
ENTRIES.map((e) => Object.freeze({ ...e }))
|
|
242
|
+
);
|
|
243
|
+
var PLUGIN_NAMES = PLUGIN_CATALOG_ENTRIES.map((e) => e.name);
|
|
244
|
+
|
|
245
|
+
// src/spec-linker/index.ts
|
|
246
|
+
var state32 = {
|
|
247
|
+
postInvocations: 0,
|
|
248
|
+
preInvocations: 0,
|
|
249
|
+
unlinkedCount: 0,
|
|
250
|
+
cleanCount: 0,
|
|
251
|
+
skippedNonMd: 0,
|
|
252
|
+
readErrorCount: 0,
|
|
253
|
+
autoFixApplied: 0,
|
|
254
|
+
postHookUnregister: null,
|
|
255
|
+
preHookUnregister: null
|
|
256
|
+
};
|
|
257
|
+
var DEFAULTS25 = {
|
|
258
|
+
enabled: true,
|
|
259
|
+
fileGlobs: ["**/*.md", "**/*.mdx"],
|
|
260
|
+
maxReferences: 8,
|
|
261
|
+
autoFix: false
|
|
262
|
+
};
|
|
263
|
+
function readConfig27(raw) {
|
|
264
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS25 };
|
|
265
|
+
const r = raw;
|
|
266
|
+
return {
|
|
267
|
+
enabled: r["enabled"] !== false,
|
|
268
|
+
fileGlobs: Array.isArray(r["fileGlobs"]) ? r["fileGlobs"].filter((g) => typeof g === "string") : DEFAULTS25.fileGlobs,
|
|
269
|
+
maxReferences: typeof r["maxReferences"] === "number" && r["maxReferences"] > 0 ? r["maxReferences"] : DEFAULTS25.maxReferences,
|
|
270
|
+
autoFix: r["autoFix"] === true
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function fileMatchesGlobs(filePath, globs) {
|
|
274
|
+
const lower = filePath.toLowerCase();
|
|
275
|
+
return globs.some((g) => {
|
|
276
|
+
const normalized = g.replace(/\\/g, "/").toLowerCase();
|
|
277
|
+
const pattern = normalized.replace(/^\*\*\//, "");
|
|
278
|
+
if (pattern.startsWith("*.")) {
|
|
279
|
+
return lower.endsWith(pattern.slice(1));
|
|
280
|
+
}
|
|
281
|
+
return lower.includes(pattern);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function isWrappedAsLinkOrCode(line, name) {
|
|
285
|
+
const lower = line.toLowerCase();
|
|
286
|
+
const target = name.toLowerCase();
|
|
287
|
+
if (lower.includes(`[${target}](`)) return true;
|
|
288
|
+
if (lower.includes(`\`${target}\``)) return true;
|
|
289
|
+
if (lower.includes(`[\``) && lower.includes(`\`](`)) return true;
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
function escapeRegExp(s) {
|
|
293
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
294
|
+
}
|
|
295
|
+
function findUnlinkedReferences(lines, names) {
|
|
296
|
+
const found = /* @__PURE__ */ new Map();
|
|
297
|
+
for (const line of lines) {
|
|
298
|
+
if (line.length === 0) continue;
|
|
299
|
+
for (const name of names) {
|
|
300
|
+
const re = new RegExp(`(^|[^\\w-])${escapeRegExp(name)}(?![\\w-])`, "i");
|
|
301
|
+
if (re.test(line) && !isWrappedAsLinkOrCode(line, name)) {
|
|
302
|
+
if (!found.has(name)) found.set(name, true);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return [...found.keys()];
|
|
307
|
+
}
|
|
308
|
+
function wrapUnlinkedReferences(content) {
|
|
309
|
+
const lines = content.split("\n");
|
|
310
|
+
let changed = false;
|
|
311
|
+
for (let i = 0; i < lines.length; i++) {
|
|
312
|
+
const line = lines[i];
|
|
313
|
+
if (line.length === 0) continue;
|
|
314
|
+
const newLine = wrapLineReferences(line);
|
|
315
|
+
if (newLine !== line) {
|
|
316
|
+
lines[i] = newLine;
|
|
317
|
+
changed = true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return changed ? lines.join("\n") : content;
|
|
321
|
+
}
|
|
322
|
+
function wrapLineReferences(line) {
|
|
323
|
+
let out = "";
|
|
324
|
+
let cursor = 0;
|
|
325
|
+
const spans = [];
|
|
326
|
+
for (const name of PLUGIN_NAMES) {
|
|
327
|
+
const re = new RegExp(`(^|[^\\w-])(${escapeRegExp(name)})(?![\\w-])`, "gi");
|
|
328
|
+
let m;
|
|
329
|
+
re.lastIndex = 0;
|
|
330
|
+
while ((m = re.exec(line)) !== null) {
|
|
331
|
+
const leadingLen = m[1].length;
|
|
332
|
+
const nameStart = m.index + leadingLen;
|
|
333
|
+
const nameEnd = nameStart + m[2].length;
|
|
334
|
+
const originalName = line.slice(nameStart, nameEnd);
|
|
335
|
+
if (isWrappedAsLinkOrCode(line, originalName)) continue;
|
|
336
|
+
if (spans.some((s) => !(nameEnd <= s.start || nameStart >= s.end))) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
spans.push({ start: nameStart, end: nameEnd, name: originalName });
|
|
340
|
+
re.lastIndex = nameEnd;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (spans.length === 0) return line;
|
|
344
|
+
spans.sort((a, b) => a.start - b.start);
|
|
345
|
+
for (const span of spans) {
|
|
346
|
+
out += line.slice(cursor, span.start);
|
|
347
|
+
const path2 = PLUGIN_CATALOG.get(span.name.toLowerCase()) ?? `./src/${span.name.toLowerCase()}`;
|
|
348
|
+
out += `[${span.name}](${path2})`;
|
|
349
|
+
cursor = span.end;
|
|
350
|
+
}
|
|
351
|
+
out += line.slice(cursor);
|
|
352
|
+
return out;
|
|
353
|
+
}
|
|
354
|
+
var plugin36 = {
|
|
355
|
+
name: "spec-linker",
|
|
356
|
+
version: "0.2.0",
|
|
357
|
+
description: "Markdown link auditor for plugin references. PostToolUse surfaces unlinked references; PreToolUse on `write` (autoFix) wraps them in markdown links via modifiedInput.",
|
|
358
|
+
apiVersion: "^0.1.10",
|
|
359
|
+
capabilities: { tools: true, hooks: true },
|
|
360
|
+
defaultConfig: { ...DEFAULTS25 },
|
|
361
|
+
configSchema: {
|
|
362
|
+
type: "object",
|
|
363
|
+
properties: {
|
|
364
|
+
enabled: { type: "boolean", default: true, description: "Master switch." },
|
|
365
|
+
fileGlobs: {
|
|
366
|
+
type: "array",
|
|
367
|
+
items: { type: "string" },
|
|
368
|
+
default: DEFAULTS25.fileGlobs,
|
|
369
|
+
description: "Glob patterns to match (markdown by default)."
|
|
370
|
+
},
|
|
371
|
+
maxReferences: {
|
|
372
|
+
type: "number",
|
|
373
|
+
minimum: 1,
|
|
374
|
+
maximum: 50,
|
|
375
|
+
default: 8,
|
|
376
|
+
description: "Hard cap on the number of unlinked references in the injected context."
|
|
377
|
+
},
|
|
378
|
+
autoFix: {
|
|
379
|
+
type: "boolean",
|
|
380
|
+
default: false,
|
|
381
|
+
description: "When true, the PreToolUse hook on `write` returns a `modifiedInput.content` where each unlinked plugin reference is wrapped in a markdown link. Default false (opt in)."
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
setup(api) {
|
|
386
|
+
state32.postInvocations = 0;
|
|
387
|
+
state32.preInvocations = 0;
|
|
388
|
+
state32.unlinkedCount = 0;
|
|
389
|
+
state32.cleanCount = 0;
|
|
390
|
+
state32.skippedNonMd = 0;
|
|
391
|
+
state32.readErrorCount = 0;
|
|
392
|
+
state32.autoFixApplied = 0;
|
|
393
|
+
state32.postHookUnregister = null;
|
|
394
|
+
state32.preHookUnregister = null;
|
|
395
|
+
const cfg = readConfig27(api.config.extensions?.["spec-linker"]);
|
|
396
|
+
const postHook = async (input) => {
|
|
397
|
+
if (!cfg.enabled) return;
|
|
398
|
+
if (input.toolResult?.isError) return;
|
|
399
|
+
const toolName = input.toolName ?? "";
|
|
400
|
+
if (toolName !== "write" && toolName !== "edit") return;
|
|
401
|
+
const inp = input.toolInput ?? {};
|
|
402
|
+
const filePath = inp.path;
|
|
403
|
+
if (!filePath || typeof filePath !== "string") return;
|
|
404
|
+
if (!fileMatchesGlobs(filePath, cfg.fileGlobs)) {
|
|
405
|
+
state32.skippedNonMd += 1;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
state32.postInvocations += 1;
|
|
409
|
+
if (!existsSync(filePath)) return;
|
|
410
|
+
let content;
|
|
411
|
+
try {
|
|
412
|
+
const stat = statSync(filePath);
|
|
413
|
+
if (!stat.isFile()) return;
|
|
414
|
+
content = await fsp.readFile(filePath, "utf-8");
|
|
415
|
+
} catch {
|
|
416
|
+
state32.readErrorCount += 1;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const unlinked = findUnlinkedReferences(content.split("\n"), [...PLUGIN_NAMES]);
|
|
420
|
+
if (unlinked.length === 0) {
|
|
421
|
+
state32.cleanCount += 1;
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
state32.unlinkedCount += 1;
|
|
425
|
+
const limited = unlinked.slice(0, cfg.maxReferences);
|
|
426
|
+
const overflow = unlinked.length - limited.length;
|
|
427
|
+
const lines = limited.map((name) => `- \`${name}\` \u2192 \`[${name}](${PLUGIN_CATALOG.get(name) ?? `./src/${name}`})\``).join("\n");
|
|
428
|
+
const overflowNote = overflow > 0 ? `
|
|
429
|
+
- \u2026and ${overflow} more` : "";
|
|
430
|
+
return {
|
|
431
|
+
additionalContext: `
|
|
432
|
+
\u{1F517} spec-linker: ${unlinked.length} unlinked plugin reference(s) in '${filePath}'. Consider wrapping them in markdown links to keep the docs navigable:
|
|
433
|
+
${lines}${overflowNote}`
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
state32.postHookUnregister = api.registerHook("PostToolUse", "write|edit", postHook);
|
|
437
|
+
if (cfg.autoFix) {
|
|
438
|
+
const preHook = async (input) => {
|
|
439
|
+
if (!cfg.enabled) return;
|
|
440
|
+
if (input.toolName !== "write") return;
|
|
441
|
+
const inp = input.toolInput ?? {};
|
|
442
|
+
const filePath = inp.path;
|
|
443
|
+
if (!filePath || typeof filePath !== "string") return;
|
|
444
|
+
if (!fileMatchesGlobs(filePath, cfg.fileGlobs)) return;
|
|
445
|
+
if (typeof inp.content !== "string" || inp.content.length === 0) return;
|
|
446
|
+
state32.preInvocations += 1;
|
|
447
|
+
const fixed = wrapUnlinkedReferences(inp.content);
|
|
448
|
+
if (fixed === inp.content) return;
|
|
449
|
+
state32.autoFixApplied += 1;
|
|
450
|
+
return {
|
|
451
|
+
decision: "allow",
|
|
452
|
+
modifiedInput: { ...inp, content: fixed, path: filePath },
|
|
453
|
+
additionalContext: `
|
|
454
|
+
\u{1F517} spec-linker (autoFix): wrapped unlinked plugin reference(s) in '${filePath}'.`
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
state32.preHookUnregister = api.registerHook("PreToolUse", "write", preHook);
|
|
458
|
+
}
|
|
459
|
+
api.tools.register({
|
|
460
|
+
name: "spec_linker_status",
|
|
461
|
+
description: "Reports spec-linker state: config, counters (post + pre hooks), and the canonical plugin catalog used for detection.",
|
|
462
|
+
inputSchema: { type: "object", properties: {} },
|
|
463
|
+
permission: "auto",
|
|
464
|
+
category: "Diagnostics",
|
|
465
|
+
mutating: false,
|
|
466
|
+
async execute() {
|
|
467
|
+
return {
|
|
468
|
+
ok: true,
|
|
469
|
+
enabled: cfg.enabled,
|
|
470
|
+
fileGlobs: cfg.fileGlobs,
|
|
471
|
+
maxReferences: cfg.maxReferences,
|
|
472
|
+
autoFix: cfg.autoFix,
|
|
473
|
+
counters: {
|
|
474
|
+
postInvocations: state32.postInvocations,
|
|
475
|
+
preInvocations: state32.preInvocations,
|
|
476
|
+
unlinked: state32.unlinkedCount,
|
|
477
|
+
clean: state32.cleanCount,
|
|
478
|
+
skippedNonMd: state32.skippedNonMd,
|
|
479
|
+
readErrors: state32.readErrorCount,
|
|
480
|
+
autoFixApplied: state32.autoFixApplied
|
|
481
|
+
},
|
|
482
|
+
catalogSize: PLUGIN_NAMES.length
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
api.log.info("spec-linker plugin loaded", {
|
|
487
|
+
version: "0.2.0",
|
|
488
|
+
enabled: cfg.enabled,
|
|
489
|
+
fileGlobs: cfg.fileGlobs,
|
|
490
|
+
autoFix: cfg.autoFix,
|
|
491
|
+
catalogSize: PLUGIN_NAMES.length
|
|
492
|
+
});
|
|
493
|
+
},
|
|
494
|
+
teardown(api) {
|
|
495
|
+
for (const off of [state32.postHookUnregister, state32.preHookUnregister]) {
|
|
496
|
+
if (off) {
|
|
497
|
+
try {
|
|
498
|
+
off();
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
state32.postHookUnregister = null;
|
|
504
|
+
state32.preHookUnregister = null;
|
|
505
|
+
const final = {
|
|
506
|
+
postInvocations: state32.postInvocations,
|
|
507
|
+
preInvocations: state32.preInvocations,
|
|
508
|
+
unlinked: state32.unlinkedCount,
|
|
509
|
+
clean: state32.cleanCount,
|
|
510
|
+
skippedNonMd: state32.skippedNonMd,
|
|
511
|
+
readErrors: state32.readErrorCount,
|
|
512
|
+
autoFixApplied: state32.autoFixApplied
|
|
513
|
+
};
|
|
514
|
+
state32.postInvocations = 0;
|
|
515
|
+
state32.preInvocations = 0;
|
|
516
|
+
state32.unlinkedCount = 0;
|
|
517
|
+
state32.cleanCount = 0;
|
|
518
|
+
state32.skippedNonMd = 0;
|
|
519
|
+
state32.readErrorCount = 0;
|
|
520
|
+
state32.autoFixApplied = 0;
|
|
521
|
+
api.log.info("spec-linker: teardown complete", { final });
|
|
522
|
+
},
|
|
523
|
+
async health() {
|
|
524
|
+
return {
|
|
525
|
+
ok: true,
|
|
526
|
+
message: `spec-linker: post=${state32.postInvocations} pre=${state32.preInvocations}, unlinked=${state32.unlinkedCount}, autoFix=${state32.autoFixApplied}, clean=${state32.cleanCount}, non-md=${state32.skippedNonMd}`,
|
|
527
|
+
counters: {
|
|
528
|
+
postInvocations: state32.postInvocations,
|
|
529
|
+
preInvocations: state32.preInvocations,
|
|
530
|
+
unlinked: state32.unlinkedCount,
|
|
531
|
+
clean: state32.cleanCount,
|
|
532
|
+
skippedNonMd: state32.skippedNonMd,
|
|
533
|
+
readErrors: state32.readErrorCount,
|
|
534
|
+
autoFixApplied: state32.autoFixApplied
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
var spec_linker_default = plugin36;
|
|
540
|
+
|
|
541
|
+
export { spec_linker_default as default };
|
package/dist/template-engine.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isAbsolute } from 'path';
|
|
|
2
2
|
|
|
3
3
|
// src/template-engine/index.ts
|
|
4
4
|
var API_VERSION = "^0.1.10";
|
|
5
|
+
var templates = /* @__PURE__ */ new Map();
|
|
5
6
|
function expandTemplate(template, variables) {
|
|
6
7
|
let result = template;
|
|
7
8
|
result = result.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
@@ -71,7 +72,7 @@ var plugin = {
|
|
|
71
72
|
}
|
|
72
73
|
},
|
|
73
74
|
setup(api) {
|
|
74
|
-
|
|
75
|
+
templates.clear();
|
|
75
76
|
const autoEscapeHtml = api.config.extensions?.["template-engine"]?.["autoEscapeHtml"] ?? true;
|
|
76
77
|
api.tools.register({
|
|
77
78
|
name: "template_expand",
|
|
@@ -270,6 +271,23 @@ var plugin = {
|
|
|
270
271
|
}
|
|
271
272
|
]);
|
|
272
273
|
api.log.info("template-engine plugin loaded", { version: "0.1.0" });
|
|
274
|
+
},
|
|
275
|
+
teardown(api) {
|
|
276
|
+
const count = templates.size;
|
|
277
|
+
templates.clear();
|
|
278
|
+
api.log.info("template-engine: teardown complete", { cleared: count });
|
|
279
|
+
},
|
|
280
|
+
async health() {
|
|
281
|
+
let totalBytes = 0;
|
|
282
|
+
for (const t of templates.values()) {
|
|
283
|
+
totalBytes += t.content.length;
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
ok: true,
|
|
287
|
+
message: `template-engine: ${templates.size} saved template(s), ${totalBytes} bytes total`,
|
|
288
|
+
count: templates.size,
|
|
289
|
+
totalBytes
|
|
290
|
+
};
|
|
273
291
|
}
|
|
274
292
|
};
|
|
275
293
|
var template_engine_default = plugin;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* test-runner-gate plugin — PostToolUse hook that runs the relevant
|
|
5
|
+
* test file after every `write` or `edit` to a source file.
|
|
6
|
+
*
|
|
7
|
+
* Tools registered:
|
|
8
|
+
* - test_gate_status : Show config + per-session counters.
|
|
9
|
+
*
|
|
10
|
+
* Hooks registered:
|
|
11
|
+
* - PostToolUse with matcher `write|edit`. After the tool completes,
|
|
12
|
+
* maps the changed source file to its test file (using configurable
|
|
13
|
+
* patterns), runs `vitest run <test-file>` and injects the result
|
|
14
|
+
* as `additionalContext`.
|
|
15
|
+
*
|
|
16
|
+
* Config (`config.extensions['test-runner-gate']`):
|
|
17
|
+
*
|
|
18
|
+
* ```jsonc
|
|
19
|
+
* {
|
|
20
|
+
* "enabled": true,
|
|
21
|
+
* "command": "npx vitest run", // base test command
|
|
22
|
+
* "timeoutMs": 30000, // test process timeout
|
|
23
|
+
* "testFilePatterns": [ // how to derive test path from source
|
|
24
|
+
* "src/{path}.test.ts", // co-located: src/foo.ts → src/foo.test.ts
|
|
25
|
+
* "tests/{name}.test.ts", // mirror dir: src/foo.ts → tests/foo.test.ts
|
|
26
|
+
* "tests/{name}-exec.test.ts" // exec variant
|
|
27
|
+
* ],
|
|
28
|
+
* "injectOnPass": false // inject context when tests pass too?
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
declare const plugin: Plugin;
|
|
36
|
+
|
|
37
|
+
export { plugin as default };
|