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,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta-test: Run similarity detection on pi-lens codebase
|
|
3
|
+
*
|
|
4
|
+
* This is a "dogfood" test - we run the reuse detection on our own code
|
|
5
|
+
* to see what it finds. Educational and useful for improving the algorithm!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { glob } from "glob";
|
|
12
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
13
|
+
import {
|
|
14
|
+
buildProjectIndex,
|
|
15
|
+
findSimilarFunctions,
|
|
16
|
+
type IndexEntry,
|
|
17
|
+
type ProjectIndex,
|
|
18
|
+
} from "./project-index.js";
|
|
19
|
+
import { calculateSimilarity as calcMatrixSimilarity } from "./state-matrix.js";
|
|
20
|
+
|
|
21
|
+
// Find project root by looking for package.json
|
|
22
|
+
async function findProjectRoot(startDir: string): Promise<string> {
|
|
23
|
+
let dir = startDir;
|
|
24
|
+
while (dir !== path.dirname(dir)) {
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(path.join(dir, "package.json"));
|
|
27
|
+
return dir;
|
|
28
|
+
} catch {
|
|
29
|
+
dir = path.dirname(dir);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error("Could not find project root (no package.json)");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Test a known similar pair
|
|
36
|
+
const _SIMILAR_FUNCTIONS = {
|
|
37
|
+
description: "Extracting similar logic patterns in pi-lens",
|
|
38
|
+
pairs: [
|
|
39
|
+
{
|
|
40
|
+
name: "runners/index.ts pattern",
|
|
41
|
+
files: [
|
|
42
|
+
"clients/dispatch/runners/index.ts",
|
|
43
|
+
"clients/dispatch/runners/architect.ts",
|
|
44
|
+
],
|
|
45
|
+
expected: "High similarity in runner registration patterns",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "Client pattern",
|
|
49
|
+
files: ["clients/typescript-client.ts", "clients/biome-client.ts"],
|
|
50
|
+
expected: "Similar client structures",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
describe("🐶 Dogfood Test: Similarity on pi-lens codebase", () => {
|
|
56
|
+
let index: ProjectIndex;
|
|
57
|
+
let projectRoot: string;
|
|
58
|
+
|
|
59
|
+
beforeAll(async () => {
|
|
60
|
+
// Find project root
|
|
61
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
62
|
+
projectRoot = await findProjectRoot(__dirname);
|
|
63
|
+
|
|
64
|
+
// Build index of the entire codebase
|
|
65
|
+
console.log("\n🏗️ Building index of pi-lens codebase...");
|
|
66
|
+
console.log(` Project root: ${projectRoot}`);
|
|
67
|
+
|
|
68
|
+
const files = await glob("clients/**/*.ts", {
|
|
69
|
+
cwd: projectRoot,
|
|
70
|
+
ignore: ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(` Found ${files.length} source files`);
|
|
74
|
+
|
|
75
|
+
const absoluteFiles = files.map((f) => path.join(projectRoot, f));
|
|
76
|
+
index = await buildProjectIndex(projectRoot, absoluteFiles);
|
|
77
|
+
|
|
78
|
+
console.log(` Indexed ${index.entries.size} functions`);
|
|
79
|
+
|
|
80
|
+
// Show some indexed functions
|
|
81
|
+
const sample = Array.from(index.entries.values()).slice(0, 5);
|
|
82
|
+
console.log("\n📋 Sample indexed functions:");
|
|
83
|
+
sample.forEach((e: IndexEntry, i: number) => {
|
|
84
|
+
console.log(` ${i + 1}. ${e.id} (${e.transitionCount} transitions)`);
|
|
85
|
+
});
|
|
86
|
+
}, 30000); // 30s timeout for indexing
|
|
87
|
+
|
|
88
|
+
describe("Index validation", () => {
|
|
89
|
+
it("should have indexed functions", () => {
|
|
90
|
+
expect(index.entries.size).toBeGreaterThan(0);
|
|
91
|
+
console.log(`\n✅ Indexed ${index.entries.size} functions`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should have functions with >20 transitions", () => {
|
|
95
|
+
const complex = Array.from(index.entries.values()).filter(
|
|
96
|
+
(e) => e.transitionCount >= 20,
|
|
97
|
+
);
|
|
98
|
+
expect(complex.length).toBeGreaterThan(0);
|
|
99
|
+
console.log(`\n✅ ${complex.length} functions pass complexity guardrail`);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("Find similar functions in our own codebase", () => {
|
|
104
|
+
it("should find similar patterns among runners", async () => {
|
|
105
|
+
// Find runner files
|
|
106
|
+
const runnerEntries = Array.from(index.entries.values()).filter(
|
|
107
|
+
(e: IndexEntry) => e.filePath.includes("dispatch/runners/"),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
console.log(`\n🔍 Testing ${runnerEntries.length} runner functions`);
|
|
111
|
+
|
|
112
|
+
const similarities: {
|
|
113
|
+
func1: string;
|
|
114
|
+
func2: string;
|
|
115
|
+
similarity: number;
|
|
116
|
+
}[] = [];
|
|
117
|
+
|
|
118
|
+
// Compare each pair
|
|
119
|
+
for (let i = 0; i < runnerEntries.length; i++) {
|
|
120
|
+
for (let j = i + 1; j < runnerEntries.length; j++) {
|
|
121
|
+
const entry1 = runnerEntries[i];
|
|
122
|
+
const entry2 = runnerEntries[j];
|
|
123
|
+
|
|
124
|
+
// Skip if same file
|
|
125
|
+
if (entry1.filePath === entry2.filePath) continue;
|
|
126
|
+
|
|
127
|
+
const sim = calcMatrixSimilarity(entry1.matrix, entry2.matrix);
|
|
128
|
+
|
|
129
|
+
if (sim >= 0.75) {
|
|
130
|
+
similarities.push({
|
|
131
|
+
func1: entry1.id,
|
|
132
|
+
func2: entry2.id,
|
|
133
|
+
similarity: sim,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Sort by similarity
|
|
140
|
+
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
141
|
+
|
|
142
|
+
console.log(`\n📊 Found ${similarities.length} similar pairs (>75%):`);
|
|
143
|
+
similarities.slice(0, 5).forEach((s, i) => {
|
|
144
|
+
console.log(` ${i + 1}. ${s.func1} ↔ ${s.func2}`);
|
|
145
|
+
console.log(` Similarity: ${(s.similarity * 100).toFixed(1)}%`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Log findings but don't fail - this is exploratory
|
|
149
|
+
expect(similarities.length).toBeGreaterThanOrEqual(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should find similar client patterns", async () => {
|
|
153
|
+
const clientEntries = Array.from(index.entries.values()).filter(
|
|
154
|
+
(e: IndexEntry) =>
|
|
155
|
+
e.filePath.includes("clients/") &&
|
|
156
|
+
e.filePath.includes("-client.ts") &&
|
|
157
|
+
!e.filePath.includes("test"),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
console.log(`\n🔍 Testing ${clientEntries.length} client functions`);
|
|
161
|
+
|
|
162
|
+
const similarities: {
|
|
163
|
+
func1: string;
|
|
164
|
+
func2: string;
|
|
165
|
+
similarity: number;
|
|
166
|
+
}[] = [];
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < clientEntries.length; i++) {
|
|
169
|
+
for (let j = i + 1; j < clientEntries.length; j++) {
|
|
170
|
+
const entry1 = clientEntries[i];
|
|
171
|
+
const entry2 = clientEntries[j];
|
|
172
|
+
|
|
173
|
+
if (entry1.filePath === entry2.filePath) continue;
|
|
174
|
+
|
|
175
|
+
const sim = calcMatrixSimilarity(entry1.matrix, entry2.matrix);
|
|
176
|
+
|
|
177
|
+
if (sim >= 0.75) {
|
|
178
|
+
similarities.push({
|
|
179
|
+
func1: entry1.id,
|
|
180
|
+
func2: entry2.id,
|
|
181
|
+
similarity: sim,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
188
|
+
|
|
189
|
+
console.log(
|
|
190
|
+
`\n📊 Found ${similarities.length} similar client patterns (>75%):`,
|
|
191
|
+
);
|
|
192
|
+
similarities.slice(0, 3).forEach((s, i) => {
|
|
193
|
+
console.log(` ${i + 1}. ${s.func1} ↔ ${s.func2}`);
|
|
194
|
+
console.log(` Similarity: ${(s.similarity * 100).toFixed(1)}%`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(similarities.length).toBeGreaterThanOrEqual(0);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("Find potential refactor opportunities", () => {
|
|
202
|
+
it("should identify duplicate utility functions", () => {
|
|
203
|
+
// Look for functions with very high similarity (>90%)
|
|
204
|
+
const entries = Array.from(index.entries.values());
|
|
205
|
+
const seenPairs = new Set<string>(); // Deduplicate A→B and B→A
|
|
206
|
+
const duplicates: {
|
|
207
|
+
func: string;
|
|
208
|
+
similarTo: string;
|
|
209
|
+
similarity: number;
|
|
210
|
+
}[] = [];
|
|
211
|
+
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
const matches = findSimilarFunctions(entry.matrix, index, 0.9, 3);
|
|
214
|
+
for (const match of matches) {
|
|
215
|
+
if (match.targetId === entry.id) continue;
|
|
216
|
+
|
|
217
|
+
// Canonical pair key (sorted to avoid A,B and B,A)
|
|
218
|
+
const pairKey = [entry.id, match.targetId].sort().join("::");
|
|
219
|
+
if (seenPairs.has(pairKey)) continue;
|
|
220
|
+
|
|
221
|
+
seenPairs.add(pairKey);
|
|
222
|
+
duplicates.push({
|
|
223
|
+
func: entry.id,
|
|
224
|
+
similarTo: match.targetId,
|
|
225
|
+
similarity: match.similarity,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log(
|
|
231
|
+
`\n🎯 Found ${duplicates.length} unique potential duplicates (>90%):`,
|
|
232
|
+
);
|
|
233
|
+
duplicates.slice(0, 5).forEach((d, i) => {
|
|
234
|
+
console.log(` ${i + 1}. ${d.func}`);
|
|
235
|
+
console.log(` Similar to: ${d.similarTo}`);
|
|
236
|
+
console.log(` Match: ${(d.similarity * 100).toFixed(1)}%`);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// This is informational - we don't assert on it
|
|
240
|
+
expect(true).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("Complexity distribution", () => {
|
|
245
|
+
it("should show transition count distribution", () => {
|
|
246
|
+
const entries = Array.from(index.entries.values());
|
|
247
|
+
const transitionCounts = entries.map((e) => e.transitionCount);
|
|
248
|
+
|
|
249
|
+
const avg =
|
|
250
|
+
transitionCounts.reduce((a, b) => a + b, 0) / transitionCounts.length;
|
|
251
|
+
const min = Math.min(...transitionCounts);
|
|
252
|
+
const max = Math.max(...transitionCounts);
|
|
253
|
+
|
|
254
|
+
const belowThreshold = transitionCounts.filter((c) => c < 20).length;
|
|
255
|
+
const aboveThreshold = transitionCounts.filter((c) => c >= 20).length;
|
|
256
|
+
|
|
257
|
+
console.log("\n📊 Complexity Distribution:");
|
|
258
|
+
console.log(` Total functions: ${entries.length}`);
|
|
259
|
+
console.log(` Below threshold (<20): ${belowThreshold}`);
|
|
260
|
+
console.log(` Above threshold (≥20): ${aboveThreshold}`);
|
|
261
|
+
console.log(` Min transitions: ${min}`);
|
|
262
|
+
console.log(` Max transitions: ${max}`);
|
|
263
|
+
console.log(` Average: ${avg.toFixed(1)}`);
|
|
264
|
+
|
|
265
|
+
// Most functions should pass the guardrail
|
|
266
|
+
expect(aboveThreshold).toBeGreaterThan(0);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileTime Tracking for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Prevents race conditions when auto-formatting or external tools modify files.
|
|
5
|
+
* Tracks file modification times and sizes to detect external changes.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by OpenCode's FileTime system - ensures agents re-read files
|
|
8
|
+
* that have been modified externally (including by formatters).
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
// --- Singleton State ---
|
|
13
|
+
const globalState = {
|
|
14
|
+
reads: new Map(),
|
|
15
|
+
locks: new Map(),
|
|
16
|
+
};
|
|
17
|
+
// --- Public API ---
|
|
18
|
+
export class FileTime {
|
|
19
|
+
constructor(sessionID) {
|
|
20
|
+
this.sessionID = sessionID;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Record a file read with current stats
|
|
24
|
+
* Call this after ANY file modification (including formatting)
|
|
25
|
+
*/
|
|
26
|
+
read(filePath) {
|
|
27
|
+
const absolutePath = path.resolve(filePath);
|
|
28
|
+
const stamp = createStamp(absolutePath);
|
|
29
|
+
let sessionReads = globalState.reads.get(this.sessionID);
|
|
30
|
+
if (!sessionReads) {
|
|
31
|
+
sessionReads = new Map();
|
|
32
|
+
globalState.reads.set(this.sessionID, sessionReads);
|
|
33
|
+
}
|
|
34
|
+
sessionReads.set(absolutePath, stamp);
|
|
35
|
+
return stamp;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get last recorded stamp for a file
|
|
39
|
+
*/
|
|
40
|
+
get(filePath) {
|
|
41
|
+
const absolutePath = path.resolve(filePath);
|
|
42
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
43
|
+
return sessionReads?.get(absolutePath);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Assert file hasn't changed since last read
|
|
47
|
+
* Throws error if file modified externally - forces agent to re-read
|
|
48
|
+
*/
|
|
49
|
+
assert(filePath) {
|
|
50
|
+
const absolutePath = path.resolve(filePath);
|
|
51
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
52
|
+
const recorded = sessionReads?.get(absolutePath);
|
|
53
|
+
if (!recorded) {
|
|
54
|
+
throw new FileTimeError(`You must read file ${absolutePath} before modifying it. Use the read tool first.`, absolutePath, "not-read");
|
|
55
|
+
}
|
|
56
|
+
const current = createStamp(absolutePath);
|
|
57
|
+
const changed = current.mtime !== recorded.mtime ||
|
|
58
|
+
current.ctime !== recorded.ctime ||
|
|
59
|
+
current.size !== recorded.size;
|
|
60
|
+
if (changed) {
|
|
61
|
+
throw new FileTimeError(`File ${absolutePath} has been modified since it was last read.\n` +
|
|
62
|
+
`Last modification: ${new Date(current.mtime ?? Date.now()).toISOString()}\n` +
|
|
63
|
+
`Last read: ${recorded.readAt.toISOString()}\n\n` +
|
|
64
|
+
`Please read the file again before modifying it.`, absolutePath, "modified");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if file has changed (non-throwing version of assert)
|
|
69
|
+
*/
|
|
70
|
+
hasChanged(filePath) {
|
|
71
|
+
const absolutePath = path.resolve(filePath);
|
|
72
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
73
|
+
const recorded = sessionReads?.get(absolutePath);
|
|
74
|
+
if (!recorded)
|
|
75
|
+
return true; // Never read = changed
|
|
76
|
+
const current = createStamp(absolutePath);
|
|
77
|
+
return (current.mtime !== recorded.mtime ||
|
|
78
|
+
current.ctime !== recorded.ctime ||
|
|
79
|
+
current.size !== recorded.size);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Acquire exclusive lock on file
|
|
83
|
+
* Prevents concurrent modifications to same file
|
|
84
|
+
*/
|
|
85
|
+
async withLock(filePath, fn) {
|
|
86
|
+
const absolutePath = path.resolve(filePath);
|
|
87
|
+
// Wait for existing lock
|
|
88
|
+
while (globalState.locks.has(absolutePath)) {
|
|
89
|
+
const existing = globalState.locks.get(absolutePath);
|
|
90
|
+
if (existing)
|
|
91
|
+
await existing;
|
|
92
|
+
}
|
|
93
|
+
// Create new lock
|
|
94
|
+
const lockPromise = fn().finally(() => {
|
|
95
|
+
globalState.locks.delete(absolutePath);
|
|
96
|
+
});
|
|
97
|
+
globalState.locks.set(absolutePath, lockPromise.then(() => { }));
|
|
98
|
+
return lockPromise;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Clear all tracked files for this session
|
|
102
|
+
*/
|
|
103
|
+
clear() {
|
|
104
|
+
globalState.reads.delete(this.sessionID);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Clear specific file tracking
|
|
108
|
+
*/
|
|
109
|
+
clearFile(filePath) {
|
|
110
|
+
const absolutePath = path.resolve(filePath);
|
|
111
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
112
|
+
sessionReads?.delete(absolutePath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// --- Error Type ---
|
|
116
|
+
export class FileTimeError extends Error {
|
|
117
|
+
constructor(message, filePath, reason) {
|
|
118
|
+
super(message);
|
|
119
|
+
this.name = "FileTimeError";
|
|
120
|
+
this.filePath = filePath;
|
|
121
|
+
this.reason = reason;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// --- Utilities ---
|
|
125
|
+
function createStamp(filePath) {
|
|
126
|
+
try {
|
|
127
|
+
const stats = fs.statSync(filePath);
|
|
128
|
+
return {
|
|
129
|
+
readAt: new Date(),
|
|
130
|
+
mtime: stats.mtime.getTime(),
|
|
131
|
+
ctime: stats.ctime.getTime(),
|
|
132
|
+
size: stats.size,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// File doesn't exist - return empty stamp
|
|
137
|
+
return {
|
|
138
|
+
readAt: new Date(),
|
|
139
|
+
mtime: undefined,
|
|
140
|
+
ctime: undefined,
|
|
141
|
+
size: undefined,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// --- Global Helpers ---
|
|
146
|
+
export function createFileTime(sessionID) {
|
|
147
|
+
return new FileTime(sessionID);
|
|
148
|
+
}
|
|
149
|
+
export function clearAllSessions() {
|
|
150
|
+
globalState.reads.clear();
|
|
151
|
+
globalState.locks.clear();
|
|
152
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileTime Tracking for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Prevents race conditions when auto-formatting or external tools modify files.
|
|
5
|
+
* Tracks file modification times and sizes to detect external changes.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by OpenCode's FileTime system - ensures agents re-read files
|
|
8
|
+
* that have been modified externally (including by formatters).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
|
|
14
|
+
// --- Types ---
|
|
15
|
+
|
|
16
|
+
export interface FileStamp {
|
|
17
|
+
readAt: Date;
|
|
18
|
+
mtime: number | undefined;
|
|
19
|
+
ctime: number | undefined;
|
|
20
|
+
size: number | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface FileTimeState {
|
|
24
|
+
reads: Map<string, Map<string, FileStamp>>; // sessionID -> filePath -> stamp
|
|
25
|
+
locks: Map<string, Promise<void>>; // filePath -> lock promise
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --- Singleton State ---
|
|
29
|
+
|
|
30
|
+
const globalState: FileTimeState = {
|
|
31
|
+
reads: new Map(),
|
|
32
|
+
locks: new Map(),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// --- Public API ---
|
|
36
|
+
|
|
37
|
+
export class FileTime {
|
|
38
|
+
private sessionID: string;
|
|
39
|
+
|
|
40
|
+
constructor(sessionID: string) {
|
|
41
|
+
this.sessionID = sessionID;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Record a file read with current stats
|
|
46
|
+
* Call this after ANY file modification (including formatting)
|
|
47
|
+
*/
|
|
48
|
+
read(filePath: string): FileStamp {
|
|
49
|
+
const absolutePath = path.resolve(filePath);
|
|
50
|
+
const stamp = createStamp(absolutePath);
|
|
51
|
+
|
|
52
|
+
let sessionReads = globalState.reads.get(this.sessionID);
|
|
53
|
+
if (!sessionReads) {
|
|
54
|
+
sessionReads = new Map();
|
|
55
|
+
globalState.reads.set(this.sessionID, sessionReads);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sessionReads.set(absolutePath, stamp);
|
|
59
|
+
return stamp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get last recorded stamp for a file
|
|
64
|
+
*/
|
|
65
|
+
get(filePath: string): FileStamp | undefined {
|
|
66
|
+
const absolutePath = path.resolve(filePath);
|
|
67
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
68
|
+
return sessionReads?.get(absolutePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Assert file hasn't changed since last read
|
|
73
|
+
* Throws error if file modified externally - forces agent to re-read
|
|
74
|
+
*/
|
|
75
|
+
assert(filePath: string): void {
|
|
76
|
+
const absolutePath = path.resolve(filePath);
|
|
77
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
78
|
+
const recorded = sessionReads?.get(absolutePath);
|
|
79
|
+
|
|
80
|
+
if (!recorded) {
|
|
81
|
+
throw new FileTimeError(
|
|
82
|
+
`You must read file ${absolutePath} before modifying it. Use the read tool first.`,
|
|
83
|
+
absolutePath,
|
|
84
|
+
"not-read"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const current = createStamp(absolutePath);
|
|
89
|
+
const changed =
|
|
90
|
+
current.mtime !== recorded.mtime ||
|
|
91
|
+
current.ctime !== recorded.ctime ||
|
|
92
|
+
current.size !== recorded.size;
|
|
93
|
+
|
|
94
|
+
if (changed) {
|
|
95
|
+
throw new FileTimeError(
|
|
96
|
+
`File ${absolutePath} has been modified since it was last read.\n` +
|
|
97
|
+
`Last modification: ${new Date(current.mtime ?? Date.now()).toISOString()}\n` +
|
|
98
|
+
`Last read: ${recorded.readAt.toISOString()}\n\n` +
|
|
99
|
+
`Please read the file again before modifying it.`,
|
|
100
|
+
absolutePath,
|
|
101
|
+
"modified"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if file has changed (non-throwing version of assert)
|
|
108
|
+
*/
|
|
109
|
+
hasChanged(filePath: string): boolean {
|
|
110
|
+
const absolutePath = path.resolve(filePath);
|
|
111
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
112
|
+
const recorded = sessionReads?.get(absolutePath);
|
|
113
|
+
|
|
114
|
+
if (!recorded) return true; // Never read = changed
|
|
115
|
+
|
|
116
|
+
const current = createStamp(absolutePath);
|
|
117
|
+
return (
|
|
118
|
+
current.mtime !== recorded.mtime ||
|
|
119
|
+
current.ctime !== recorded.ctime ||
|
|
120
|
+
current.size !== recorded.size
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Acquire exclusive lock on file
|
|
126
|
+
* Prevents concurrent modifications to same file
|
|
127
|
+
*/
|
|
128
|
+
async withLock<T>(filePath: string, fn: () => Promise<T>): Promise<T> {
|
|
129
|
+
const absolutePath = path.resolve(filePath);
|
|
130
|
+
|
|
131
|
+
// Wait for existing lock
|
|
132
|
+
while (globalState.locks.has(absolutePath)) {
|
|
133
|
+
const existing = globalState.locks.get(absolutePath);
|
|
134
|
+
if (existing) await existing;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create new lock
|
|
138
|
+
const lockPromise = fn().finally(() => {
|
|
139
|
+
globalState.locks.delete(absolutePath);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
globalState.locks.set(absolutePath, lockPromise.then(() => {}));
|
|
143
|
+
return lockPromise;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear all tracked files for this session
|
|
148
|
+
*/
|
|
149
|
+
clear(): void {
|
|
150
|
+
globalState.reads.delete(this.sessionID);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Clear specific file tracking
|
|
155
|
+
*/
|
|
156
|
+
clearFile(filePath: string): void {
|
|
157
|
+
const absolutePath = path.resolve(filePath);
|
|
158
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
159
|
+
sessionReads?.delete(absolutePath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- Error Type ---
|
|
164
|
+
|
|
165
|
+
export class FileTimeError extends Error {
|
|
166
|
+
readonly filePath: string;
|
|
167
|
+
readonly reason: "not-read" | "modified";
|
|
168
|
+
|
|
169
|
+
constructor(message: string, filePath: string, reason: "not-read" | "modified") {
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "FileTimeError";
|
|
172
|
+
this.filePath = filePath;
|
|
173
|
+
this.reason = reason;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Utilities ---
|
|
178
|
+
|
|
179
|
+
function createStamp(filePath: string): FileStamp {
|
|
180
|
+
try {
|
|
181
|
+
const stats = fs.statSync(filePath);
|
|
182
|
+
return {
|
|
183
|
+
readAt: new Date(),
|
|
184
|
+
mtime: stats.mtime.getTime(),
|
|
185
|
+
ctime: stats.ctime.getTime(),
|
|
186
|
+
size: stats.size,
|
|
187
|
+
};
|
|
188
|
+
} catch {
|
|
189
|
+
// File doesn't exist - return empty stamp
|
|
190
|
+
return {
|
|
191
|
+
readAt: new Date(),
|
|
192
|
+
mtime: undefined,
|
|
193
|
+
ctime: undefined,
|
|
194
|
+
size: undefined,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// --- Global Helpers ---
|
|
200
|
+
|
|
201
|
+
export function createFileTime(sessionID: string): FileTime {
|
|
202
|
+
return new FileTime(sessionID);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function clearAllSessions(): void {
|
|
206
|
+
globalState.reads.clear();
|
|
207
|
+
globalState.locks.clear();
|
|
208
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared file path utilities for pi-lens
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Directories to exclude from all scans (build outputs, dependencies, caches).
|
|
6
|
+
* Used consistently across all scanners to avoid noise from generated files.
|
|
7
|
+
*/
|
|
8
|
+
export const EXCLUDED_DIRS = [
|
|
9
|
+
"node_modules",
|
|
10
|
+
".git",
|
|
11
|
+
"dist",
|
|
12
|
+
"build",
|
|
13
|
+
".next",
|
|
14
|
+
".pi-lens",
|
|
15
|
+
".pi", // pi agent directory
|
|
16
|
+
".ruff_cache", // Python linter cache
|
|
17
|
+
"venv",
|
|
18
|
+
".venv",
|
|
19
|
+
"coverage",
|
|
20
|
+
"__pycache__",
|
|
21
|
+
".tox",
|
|
22
|
+
".pytest_cache",
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Check if file path is a test/fixture/mock file.
|
|
26
|
+
* Used by secrets scanner, rate command, and dispatch runners
|
|
27
|
+
* to skip these files (false positives on fake credentials, etc).
|
|
28
|
+
*/
|
|
29
|
+
export function isTestFile(filePath) {
|
|
30
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
31
|
+
return (normalized.includes(".test.") ||
|
|
32
|
+
normalized.includes(".spec.") ||
|
|
33
|
+
normalized.includes("/test/") ||
|
|
34
|
+
normalized.includes("/tests/") ||
|
|
35
|
+
normalized.includes("__tests__/") ||
|
|
36
|
+
normalized.includes("test-utils") ||
|
|
37
|
+
normalized.startsWith("test-") ||
|
|
38
|
+
normalized.includes(".fixture.") ||
|
|
39
|
+
normalized.includes(".mock."));
|
|
40
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared file path utilities for pi-lens
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Directories to exclude from all scans (build outputs, dependencies, caches).
|
|
7
|
+
* Used consistently across all scanners to avoid noise from generated files.
|
|
8
|
+
*/
|
|
9
|
+
export const EXCLUDED_DIRS = [
|
|
10
|
+
"node_modules",
|
|
11
|
+
".git",
|
|
12
|
+
"dist",
|
|
13
|
+
"build",
|
|
14
|
+
".next",
|
|
15
|
+
".pi-lens",
|
|
16
|
+
".pi", // pi agent directory
|
|
17
|
+
".ruff_cache", // Python linter cache
|
|
18
|
+
"venv",
|
|
19
|
+
".venv",
|
|
20
|
+
"coverage",
|
|
21
|
+
"__pycache__",
|
|
22
|
+
".tox",
|
|
23
|
+
".pytest_cache",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if file path is a test/fixture/mock file.
|
|
28
|
+
* Used by secrets scanner, rate command, and dispatch runners
|
|
29
|
+
* to skip these files (false positives on fake credentials, etc).
|
|
30
|
+
*/
|
|
31
|
+
export function isTestFile(filePath: string): boolean {
|
|
32
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
33
|
+
return (
|
|
34
|
+
normalized.includes(".test.") ||
|
|
35
|
+
normalized.includes(".spec.") ||
|
|
36
|
+
normalized.includes("/test/") ||
|
|
37
|
+
normalized.includes("/tests/") ||
|
|
38
|
+
normalized.includes("__tests__/") ||
|
|
39
|
+
normalized.includes("test-utils") ||
|
|
40
|
+
normalized.startsWith("test-") ||
|
|
41
|
+
normalized.includes(".fixture.") ||
|
|
42
|
+
normalized.includes(".mock.")
|
|
43
|
+
);
|
|
44
|
+
}
|