pi-lens 3.6.2 → 3.6.4
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 +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
package/CHANGELOG.md
CHANGED
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [3.6.
|
|
5
|
+
## [3.6.2] - 2026-04-02
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
|
-
- **Condensed skill auto-loading** — Injects 70-token tool selection guidance at session start:
|
|
8
|
+
- **Condensed skill auto-loading** — Injects ~70-token tool selection guidance at session start (vs 1,355 for full skills):
|
|
9
9
|
- Quick reference for when to use lsp_navigation vs ast_grep_search vs grep
|
|
10
10
|
- References full skills for lazy loading (ast-grep, lsp-navigation)
|
|
11
11
|
- Prevents common tool selection errors without loading full skill content
|
|
12
12
|
|
|
13
|
+
### Changed
|
|
14
|
+
- **Streamlined session start injection** — Removed TODO/Knip/jscpd reports from initial context:
|
|
15
|
+
- Scans still run and cache for on-demand access via `/lens-booboo`
|
|
16
|
+
- Reduces session start noise (only active tools list, error reminder, skill guidance remain)
|
|
17
|
+
- Caching preserved for duplicate detection on file writes
|
|
18
|
+
|
|
19
|
+
## [3.6.1] - 2026-04-02
|
|
20
|
+
|
|
13
21
|
### Changed
|
|
14
22
|
- **Updated package description** — More concise: "Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo"
|
|
15
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo",
|
|
6
6
|
"repository": {
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"jscpd",
|
|
30
30
|
"knip"
|
|
31
31
|
],
|
|
32
|
-
"author": "
|
|
32
|
+
"author": "Apostolos Mantzaris",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"files": [
|
|
35
35
|
"index.ts",
|
|
36
|
-
"clients
|
|
37
|
-
"commands
|
|
36
|
+
"clients/**/*.ts",
|
|
37
|
+
"commands/**/*.ts",
|
|
38
38
|
"rules/",
|
|
39
39
|
"skills/",
|
|
40
40
|
"default-architect.yaml",
|
package/tsconfig.json
CHANGED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileTime Tracking Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests the safety mechanism that prevents race conditions
|
|
5
|
-
* between auto-formatting and agent edits.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
import { FileTime, FileTimeError, createFileTime, clearAllSessions } from "../file-time.js";
|
|
11
|
-
import { fileURLToPath } from "url";
|
|
12
|
-
import { dirname } from "path";
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = dirname(__filename);
|
|
15
|
-
const TEST_DIR = path.join(__dirname, "..", "..", "test-filetime");
|
|
16
|
-
describe("FileTime", () => {
|
|
17
|
-
let fileTime;
|
|
18
|
-
const sessionID = "test-session";
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
clearAllSessions();
|
|
21
|
-
fileTime = new FileTime(sessionID);
|
|
22
|
-
// Ensure test directory exists
|
|
23
|
-
if (!fs.existsSync(TEST_DIR)) {
|
|
24
|
-
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
describe("read()", () => {
|
|
28
|
-
it("should record file stamp with mtime/ctime/size", () => {
|
|
29
|
-
const testFile = path.join(TEST_DIR, "test1.txt");
|
|
30
|
-
fs.writeFileSync(testFile, "hello");
|
|
31
|
-
const stamp = fileTime.read(testFile);
|
|
32
|
-
expect(stamp.readAt).toBeInstanceOf(Date);
|
|
33
|
-
expect(stamp.mtime).toBeDefined();
|
|
34
|
-
expect(stamp.ctime).toBeDefined();
|
|
35
|
-
expect(stamp.size).toBe(5);
|
|
36
|
-
});
|
|
37
|
-
it("should handle non-existent files gracefully", () => {
|
|
38
|
-
const testFile = path.join(TEST_DIR, "nonexistent.txt");
|
|
39
|
-
const stamp = fileTime.read(testFile);
|
|
40
|
-
expect(stamp.readAt).toBeInstanceOf(Date);
|
|
41
|
-
expect(stamp.mtime).toBeUndefined();
|
|
42
|
-
expect(stamp.ctime).toBeUndefined();
|
|
43
|
-
expect(stamp.size).toBeUndefined();
|
|
44
|
-
});
|
|
45
|
-
it("should track multiple files per session", () => {
|
|
46
|
-
const file1 = path.join(TEST_DIR, "file1.txt");
|
|
47
|
-
const file2 = path.join(TEST_DIR, "file2.txt");
|
|
48
|
-
fs.writeFileSync(file1, "content1");
|
|
49
|
-
fs.writeFileSync(file2, "content2");
|
|
50
|
-
fileTime.read(file1);
|
|
51
|
-
fileTime.read(file2);
|
|
52
|
-
expect(fileTime.get(file1)).toBeDefined();
|
|
53
|
-
expect(fileTime.get(file2)).toBeDefined();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
describe("get()", () => {
|
|
57
|
-
it("should return undefined for unread files", () => {
|
|
58
|
-
const testFile = path.join(TEST_DIR, "unread.txt");
|
|
59
|
-
const stamp = fileTime.get(testFile);
|
|
60
|
-
expect(stamp).toBeUndefined();
|
|
61
|
-
});
|
|
62
|
-
it("should return recorded stamp for read files", () => {
|
|
63
|
-
const testFile = path.join(TEST_DIR, "test2.txt");
|
|
64
|
-
fs.writeFileSync(testFile, "content");
|
|
65
|
-
const recorded = fileTime.read(testFile);
|
|
66
|
-
const retrieved = fileTime.get(testFile);
|
|
67
|
-
expect(retrieved?.mtime).toBe(recorded.mtime);
|
|
68
|
-
expect(retrieved?.ctime).toBe(recorded.ctime);
|
|
69
|
-
expect(retrieved?.size).toBe(recorded.size);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe("assert()", () => {
|
|
73
|
-
it("should throw FileTimeError for unread files", () => {
|
|
74
|
-
const testFile = path.join(TEST_DIR, "never-read.txt");
|
|
75
|
-
fs.writeFileSync(testFile, "content");
|
|
76
|
-
expect(() => fileTime.assert(testFile)).toThrow(FileTimeError);
|
|
77
|
-
expect(() => fileTime.assert(testFile)).toThrow(/must read file/);
|
|
78
|
-
});
|
|
79
|
-
it("should not throw for unchanged files", () => {
|
|
80
|
-
const testFile = path.join(TEST_DIR, "unchanged.txt");
|
|
81
|
-
fs.writeFileSync(testFile, "content");
|
|
82
|
-
fileTime.read(testFile);
|
|
83
|
-
expect(() => fileTime.assert(testFile)).not.toThrow();
|
|
84
|
-
});
|
|
85
|
-
it("should throw FileTimeError when file modified externally", () => {
|
|
86
|
-
const testFile = path.join(TEST_DIR, "modified.txt");
|
|
87
|
-
fs.writeFileSync(testFile, "original");
|
|
88
|
-
fileTime.read(testFile);
|
|
89
|
-
// Simulate external modification
|
|
90
|
-
fs.writeFileSync(testFile, "modified content");
|
|
91
|
-
expect(() => fileTime.assert(testFile)).toThrow(FileTimeError);
|
|
92
|
-
expect(() => fileTime.assert(testFile)).toThrow(/modified since it was last read/);
|
|
93
|
-
});
|
|
94
|
-
it("should detect size changes", () => {
|
|
95
|
-
const testFile = path.join(TEST_DIR, "size-change.txt");
|
|
96
|
-
fs.writeFileSync(testFile, "original content");
|
|
97
|
-
fileTime.read(testFile);
|
|
98
|
-
// Truncate file (size change, mtime change)
|
|
99
|
-
fs.writeFileSync(testFile, "x");
|
|
100
|
-
expect(() => fileTime.assert(testFile)).toThrow(FileTimeError);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
describe("hasChanged()", () => {
|
|
104
|
-
it("should return true for unread files", () => {
|
|
105
|
-
const testFile = path.join(TEST_DIR, "unread-check.txt");
|
|
106
|
-
fs.writeFileSync(testFile, "content");
|
|
107
|
-
expect(fileTime.hasChanged(testFile)).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
it("should return false for unchanged files", () => {
|
|
110
|
-
const testFile = path.join(TEST_DIR, "unchanged-check.txt");
|
|
111
|
-
fs.writeFileSync(testFile, "content");
|
|
112
|
-
fileTime.read(testFile);
|
|
113
|
-
expect(fileTime.hasChanged(testFile)).toBe(false);
|
|
114
|
-
});
|
|
115
|
-
it("should return true when file modified", () => {
|
|
116
|
-
const testFile = path.join(TEST_DIR, "changed-check.txt");
|
|
117
|
-
fs.writeFileSync(testFile, "original");
|
|
118
|
-
fileTime.read(testFile);
|
|
119
|
-
fs.writeFileSync(testFile, "changed");
|
|
120
|
-
expect(fileTime.hasChanged(testFile)).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
describe("withLock()", () => {
|
|
124
|
-
it("should execute function exclusively", async () => {
|
|
125
|
-
const testFile = path.join(TEST_DIR, "locked.txt");
|
|
126
|
-
const executionOrder = [];
|
|
127
|
-
const fn1 = async () => {
|
|
128
|
-
executionOrder.push("start1");
|
|
129
|
-
await new Promise(r => setTimeout(r, 50));
|
|
130
|
-
executionOrder.push("end1");
|
|
131
|
-
return "result1";
|
|
132
|
-
};
|
|
133
|
-
const fn2 = async () => {
|
|
134
|
-
executionOrder.push("start2");
|
|
135
|
-
await new Promise(r => setTimeout(r, 50));
|
|
136
|
-
executionOrder.push("end2");
|
|
137
|
-
return "result2";
|
|
138
|
-
};
|
|
139
|
-
// Start both, but they should execute sequentially
|
|
140
|
-
const promise1 = fileTime.withLock(testFile, fn1);
|
|
141
|
-
const promise2 = fileTime.withLock(testFile, fn2);
|
|
142
|
-
await Promise.all([promise1, promise2]);
|
|
143
|
-
// Should be sequential, not interleaved
|
|
144
|
-
expect(executionOrder).toEqual(["start1", "end1", "start2", "end2"]);
|
|
145
|
-
});
|
|
146
|
-
it("should return function result", async () => {
|
|
147
|
-
const testFile = path.join(TEST_DIR, "lock-result.txt");
|
|
148
|
-
const result = await fileTime.withLock(testFile, async () => {
|
|
149
|
-
return "success";
|
|
150
|
-
});
|
|
151
|
-
expect(result).toBe("success");
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
describe("clear()", () => {
|
|
155
|
-
it("should clear all tracked files for session", () => {
|
|
156
|
-
const file1 = path.join(TEST_DIR, "clear1.txt");
|
|
157
|
-
const file2 = path.join(TEST_DIR, "clear2.txt");
|
|
158
|
-
fs.writeFileSync(file1, "a");
|
|
159
|
-
fs.writeFileSync(file2, "b");
|
|
160
|
-
fileTime.read(file1);
|
|
161
|
-
fileTime.read(file2);
|
|
162
|
-
fileTime.clear();
|
|
163
|
-
expect(fileTime.get(file1)).toBeUndefined();
|
|
164
|
-
expect(fileTime.get(file2)).toBeUndefined();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe("cross-session isolation", () => {
|
|
168
|
-
it("should isolate file tracking between sessions", () => {
|
|
169
|
-
const testFile = path.join(TEST_DIR, "isolated.txt");
|
|
170
|
-
fs.writeFileSync(testFile, "content");
|
|
171
|
-
const session1 = new FileTime("session1");
|
|
172
|
-
const session2 = new FileTime("session2");
|
|
173
|
-
session1.read(testFile);
|
|
174
|
-
// session2 should not see session1's reads
|
|
175
|
-
expect(() => session2.assert(testFile)).toThrow(FileTimeError);
|
|
176
|
-
expect(() => session2.assert(testFile)).toThrow(/must read file/);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
describe("FileTimeError", () => {
|
|
180
|
-
it("should have correct error properties", () => {
|
|
181
|
-
const testFile = path.join(TEST_DIR, "error.txt");
|
|
182
|
-
fs.writeFileSync(testFile, "content");
|
|
183
|
-
try {
|
|
184
|
-
fileTime.assert(testFile);
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
expect(error).toBeInstanceOf(FileTimeError);
|
|
188
|
-
expect(error.name).toBe("FileTimeError");
|
|
189
|
-
expect(error.filePath).toBe(path.resolve(testFile));
|
|
190
|
-
expect(error.reason).toBe("not-read");
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
describe("createFileTime helper", () => {
|
|
196
|
-
it("should create FileTime instance with session ID", () => {
|
|
197
|
-
const ft = createFileTime("my-session");
|
|
198
|
-
expect(ft).toBeInstanceOf(FileTime);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
describe("clearAllSessions helper", () => {
|
|
202
|
-
it("should clear all session tracking", () => {
|
|
203
|
-
const ft1 = createFileTime("session1");
|
|
204
|
-
const ft2 = createFileTime("session2");
|
|
205
|
-
const testFile = path.join(TEST_DIR, "clearall.txt");
|
|
206
|
-
fs.writeFileSync(testFile, "x");
|
|
207
|
-
ft1.read(testFile);
|
|
208
|
-
ft2.read(testFile);
|
|
209
|
-
clearAllSessions();
|
|
210
|
-
// After clearing, both should throw "not read"
|
|
211
|
-
const ft1New = createFileTime("session1");
|
|
212
|
-
const ft2New = createFileTime("session2");
|
|
213
|
-
expect(() => ft1New.assert(testFile)).toThrow(/must read file/);
|
|
214
|
-
expect(() => ft2New.assert(testFile)).toThrow(/must read file/);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileTime Tracking Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests the safety mechanism that prevents race conditions
|
|
5
|
-
* between auto-formatting and agent edits.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { FileTime, FileTimeError, createFileTime, clearAllSessions } from "../file-time.js";
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
13
|
-
import { dirname } from "path";
|
|
14
|
-
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = dirname(__filename);
|
|
17
|
-
|
|
18
|
-
const TEST_DIR = path.join(__dirname, "..", "..", "test-filetime");
|
|
19
|
-
|
|
20
|
-
describe("FileTime", () => {
|
|
21
|
-
let fileTime: FileTime;
|
|
22
|
-
const sessionID = "test-session";
|
|
23
|
-
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
clearAllSessions();
|
|
26
|
-
fileTime = new FileTime(sessionID);
|
|
27
|
-
|
|
28
|
-
// Ensure test directory exists
|
|
29
|
-
if (!fs.existsSync(TEST_DIR)) {
|
|
30
|
-
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("read()", () => {
|
|
35
|
-
it("should record file stamp with mtime/ctime/size", () => {
|
|
36
|
-
const testFile = path.join(TEST_DIR, "test1.txt");
|
|
37
|
-
fs.writeFileSync(testFile, "hello");
|
|
38
|
-
|
|
39
|
-
const stamp = fileTime.read(testFile);
|
|
40
|
-
|
|
41
|
-
expect(stamp.readAt).toBeInstanceOf(Date);
|
|
42
|
-
expect(stamp.mtime).toBeDefined();
|
|
43
|
-
expect(stamp.ctime).toBeDefined();
|
|
44
|
-
expect(stamp.size).toBe(5);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("should handle non-existent files gracefully", () => {
|
|
48
|
-
const testFile = path.join(TEST_DIR, "nonexistent.txt");
|
|
49
|
-
|
|
50
|
-
const stamp = fileTime.read(testFile);
|
|
51
|
-
|
|
52
|
-
expect(stamp.readAt).toBeInstanceOf(Date);
|
|
53
|
-
expect(stamp.mtime).toBeUndefined();
|
|
54
|
-
expect(stamp.ctime).toBeUndefined();
|
|
55
|
-
expect(stamp.size).toBeUndefined();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should track multiple files per session", () => {
|
|
59
|
-
const file1 = path.join(TEST_DIR, "file1.txt");
|
|
60
|
-
const file2 = path.join(TEST_DIR, "file2.txt");
|
|
61
|
-
fs.writeFileSync(file1, "content1");
|
|
62
|
-
fs.writeFileSync(file2, "content2");
|
|
63
|
-
|
|
64
|
-
fileTime.read(file1);
|
|
65
|
-
fileTime.read(file2);
|
|
66
|
-
|
|
67
|
-
expect(fileTime.get(file1)).toBeDefined();
|
|
68
|
-
expect(fileTime.get(file2)).toBeDefined();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe("get()", () => {
|
|
73
|
-
it("should return undefined for unread files", () => {
|
|
74
|
-
const testFile = path.join(TEST_DIR, "unread.txt");
|
|
75
|
-
|
|
76
|
-
const stamp = fileTime.get(testFile);
|
|
77
|
-
|
|
78
|
-
expect(stamp).toBeUndefined();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("should return recorded stamp for read files", () => {
|
|
82
|
-
const testFile = path.join(TEST_DIR, "test2.txt");
|
|
83
|
-
fs.writeFileSync(testFile, "content");
|
|
84
|
-
const recorded = fileTime.read(testFile);
|
|
85
|
-
|
|
86
|
-
const retrieved = fileTime.get(testFile);
|
|
87
|
-
|
|
88
|
-
expect(retrieved?.mtime).toBe(recorded.mtime);
|
|
89
|
-
expect(retrieved?.ctime).toBe(recorded.ctime);
|
|
90
|
-
expect(retrieved?.size).toBe(recorded.size);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("assert()", () => {
|
|
95
|
-
it("should throw FileTimeError for unread files", () => {
|
|
96
|
-
const testFile = path.join(TEST_DIR, "never-read.txt");
|
|
97
|
-
fs.writeFileSync(testFile, "content");
|
|
98
|
-
|
|
99
|
-
expect(() => fileTime.assert(testFile)).toThrow(FileTimeError);
|
|
100
|
-
expect(() => fileTime.assert(testFile)).toThrow(/must read file/);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("should not throw for unchanged files", () => {
|
|
104
|
-
const testFile = path.join(TEST_DIR, "unchanged.txt");
|
|
105
|
-
fs.writeFileSync(testFile, "content");
|
|
106
|
-
fileTime.read(testFile);
|
|
107
|
-
|
|
108
|
-
expect(() => fileTime.assert(testFile)).not.toThrow();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("should throw FileTimeError when file modified externally", () => {
|
|
112
|
-
const testFile = path.join(TEST_DIR, "modified.txt");
|
|
113
|
-
fs.writeFileSync(testFile, "original");
|
|
114
|
-
fileTime.read(testFile);
|
|
115
|
-
|
|
116
|
-
// Simulate external modification
|
|
117
|
-
fs.writeFileSync(testFile, "modified content");
|
|
118
|
-
|
|
119
|
-
expect(() => fileTime.assert(testFile)).toThrow(FileTimeError);
|
|
120
|
-
expect(() => fileTime.assert(testFile)).toThrow(/modified since it was last read/);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("should detect size changes", () => {
|
|
124
|
-
const testFile = path.join(TEST_DIR, "size-change.txt");
|
|
125
|
-
fs.writeFileSync(testFile, "original content");
|
|
126
|
-
fileTime.read(testFile);
|
|
127
|
-
|
|
128
|
-
// Truncate file (size change, mtime change)
|
|
129
|
-
fs.writeFileSync(testFile, "x");
|
|
130
|
-
|
|
131
|
-
expect(() => fileTime.assert(testFile)).toThrow(FileTimeError);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe("hasChanged()", () => {
|
|
136
|
-
it("should return true for unread files", () => {
|
|
137
|
-
const testFile = path.join(TEST_DIR, "unread-check.txt");
|
|
138
|
-
fs.writeFileSync(testFile, "content");
|
|
139
|
-
|
|
140
|
-
expect(fileTime.hasChanged(testFile)).toBe(true);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("should return false for unchanged files", () => {
|
|
144
|
-
const testFile = path.join(TEST_DIR, "unchanged-check.txt");
|
|
145
|
-
fs.writeFileSync(testFile, "content");
|
|
146
|
-
fileTime.read(testFile);
|
|
147
|
-
|
|
148
|
-
expect(fileTime.hasChanged(testFile)).toBe(false);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("should return true when file modified", () => {
|
|
152
|
-
const testFile = path.join(TEST_DIR, "changed-check.txt");
|
|
153
|
-
fs.writeFileSync(testFile, "original");
|
|
154
|
-
fileTime.read(testFile);
|
|
155
|
-
|
|
156
|
-
fs.writeFileSync(testFile, "changed");
|
|
157
|
-
|
|
158
|
-
expect(fileTime.hasChanged(testFile)).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe("withLock()", () => {
|
|
163
|
-
it("should execute function exclusively", async () => {
|
|
164
|
-
const testFile = path.join(TEST_DIR, "locked.txt");
|
|
165
|
-
const executionOrder: string[] = [];
|
|
166
|
-
|
|
167
|
-
const fn1 = async () => {
|
|
168
|
-
executionOrder.push("start1");
|
|
169
|
-
await new Promise(r => setTimeout(r, 50));
|
|
170
|
-
executionOrder.push("end1");
|
|
171
|
-
return "result1";
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const fn2 = async () => {
|
|
175
|
-
executionOrder.push("start2");
|
|
176
|
-
await new Promise(r => setTimeout(r, 50));
|
|
177
|
-
executionOrder.push("end2");
|
|
178
|
-
return "result2";
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Start both, but they should execute sequentially
|
|
182
|
-
const promise1 = fileTime.withLock(testFile, fn1);
|
|
183
|
-
const promise2 = fileTime.withLock(testFile, fn2);
|
|
184
|
-
|
|
185
|
-
await Promise.all([promise1, promise2]);
|
|
186
|
-
|
|
187
|
-
// Should be sequential, not interleaved
|
|
188
|
-
expect(executionOrder).toEqual(["start1", "end1", "start2", "end2"]);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("should return function result", async () => {
|
|
192
|
-
const testFile = path.join(TEST_DIR, "lock-result.txt");
|
|
193
|
-
|
|
194
|
-
const result = await fileTime.withLock(testFile, async () => {
|
|
195
|
-
return "success";
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
expect(result).toBe("success");
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe("clear()", () => {
|
|
203
|
-
it("should clear all tracked files for session", () => {
|
|
204
|
-
const file1 = path.join(TEST_DIR, "clear1.txt");
|
|
205
|
-
const file2 = path.join(TEST_DIR, "clear2.txt");
|
|
206
|
-
fs.writeFileSync(file1, "a");
|
|
207
|
-
fs.writeFileSync(file2, "b");
|
|
208
|
-
fileTime.read(file1);
|
|
209
|
-
fileTime.read(file2);
|
|
210
|
-
|
|
211
|
-
fileTime.clear();
|
|
212
|
-
|
|
213
|
-
expect(fileTime.get(file1)).toBeUndefined();
|
|
214
|
-
expect(fileTime.get(file2)).toBeUndefined();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe("cross-session isolation", () => {
|
|
219
|
-
it("should isolate file tracking between sessions", () => {
|
|
220
|
-
const testFile = path.join(TEST_DIR, "isolated.txt");
|
|
221
|
-
fs.writeFileSync(testFile, "content");
|
|
222
|
-
|
|
223
|
-
const session1 = new FileTime("session1");
|
|
224
|
-
const session2 = new FileTime("session2");
|
|
225
|
-
|
|
226
|
-
session1.read(testFile);
|
|
227
|
-
|
|
228
|
-
// session2 should not see session1's reads
|
|
229
|
-
expect(() => session2.assert(testFile)).toThrow(FileTimeError);
|
|
230
|
-
expect(() => session2.assert(testFile)).toThrow(/must read file/);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe("FileTimeError", () => {
|
|
235
|
-
it("should have correct error properties", () => {
|
|
236
|
-
const testFile = path.join(TEST_DIR, "error.txt");
|
|
237
|
-
fs.writeFileSync(testFile, "content");
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
fileTime.assert(testFile);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
expect(error).toBeInstanceOf(FileTimeError);
|
|
243
|
-
expect((error as FileTimeError).name).toBe("FileTimeError");
|
|
244
|
-
expect((error as FileTimeError).filePath).toBe(path.resolve(testFile));
|
|
245
|
-
expect((error as FileTimeError).reason).toBe("not-read");
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe("createFileTime helper", () => {
|
|
252
|
-
it("should create FileTime instance with session ID", () => {
|
|
253
|
-
const ft = createFileTime("my-session");
|
|
254
|
-
expect(ft).toBeInstanceOf(FileTime);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe("clearAllSessions helper", () => {
|
|
259
|
-
it("should clear all session tracking", () => {
|
|
260
|
-
const ft1 = createFileTime("session1");
|
|
261
|
-
const ft2 = createFileTime("session2");
|
|
262
|
-
const testFile = path.join(TEST_DIR, "clearall.txt");
|
|
263
|
-
fs.writeFileSync(testFile, "x");
|
|
264
|
-
|
|
265
|
-
ft1.read(testFile);
|
|
266
|
-
ft2.read(testFile);
|
|
267
|
-
|
|
268
|
-
clearAllSessions();
|
|
269
|
-
|
|
270
|
-
// After clearing, both should throw "not read"
|
|
271
|
-
const ft1New = createFileTime("session1");
|
|
272
|
-
const ft2New = createFileTime("session2");
|
|
273
|
-
expect(() => ft1New.assert(testFile)).toThrow(/must read file/);
|
|
274
|
-
expect(() => ft2New.assert(testFile)).toThrow(/must read file/);
|
|
275
|
-
});
|
|
276
|
-
});
|