pi-lens 2.2.9 → 3.0.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/CHANGELOG.md +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for spellcheck runner (typos-cli)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import type { DispatchContext } from "../types.js";
|
|
10
|
+
|
|
11
|
+
function createMockContext(filePath: string): DispatchContext {
|
|
12
|
+
return {
|
|
13
|
+
filePath,
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
kind: "markdown" as any,
|
|
16
|
+
autofix: false,
|
|
17
|
+
deltaMode: false,
|
|
18
|
+
baselines: { get: () => [], add: () => {}, save: () => {} } as any,
|
|
19
|
+
pi: {} as any,
|
|
20
|
+
hasTool: async () => false,
|
|
21
|
+
log: () => {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("spellcheck runner", () => {
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
|
|
28
|
+
it("should have correct runner definition", async () => {
|
|
29
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
30
|
+
const runner = spellcheckModule.default;
|
|
31
|
+
|
|
32
|
+
expect(runner.id).toBe("spellcheck");
|
|
33
|
+
expect(runner.appliesTo).toEqual(["markdown"]);
|
|
34
|
+
expect(runner.priority).toBe(30);
|
|
35
|
+
expect(runner.enabledByDefault).toBe(true);
|
|
36
|
+
expect(runner.skipTestFiles).toBe(false); // Check docs in test files too
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should detect typos-cli availability", () => {
|
|
40
|
+
const { spawnSync } =
|
|
41
|
+
require("node:child_process") as typeof import("node:child_process");
|
|
42
|
+
const result = spawnSync("typos", ["--version"], {
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
timeout: 10000,
|
|
45
|
+
shell: true,
|
|
46
|
+
});
|
|
47
|
+
expect(
|
|
48
|
+
result.error || result.status !== 0 ? "not available" : "available",
|
|
49
|
+
).toBeTruthy(); // May or may not be installed
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should detect typos in markdown content", async () => {
|
|
53
|
+
const tmpFile = path.join(
|
|
54
|
+
process.env.TEMP || "/tmp",
|
|
55
|
+
`spellcheck_test_${Date.now()}.md`,
|
|
56
|
+
);
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
tmpFile,
|
|
59
|
+
`# README
|
|
60
|
+
|
|
61
|
+
This is a documnet about recieving data.
|
|
62
|
+
The seperation of concerns is important.
|
|
63
|
+
`,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
68
|
+
const runner = spellcheckModule.default;
|
|
69
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
70
|
+
|
|
71
|
+
// If typos-cli is installed, should detect typos
|
|
72
|
+
// If not installed, will be skipped
|
|
73
|
+
if (result.status !== "skipped") {
|
|
74
|
+
// Should detect at least "documnet" and "recieving"
|
|
75
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
76
|
+
expect(
|
|
77
|
+
result.diagnostics.some(
|
|
78
|
+
(d) =>
|
|
79
|
+
d.tool === "typos" &&
|
|
80
|
+
(d.message.includes("documnet") ||
|
|
81
|
+
d.message.includes("recieving") ||
|
|
82
|
+
d.message.includes("seperation")),
|
|
83
|
+
),
|
|
84
|
+
).toBe(true);
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
if (fs.existsSync(tmpFile)) {
|
|
88
|
+
fs.unlinkSync(tmpFile);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should suggest corrections for typos", async () => {
|
|
94
|
+
const tmpFile = path.join(
|
|
95
|
+
process.env.TEMP || "/tmp",
|
|
96
|
+
`spellcheck_fix_${Date.now()}.md`,
|
|
97
|
+
);
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
tmpFile,
|
|
100
|
+
`# Test
|
|
101
|
+
|
|
102
|
+
This is a recieving test.
|
|
103
|
+
`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
108
|
+
const runner = spellcheckModule.default;
|
|
109
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
110
|
+
|
|
111
|
+
if (result.status !== "skipped" && result.diagnostics.length > 0) {
|
|
112
|
+
// Should have fix suggestions
|
|
113
|
+
const fixableDiags = result.diagnostics.filter((d) => d.fixable);
|
|
114
|
+
expect(fixableDiags.length).toBeGreaterThanOrEqual(1);
|
|
115
|
+
expect(
|
|
116
|
+
fixableDiags.some((d) =>
|
|
117
|
+
d.fixSuggestion?.toLowerCase().includes("receive"),
|
|
118
|
+
),
|
|
119
|
+
).toBe(true);
|
|
120
|
+
}
|
|
121
|
+
} finally {
|
|
122
|
+
if (fs.existsSync(tmpFile)) {
|
|
123
|
+
fs.unlinkSync(tmpFile);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should pass clean markdown files", async () => {
|
|
129
|
+
const tmpFile = path.join(
|
|
130
|
+
process.env.TEMP || "/tmp",
|
|
131
|
+
`spellcheck_ok_${Date.now()}.md`,
|
|
132
|
+
);
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
tmpFile,
|
|
135
|
+
`# Clean README
|
|
136
|
+
|
|
137
|
+
This is a correct document about receiving data.
|
|
138
|
+
The separation of concerns is important.
|
|
139
|
+
All spelling is proper in this file.
|
|
140
|
+
`,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
145
|
+
const runner = spellcheckModule.default;
|
|
146
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
147
|
+
|
|
148
|
+
if (result.status !== "skipped") {
|
|
149
|
+
// Should have no typos
|
|
150
|
+
expect(result.diagnostics.length).toBe(0);
|
|
151
|
+
expect(result.status).toBe("succeeded");
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
if (fs.existsSync(tmpFile)) {
|
|
155
|
+
fs.unlinkSync(tmpFile);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should handle JSON parse errors gracefully", async () => {
|
|
161
|
+
const tmpFile = path.join(
|
|
162
|
+
process.env.TEMP || "/tmp",
|
|
163
|
+
`spellcheck_json_${Date.now()}.md`,
|
|
164
|
+
);
|
|
165
|
+
fs.writeFileSync(tmpFile, `# Test\n\nSimple file.`);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
169
|
+
const runner = spellcheckModule.default;
|
|
170
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
171
|
+
|
|
172
|
+
// Should not crash on JSON parse issues
|
|
173
|
+
expect(["succeeded", "failed", "skipped"]).toContain(result.status);
|
|
174
|
+
} finally {
|
|
175
|
+
if (fs.existsSync(tmpFile)) {
|
|
176
|
+
fs.unlinkSync(tmpFile);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should skip when typos-cli is not available", async () => {
|
|
182
|
+
const tmpFile = path.join(
|
|
183
|
+
process.env.TEMP || "/tmp",
|
|
184
|
+
`spellcheck_skip_${Date.now()}.md`,
|
|
185
|
+
);
|
|
186
|
+
fs.writeFileSync(tmpFile, `# Test\n\nContent with typo: recieve.`);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const spellcheckModule = await import("./spellcheck.js");
|
|
190
|
+
const runner = spellcheckModule.default;
|
|
191
|
+
|
|
192
|
+
// Check if typos is available
|
|
193
|
+
const { spawnSync } =
|
|
194
|
+
require("node:child_process") as typeof import("node:child_process");
|
|
195
|
+
const checkResult = spawnSync("typos", ["--version"], {
|
|
196
|
+
encoding: "utf-8",
|
|
197
|
+
timeout: 5000,
|
|
198
|
+
shell: true,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const isAvailable = !checkResult.error && checkResult.status === 0;
|
|
202
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
203
|
+
|
|
204
|
+
if (!isAvailable) {
|
|
205
|
+
expect(result.status).toBe("skipped");
|
|
206
|
+
expect(result.diagnostics).toHaveLength(0);
|
|
207
|
+
}
|
|
208
|
+
} finally {
|
|
209
|
+
if (fs.existsSync(tmpFile)) {
|
|
210
|
+
fs.unlinkSync(tmpFile);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spellcheck runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Uses typos-cli (Rust-based, fast, zero-config) to check spelling in:
|
|
5
|
+
* - Markdown files (.md, .mdx)
|
|
6
|
+
* - Code comments (optional, if typos is configured)
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Fast (Rust-based, ~10x faster than cspell)
|
|
10
|
+
* - Low false positives (only checks known typos)
|
|
11
|
+
* - Zero-config by default
|
|
12
|
+
* - JSON output for easy parsing
|
|
13
|
+
*
|
|
14
|
+
* Alternative considered: cspell
|
|
15
|
+
* - cspell: More comprehensive, but higher false positives, needs config
|
|
16
|
+
* - typos-cli: Faster, less noise, works out of the box
|
|
17
|
+
*
|
|
18
|
+
* Install: cargo install typos-cli
|
|
19
|
+
* Or: npm install -g typos-cli (if wrapped)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
23
|
+
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
24
|
+
import type {
|
|
25
|
+
Diagnostic,
|
|
26
|
+
DispatchContext,
|
|
27
|
+
RunnerDefinition,
|
|
28
|
+
RunnerResult,
|
|
29
|
+
} from "../types.js";
|
|
30
|
+
|
|
31
|
+
const typos = createAvailabilityChecker("typos", ".exe");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse typos-cli JSON output (JSON Lines format)
|
|
35
|
+
*
|
|
36
|
+
* Each line is a JSON object:
|
|
37
|
+
* {
|
|
38
|
+
* "path": "file.md",
|
|
39
|
+
* "line_num": 42,
|
|
40
|
+
* "byte_offset": 1234,
|
|
41
|
+
* "typo": "recieve",
|
|
42
|
+
* "corrections": ["receive"]
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
function parseTyposOutput(raw: string, filePath: string): Diagnostic[] {
|
|
46
|
+
const diagnostics: Diagnostic[] = [];
|
|
47
|
+
|
|
48
|
+
if (!raw.trim()) {
|
|
49
|
+
return diagnostics;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lines = raw.trim().split("\n").filter((l) => l.trim());
|
|
53
|
+
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(line) as {
|
|
57
|
+
path?: string;
|
|
58
|
+
line_num?: number;
|
|
59
|
+
byte_offset?: number;
|
|
60
|
+
typo?: string;
|
|
61
|
+
corrections?: string[];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (!parsed.typo || !parsed.line_num) continue;
|
|
65
|
+
|
|
66
|
+
const corrections = parsed.corrections?.join(", ") || "no suggestions";
|
|
67
|
+
const message = `Typo: "${parsed.typo}" → ${corrections}`;
|
|
68
|
+
|
|
69
|
+
diagnostics.push({
|
|
70
|
+
id: `typos-${parsed.line_num}-${parsed.typo}`,
|
|
71
|
+
message,
|
|
72
|
+
filePath,
|
|
73
|
+
line: parsed.line_num,
|
|
74
|
+
column: 1, // typos-cli doesn't provide column, just byte offset
|
|
75
|
+
severity: "warning",
|
|
76
|
+
semantic: "warning",
|
|
77
|
+
tool: "typos",
|
|
78
|
+
rule: "typo",
|
|
79
|
+
fixable: !!parsed.corrections?.length,
|
|
80
|
+
fixSuggestion: parsed.corrections?.[0],
|
|
81
|
+
});
|
|
82
|
+
} catch {
|
|
83
|
+
// Skip invalid JSON lines
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return diagnostics;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const spellcheckRunner: RunnerDefinition = {
|
|
92
|
+
id: "spellcheck",
|
|
93
|
+
appliesTo: ["markdown"],
|
|
94
|
+
priority: 30, // Run after code quality checks (biome=10, slop=25)
|
|
95
|
+
enabledByDefault: true,
|
|
96
|
+
skipTestFiles: false, // Check docs in test files too
|
|
97
|
+
|
|
98
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
99
|
+
// Skip if typos-cli is not installed
|
|
100
|
+
if (!typos.isAvailable(ctx.cwd || process.cwd())) {
|
|
101
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Run typos-cli with JSON output
|
|
105
|
+
// --format json: Output JSON Lines
|
|
106
|
+
// --exclude <pattern>: Could be used to exclude code blocks if needed
|
|
107
|
+
const args = ["--format", "json", ctx.filePath];
|
|
108
|
+
|
|
109
|
+
const result = safeSpawn(typos.getCommand()!, args, {
|
|
110
|
+
timeout: 15000,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// typos-cli exits with code 2 if typos found, 0 if clean
|
|
114
|
+
const hasTypos = result.status === 2 || result.stdout?.trim();
|
|
115
|
+
|
|
116
|
+
if (!hasTypos) {
|
|
117
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Parse diagnostics
|
|
121
|
+
const raw = result.stdout + result.stderr;
|
|
122
|
+
const diagnostics = parseTyposOutput(raw, ctx.filePath);
|
|
123
|
+
|
|
124
|
+
if (diagnostics.length === 0) {
|
|
125
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
status: "failed",
|
|
130
|
+
diagnostics,
|
|
131
|
+
semantic: "warning",
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default spellcheckRunner;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter Structural Analysis Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes all loaded tree-sitter query files from rules/tree-sitter-queries/
|
|
5
|
+
* for fast AST-based pattern matching.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { TreeSitterClient } from "../../tree-sitter-client.js";
|
|
9
|
+
import { queryLoader } from "../../tree-sitter-query-loader.js";
|
|
10
|
+
const treeSitterRunner = {
|
|
11
|
+
id: "tree-sitter",
|
|
12
|
+
appliesTo: ["jsts", "python"],
|
|
13
|
+
priority: 14, // Between oxlint (12) and ast-grep-napi (15)
|
|
14
|
+
enabledByDefault: true,
|
|
15
|
+
skipTestFiles: false, // Run on test files too (structural issues matter there)
|
|
16
|
+
async run(ctx) {
|
|
17
|
+
// Initialize tree-sitter client
|
|
18
|
+
const client = new TreeSitterClient();
|
|
19
|
+
if (!client.isAvailable()) {
|
|
20
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
21
|
+
}
|
|
22
|
+
const initialized = await client.init();
|
|
23
|
+
if (!initialized) {
|
|
24
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
25
|
+
}
|
|
26
|
+
// Determine language from file extension
|
|
27
|
+
const filePath = ctx.filePath;
|
|
28
|
+
const isPython = filePath.endsWith(".py");
|
|
29
|
+
const isTypeScript = filePath.endsWith(".ts");
|
|
30
|
+
const isTSX = filePath.endsWith(".tsx");
|
|
31
|
+
const isJavaScript = filePath.endsWith(".js") || filePath.endsWith(".jsx");
|
|
32
|
+
let languageId;
|
|
33
|
+
if (isPython) {
|
|
34
|
+
languageId = "python";
|
|
35
|
+
}
|
|
36
|
+
else if (isTSX) {
|
|
37
|
+
languageId = "tsx";
|
|
38
|
+
}
|
|
39
|
+
else if (isTypeScript) {
|
|
40
|
+
languageId = "typescript";
|
|
41
|
+
}
|
|
42
|
+
else if (isJavaScript) {
|
|
43
|
+
languageId = "javascript";
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
47
|
+
}
|
|
48
|
+
// Load queries if not already loaded
|
|
49
|
+
if (!queryLoader.getAllQueries().length) {
|
|
50
|
+
await queryLoader.loadQueries();
|
|
51
|
+
}
|
|
52
|
+
// Get all loaded queries for this language
|
|
53
|
+
const allQueries = queryLoader.getAllQueries();
|
|
54
|
+
const languageQueries = allQueries.filter((q) => q.language === languageId ||
|
|
55
|
+
(isJavaScript && q.language === "typescript"));
|
|
56
|
+
if (languageQueries.length === 0) {
|
|
57
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
58
|
+
}
|
|
59
|
+
const diagnostics = [];
|
|
60
|
+
// Run each query against the file
|
|
61
|
+
for (const query of languageQueries) {
|
|
62
|
+
try {
|
|
63
|
+
// Extract directory from file path (use path.dirname for cross-platform)
|
|
64
|
+
const rootDir = path.dirname(filePath);
|
|
65
|
+
const matches = await client.structuralSearch(query.id, // Use query ID as pattern (findMatchingQuery will resolve it)
|
|
66
|
+
languageId, rootDir, { maxResults: 10, fileFilter: (f) => f === filePath });
|
|
67
|
+
for (const match of matches) {
|
|
68
|
+
// Get line/column from match (already 0-indexed from tree-sitter)
|
|
69
|
+
const line = match.line;
|
|
70
|
+
const column = match.column;
|
|
71
|
+
// Map severity to semantic
|
|
72
|
+
const semantic = query.severity === "error"
|
|
73
|
+
? "blocking"
|
|
74
|
+
: query.severity === "warning"
|
|
75
|
+
? "warning"
|
|
76
|
+
: "none";
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
id: `tree-sitter:${query.id}:${line}`,
|
|
79
|
+
message: query.message,
|
|
80
|
+
filePath,
|
|
81
|
+
line: line + 1, // 1-indexed
|
|
82
|
+
column: column + 1, // 1-indexed
|
|
83
|
+
severity: query.severity,
|
|
84
|
+
semantic,
|
|
85
|
+
tool: "tree-sitter",
|
|
86
|
+
rule: query.id,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
// Individual query failure shouldn't stop other queries
|
|
92
|
+
console.error(`[tree-sitter] Query ${query.id} failed:`, err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (diagnostics.length === 0) {
|
|
96
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
97
|
+
}
|
|
98
|
+
// Check if any blocking issues
|
|
99
|
+
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
100
|
+
return {
|
|
101
|
+
status: hasBlocking ? "failed" : "succeeded",
|
|
102
|
+
diagnostics,
|
|
103
|
+
semantic: hasBlocking ? "blocking" : "warning",
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
export default treeSitterRunner;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter Structural Analysis Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes all loaded tree-sitter query files from rules/tree-sitter-queries/
|
|
5
|
+
* for fast AST-based pattern matching.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { TreeSitterClient } from "../../tree-sitter-client.js";
|
|
10
|
+
import { queryLoader } from "../../tree-sitter-query-loader.js";
|
|
11
|
+
import type {
|
|
12
|
+
Diagnostic,
|
|
13
|
+
DispatchContext,
|
|
14
|
+
RunnerDefinition,
|
|
15
|
+
RunnerResult,
|
|
16
|
+
} from "../types.js";
|
|
17
|
+
|
|
18
|
+
const treeSitterRunner: RunnerDefinition = {
|
|
19
|
+
id: "tree-sitter",
|
|
20
|
+
appliesTo: ["jsts", "python"],
|
|
21
|
+
priority: 14, // Between oxlint (12) and ast-grep-napi (15)
|
|
22
|
+
enabledByDefault: true,
|
|
23
|
+
skipTestFiles: false, // Run on test files too (structural issues matter there)
|
|
24
|
+
|
|
25
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
26
|
+
// Initialize tree-sitter client
|
|
27
|
+
const client = new TreeSitterClient();
|
|
28
|
+
if (!client.isAvailable()) {
|
|
29
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const initialized = await client.init();
|
|
33
|
+
if (!initialized) {
|
|
34
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Determine language from file extension
|
|
38
|
+
const filePath = ctx.filePath;
|
|
39
|
+
const isPython = filePath.endsWith(".py");
|
|
40
|
+
const isTypeScript = filePath.endsWith(".ts");
|
|
41
|
+
const isTSX = filePath.endsWith(".tsx");
|
|
42
|
+
const isJavaScript = filePath.endsWith(".js") || filePath.endsWith(".jsx");
|
|
43
|
+
|
|
44
|
+
let languageId: string;
|
|
45
|
+
if (isPython) {
|
|
46
|
+
languageId = "python";
|
|
47
|
+
} else if (isTSX) {
|
|
48
|
+
languageId = "tsx";
|
|
49
|
+
} else if (isTypeScript) {
|
|
50
|
+
languageId = "typescript";
|
|
51
|
+
} else if (isJavaScript) {
|
|
52
|
+
languageId = "javascript";
|
|
53
|
+
} else {
|
|
54
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Load queries if not already loaded
|
|
58
|
+
if (!queryLoader.getAllQueries().length) {
|
|
59
|
+
await queryLoader.loadQueries();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Get all loaded queries for this language
|
|
63
|
+
const allQueries = queryLoader.getAllQueries();
|
|
64
|
+
const languageQueries = allQueries.filter(
|
|
65
|
+
(q) =>
|
|
66
|
+
q.language === languageId ||
|
|
67
|
+
(isJavaScript && q.language === "typescript"),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (languageQueries.length === 0) {
|
|
71
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const diagnostics: Diagnostic[] = [];
|
|
75
|
+
|
|
76
|
+
// Run each query against the file
|
|
77
|
+
for (const query of languageQueries) {
|
|
78
|
+
try {
|
|
79
|
+
// Extract directory from file path (use path.dirname for cross-platform)
|
|
80
|
+
const rootDir = path.dirname(filePath);
|
|
81
|
+
|
|
82
|
+
const matches = await client.structuralSearch(
|
|
83
|
+
query.id, // Use query ID as pattern (findMatchingQuery will resolve it)
|
|
84
|
+
languageId,
|
|
85
|
+
rootDir,
|
|
86
|
+
{ maxResults: 10, fileFilter: (f) => f === filePath },
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
for (const match of matches) {
|
|
90
|
+
// Get line/column from match (already 0-indexed from tree-sitter)
|
|
91
|
+
const line = match.line;
|
|
92
|
+
const column = match.column;
|
|
93
|
+
|
|
94
|
+
// Map severity to semantic
|
|
95
|
+
const semantic =
|
|
96
|
+
query.severity === "error"
|
|
97
|
+
? "blocking"
|
|
98
|
+
: query.severity === "warning"
|
|
99
|
+
? "warning"
|
|
100
|
+
: "none";
|
|
101
|
+
|
|
102
|
+
diagnostics.push({
|
|
103
|
+
id: `tree-sitter:${query.id}:${line}`,
|
|
104
|
+
message: query.message,
|
|
105
|
+
filePath,
|
|
106
|
+
line: line + 1, // 1-indexed
|
|
107
|
+
column: column + 1, // 1-indexed
|
|
108
|
+
severity: query.severity,
|
|
109
|
+
semantic,
|
|
110
|
+
tool: "tree-sitter",
|
|
111
|
+
rule: query.id,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
// Individual query failure shouldn't stop other queries
|
|
116
|
+
console.error(`[tree-sitter] Query ${query.id} failed:`, err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (diagnostics.length === 0) {
|
|
121
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check if any blocking issues
|
|
125
|
+
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
status: hasBlocking ? "failed" : "succeeded",
|
|
129
|
+
diagnostics,
|
|
130
|
+
semantic: hasBlocking ? "blocking" : "warning",
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export default treeSitterRunner;
|