gsd-pi 2.10.0 → 2.10.2
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/node_modules/@gsd/native/dist/ast/index.d.ts +4 -0
- package/node_modules/@gsd/native/dist/ast/index.js +7 -0
- package/node_modules/@gsd/native/dist/ast/types.d.ts +69 -0
- package/node_modules/@gsd/native/dist/ast/types.js +1 -0
- package/node_modules/@gsd/native/{src/clipboard/index.ts → dist/clipboard/index.d.ts} +3 -15
- package/node_modules/@gsd/native/dist/clipboard/index.js +33 -0
- package/node_modules/@gsd/native/dist/clipboard/types.d.ts +7 -0
- package/node_modules/@gsd/native/dist/clipboard/types.js +1 -0
- package/node_modules/@gsd/native/dist/diff/index.d.ts +33 -0
- package/node_modules/@gsd/native/dist/diff/index.js +38 -0
- package/node_modules/@gsd/native/dist/diff/types.d.ts +23 -0
- package/node_modules/@gsd/native/dist/diff/types.js +1 -0
- package/node_modules/@gsd/native/{src/fd/index.ts → dist/fd/index.d.ts} +2 -12
- package/node_modules/@gsd/native/dist/fd/index.js +26 -0
- package/node_modules/@gsd/native/dist/fd/types.d.ts +29 -0
- package/node_modules/@gsd/native/dist/fd/types.js +1 -0
- package/node_modules/@gsd/native/{src/glob/index.ts → dist/glob/index.d.ts} +3 -19
- package/node_modules/@gsd/native/dist/glob/index.js +31 -0
- package/node_modules/@gsd/native/dist/glob/types.d.ts +50 -0
- package/node_modules/@gsd/native/dist/glob/types.js +1 -0
- package/node_modules/@gsd/native/dist/grep/index.d.ts +20 -0
- package/node_modules/@gsd/native/dist/grep/index.js +23 -0
- package/node_modules/@gsd/native/dist/grep/types.d.ts +99 -0
- package/node_modules/@gsd/native/dist/grep/types.js +1 -0
- package/node_modules/@gsd/native/dist/gsd-parser/index.d.ts +45 -0
- package/node_modules/@gsd/native/dist/gsd-parser/index.js +54 -0
- package/node_modules/@gsd/native/dist/gsd-parser/types.d.ts +55 -0
- package/node_modules/@gsd/native/dist/gsd-parser/types.js +7 -0
- package/node_modules/@gsd/native/{src/highlight/index.ts → dist/highlight/index.d.ts} +3 -19
- package/node_modules/@gsd/native/dist/highlight/index.js +33 -0
- package/node_modules/@gsd/native/dist/highlight/types.d.ts +25 -0
- package/node_modules/@gsd/native/dist/highlight/types.js +1 -0
- package/node_modules/@gsd/native/{src/html/index.ts → dist/html/index.d.ts} +1 -10
- package/node_modules/@gsd/native/dist/html/index.js +16 -0
- package/node_modules/@gsd/native/dist/html/types.d.ts +7 -0
- package/node_modules/@gsd/native/dist/html/types.js +1 -0
- package/node_modules/@gsd/native/{src/image/index.ts → dist/image/index.d.ts} +1 -14
- package/node_modules/@gsd/native/dist/image/index.js +18 -0
- package/node_modules/@gsd/native/dist/image/types.d.ts +35 -0
- package/node_modules/@gsd/native/dist/image/types.js +26 -0
- package/node_modules/@gsd/native/{src/index.ts → dist/index.d.ts} +12 -60
- package/node_modules/@gsd/native/dist/index.js +28 -0
- package/node_modules/@gsd/native/dist/native.d.ts +44 -0
- package/node_modules/@gsd/native/dist/native.js +34 -0
- package/node_modules/@gsd/native/dist/ps/index.d.ts +38 -0
- package/node_modules/@gsd/native/{src/ps/index.ts → dist/ps/index.js} +8 -13
- package/node_modules/@gsd/native/{src/ps/types.ts → dist/ps/types.d.ts} +2 -2
- package/node_modules/@gsd/native/dist/ps/types.js +1 -0
- package/node_modules/@gsd/native/{src/text/index.ts → dist/text/index.d.ts} +6 -76
- package/node_modules/@gsd/native/dist/text/index.js +66 -0
- package/node_modules/@gsd/native/dist/text/types.d.ts +27 -0
- package/node_modules/@gsd/native/dist/text/types.js +10 -0
- package/node_modules/@gsd/native/{src/ttsr/index.ts → dist/ttsr/index.d.ts} +3 -15
- package/node_modules/@gsd/native/dist/ttsr/index.js +32 -0
- package/node_modules/@gsd/native/{src/ttsr/types.ts → dist/ttsr/types.d.ts} +4 -5
- package/node_modules/@gsd/native/dist/ttsr/types.js +1 -0
- package/node_modules/@gsd/native/package.json +24 -23
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.d.ts +11 -5
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.js +19 -142
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/edit-diff.ts +23 -157
- package/package.json +4 -2
- package/packages/native/dist/ast/index.d.ts +4 -0
- package/packages/native/dist/ast/index.js +7 -0
- package/packages/native/dist/ast/types.d.ts +69 -0
- package/packages/native/dist/ast/types.js +1 -0
- package/packages/native/dist/clipboard/index.d.ts +28 -0
- package/packages/native/dist/clipboard/index.js +33 -0
- package/packages/native/dist/clipboard/types.d.ts +7 -0
- package/packages/native/dist/clipboard/types.js +1 -0
- package/packages/native/dist/diff/index.d.ts +33 -0
- package/packages/native/dist/diff/index.js +38 -0
- package/packages/native/dist/diff/types.d.ts +23 -0
- package/packages/native/dist/diff/types.js +1 -0
- package/packages/native/dist/fd/index.d.ts +25 -0
- package/packages/native/dist/fd/index.js +26 -0
- package/packages/native/dist/fd/types.d.ts +29 -0
- package/packages/native/dist/fd/types.js +1 -0
- package/packages/native/dist/glob/index.d.ts +28 -0
- package/packages/native/dist/glob/index.js +31 -0
- package/packages/native/dist/glob/types.d.ts +50 -0
- package/packages/native/dist/glob/types.js +1 -0
- package/packages/native/dist/grep/index.d.ts +20 -0
- package/packages/native/dist/grep/index.js +23 -0
- package/packages/native/dist/grep/types.d.ts +99 -0
- package/packages/native/dist/grep/types.js +1 -0
- package/packages/native/dist/gsd-parser/index.d.ts +45 -0
- package/packages/native/dist/gsd-parser/index.js +54 -0
- package/packages/native/dist/gsd-parser/types.d.ts +55 -0
- package/packages/native/dist/gsd-parser/types.js +7 -0
- package/packages/native/dist/highlight/index.d.ts +28 -0
- package/packages/native/dist/highlight/index.js +33 -0
- package/packages/native/dist/highlight/types.d.ts +25 -0
- package/packages/native/dist/highlight/types.js +1 -0
- package/packages/native/dist/html/index.d.ts +15 -0
- package/packages/native/dist/html/index.js +16 -0
- package/packages/native/dist/html/types.d.ts +7 -0
- package/packages/native/dist/html/types.js +1 -0
- package/packages/native/dist/image/index.d.ts +15 -0
- package/packages/native/dist/image/index.js +18 -0
- package/packages/native/dist/image/types.d.ts +35 -0
- package/packages/native/dist/image/types.js +26 -0
- package/packages/native/dist/index.d.ts +40 -0
- package/packages/native/dist/index.js +28 -0
- package/packages/native/dist/native.d.ts +44 -0
- package/packages/native/dist/native.js +34 -0
- package/packages/native/dist/ps/index.d.ts +38 -0
- package/packages/native/dist/ps/index.js +47 -0
- package/packages/native/dist/ps/types.d.ts +5 -0
- package/packages/native/dist/ps/types.js +1 -0
- package/packages/native/dist/text/index.d.ts +55 -0
- package/packages/native/dist/text/index.js +66 -0
- package/packages/native/dist/text/types.d.ts +27 -0
- package/packages/native/dist/text/types.js +10 -0
- package/packages/native/dist/ttsr/index.d.ts +27 -0
- package/packages/native/dist/ttsr/index.js +32 -0
- package/packages/native/dist/ttsr/types.d.ts +9 -0
- package/packages/native/dist/ttsr/types.js +1 -0
- package/packages/native/package.json +24 -23
- package/packages/native/src/__tests__/diff.test.mjs +189 -0
- package/packages/native/src/__tests__/ttsr.test.mjs +135 -0
- package/packages/native/src/diff/index.ts +61 -0
- package/packages/native/src/diff/types.ts +24 -0
- package/packages/native/src/gsd-parser/index.ts +98 -0
- package/packages/native/src/gsd-parser/types.ts +62 -0
- package/packages/native/src/index.ts +23 -0
- package/packages/native/src/native.ts +8 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +11 -5
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +19 -142
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +23 -157
- package/src/resources/extensions/gsd/files.ts +9 -0
- package/src/resources/extensions/gsd/native-parser-bridge.ts +135 -0
- package/src/resources/extensions/ttsr/ttsr-manager.ts +86 -0
- package/node_modules/@gsd/native/src/__tests__/clipboard.test.mjs +0 -79
- package/node_modules/@gsd/native/src/__tests__/fd.test.mjs +0 -164
- package/node_modules/@gsd/native/src/__tests__/glob.test.mjs +0 -237
- package/node_modules/@gsd/native/src/__tests__/grep.test.mjs +0 -162
- package/node_modules/@gsd/native/src/__tests__/highlight.test.mjs +0 -156
- package/node_modules/@gsd/native/src/__tests__/html.test.mjs +0 -98
- package/node_modules/@gsd/native/src/__tests__/image.test.mjs +0 -137
- package/node_modules/@gsd/native/src/__tests__/ps.test.mjs +0 -109
- package/node_modules/@gsd/native/src/__tests__/text.test.mjs +0 -262
- package/node_modules/@gsd/native/src/ast/index.ts +0 -12
- package/node_modules/@gsd/native/src/ast/types.ts +0 -75
- package/node_modules/@gsd/native/src/clipboard/types.ts +0 -7
- package/node_modules/@gsd/native/src/fd/types.ts +0 -31
- package/node_modules/@gsd/native/src/glob/types.ts +0 -53
- package/node_modules/@gsd/native/src/grep/index.ts +0 -48
- package/node_modules/@gsd/native/src/grep/types.ts +0 -105
- package/node_modules/@gsd/native/src/highlight/types.ts +0 -25
- package/node_modules/@gsd/native/src/html/types.ts +0 -7
- package/node_modules/@gsd/native/src/image/types.ts +0 -41
- package/node_modules/@gsd/native/src/native.ts +0 -94
- package/node_modules/@gsd/native/src/text/types.ts +0 -29
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native TTSR regex engine.
|
|
3
|
+
*
|
|
4
|
+
* Pre-compiles all rule condition patterns into a single Rust RegexSet for
|
|
5
|
+
* O(1)-style matching per buffer check, replacing per-rule JS regex iteration.
|
|
6
|
+
*/
|
|
7
|
+
import { native } from "../native.js";
|
|
8
|
+
/**
|
|
9
|
+
* Compile TTSR rules into an optimized native regex engine.
|
|
10
|
+
*
|
|
11
|
+
* Returns an opaque handle for use with `ttsrCheckBuffer` and `ttsrFreeRules`.
|
|
12
|
+
*/
|
|
13
|
+
export function ttsrCompileRules(rules) {
|
|
14
|
+
return native.ttsrCompileRules(rules);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check a buffer against compiled TTSR rules.
|
|
18
|
+
*
|
|
19
|
+
* Returns an array of unique rule names whose conditions matched.
|
|
20
|
+
* All patterns are tested in a single pass via Rust's RegexSet.
|
|
21
|
+
*/
|
|
22
|
+
export function ttsrCheckBuffer(handle, buffer) {
|
|
23
|
+
return native.ttsrCheckBuffer(handle, buffer);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Free a compiled TTSR rule set, releasing native memory.
|
|
27
|
+
*
|
|
28
|
+
* Call when rules are no longer needed (e.g., session end).
|
|
29
|
+
*/
|
|
30
|
+
export function ttsrFreeRules(handle) {
|
|
31
|
+
native.ttsrFreeRules(handle);
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Input rule for TTSR regex compilation. */
|
|
2
|
+
export interface TtsrRuleInput {
|
|
3
|
+
/** Unique rule name. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Regex condition patterns (any match triggers the rule). */
|
|
6
|
+
conditions: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Opaque handle to a compiled TTSR rule set. */
|
|
9
|
+
export type TtsrHandle = number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,57 +3,58 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"description": "Native Rust bindings for GSD \u2014 high-performance native modules via N-API",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
9
10
|
"build:native": "node ../../native/scripts/build.js",
|
|
10
11
|
"build:native:dev": "node ../../native/scripts/build.js --dev",
|
|
11
12
|
"test": "node --test src/__tests__/grep.test.mjs src/__tests__/ps.test.mjs src/__tests__/glob.test.mjs src/__tests__/clipboard.test.mjs src/__tests__/highlight.test.mjs src/__tests__/html.test.mjs src/__tests__/text.test.mjs src/__tests__/fd.test.mjs src/__tests__/image.test.mjs"
|
|
12
13
|
},
|
|
13
14
|
"exports": {
|
|
14
15
|
".": {
|
|
15
|
-
"types": "./
|
|
16
|
-
"import": "./
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
17
18
|
},
|
|
18
19
|
"./grep": {
|
|
19
|
-
"types": "./
|
|
20
|
-
"import": "./
|
|
20
|
+
"types": "./dist/grep/index.d.ts",
|
|
21
|
+
"import": "./dist/grep/index.js"
|
|
21
22
|
},
|
|
22
23
|
"./ps": {
|
|
23
|
-
"types": "./
|
|
24
|
-
"import": "./
|
|
24
|
+
"types": "./dist/ps/index.d.ts",
|
|
25
|
+
"import": "./dist/ps/index.js"
|
|
25
26
|
},
|
|
26
27
|
"./glob": {
|
|
27
|
-
"types": "./
|
|
28
|
-
"import": "./
|
|
28
|
+
"types": "./dist/glob/index.d.ts",
|
|
29
|
+
"import": "./dist/glob/index.js"
|
|
29
30
|
},
|
|
30
31
|
"./clipboard": {
|
|
31
|
-
"types": "./
|
|
32
|
-
"import": "./
|
|
32
|
+
"types": "./dist/clipboard/index.d.ts",
|
|
33
|
+
"import": "./dist/clipboard/index.js"
|
|
33
34
|
},
|
|
34
35
|
"./ast": {
|
|
35
|
-
"types": "./
|
|
36
|
-
"import": "./
|
|
36
|
+
"types": "./dist/ast/index.d.ts",
|
|
37
|
+
"import": "./dist/ast/index.js"
|
|
37
38
|
},
|
|
38
39
|
"./html": {
|
|
39
|
-
"types": "./
|
|
40
|
-
"import": "./
|
|
40
|
+
"types": "./dist/html/index.d.ts",
|
|
41
|
+
"import": "./dist/html/index.js"
|
|
41
42
|
},
|
|
42
43
|
"./text": {
|
|
43
|
-
"types": "./
|
|
44
|
-
"import": "./
|
|
44
|
+
"types": "./dist/text/index.d.ts",
|
|
45
|
+
"import": "./dist/text/index.js"
|
|
45
46
|
},
|
|
46
47
|
"./fd": {
|
|
47
|
-
"types": "./
|
|
48
|
-
"import": "./
|
|
48
|
+
"types": "./dist/fd/index.d.ts",
|
|
49
|
+
"import": "./dist/fd/index.js"
|
|
49
50
|
},
|
|
50
51
|
"./image": {
|
|
51
|
-
"types": "./
|
|
52
|
-
"import": "./
|
|
52
|
+
"types": "./dist/image/index.d.ts",
|
|
53
|
+
"import": "./dist/image/index.js"
|
|
53
54
|
}
|
|
54
55
|
},
|
|
55
56
|
"files": [
|
|
56
|
-
"
|
|
57
|
+
"dist"
|
|
57
58
|
],
|
|
58
59
|
"license": "MIT"
|
|
59
60
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { test, describe } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
// Load the native addon directly
|
|
11
|
+
const addonDir = path.resolve(
|
|
12
|
+
__dirname,
|
|
13
|
+
"..",
|
|
14
|
+
"..",
|
|
15
|
+
"..",
|
|
16
|
+
"..",
|
|
17
|
+
"native",
|
|
18
|
+
"addon",
|
|
19
|
+
);
|
|
20
|
+
const platformTag = `${process.platform}-${process.arch}`;
|
|
21
|
+
const candidates = [
|
|
22
|
+
path.join(addonDir, `gsd_engine.${platformTag}.node`),
|
|
23
|
+
path.join(addonDir, "gsd_engine.dev.node"),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
let native;
|
|
27
|
+
for (const candidate of candidates) {
|
|
28
|
+
try {
|
|
29
|
+
native = require(candidate);
|
|
30
|
+
break;
|
|
31
|
+
} catch {
|
|
32
|
+
// try next
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!native) {
|
|
37
|
+
console.error(
|
|
38
|
+
"Native addon not found. Run `npm run build:native -w @gsd/native` first.",
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── normalizeForFuzzyMatch ──────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
describe("normalizeForFuzzyMatch", () => {
|
|
46
|
+
test("strips trailing whitespace per line", () => {
|
|
47
|
+
assert.equal(native.normalizeForFuzzyMatch("hello \nworld "), "hello\nworld");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("normalizes smart quotes to ASCII", () => {
|
|
51
|
+
assert.equal(
|
|
52
|
+
native.normalizeForFuzzyMatch("\u201Chello\u201D \u2018world\u2019"),
|
|
53
|
+
'"hello" \'world\'',
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("normalizes dashes to ASCII hyphen", () => {
|
|
58
|
+
assert.equal(native.normalizeForFuzzyMatch("a\u2013b\u2014c"), "a-b-c");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("normalizes special spaces to regular space", () => {
|
|
62
|
+
assert.equal(native.normalizeForFuzzyMatch("a\u00A0b\u3000c"), "a b c");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("handles empty string", () => {
|
|
66
|
+
assert.equal(native.normalizeForFuzzyMatch(""), "");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("preserves leading whitespace", () => {
|
|
70
|
+
assert.equal(native.normalizeForFuzzyMatch(" hello "), " hello");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ── fuzzyFindText ───────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
describe("fuzzyFindText", () => {
|
|
77
|
+
test("finds exact match", () => {
|
|
78
|
+
const result = native.fuzzyFindText("hello world", "world");
|
|
79
|
+
assert.equal(result.found, true);
|
|
80
|
+
assert.equal(result.index, 6);
|
|
81
|
+
assert.equal(result.matchLength, 5);
|
|
82
|
+
assert.equal(result.usedFuzzyMatch, false);
|
|
83
|
+
assert.equal(result.contentForReplacement, "hello world");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("finds fuzzy match with smart quotes", () => {
|
|
87
|
+
const content = 'let x = \u201Chello\u201D;';
|
|
88
|
+
const oldText = 'let x = "hello";';
|
|
89
|
+
const result = native.fuzzyFindText(content, oldText);
|
|
90
|
+
assert.equal(result.found, true);
|
|
91
|
+
assert.equal(result.usedFuzzyMatch, true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns not found for missing text", () => {
|
|
95
|
+
const result = native.fuzzyFindText("hello world", "xyz");
|
|
96
|
+
assert.equal(result.found, false);
|
|
97
|
+
assert.equal(result.index, -1);
|
|
98
|
+
assert.equal(result.matchLength, 0);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("returns correct UTF-16 index for non-ASCII content", () => {
|
|
102
|
+
// Emoji U+1F600 is 2 UTF-16 code units (surrogate pair), 4 UTF-8 bytes
|
|
103
|
+
const content = "\u{1F600}hello";
|
|
104
|
+
const result = native.fuzzyFindText(content, "hello");
|
|
105
|
+
assert.equal(result.found, true);
|
|
106
|
+
// Emoji is 2 UTF-16 code units, so "hello" starts at index 2
|
|
107
|
+
assert.equal(result.index, 2);
|
|
108
|
+
assert.equal(result.matchLength, 5);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("index is compatible with JS substring()", () => {
|
|
112
|
+
const content = "abc\u{1F600}def";
|
|
113
|
+
const result = native.fuzzyFindText(content, "def");
|
|
114
|
+
assert.equal(result.found, true);
|
|
115
|
+
// "abc" = 3, emoji = 2 UTF-16 code units → index 5
|
|
116
|
+
assert.equal(result.index, 5);
|
|
117
|
+
// Verify substring works correctly with the returned index
|
|
118
|
+
const extracted = result.contentForReplacement.substring(
|
|
119
|
+
result.index,
|
|
120
|
+
result.index + result.matchLength,
|
|
121
|
+
);
|
|
122
|
+
assert.equal(extracted, "def");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("fuzzy match with trailing whitespace differences", () => {
|
|
126
|
+
const content = "hello \nworld ";
|
|
127
|
+
const oldText = "hello\nworld";
|
|
128
|
+
const result = native.fuzzyFindText(content, oldText);
|
|
129
|
+
assert.equal(result.found, true);
|
|
130
|
+
assert.equal(result.usedFuzzyMatch, true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ── generateDiff ────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
describe("generateDiff", () => {
|
|
137
|
+
test("generates diff for a line change", () => {
|
|
138
|
+
const old = "line1\nline2\nline3";
|
|
139
|
+
const newText = "line1\nmodified\nline3";
|
|
140
|
+
const result = native.generateDiff(old, newText);
|
|
141
|
+
assert.ok(result.diff.includes("line2"));
|
|
142
|
+
assert.ok(result.diff.includes("modified"));
|
|
143
|
+
assert.ok(result.diff.includes("-"));
|
|
144
|
+
assert.ok(result.diff.includes("+"));
|
|
145
|
+
assert.notEqual(result.firstChangedLine, null);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("generates diff for an addition", () => {
|
|
149
|
+
const old = "line1\nline3";
|
|
150
|
+
const newText = "line1\nline2\nline3";
|
|
151
|
+
const result = native.generateDiff(old, newText);
|
|
152
|
+
assert.ok(result.diff.includes("+"));
|
|
153
|
+
assert.ok(result.diff.includes("line2"));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("generates diff for a deletion", () => {
|
|
157
|
+
const old = "line1\nline2\nline3";
|
|
158
|
+
const newText = "line1\nline3";
|
|
159
|
+
const result = native.generateDiff(old, newText);
|
|
160
|
+
assert.ok(result.diff.includes("-"));
|
|
161
|
+
assert.ok(result.diff.includes("line2"));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("returns empty diff for identical content", () => {
|
|
165
|
+
const result = native.generateDiff("same", "same");
|
|
166
|
+
assert.equal(result.diff, "");
|
|
167
|
+
// napi-rs maps Option::None to undefined (not null)
|
|
168
|
+
assert.equal(result.firstChangedLine, undefined);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("respects context lines parameter", () => {
|
|
172
|
+
const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`);
|
|
173
|
+
const old = lines.join("\n");
|
|
174
|
+
lines[10] = "modified";
|
|
175
|
+
const newText = lines.join("\n");
|
|
176
|
+
const result = native.generateDiff(old, newText, 2);
|
|
177
|
+
assert.ok(result.diff.includes("..."));
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("default context is 4 lines", () => {
|
|
181
|
+
const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`);
|
|
182
|
+
const old = lines.join("\n");
|
|
183
|
+
lines[10] = "modified";
|
|
184
|
+
const newText = lines.join("\n");
|
|
185
|
+
const result = native.generateDiff(old, newText);
|
|
186
|
+
// Should show 4 context lines before and after
|
|
187
|
+
assert.ok(result.diff.length > 0);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { test, describe } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
// Load the native addon directly
|
|
11
|
+
const addonDir = path.resolve(__dirname, "..", "..", "..", "..", "native", "addon");
|
|
12
|
+
const platformTag = `${process.platform}-${process.arch}`;
|
|
13
|
+
const candidates = [
|
|
14
|
+
path.join(addonDir, `gsd_engine.${platformTag}.node`),
|
|
15
|
+
path.join(addonDir, "gsd_engine.dev.node"),
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
let native;
|
|
19
|
+
for (const candidate of candidates) {
|
|
20
|
+
try {
|
|
21
|
+
native = require(candidate);
|
|
22
|
+
break;
|
|
23
|
+
} catch {
|
|
24
|
+
// try next
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!native) {
|
|
29
|
+
console.error("Native addon not found. Run `npm run build:native -w @gsd/native` first.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("native ttsr: ttsrCompileRules()", () => {
|
|
34
|
+
test("compiles rules and returns a numeric handle", () => {
|
|
35
|
+
const handle = native.ttsrCompileRules([
|
|
36
|
+
{ name: "rule1", conditions: ["foo", "bar"] },
|
|
37
|
+
]);
|
|
38
|
+
assert.equal(typeof handle, "number");
|
|
39
|
+
assert.ok(handle > 0);
|
|
40
|
+
native.ttsrFreeRules(handle);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("rejects empty conditions", () => {
|
|
44
|
+
assert.throws(() => {
|
|
45
|
+
native.ttsrCompileRules([]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("rejects invalid regex patterns", () => {
|
|
50
|
+
assert.throws(() => {
|
|
51
|
+
native.ttsrCompileRules([
|
|
52
|
+
{ name: "bad", conditions: ["(unclosed"] },
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("native ttsr: ttsrCheckBuffer()", () => {
|
|
59
|
+
test("returns matching rule names", () => {
|
|
60
|
+
const handle = native.ttsrCompileRules([
|
|
61
|
+
{ name: "greet", conditions: ["hello\\s+world"] },
|
|
62
|
+
{ name: "farewell", conditions: ["goodbye"] },
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const matches = native.ttsrCheckBuffer(handle, "say hello world please");
|
|
66
|
+
assert.deepEqual(matches, ["greet"]);
|
|
67
|
+
|
|
68
|
+
native.ttsrFreeRules(handle);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("returns multiple matching rules", () => {
|
|
72
|
+
const handle = native.ttsrCompileRules([
|
|
73
|
+
{ name: "a", conditions: ["alpha"] },
|
|
74
|
+
{ name: "b", conditions: ["beta"] },
|
|
75
|
+
{ name: "c", conditions: ["gamma"] },
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const matches = native.ttsrCheckBuffer(handle, "alpha and beta together");
|
|
79
|
+
assert.ok(matches.includes("a"));
|
|
80
|
+
assert.ok(matches.includes("b"));
|
|
81
|
+
assert.ok(!matches.includes("c"));
|
|
82
|
+
|
|
83
|
+
native.ttsrFreeRules(handle);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns empty array on no match", () => {
|
|
87
|
+
const handle = native.ttsrCompileRules([
|
|
88
|
+
{ name: "x", conditions: ["zzz_no_match"] },
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const matches = native.ttsrCheckBuffer(handle, "nothing here");
|
|
92
|
+
assert.deepEqual(matches, []);
|
|
93
|
+
|
|
94
|
+
native.ttsrFreeRules(handle);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("deduplicates when multiple conditions of same rule match", () => {
|
|
98
|
+
const handle = native.ttsrCompileRules([
|
|
99
|
+
{ name: "multi", conditions: ["foo", "bar"] },
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const matches = native.ttsrCheckBuffer(handle, "foo and bar");
|
|
103
|
+
assert.deepEqual(matches, ["multi"]);
|
|
104
|
+
|
|
105
|
+
native.ttsrFreeRules(handle);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("handles large buffers efficiently", () => {
|
|
109
|
+
const handle = native.ttsrCompileRules([
|
|
110
|
+
{ name: "needle", conditions: ["NEEDLE_PATTERN_XYZ"] },
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
// 1MB buffer with the needle near the end
|
|
114
|
+
const bigBuffer = "x".repeat(1024 * 1024) + "NEEDLE_PATTERN_XYZ";
|
|
115
|
+
const matches = native.ttsrCheckBuffer(handle, bigBuffer);
|
|
116
|
+
assert.deepEqual(matches, ["needle"]);
|
|
117
|
+
|
|
118
|
+
native.ttsrFreeRules(handle);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("native ttsr: ttsrFreeRules()", () => {
|
|
123
|
+
test("frees handle without error", () => {
|
|
124
|
+
const handle = native.ttsrCompileRules([
|
|
125
|
+
{ name: "temp", conditions: ["tmp"] },
|
|
126
|
+
]);
|
|
127
|
+
native.ttsrFreeRules(handle);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("rejects invalid handle on check", () => {
|
|
131
|
+
assert.throws(() => {
|
|
132
|
+
native.ttsrCheckBuffer(99999, "test");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native fuzzy text matching and diff generation for the edit tool.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `similar` Rust crate (Myers' algorithm) for O(n+d) diffing,
|
|
5
|
+
* and single-pass Unicode normalization for fuzzy matching.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { native } from "../native.js";
|
|
9
|
+
import type { DiffResult, FuzzyMatchResult } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export type { DiffResult, FuzzyMatchResult };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalize text for fuzzy matching:
|
|
15
|
+
* - Strip trailing whitespace from each line
|
|
16
|
+
* - Smart quotes to ASCII equivalents
|
|
17
|
+
* - Unicode dashes/hyphens to ASCII hyphen
|
|
18
|
+
* - Special Unicode spaces to regular space
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeForFuzzyMatch(text: string): string {
|
|
21
|
+
return (native as Record<string, Function>).normalizeForFuzzyMatch(
|
|
22
|
+
text,
|
|
23
|
+
) as string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Find `oldText` in `content`, trying exact match first, then fuzzy match.
|
|
28
|
+
*
|
|
29
|
+
* When fuzzy matching is used, `contentForReplacement` is the normalized
|
|
30
|
+
* version of `content`.
|
|
31
|
+
*/
|
|
32
|
+
export function fuzzyFindText(
|
|
33
|
+
content: string,
|
|
34
|
+
oldText: string,
|
|
35
|
+
): FuzzyMatchResult {
|
|
36
|
+
return (native as Record<string, Function>).fuzzyFindText(
|
|
37
|
+
content,
|
|
38
|
+
oldText,
|
|
39
|
+
) as FuzzyMatchResult;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate a unified diff string with line numbers and context.
|
|
44
|
+
*
|
|
45
|
+
* Uses Myers' diff algorithm via the `similar` Rust crate.
|
|
46
|
+
*
|
|
47
|
+
* @param oldContent Original text
|
|
48
|
+
* @param newContent Modified text
|
|
49
|
+
* @param contextLines Number of context lines around changes (default: 4)
|
|
50
|
+
*/
|
|
51
|
+
export function generateDiff(
|
|
52
|
+
oldContent: string,
|
|
53
|
+
newContent: string,
|
|
54
|
+
contextLines?: number,
|
|
55
|
+
): DiffResult {
|
|
56
|
+
return (native as Record<string, Function>).generateDiff(
|
|
57
|
+
oldContent,
|
|
58
|
+
newContent,
|
|
59
|
+
contextLines,
|
|
60
|
+
) as DiffResult;
|
|
61
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Result of fuzzy text matching (exact match tried first, then normalized). */
|
|
2
|
+
export interface FuzzyMatchResult {
|
|
3
|
+
/** Whether a match was found. */
|
|
4
|
+
found: boolean;
|
|
5
|
+
/** UTF-16 code unit index where the match starts (-1 if not found). */
|
|
6
|
+
index: number;
|
|
7
|
+
/** Length of the matched text in UTF-16 code units (0 if not found). */
|
|
8
|
+
matchLength: number;
|
|
9
|
+
/** Whether fuzzy (normalized) matching was used instead of exact. */
|
|
10
|
+
usedFuzzyMatch: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Content to use for replacement operations.
|
|
13
|
+
* Original content when exact match; normalized content when fuzzy match.
|
|
14
|
+
*/
|
|
15
|
+
contentForReplacement: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Result of unified diff generation. */
|
|
19
|
+
export interface DiffResult {
|
|
20
|
+
/** The unified diff string with line numbers. */
|
|
21
|
+
diff: string;
|
|
22
|
+
/** Line number of the first change in the new file (undefined if no changes). */
|
|
23
|
+
firstChangedLine: number | undefined;
|
|
24
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD file parser — native Rust implementation.
|
|
3
|
+
*
|
|
4
|
+
* Parses `.gsd/` directory markdown files containing YAML-like frontmatter
|
|
5
|
+
* and structured sections. Replaces the JS regex-based parser for
|
|
6
|
+
* performance-critical batch operations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { native } from "../native.js";
|
|
10
|
+
import type {
|
|
11
|
+
BatchParseResult,
|
|
12
|
+
FrontmatterResult,
|
|
13
|
+
NativeRoadmap,
|
|
14
|
+
SectionResult,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
BatchParseResult,
|
|
19
|
+
FrontmatterResult,
|
|
20
|
+
NativeBoundaryMapEntry,
|
|
21
|
+
NativeRoadmap,
|
|
22
|
+
NativeRoadmapSlice,
|
|
23
|
+
ParsedGsdFile,
|
|
24
|
+
SectionResult,
|
|
25
|
+
} from "./types.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse YAML-like frontmatter from markdown content.
|
|
29
|
+
*
|
|
30
|
+
* Returns `{ metadata, body }` where `metadata` is a JSON string
|
|
31
|
+
* of the parsed frontmatter key-value pairs. Parse it with `JSON.parse()`.
|
|
32
|
+
*/
|
|
33
|
+
export function parseFrontmatter(content: string): FrontmatterResult {
|
|
34
|
+
return (native as Record<string, Function>).parseFrontmatter(
|
|
35
|
+
content,
|
|
36
|
+
) as FrontmatterResult;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extract a section from markdown content by heading name.
|
|
41
|
+
*
|
|
42
|
+
* @param content Markdown content to search.
|
|
43
|
+
* @param heading Heading text to match (without the `#` prefix).
|
|
44
|
+
* @param level Heading level (default 2 for `##`).
|
|
45
|
+
*/
|
|
46
|
+
export function extractSection(
|
|
47
|
+
content: string,
|
|
48
|
+
heading: string,
|
|
49
|
+
level?: number,
|
|
50
|
+
): SectionResult {
|
|
51
|
+
return (native as Record<string, Function>).extractSection(
|
|
52
|
+
content,
|
|
53
|
+
heading,
|
|
54
|
+
level,
|
|
55
|
+
) as SectionResult;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract all sections at a given heading level.
|
|
60
|
+
*
|
|
61
|
+
* Returns a JSON string mapping heading names to their content.
|
|
62
|
+
* Parse with `JSON.parse()`.
|
|
63
|
+
*/
|
|
64
|
+
export function extractAllSections(
|
|
65
|
+
content: string,
|
|
66
|
+
level?: number,
|
|
67
|
+
): string {
|
|
68
|
+
return (native as Record<string, Function>).extractAllSections(
|
|
69
|
+
content,
|
|
70
|
+
level,
|
|
71
|
+
) as string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Batch-parse all `.md` files in a `.gsd/` directory tree.
|
|
76
|
+
*
|
|
77
|
+
* Reads and parses all markdown files under the given directory.
|
|
78
|
+
* Each file gets frontmatter parsing and section extraction.
|
|
79
|
+
*/
|
|
80
|
+
export function batchParseGsdFiles(
|
|
81
|
+
directory: string,
|
|
82
|
+
): BatchParseResult {
|
|
83
|
+
return (native as Record<string, Function>).batchParseGsdFiles(
|
|
84
|
+
directory,
|
|
85
|
+
) as BatchParseResult;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse a roadmap file's content into structured data.
|
|
90
|
+
*
|
|
91
|
+
* Extracts title, vision, success criteria, slices (with risk/depends),
|
|
92
|
+
* and boundary map entries.
|
|
93
|
+
*/
|
|
94
|
+
export function parseRoadmapFile(content: string): NativeRoadmap {
|
|
95
|
+
return (native as Record<string, Function>).parseRoadmapFile(
|
|
96
|
+
content,
|
|
97
|
+
) as NativeRoadmap;
|
|
98
|
+
}
|