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,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect-TS Service Infrastructure for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Simplified implementation focusing on:
|
|
5
|
+
* - Concurrent runner execution
|
|
6
|
+
* - Timeout handling
|
|
7
|
+
* - Error recovery
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
|
|
12
|
+
// --- Error Types ---
|
|
13
|
+
|
|
14
|
+
export class RunnerError {
|
|
15
|
+
readonly _tag = "RunnerError";
|
|
16
|
+
constructor(
|
|
17
|
+
readonly runnerId: string,
|
|
18
|
+
readonly cause: unknown,
|
|
19
|
+
) {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class TimeoutError {
|
|
23
|
+
readonly _tag = "TimeoutError";
|
|
24
|
+
constructor(
|
|
25
|
+
readonly operation: string,
|
|
26
|
+
readonly timeoutMs: number,
|
|
27
|
+
) {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- Result Types ---
|
|
31
|
+
|
|
32
|
+
export interface RunnerResult {
|
|
33
|
+
diagnostics: Array<{
|
|
34
|
+
id: string;
|
|
35
|
+
message: string;
|
|
36
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
37
|
+
semantic?: "blocking" | "warning" | "fixed" | "silent" | "none";
|
|
38
|
+
}>;
|
|
39
|
+
durationMs: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ConcurrentRunnerResult {
|
|
44
|
+
runnerId: string;
|
|
45
|
+
status: "success" | "failure" | "skipped";
|
|
46
|
+
diagnostics: Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
message: string;
|
|
49
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
50
|
+
semantic?: "blocking" | "warning" | "fixed" | "silent" | "none";
|
|
51
|
+
}>;
|
|
52
|
+
durationMs: number;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- Concurrent Execution Helper ---
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Run multiple runners concurrently with Effect
|
|
60
|
+
*
|
|
61
|
+
* Features:
|
|
62
|
+
* - Parallel execution with Effect.all
|
|
63
|
+
* - Per-runner timeout handling
|
|
64
|
+
* - Graceful error recovery (individual failures don't stop others)
|
|
65
|
+
* - Automatic resource cleanup
|
|
66
|
+
*/
|
|
67
|
+
export function runRunnersConcurrent(
|
|
68
|
+
filePath: string,
|
|
69
|
+
runnerIds: string[],
|
|
70
|
+
runSingle: (filePath: string, runnerId: string) => Promise<RunnerResult>,
|
|
71
|
+
timeoutMs = 30_000
|
|
72
|
+
): Effect.Effect<ConcurrentRunnerResult[], never, never> {
|
|
73
|
+
return Effect.gen(function* () {
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
|
|
76
|
+
// Run all runners in parallel
|
|
77
|
+
const results = yield* Effect.all(
|
|
78
|
+
runnerIds.map((runnerId) =>
|
|
79
|
+
Effect.gen(function* () {
|
|
80
|
+
const runnerStart = Date.now();
|
|
81
|
+
|
|
82
|
+
// Execute with timeout and error handling
|
|
83
|
+
const result = yield* Effect.tryPromise({
|
|
84
|
+
try: () => runSingle(filePath, runnerId),
|
|
85
|
+
catch: (err) => err,
|
|
86
|
+
}).pipe(
|
|
87
|
+
Effect.timeout(timeoutMs),
|
|
88
|
+
Effect.catchAll((err) =>
|
|
89
|
+
Effect.succeed({
|
|
90
|
+
diagnostics: [],
|
|
91
|
+
durationMs: Date.now() - runnerStart,
|
|
92
|
+
error: String(err),
|
|
93
|
+
})
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const isError = "error" in result;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
runnerId,
|
|
101
|
+
status: isError ? "failure" as const : "success" as const,
|
|
102
|
+
diagnostics: isError ? [] : result.diagnostics,
|
|
103
|
+
durationMs: isError ? result.durationMs : result.durationMs,
|
|
104
|
+
error: isError ? result.error : undefined,
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
),
|
|
108
|
+
{ concurrency: "unbounded" }
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return results;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Run a single runner with timeout and error handling
|
|
117
|
+
*/
|
|
118
|
+
export function runRunnerWithTimeout(
|
|
119
|
+
filePath: string,
|
|
120
|
+
runnerId: string,
|
|
121
|
+
runSingle: (filePath: string, runnerId: string) => Promise<RunnerResult>,
|
|
122
|
+
timeoutMs = 30_000
|
|
123
|
+
): Effect.Effect<RunnerResult, RunnerError | TimeoutError, never> {
|
|
124
|
+
return Effect.gen(function* () {
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
|
|
127
|
+
const result = yield* Effect.tryPromise({
|
|
128
|
+
try: () => runSingle(filePath, runnerId),
|
|
129
|
+
catch: (err) => new RunnerError(runnerId, err),
|
|
130
|
+
}).pipe(
|
|
131
|
+
Effect.timeout(timeoutMs),
|
|
132
|
+
Effect.mapError((err) => {
|
|
133
|
+
if (err instanceof RunnerError) return err;
|
|
134
|
+
return new TimeoutError(`runner:${runnerId}`, timeoutMs);
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
diagnostics: result.diagnostics,
|
|
140
|
+
durationMs: Date.now() - startTime,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- Execution Helpers ---
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Execute Effect and get result
|
|
149
|
+
*/
|
|
150
|
+
export function executeEffect<T>(
|
|
151
|
+
effect: Effect.Effect<T, never, never>
|
|
152
|
+
): Promise<T> {
|
|
153
|
+
return Effect.runPromise(effect);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Execute Effect with error handling
|
|
158
|
+
*/
|
|
159
|
+
export function executeEffectWithError<T, E>(
|
|
160
|
+
effect: Effect.Effect<T, E, never>,
|
|
161
|
+
onError: (err: E) => T
|
|
162
|
+
): Promise<T> {
|
|
163
|
+
return Effect.runPromise(
|
|
164
|
+
Effect.catchAll(effect, (err) => Effect.succeed(onError(err)))
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- Error Formatting ---
|
|
169
|
+
|
|
170
|
+
export function formatError(err: RunnerError | TimeoutError): string {
|
|
171
|
+
switch (err._tag) {
|
|
172
|
+
case "RunnerError":
|
|
173
|
+
return `Runner ${err.runnerId} failed: ${err.cause}`;
|
|
174
|
+
case "TimeoutError":
|
|
175
|
+
return `Operation ${err.operation} timed out after ${err.timeoutMs}ms`;
|
|
176
|
+
default:
|
|
177
|
+
return "Unknown error";
|
|
178
|
+
}
|
|
179
|
+
}
|
package/clients/sg-runner.js
CHANGED
|
@@ -4,10 +4,22 @@
|
|
|
4
4
|
* Extracted from AstGrepClient to simplify the main client.
|
|
5
5
|
* Handles: spawn, spawnSync, temp dir management, JSON parsing.
|
|
6
6
|
*/
|
|
7
|
-
import { spawn
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
+
import { safeSpawn } from "./safe-spawn.js";
|
|
12
|
+
/**
|
|
13
|
+
* Escape an argument for Windows cmd.exe shell execution.
|
|
14
|
+
* Handles spaces, quotes, and special characters.
|
|
15
|
+
*/
|
|
16
|
+
function escapeWindowsArg(arg) {
|
|
17
|
+
// If no special characters, return as-is
|
|
18
|
+
if (!/[\s\"]/.test(arg))
|
|
19
|
+
return arg;
|
|
20
|
+
// Escape quotes by doubling them
|
|
21
|
+
return `"${arg.replace(/"/g, "\"\"")}"`;
|
|
22
|
+
}
|
|
11
23
|
export class SgRunner {
|
|
12
24
|
constructor(verbose = false) {
|
|
13
25
|
this.log = verbose
|
|
@@ -18,10 +30,8 @@ export class SgRunner {
|
|
|
18
30
|
* Check if ast-grep CLI is available
|
|
19
31
|
*/
|
|
20
32
|
isAvailable() {
|
|
21
|
-
const result =
|
|
22
|
-
encoding: "utf-8",
|
|
33
|
+
const result = safeSpawn("npx", ["sg", "--version"], {
|
|
23
34
|
timeout: 10000,
|
|
24
|
-
shell: true,
|
|
25
35
|
});
|
|
26
36
|
return !result.error && result.status === 0;
|
|
27
37
|
}
|
|
@@ -30,10 +40,46 @@ export class SgRunner {
|
|
|
30
40
|
*/
|
|
31
41
|
async exec(args) {
|
|
32
42
|
return new Promise((resolve) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
// On Windows with Git Bash/MSYS2, we need to use bash to properly
|
|
44
|
+
// handle $variables in patterns (prevent shell expansion)
|
|
45
|
+
const isWindows = process.platform === "win32";
|
|
46
|
+
const hasBash = process.env.MSYSTEM || process.env.GIT_SHELL;
|
|
47
|
+
let proc;
|
|
48
|
+
if (isWindows && hasBash) {
|
|
49
|
+
// Use bash -c with properly escaped command
|
|
50
|
+
// In bash, use single quotes around arguments containing $ to prevent expansion
|
|
51
|
+
const escapedArgs = args.map((arg) => {
|
|
52
|
+
// For bash, wrap $-containing args in single quotes
|
|
53
|
+
if (arg.includes("$")) {
|
|
54
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
55
|
+
}
|
|
56
|
+
// For other args with spaces/special chars, use double quotes
|
|
57
|
+
if (/[\s\"]/.test(arg)) {
|
|
58
|
+
return `"${arg.replace(/"/g, "\\\"")}"`;
|
|
59
|
+
}
|
|
60
|
+
return arg;
|
|
61
|
+
});
|
|
62
|
+
const bashCommand = `npx sg ${escapedArgs.join(" ")}`;
|
|
63
|
+
proc = spawn("bash", ["-c", bashCommand], {
|
|
64
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
65
|
+
windowsHide: true,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else if (isWindows) {
|
|
69
|
+
// Fallback: use cmd.exe with standard escaping
|
|
70
|
+
const fullCommand = `npx sg ${args.map(escapeWindowsArg).join(" ")}`;
|
|
71
|
+
proc = spawn(fullCommand, {
|
|
72
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
73
|
+
shell: true,
|
|
74
|
+
windowsHide: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Unix: normal spawn without shell
|
|
79
|
+
proc = spawn("npx", ["sg", ...args], {
|
|
80
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
37
83
|
let stdout = "";
|
|
38
84
|
let stderr = "";
|
|
39
85
|
proc.stdout.on("data", (data) => (stdout += data.toString()));
|
|
@@ -78,11 +124,8 @@ export class SgRunner {
|
|
|
78
124
|
* Run ast-grep synchronously (for simple scans)
|
|
79
125
|
*/
|
|
80
126
|
execSync(args) {
|
|
81
|
-
const result =
|
|
82
|
-
encoding: "utf-8",
|
|
127
|
+
const result = safeSpawn("npx", ["sg", ...args], {
|
|
83
128
|
timeout: 30000,
|
|
84
|
-
shell: true,
|
|
85
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
86
129
|
});
|
|
87
130
|
if (result.error) {
|
|
88
131
|
return { output: "", error: result.error.message };
|
|
@@ -104,7 +147,38 @@ export class SgRunner {
|
|
|
104
147
|
fs.mkdirSync(rulesSubdir, { recursive: true });
|
|
105
148
|
fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
|
|
106
149
|
fs.writeFileSync(ruleFile, ruleYaml);
|
|
107
|
-
const result =
|
|
150
|
+
const result = safeSpawn("npx", ["sg", "scan", "--config", configFile, "--json", dir], { timeout });
|
|
151
|
+
const output = result.stdout || result.stderr || "";
|
|
152
|
+
if (!output.trim())
|
|
153
|
+
return [];
|
|
154
|
+
const items = JSON.parse(output);
|
|
155
|
+
return Array.isArray(items) ? items : [items];
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
try {
|
|
162
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
this.log(`Cleanup failed: ${err.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Run a rule file scan (temporary config approach) - alias for tempScan
|
|
171
|
+
*/
|
|
172
|
+
scanWithRule(ruleYaml, dir, timeout = 30000) {
|
|
173
|
+
const sessionDir = path.join(os.tmpdir(), `sg-scan-${Date.now()}`);
|
|
174
|
+
const rulesSubdir = path.join(sessionDir, "rules");
|
|
175
|
+
const configFile = path.join(sessionDir, ".sgconfig.yml");
|
|
176
|
+
const ruleFile = path.join(rulesSubdir, "rule.yml");
|
|
177
|
+
try {
|
|
178
|
+
fs.mkdirSync(rulesSubdir, { recursive: true });
|
|
179
|
+
fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
|
|
180
|
+
fs.writeFileSync(ruleFile, ruleYaml);
|
|
181
|
+
const result = safeSpawn("npx", ["sg", "scan", "--config", configFile, "--json", dir], { timeout });
|
|
108
182
|
const output = result.stdout || result.stderr || "";
|
|
109
183
|
if (!output.trim())
|
|
110
184
|
return [];
|
package/clients/sg-runner.ts
CHANGED
|
@@ -9,6 +9,19 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import * as path from "node:path";
|
|
12
|
+
import { safeSpawn } from "./safe-spawn.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Escape an argument for Windows cmd.exe shell execution.
|
|
16
|
+
* Handles spaces, quotes, and special characters.
|
|
17
|
+
*/
|
|
18
|
+
function escapeWindowsArg(arg: string): string {
|
|
19
|
+
// If no special characters, return as-is
|
|
20
|
+
if (!/[\s\"]/.test(arg)) return arg;
|
|
21
|
+
|
|
22
|
+
// Escape quotes by doubling them
|
|
23
|
+
return `"${arg.replace(/"/g, "\"\"")}"`;
|
|
24
|
+
}
|
|
12
25
|
|
|
13
26
|
export interface SgMatch {
|
|
14
27
|
file: string;
|
|
@@ -38,10 +51,8 @@ export class SgRunner {
|
|
|
38
51
|
* Check if ast-grep CLI is available
|
|
39
52
|
*/
|
|
40
53
|
isAvailable(): boolean {
|
|
41
|
-
const result =
|
|
42
|
-
encoding: "utf-8",
|
|
54
|
+
const result = safeSpawn("npx", ["sg", "--version"], {
|
|
43
55
|
timeout: 10000,
|
|
44
|
-
shell: true,
|
|
45
56
|
});
|
|
46
57
|
return !result.error && result.status === 0;
|
|
47
58
|
}
|
|
@@ -51,10 +62,46 @@ export class SgRunner {
|
|
|
51
62
|
*/
|
|
52
63
|
async exec(args: string[]): Promise<SgResult> {
|
|
53
64
|
return new Promise((resolve) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
// On Windows with Git Bash/MSYS2, we need to use bash to properly
|
|
66
|
+
// handle $variables in patterns (prevent shell expansion)
|
|
67
|
+
const isWindows = process.platform === "win32";
|
|
68
|
+
const hasBash = process.env.MSYSTEM || process.env.GIT_SHELL;
|
|
69
|
+
|
|
70
|
+
let proc;
|
|
71
|
+
if (isWindows && hasBash) {
|
|
72
|
+
// Use bash -c with properly escaped command
|
|
73
|
+
// In bash, use single quotes around arguments containing $ to prevent expansion
|
|
74
|
+
const escapedArgs = args.map((arg) => {
|
|
75
|
+
// For bash, wrap $-containing args in single quotes
|
|
76
|
+
if (arg.includes("$")) {
|
|
77
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
78
|
+
}
|
|
79
|
+
// For other args with spaces/special chars, use double quotes
|
|
80
|
+
if (/[\s\"]/.test(arg)) {
|
|
81
|
+
return `"${arg.replace(/"/g, "\\\"")}"`;
|
|
82
|
+
}
|
|
83
|
+
return arg;
|
|
84
|
+
});
|
|
85
|
+
const bashCommand = `npx sg ${escapedArgs.join(" ")}`;
|
|
86
|
+
proc = spawn("bash", ["-c", bashCommand], {
|
|
87
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
88
|
+
windowsHide: true,
|
|
89
|
+
});
|
|
90
|
+
} else if (isWindows) {
|
|
91
|
+
// Fallback: use cmd.exe with standard escaping
|
|
92
|
+
const fullCommand = `npx sg ${args.map(escapeWindowsArg).join(" ")}`;
|
|
93
|
+
proc = spawn(fullCommand, {
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
shell: true,
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
// Unix: normal spawn without shell
|
|
100
|
+
proc = spawn("npx", ["sg", ...args], {
|
|
101
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
58
105
|
let stdout = "";
|
|
59
106
|
let stderr = "";
|
|
60
107
|
|
|
@@ -101,11 +148,8 @@ export class SgRunner {
|
|
|
101
148
|
* Run ast-grep synchronously (for simple scans)
|
|
102
149
|
*/
|
|
103
150
|
execSync(args: string[]): { output: string; error?: string } {
|
|
104
|
-
const result =
|
|
105
|
-
encoding: "utf-8",
|
|
151
|
+
const result = safeSpawn("npx", ["sg", ...args], {
|
|
106
152
|
timeout: 30000,
|
|
107
|
-
shell: true,
|
|
108
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
109
153
|
});
|
|
110
154
|
|
|
111
155
|
if (result.error) {
|
|
@@ -137,10 +181,50 @@ export class SgRunner {
|
|
|
137
181
|
fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
|
|
138
182
|
fs.writeFileSync(ruleFile, ruleYaml);
|
|
139
183
|
|
|
140
|
-
const result =
|
|
184
|
+
const result = safeSpawn(
|
|
185
|
+
"npx",
|
|
186
|
+
["sg", "scan", "--config", configFile, "--json", dir],
|
|
187
|
+
{ timeout },
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const output = result.stdout || result.stderr || "";
|
|
191
|
+
if (!output.trim()) return [];
|
|
192
|
+
|
|
193
|
+
const items = JSON.parse(output);
|
|
194
|
+
return Array.isArray(items) ? items : [items];
|
|
195
|
+
} catch {
|
|
196
|
+
return [];
|
|
197
|
+
} finally {
|
|
198
|
+
try {
|
|
199
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
200
|
+
} catch (err) {
|
|
201
|
+
this.log(`Cleanup failed: ${(err as Error).message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Run a rule file scan (temporary config approach) - alias for tempScan
|
|
208
|
+
*/
|
|
209
|
+
scanWithRule(
|
|
210
|
+
ruleYaml: string,
|
|
211
|
+
dir: string,
|
|
212
|
+
timeout = 30000,
|
|
213
|
+
): SgMatch[] {
|
|
214
|
+
const sessionDir = path.join(os.tmpdir(), `sg-scan-${Date.now()}`);
|
|
215
|
+
const rulesSubdir = path.join(sessionDir, "rules");
|
|
216
|
+
const configFile = path.join(sessionDir, ".sgconfig.yml");
|
|
217
|
+
const ruleFile = path.join(rulesSubdir, "rule.yml");
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
fs.mkdirSync(rulesSubdir, { recursive: true });
|
|
221
|
+
fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
|
|
222
|
+
fs.writeFileSync(ruleFile, ruleYaml);
|
|
223
|
+
|
|
224
|
+
const result = safeSpawn(
|
|
141
225
|
"npx",
|
|
142
226
|
["sg", "scan", "--config", configFile, "--json", dir],
|
|
143
|
-
{
|
|
227
|
+
{ timeout },
|
|
144
228
|
);
|
|
145
229
|
|
|
146
230
|
const output = result.stdout || result.stderr || "";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Matrix Builder
|
|
3
|
+
*
|
|
4
|
+
* Implements Amain's 57×72 state transfer matrix construction.
|
|
5
|
+
* Counts parent→child transitions in the TypeScript AST.
|
|
6
|
+
*/
|
|
7
|
+
import * as ts from "typescript";
|
|
8
|
+
import { getStateIndex, NUM_STATES, NUM_SYNTAX } from "./amain-types.js";
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Matrix Construction
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Build a 57×72 state transfer matrix from TypeScript source code.
|
|
14
|
+
*
|
|
15
|
+
* matrix[i][j] = count of transitions from syntax state i to state j
|
|
16
|
+
*
|
|
17
|
+
* @param sourceCode TypeScript source code
|
|
18
|
+
* @returns 57×72 matrix of transition counts
|
|
19
|
+
*/
|
|
20
|
+
export function buildStateMatrix(sourceCode) {
|
|
21
|
+
const sourceFile = ts.createSourceFile("temp.ts", sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
22
|
+
// Initialize 57×72 matrix with zeros
|
|
23
|
+
const matrix = Array(NUM_SYNTAX)
|
|
24
|
+
.fill(0)
|
|
25
|
+
.map(() => Array(NUM_STATES).fill(0));
|
|
26
|
+
// Walk AST and count transitions
|
|
27
|
+
function visitNode(node, parentKind) {
|
|
28
|
+
const nodeState = getStateIndex(node);
|
|
29
|
+
if (parentKind !== undefined) {
|
|
30
|
+
const parentState = getStateIndex({ kind: parentKind });
|
|
31
|
+
// Only count transitions from syntax states (first 57)
|
|
32
|
+
if (parentState < NUM_SYNTAX) {
|
|
33
|
+
matrix[parentState][nodeState]++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Continue to children
|
|
37
|
+
ts.forEachChild(node, (child) => visitNode(child, node.kind));
|
|
38
|
+
}
|
|
39
|
+
visitNode(sourceFile);
|
|
40
|
+
return matrix;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build matrix from a source file node (for incremental updates)
|
|
44
|
+
*/
|
|
45
|
+
export function buildStateMatrixFromFile(sourceFile) {
|
|
46
|
+
// Initialize 57×72 matrix with zeros
|
|
47
|
+
const matrix = Array(NUM_SYNTAX)
|
|
48
|
+
.fill(0)
|
|
49
|
+
.map(() => Array(NUM_STATES).fill(0));
|
|
50
|
+
// Walk AST and count transitions
|
|
51
|
+
function visitNode(node, parentKind) {
|
|
52
|
+
const nodeState = getStateIndex(node);
|
|
53
|
+
if (parentKind !== undefined) {
|
|
54
|
+
const parentState = getStateIndex({ kind: parentKind });
|
|
55
|
+
// Only count transitions from syntax states (first 57)
|
|
56
|
+
if (parentState < NUM_SYNTAX) {
|
|
57
|
+
matrix[parentState][nodeState]++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Continue to children
|
|
61
|
+
ts.forEachChild(node, (child) => visitNode(child, node.kind));
|
|
62
|
+
}
|
|
63
|
+
visitNode(sourceFile);
|
|
64
|
+
return matrix;
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Probability Normalization
|
|
68
|
+
// ============================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Convert count matrix to probability matrix.
|
|
71
|
+
* Each row sums to 1 (Markov chain property).
|
|
72
|
+
*
|
|
73
|
+
* @param matrix 57×72 count matrix
|
|
74
|
+
* @returns 57×72 probability matrix
|
|
75
|
+
*/
|
|
76
|
+
export function toProbabilityMatrix(matrix) {
|
|
77
|
+
return matrix.map((row) => {
|
|
78
|
+
const sum = row.reduce((a, b) => a + b, 0);
|
|
79
|
+
if (sum === 0)
|
|
80
|
+
return row.map(() => 0);
|
|
81
|
+
return row.map((val) => val / sum);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Similarity Calculation
|
|
86
|
+
// ============================================================================
|
|
87
|
+
/**
|
|
88
|
+
* Calculate cosine similarity between two state matrices.
|
|
89
|
+
* Returns 0-1 similarity score (1 = identical).
|
|
90
|
+
*
|
|
91
|
+
* @param matrix1 57×72 count matrix
|
|
92
|
+
* @param matrix2 57×72 count matrix
|
|
93
|
+
* @returns similarity score 0-1
|
|
94
|
+
*/
|
|
95
|
+
export function calculateSimilarity(matrix1, matrix2) {
|
|
96
|
+
const prob1 = toProbabilityMatrix(matrix1);
|
|
97
|
+
const prob2 = toProbabilityMatrix(matrix2);
|
|
98
|
+
const similarities = [];
|
|
99
|
+
for (let i = 0; i < NUM_SYNTAX; i++) {
|
|
100
|
+
const row1 = prob1[i];
|
|
101
|
+
const row2 = prob2[i];
|
|
102
|
+
// Skip if both rows are empty
|
|
103
|
+
const hasData1 = row1.some((v) => v > 0);
|
|
104
|
+
const hasData2 = row2.some((v) => v > 0);
|
|
105
|
+
if (hasData1 || hasData2) {
|
|
106
|
+
// Calculate cosine similarity for this state
|
|
107
|
+
let dotProduct = 0;
|
|
108
|
+
let norm1 = 0;
|
|
109
|
+
let norm2 = 0;
|
|
110
|
+
for (let j = 0; j < NUM_STATES; j++) {
|
|
111
|
+
dotProduct += row1[j] * row2[j];
|
|
112
|
+
norm1 += row1[j] * row1[j];
|
|
113
|
+
norm2 += row2[j] * row2[j];
|
|
114
|
+
}
|
|
115
|
+
if (norm1 > 0 && norm2 > 0) {
|
|
116
|
+
similarities.push(dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Return average similarity across all states
|
|
121
|
+
if (similarities.length === 0)
|
|
122
|
+
return 0;
|
|
123
|
+
return similarities.reduce((a, b) => a + b, 0) / similarities.length;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calculate similarity from source code directly (convenience method)
|
|
127
|
+
*/
|
|
128
|
+
export function calculateSimilarityFromCode(code1, code2) {
|
|
129
|
+
const matrix1 = buildStateMatrix(code1);
|
|
130
|
+
const matrix2 = buildStateMatrix(code2);
|
|
131
|
+
return calculateSimilarity(matrix1, matrix2);
|
|
132
|
+
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Matrix Statistics (for guardrails)
|
|
135
|
+
// ============================================================================
|
|
136
|
+
/**
|
|
137
|
+
* Count total non-zero transitions in matrix (proxy for function complexity)
|
|
138
|
+
*/
|
|
139
|
+
export function countTransitions(matrix) {
|
|
140
|
+
return matrix.flat().filter((v) => v > 0).length;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if function meets complexity threshold (>20 transitions)
|
|
144
|
+
*/
|
|
145
|
+
export function isComplexEnough(matrix) {
|
|
146
|
+
return countTransitions(matrix) >= 20;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Serialize matrix for storage (sparse format to save space)
|
|
150
|
+
*/
|
|
151
|
+
export function serializeMatrix(matrix) {
|
|
152
|
+
// Return full matrix for now - can optimize to sparse later if needed
|
|
153
|
+
return matrix;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Deserialize matrix from storage
|
|
157
|
+
*/
|
|
158
|
+
export function deserializeMatrix(data) {
|
|
159
|
+
return data;
|
|
160
|
+
}
|