@wrongstack/plugins 0.285.0 → 0.287.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 +20 -11
- package/dist/accessibility-auditor/index.d.ts.map +1 -1
- package/dist/accessibility-auditor.js +30 -26
- package/dist/api-compatibility-gate.js +1 -1
- package/dist/auto-doc.js +1 -1
- package/dist/auto-i18n-extractor/index.d.ts.map +1 -1
- package/dist/auto-i18n-extractor.js +21 -14
- package/dist/changelog-writer.js +1 -1
- package/dist/code-metrics/index.d.ts.map +1 -1
- package/dist/code-metrics.js +34 -27
- package/dist/commit-validator.js +1 -1
- package/dist/config-validator.js +1 -1
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron.js +29 -0
- package/dist/dead-code-detector/index.d.ts.map +1 -1
- package/dist/dead-code-detector.js +30 -22
- package/dist/dep-guard.js +1 -1
- package/dist/diff-summary/index.d.ts.map +1 -1
- package/dist/diff-summary.js +19 -11
- package/dist/doc-sync-guard/index.d.ts.map +1 -1
- package/dist/doc-sync-guard.js +24 -15
- package/dist/duplicate-code-detector/index.d.ts.map +1 -1
- package/dist/duplicate-code-detector.js +27 -18
- package/dist/error-lens.js +1 -1
- package/dist/feature-flag-tracker/index.d.ts.map +1 -1
- package/dist/feature-flag-tracker.js +27 -19
- package/dist/file-watcher/index.d.ts.map +1 -1
- package/dist/file-watcher.js +15 -2
- package/dist/format-on-save/index.d.ts.map +1 -1
- package/dist/format-on-save.js +19 -11
- package/dist/git-autocommit.js +1 -1
- package/dist/import-organizer/index.d.ts.map +1 -1
- package/dist/import-organizer.js +22 -13
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1007 -759
- package/dist/interface-contract-guard/index.d.ts.map +1 -1
- package/dist/interface-contract-guard.js +27 -19
- package/dist/license-audit-gate/index.d.ts.map +1 -1
- package/dist/license-audit-gate.js +1 -2
- package/dist/migration-planner/index.d.ts +12 -2
- package/dist/migration-planner/index.d.ts.map +1 -1
- package/dist/migration-planner.js +210 -28
- package/dist/pr-drafter.js +1 -1
- package/dist/refactor-suggester/index.d.ts.map +1 -1
- package/dist/refactor-suggester.js +37 -29
- package/dist/release-notes-generator/index.d.ts +3 -1
- package/dist/release-notes-generator/index.d.ts.map +1 -1
- package/dist/release-notes-generator.js +152 -12
- package/dist/runtime/index.d.ts +12 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/llm.d.ts +33 -0
- package/dist/runtime/llm.d.ts.map +1 -0
- package/dist/runtime.js +57 -2
- package/dist/schema-evolution-guard/index.d.ts.map +1 -1
- package/dist/schema-evolution-guard.js +21 -12
- package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
- package/dist/security-hotspot-scanner.js +25 -16
- package/dist/session-recap.js +1 -1
- package/dist/spec-linker.js +1007 -759
- package/dist/test-generator/index.d.ts +5 -3
- package/dist/test-generator/index.d.ts.map +1 -1
- package/dist/test-generator.js +183 -41
- package/dist/test-runner-gate/index.d.ts.map +1 -1
- package/dist/test-runner-gate.js +65 -43
- package/dist/type-gate/index.d.ts.map +1 -1
- package/dist/type-gate.js +5 -13
- package/package.json +3 -3
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
// src/dead-code-detector/index.ts
|
|
2
2
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { extname,
|
|
3
|
+
import { extname, join, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
function hasLeadingDash(arg) {
|
|
9
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
10
|
+
}
|
|
11
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
12
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
13
|
+
if (hasLeadingDash(candidate)) return false;
|
|
14
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
15
|
+
const rel = relative(projectRoot, resolved);
|
|
16
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17
|
+
}
|
|
18
|
+
function withinProject(p) {
|
|
19
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/dead-code-detector/index.ts
|
|
4
23
|
var API_VERSION = "^0.1.10";
|
|
5
24
|
var state = {
|
|
6
25
|
scanCount: 0,
|
|
@@ -51,7 +70,7 @@ function gatherFiles(root, depth, cfg) {
|
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
72
|
}
|
|
54
|
-
walk(
|
|
73
|
+
walk(resolve2(root), depth);
|
|
55
74
|
return files;
|
|
56
75
|
}
|
|
57
76
|
function stripNoise(content) {
|
|
@@ -73,9 +92,8 @@ var DECLARATION_EXPORT_RE = /export\s+(?:async\s+|abstract\s+)?(?:const|let|var|
|
|
|
73
92
|
var NAMED_EXPORT_RE = /export\s*\{([^}]+)\}/g;
|
|
74
93
|
function extractExports(content, filePath) {
|
|
75
94
|
const exports = [];
|
|
76
|
-
let match;
|
|
77
95
|
DECLARATION_EXPORT_RE.lastIndex = 0;
|
|
78
|
-
|
|
96
|
+
for (const match of content.matchAll(DECLARATION_EXPORT_RE)) {
|
|
79
97
|
exports.push({
|
|
80
98
|
identifier: match[1],
|
|
81
99
|
file: filePath,
|
|
@@ -84,7 +102,7 @@ function extractExports(content, filePath) {
|
|
|
84
102
|
});
|
|
85
103
|
}
|
|
86
104
|
NAMED_EXPORT_RE.lastIndex = 0;
|
|
87
|
-
|
|
105
|
+
for (const match of content.matchAll(NAMED_EXPORT_RE)) {
|
|
88
106
|
const line = lineNumber(content, match.index);
|
|
89
107
|
const names = match[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
90
108
|
for (const name of names) {
|
|
@@ -135,22 +153,12 @@ function scan(root, depth, cfg) {
|
|
|
135
153
|
}
|
|
136
154
|
return { findings: findUnusedSymbols(files), scannedFiles: files.length };
|
|
137
155
|
}
|
|
138
|
-
function withinProject(p) {
|
|
139
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
140
|
-
const root = process.cwd();
|
|
141
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
142
|
-
const rel = relative(root, resolved);
|
|
143
|
-
if (rel === "" || rel === ".") return true;
|
|
144
|
-
if (rel.startsWith("..")) return false;
|
|
145
|
-
if (isAbsolute(rel)) return false;
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
156
|
function resolveScanRoot(rawPath) {
|
|
149
|
-
const resolved =
|
|
157
|
+
const resolved = resolve2(process.cwd(), rawPath);
|
|
150
158
|
try {
|
|
151
159
|
const stats = statSync(resolved);
|
|
152
160
|
if (!stats.isDirectory()) {
|
|
153
|
-
return
|
|
161
|
+
return resolve2(resolved, "..");
|
|
154
162
|
}
|
|
155
163
|
} catch {
|
|
156
164
|
}
|
|
@@ -160,7 +168,7 @@ function toPosix(p) {
|
|
|
160
168
|
return p.replace(/\\/g, "/");
|
|
161
169
|
}
|
|
162
170
|
function relativePath(p) {
|
|
163
|
-
return toPosix(
|
|
171
|
+
return toPosix(relative2(process.cwd(), p));
|
|
164
172
|
}
|
|
165
173
|
var plugin = {
|
|
166
174
|
name: "dead-code-detector",
|
|
@@ -227,12 +235,12 @@ var plugin = {
|
|
|
227
235
|
let result;
|
|
228
236
|
try {
|
|
229
237
|
result = scan(scanRoot, 1, cfg);
|
|
230
|
-
} catch
|
|
238
|
+
} catch {
|
|
231
239
|
state.errorCount += 1;
|
|
232
240
|
return;
|
|
233
241
|
}
|
|
234
|
-
const changedFile =
|
|
235
|
-
const relevant = result.findings.filter((f) =>
|
|
242
|
+
const changedFile = resolve2(process.cwd(), sourcePath);
|
|
243
|
+
const relevant = result.findings.filter((f) => resolve2(f.file) === changedFile);
|
|
236
244
|
if (relevant.length === 0) {
|
|
237
245
|
state.missCount += 1;
|
|
238
246
|
return;
|
|
@@ -248,7 +256,7 @@ ${list}
|
|
|
248
256
|
Consider removing the export if it is not part of the public API.`;
|
|
249
257
|
return { additionalContext: message };
|
|
250
258
|
};
|
|
251
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
259
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
252
260
|
api.tools.register({
|
|
253
261
|
name: "dead_code_scan",
|
|
254
262
|
description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project.",
|
package/dist/dep-guard.js
CHANGED
|
@@ -138,7 +138,7 @@ var plugin = {
|
|
|
138
138
|
version: "0.1.0",
|
|
139
139
|
description: "Supervises dependency installs: blocks deny-listed packages, flags typosquat lookalikes, and optionally warns on unpinned versions",
|
|
140
140
|
apiVersion: "^0.1.10",
|
|
141
|
-
capabilities: { tools: true, hooks: true },
|
|
141
|
+
capabilities: { tools: true, hooks: true, llm: true },
|
|
142
142
|
defaultConfig: { ...DEFAULTS },
|
|
143
143
|
configSchema: {
|
|
144
144
|
type: "object",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwP/C,QAAA,MAAM,MAAM,EAAE,MA4Pb,CAAC;eAEa,MAAM"}
|
package/dist/diff-summary.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
// src/diff-summary/index.ts
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
// src/runtime/index.ts
|
|
5
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
6
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
function hasLeadingDash(arg) {
|
|
8
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
9
|
+
}
|
|
10
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
11
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
12
|
+
if (hasLeadingDash(candidate)) return false;
|
|
13
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
14
|
+
const rel = relative(projectRoot, resolved);
|
|
15
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
16
|
+
}
|
|
5
17
|
function withinProject(p) {
|
|
6
|
-
|
|
7
|
-
const root = process.cwd();
|
|
8
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
9
|
-
const rel = relative(root, resolved);
|
|
10
|
-
if (rel === "" || rel === ".") return true;
|
|
11
|
-
if (rel.startsWith("..")) return false;
|
|
12
|
-
if (isAbsolute(rel)) return false;
|
|
13
|
-
return true;
|
|
18
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
14
19
|
}
|
|
20
|
+
|
|
21
|
+
// src/diff-summary/index.ts
|
|
22
|
+
var API_VERSION = "^0.1.10";
|
|
15
23
|
var state = {
|
|
16
24
|
invocationCount: 0,
|
|
17
25
|
/** Times a diff was successfully generated and injected. */
|
|
@@ -243,7 +251,7 @@ var plugin = {
|
|
|
243
251
|
additionalContext: header + summary
|
|
244
252
|
};
|
|
245
253
|
};
|
|
246
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
254
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
247
255
|
api.tools.register({
|
|
248
256
|
name: "diff_summary_status",
|
|
249
257
|
description: "Reports diff-summary state: mode, maxLines, and per-session invocation/injected/fallback counters.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA0I/C,QAAA,MAAM,MAAM,EAAE,MAuJb,CAAC;eAEa,MAAM"}
|
package/dist/doc-sync-guard.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
// src/doc-sync-guard/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { basename as basename2, extname } from "node:path";
|
|
3
|
+
|
|
4
|
+
// src/runtime/index.ts
|
|
5
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
6
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
function hasLeadingDash(arg) {
|
|
8
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
9
|
+
}
|
|
10
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
11
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
12
|
+
if (hasLeadingDash(candidate)) return false;
|
|
13
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
14
|
+
const rel = relative(projectRoot, resolved);
|
|
15
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
16
|
+
}
|
|
17
|
+
function withinProject(p) {
|
|
18
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/doc-sync-guard/index.ts
|
|
3
22
|
var API_VERSION = "^0.1.10";
|
|
4
23
|
var state = {
|
|
5
24
|
changedFiles: [],
|
|
@@ -24,16 +43,6 @@ function readConfig(raw) {
|
|
|
24
43
|
maxTrackedFiles: typeof r["maxTrackedFiles"] === "number" && r["maxTrackedFiles"] >= 1 && r["maxTrackedFiles"] <= 200 ? r["maxTrackedFiles"] : DEFAULTS.maxTrackedFiles
|
|
25
44
|
};
|
|
26
45
|
}
|
|
27
|
-
function withinProject(p) {
|
|
28
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
29
|
-
const root = process.cwd();
|
|
30
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
31
|
-
const rel = relative(root, resolved);
|
|
32
|
-
if (rel === "" || rel === ".") return true;
|
|
33
|
-
if (rel.startsWith("..")) return false;
|
|
34
|
-
if (isAbsolute(rel)) return false;
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
46
|
function normalizeSlashes(p) {
|
|
38
47
|
return p.replace(/\\/g, "/");
|
|
39
48
|
}
|
|
@@ -45,14 +54,14 @@ function isPublicSource(p, extensions) {
|
|
|
45
54
|
if (!isSourceFile(p, extensions)) return false;
|
|
46
55
|
const norm = normalizeSlashes(p);
|
|
47
56
|
if (norm.includes("/node_modules/") || norm.startsWith("node_modules/")) return false;
|
|
48
|
-
const base =
|
|
57
|
+
const base = basename2(norm).toLowerCase();
|
|
49
58
|
if (/\.(test|spec)\./.test(base)) return false;
|
|
50
59
|
if (base.startsWith("_")) return false;
|
|
51
60
|
return true;
|
|
52
61
|
}
|
|
53
62
|
function isDocFile(p, docNames) {
|
|
54
63
|
const norm = normalizeSlashes(p);
|
|
55
|
-
const base =
|
|
64
|
+
const base = basename2(norm);
|
|
56
65
|
const lowerBase = base.toLowerCase();
|
|
57
66
|
if (docNames.some((name) => lowerBase === name.toLowerCase())) return true;
|
|
58
67
|
if (norm.includes("/docs/") || norm.startsWith("docs/")) return true;
|
|
@@ -70,7 +79,7 @@ function extractDocContent(toolInput) {
|
|
|
70
79
|
return void 0;
|
|
71
80
|
}
|
|
72
81
|
function referenceTokens(p) {
|
|
73
|
-
const base =
|
|
82
|
+
const base = basename2(p);
|
|
74
83
|
const withoutExt = base.replace(/\.[^.]+$/, "");
|
|
75
84
|
const tokens = [base];
|
|
76
85
|
if (withoutExt && withoutExt !== base) tokens.push(withoutExt);
|
|
@@ -155,7 +164,7 @@ Consider mentioning these changes in the documentation.`;
|
|
|
155
164
|
return { additionalContext: message };
|
|
156
165
|
}
|
|
157
166
|
};
|
|
158
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
167
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
159
168
|
api.tools.register({
|
|
160
169
|
name: "doc_sync_status",
|
|
161
170
|
description: "Reports doc-sync-guard state: tracked changed files, doc-write count, and warning count.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duplicate-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duplicate-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA8O/C,QAAA,MAAM,MAAM,EAAE,MA8Qb,CAAC;eAEa,MAAM"}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
// src/duplicate-code-detector/index.ts
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { extname, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
function hasLeadingDash(arg) {
|
|
9
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
10
|
+
}
|
|
11
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
12
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
13
|
+
if (hasLeadingDash(candidate)) return false;
|
|
14
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
15
|
+
const rel = relative(projectRoot, resolved);
|
|
16
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17
|
+
}
|
|
18
|
+
function withinProject(p) {
|
|
19
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/duplicate-code-detector/index.ts
|
|
4
23
|
var API_VERSION = "^0.1.10";
|
|
5
24
|
var state = {
|
|
6
25
|
scanCount: 0,
|
|
@@ -31,16 +50,6 @@ function readConfig(raw) {
|
|
|
31
50
|
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
|
|
32
51
|
};
|
|
33
52
|
}
|
|
34
|
-
function withinProject(p) {
|
|
35
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
36
|
-
const root = process.cwd();
|
|
37
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
38
|
-
const rel = relative(root, resolved);
|
|
39
|
-
if (rel === "" || rel === ".") return true;
|
|
40
|
-
if (rel.startsWith("..")) return false;
|
|
41
|
-
if (isAbsolute(rel)) return false;
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
53
|
function matchesExtension(p, exts) {
|
|
45
54
|
const ext = extname(p).toLowerCase();
|
|
46
55
|
return exts.includes(ext);
|
|
@@ -62,7 +71,7 @@ function collectSourceFiles(root, cfg) {
|
|
|
62
71
|
return;
|
|
63
72
|
}
|
|
64
73
|
for (const entry of entries) {
|
|
65
|
-
const full =
|
|
74
|
+
const full = resolve2(dir, entry);
|
|
66
75
|
let st;
|
|
67
76
|
try {
|
|
68
77
|
st = statSync(full);
|
|
@@ -83,7 +92,7 @@ function toPosix(p) {
|
|
|
83
92
|
return p.replace(/\\/g, "/");
|
|
84
93
|
}
|
|
85
94
|
function relativePath(p) {
|
|
86
|
-
return toPosix(
|
|
95
|
+
return toPosix(relative2(process.cwd(), p));
|
|
87
96
|
}
|
|
88
97
|
function removeInlineComments(line) {
|
|
89
98
|
return line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//, "");
|
|
@@ -141,7 +150,7 @@ function findDuplicates(files, minLines, maxFindings) {
|
|
|
141
150
|
}
|
|
142
151
|
function scanPath(rawPath, cfg) {
|
|
143
152
|
const root = process.cwd();
|
|
144
|
-
const resolved =
|
|
153
|
+
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
145
154
|
const filePaths = collectSourceFiles(resolved, cfg);
|
|
146
155
|
const files = /* @__PURE__ */ new Map();
|
|
147
156
|
for (const p of filePaths) {
|
|
@@ -226,7 +235,7 @@ var plugin = {
|
|
|
226
235
|
const now = Date.now();
|
|
227
236
|
const lastWarning = state.lastHookWarning.get(sourcePath);
|
|
228
237
|
if (lastWarning !== void 0 && now - lastWarning < 6e4) return;
|
|
229
|
-
const changedFile =
|
|
238
|
+
const changedFile = resolve2(process.cwd(), sourcePath);
|
|
230
239
|
let content;
|
|
231
240
|
try {
|
|
232
241
|
content = readFileSync(changedFile, "utf-8");
|
|
@@ -236,7 +245,7 @@ var plugin = {
|
|
|
236
245
|
}
|
|
237
246
|
const changedWindows = extractWindows(changedFile, content, cfg.minLines);
|
|
238
247
|
if (changedWindows.length === 0) return;
|
|
239
|
-
const projectRoot =
|
|
248
|
+
const projectRoot = resolve2(process.cwd());
|
|
240
249
|
let otherFiles;
|
|
241
250
|
try {
|
|
242
251
|
const filePaths = collectSourceFiles(projectRoot, cfg).filter((p) => p !== changedFile);
|
|
@@ -272,7 +281,7 @@ var plugin = {
|
|
|
272
281
|
contextAs: "separate"
|
|
273
282
|
};
|
|
274
283
|
};
|
|
275
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
284
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
276
285
|
api.tools.register({
|
|
277
286
|
name: "detect_duplicate_code",
|
|
278
287
|
description: "Scan source files for duplicated code blocks. Uses normalized-line fingerprinting to find identical multi-line blocks across files.",
|
|
@@ -302,7 +311,7 @@ var plugin = {
|
|
|
302
311
|
state.findingCount += result.findings.length;
|
|
303
312
|
return {
|
|
304
313
|
ok: true,
|
|
305
|
-
path: relativePath(
|
|
314
|
+
path: relativePath(resolve2(process.cwd(), rawPath)),
|
|
306
315
|
scannedFiles: result.scannedFiles,
|
|
307
316
|
minLines: cfg.minLines,
|
|
308
317
|
findings: result.findings
|
package/dist/error-lens.js
CHANGED
|
@@ -85,7 +85,7 @@ var plugin = {
|
|
|
85
85
|
version: "0.1.0",
|
|
86
86
|
description: "Distills failed command output into a compact digest (error line + project stack frames) and flags repeated failures",
|
|
87
87
|
apiVersion: "^0.1.10",
|
|
88
|
-
capabilities: { tools: true, hooks: true },
|
|
88
|
+
capabilities: { tools: true, hooks: true, llm: true },
|
|
89
89
|
defaultConfig: { ...DEFAULTS },
|
|
90
90
|
configSchema: {
|
|
91
91
|
type: "object",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feature-flag-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feature-flag-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAoLD,QAAA,MAAM,MAAM,EAAE,MA+Mb,CAAC;eAEa,MAAM"}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
// src/feature-flag-tracker/index.ts
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { extname, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
function hasLeadingDash(arg) {
|
|
9
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
10
|
+
}
|
|
11
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
12
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
13
|
+
if (hasLeadingDash(candidate)) return false;
|
|
14
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
15
|
+
const rel = relative(projectRoot, resolved);
|
|
16
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17
|
+
}
|
|
18
|
+
function withinProject(p) {
|
|
19
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/feature-flag-tracker/index.ts
|
|
4
23
|
var API_VERSION = "^0.1.10";
|
|
5
24
|
var state = {
|
|
6
25
|
scanCount: 0,
|
|
@@ -32,16 +51,6 @@ function readConfig(raw) {
|
|
|
32
51
|
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
|
|
33
52
|
};
|
|
34
53
|
}
|
|
35
|
-
function withinProject(p) {
|
|
36
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
37
|
-
const root = process.cwd();
|
|
38
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
39
|
-
const rel = relative(root, resolved);
|
|
40
|
-
if (rel === "" || rel === ".") return true;
|
|
41
|
-
if (rel.startsWith("..")) return false;
|
|
42
|
-
if (isAbsolute(rel)) return false;
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
54
|
function normalizeExtensions(exts) {
|
|
46
55
|
return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
47
56
|
}
|
|
@@ -66,7 +75,7 @@ function collectSourceFiles(root, exts) {
|
|
|
66
75
|
}
|
|
67
76
|
for (const entry of entries) {
|
|
68
77
|
if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
|
|
69
|
-
const full =
|
|
78
|
+
const full = resolve2(dir, entry);
|
|
70
79
|
let st;
|
|
71
80
|
try {
|
|
72
81
|
st = statSync(full);
|
|
@@ -87,7 +96,7 @@ function toPosix(p) {
|
|
|
87
96
|
return p.replace(/\\/g, "/");
|
|
88
97
|
}
|
|
89
98
|
function relativePath(p) {
|
|
90
|
-
return toPosix(
|
|
99
|
+
return toPosix(relative2(process.cwd(), p));
|
|
91
100
|
}
|
|
92
101
|
function compilePatterns(patterns) {
|
|
93
102
|
const sources = [...DEFAULT_PATTERNS, ...patterns];
|
|
@@ -98,8 +107,7 @@ function scanFile(filePath, content, patterns, maxFindings) {
|
|
|
98
107
|
const lines = content.split(/\r?\n/);
|
|
99
108
|
for (const re of patterns) {
|
|
100
109
|
re.lastIndex = 0;
|
|
101
|
-
|
|
102
|
-
while ((m = re.exec(content)) !== null) {
|
|
110
|
+
for (const m of content.matchAll(re)) {
|
|
103
111
|
const flag = m[1] ?? m[0];
|
|
104
112
|
const lineNo = content.slice(0, m.index).split(/\r?\n/).length;
|
|
105
113
|
const context = (lines[lineNo - 1] ?? "").trim();
|
|
@@ -118,7 +126,7 @@ function scanFile(filePath, content, patterns, maxFindings) {
|
|
|
118
126
|
}
|
|
119
127
|
function scanPath(rawPath, cfg) {
|
|
120
128
|
const root = process.cwd();
|
|
121
|
-
const resolved =
|
|
129
|
+
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
122
130
|
const exts = normalizeExtensions(cfg.extensions);
|
|
123
131
|
const files = collectSourceFiles(resolved, exts);
|
|
124
132
|
const patterns = compilePatterns(cfg.patterns);
|
|
@@ -189,7 +197,7 @@ var plugin = {
|
|
|
189
197
|
const exts = normalizeExtensions(cfg.extensions);
|
|
190
198
|
if (!matchesExtension(sourcePath, exts)) return;
|
|
191
199
|
state.hookInvocationCount += 1;
|
|
192
|
-
const resolved =
|
|
200
|
+
const resolved = resolve2(process.cwd(), sourcePath);
|
|
193
201
|
let content;
|
|
194
202
|
try {
|
|
195
203
|
content = readFileSync(resolved, "utf-8");
|
|
@@ -208,7 +216,7 @@ var plugin = {
|
|
|
208
216
|
Make sure flag behavior is intentional and consider updating flag inventory/docs.`
|
|
209
217
|
};
|
|
210
218
|
};
|
|
211
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
219
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
212
220
|
api.tools.register({
|
|
213
221
|
name: "scan_feature_flags",
|
|
214
222
|
description: "Scan source files for feature-flag-like expressions (isFeatureEnabled, featureFlags.*, useFeatureFlag, flags.*, plus custom patterns).",
|
|
@@ -238,7 +246,7 @@ Make sure flag behavior is intentional and consider updating flag inventory/docs
|
|
|
238
246
|
state.flagCount += result.usages.length;
|
|
239
247
|
return {
|
|
240
248
|
ok: true,
|
|
241
|
-
path: relativePath(
|
|
249
|
+
path: relativePath(resolve2(process.cwd(), rawPath)),
|
|
242
250
|
scannedFiles: result.scannedFiles,
|
|
243
251
|
usages: result.usages
|
|
244
252
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file-watcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2D/C,QAAA,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file-watcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2D/C,QAAA,MAAM,MAAM,EAAE,MAqXb,CAAC;eAEa,MAAM"}
|
package/dist/file-watcher.js
CHANGED
|
@@ -294,6 +294,7 @@ var plugin = {
|
|
|
294
294
|
api.log.info("file-watcher plugin loaded", { version: "0.1.0" });
|
|
295
295
|
},
|
|
296
296
|
teardown(api) {
|
|
297
|
+
const closed = watches.size;
|
|
297
298
|
for (const handle of watches.values()) {
|
|
298
299
|
for (const w of handle.watchers) {
|
|
299
300
|
try {
|
|
@@ -306,9 +307,21 @@ var plugin = {
|
|
|
306
307
|
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
307
308
|
debounceTimers.clear();
|
|
308
309
|
api.log.info("file-watcher: teardown complete", {
|
|
309
|
-
closed
|
|
310
|
-
// recorded for log symmetry; actual count cleared above
|
|
310
|
+
closed
|
|
311
311
|
});
|
|
312
|
+
},
|
|
313
|
+
async health() {
|
|
314
|
+
const watcherCount = Array.from(watches.values()).reduce(
|
|
315
|
+
(sum, handle) => sum + handle.watchers.length,
|
|
316
|
+
0
|
|
317
|
+
);
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
message: `file-watcher: ${watches.size} active watch group(s), ${watcherCount} filesystem watcher(s)`,
|
|
321
|
+
activeWatchGroups: watches.size,
|
|
322
|
+
filesystemWatchers: watcherCount,
|
|
323
|
+
pendingDebounces: debounceTimers.size
|
|
324
|
+
};
|
|
312
325
|
}
|
|
313
326
|
};
|
|
314
327
|
var file_watcher_default = plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/format-on-save/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/format-on-save/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2N/C,QAAA,MAAM,MAAM,EAAE,MA2Ob,CAAC;eAEa,MAAM"}
|
package/dist/format-on-save.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
// src/format-on-save/index.ts
|
|
2
2
|
import { execFileSync, execSync } from "node:child_process";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
function hasLeadingDash(arg) {
|
|
9
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
10
|
+
}
|
|
11
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
12
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
13
|
+
if (hasLeadingDash(candidate)) return false;
|
|
14
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
15
|
+
const rel = relative(projectRoot, resolved);
|
|
16
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17
|
+
}
|
|
6
18
|
function withinProject(p) {
|
|
7
|
-
|
|
8
|
-
const root = process.cwd();
|
|
9
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
10
|
-
const rel = relative(root, resolved);
|
|
11
|
-
if (rel === "" || rel === ".") return true;
|
|
12
|
-
if (rel.startsWith("..")) return false;
|
|
13
|
-
if (isAbsolute(rel)) return false;
|
|
14
|
-
return true;
|
|
19
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
15
20
|
}
|
|
21
|
+
|
|
22
|
+
// src/format-on-save/index.ts
|
|
23
|
+
var API_VERSION = "^0.1.10";
|
|
16
24
|
var state = {
|
|
17
25
|
invocationCount: 0,
|
|
18
26
|
/** Times formatting was applied (file changed). */
|
|
@@ -219,7 +227,7 @@ var plugin = {
|
|
|
219
227
|
state.cleanCount += 1;
|
|
220
228
|
return;
|
|
221
229
|
};
|
|
222
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
230
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
223
231
|
state.patternUnregister = api.onPattern(
|
|
224
232
|
"import-organizer:done",
|
|
225
233
|
(_eventName, payload) => {
|
package/dist/git-autocommit.js
CHANGED
|
@@ -160,7 +160,7 @@ var plugin = {
|
|
|
160
160
|
version: "0.2.0",
|
|
161
161
|
description: "AI-powered git staging and conventional commit message generation",
|
|
162
162
|
apiVersion: API_VERSION,
|
|
163
|
-
capabilities: { tools: true },
|
|
163
|
+
capabilities: { tools: true, llm: true },
|
|
164
164
|
defaultConfig: {
|
|
165
165
|
conventionalCommits: true,
|
|
166
166
|
autoStage: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/import-organizer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/import-organizer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAoR/C,QAAA,MAAM,MAAM,EAAE,MA8Nb,CAAC;eAEa,MAAM"}
|
package/dist/import-organizer.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
// src/import-organizer/index.ts
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
|
+
import { basename as basename2, isAbsolute as isAbsolute2 } from "node:path";
|
|
5
|
+
|
|
6
|
+
// src/runtime/index.ts
|
|
4
7
|
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
8
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
function hasLeadingDash(arg) {
|
|
10
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
11
|
+
}
|
|
12
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
13
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
14
|
+
if (hasLeadingDash(candidate)) return false;
|
|
15
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
16
|
+
const rel = relative(projectRoot, resolved);
|
|
17
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
18
|
+
}
|
|
5
19
|
function withinProject(p) {
|
|
6
|
-
|
|
7
|
-
const root = process.cwd();
|
|
8
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
9
|
-
const rel = relative(root, resolved);
|
|
10
|
-
if (rel === "" || rel === ".") return true;
|
|
11
|
-
if (rel.startsWith("..")) return false;
|
|
12
|
-
if (isAbsolute(rel)) return false;
|
|
13
|
-
return true;
|
|
20
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
14
21
|
}
|
|
22
|
+
|
|
23
|
+
// src/import-organizer/index.ts
|
|
15
24
|
var ALLOWED_FIRST_TOKENS = /* @__PURE__ */ new Set([
|
|
16
25
|
"npx",
|
|
17
26
|
"pnpm",
|
|
@@ -31,13 +40,13 @@ function resolveAllowedCommand(command) {
|
|
|
31
40
|
if (ALLOWED_FIRST_TOKENS.has(head)) {
|
|
32
41
|
return { cmd: head, args: tokens.slice(1) };
|
|
33
42
|
}
|
|
34
|
-
if (
|
|
43
|
+
if (isAbsolute2(head)) {
|
|
35
44
|
if (!withinProject(head)) return null;
|
|
36
|
-
const base =
|
|
45
|
+
const base = basename2(head);
|
|
37
46
|
if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
|
|
38
47
|
}
|
|
39
|
-
if (!
|
|
40
|
-
const base =
|
|
48
|
+
if (!isAbsolute2(head) && withinProject(head)) {
|
|
49
|
+
const base = basename2(head);
|
|
41
50
|
if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
|
|
42
51
|
}
|
|
43
52
|
return null;
|
|
@@ -256,7 +265,7 @@ ${result.stderr.trim()}`
|
|
|
256
265
|
}
|
|
257
266
|
return;
|
|
258
267
|
};
|
|
259
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
268
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
260
269
|
api.tools.register({
|
|
261
270
|
name: "import_organizer_status",
|
|
262
271
|
description: "Reports import-organizer state: linter availability, config, and per-session organized/clean/error counters.",
|