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.
Files changed (157) hide show
  1. package/node_modules/@gsd/native/dist/ast/index.d.ts +4 -0
  2. package/node_modules/@gsd/native/dist/ast/index.js +7 -0
  3. package/node_modules/@gsd/native/dist/ast/types.d.ts +69 -0
  4. package/node_modules/@gsd/native/dist/ast/types.js +1 -0
  5. package/node_modules/@gsd/native/{src/clipboard/index.ts → dist/clipboard/index.d.ts} +3 -15
  6. package/node_modules/@gsd/native/dist/clipboard/index.js +33 -0
  7. package/node_modules/@gsd/native/dist/clipboard/types.d.ts +7 -0
  8. package/node_modules/@gsd/native/dist/clipboard/types.js +1 -0
  9. package/node_modules/@gsd/native/dist/diff/index.d.ts +33 -0
  10. package/node_modules/@gsd/native/dist/diff/index.js +38 -0
  11. package/node_modules/@gsd/native/dist/diff/types.d.ts +23 -0
  12. package/node_modules/@gsd/native/dist/diff/types.js +1 -0
  13. package/node_modules/@gsd/native/{src/fd/index.ts → dist/fd/index.d.ts} +2 -12
  14. package/node_modules/@gsd/native/dist/fd/index.js +26 -0
  15. package/node_modules/@gsd/native/dist/fd/types.d.ts +29 -0
  16. package/node_modules/@gsd/native/dist/fd/types.js +1 -0
  17. package/node_modules/@gsd/native/{src/glob/index.ts → dist/glob/index.d.ts} +3 -19
  18. package/node_modules/@gsd/native/dist/glob/index.js +31 -0
  19. package/node_modules/@gsd/native/dist/glob/types.d.ts +50 -0
  20. package/node_modules/@gsd/native/dist/glob/types.js +1 -0
  21. package/node_modules/@gsd/native/dist/grep/index.d.ts +20 -0
  22. package/node_modules/@gsd/native/dist/grep/index.js +23 -0
  23. package/node_modules/@gsd/native/dist/grep/types.d.ts +99 -0
  24. package/node_modules/@gsd/native/dist/grep/types.js +1 -0
  25. package/node_modules/@gsd/native/dist/gsd-parser/index.d.ts +45 -0
  26. package/node_modules/@gsd/native/dist/gsd-parser/index.js +54 -0
  27. package/node_modules/@gsd/native/dist/gsd-parser/types.d.ts +55 -0
  28. package/node_modules/@gsd/native/dist/gsd-parser/types.js +7 -0
  29. package/node_modules/@gsd/native/{src/highlight/index.ts → dist/highlight/index.d.ts} +3 -19
  30. package/node_modules/@gsd/native/dist/highlight/index.js +33 -0
  31. package/node_modules/@gsd/native/dist/highlight/types.d.ts +25 -0
  32. package/node_modules/@gsd/native/dist/highlight/types.js +1 -0
  33. package/node_modules/@gsd/native/{src/html/index.ts → dist/html/index.d.ts} +1 -10
  34. package/node_modules/@gsd/native/dist/html/index.js +16 -0
  35. package/node_modules/@gsd/native/dist/html/types.d.ts +7 -0
  36. package/node_modules/@gsd/native/dist/html/types.js +1 -0
  37. package/node_modules/@gsd/native/{src/image/index.ts → dist/image/index.d.ts} +1 -14
  38. package/node_modules/@gsd/native/dist/image/index.js +18 -0
  39. package/node_modules/@gsd/native/dist/image/types.d.ts +35 -0
  40. package/node_modules/@gsd/native/dist/image/types.js +26 -0
  41. package/node_modules/@gsd/native/{src/index.ts → dist/index.d.ts} +12 -60
  42. package/node_modules/@gsd/native/dist/index.js +28 -0
  43. package/node_modules/@gsd/native/dist/native.d.ts +44 -0
  44. package/node_modules/@gsd/native/dist/native.js +34 -0
  45. package/node_modules/@gsd/native/dist/ps/index.d.ts +38 -0
  46. package/node_modules/@gsd/native/{src/ps/index.ts → dist/ps/index.js} +8 -13
  47. package/node_modules/@gsd/native/{src/ps/types.ts → dist/ps/types.d.ts} +2 -2
  48. package/node_modules/@gsd/native/dist/ps/types.js +1 -0
  49. package/node_modules/@gsd/native/{src/text/index.ts → dist/text/index.d.ts} +6 -76
  50. package/node_modules/@gsd/native/dist/text/index.js +66 -0
  51. package/node_modules/@gsd/native/dist/text/types.d.ts +27 -0
  52. package/node_modules/@gsd/native/dist/text/types.js +10 -0
  53. package/node_modules/@gsd/native/{src/ttsr/index.ts → dist/ttsr/index.d.ts} +3 -15
  54. package/node_modules/@gsd/native/dist/ttsr/index.js +32 -0
  55. package/node_modules/@gsd/native/{src/ttsr/types.ts → dist/ttsr/types.d.ts} +4 -5
  56. package/node_modules/@gsd/native/dist/ttsr/types.js +1 -0
  57. package/node_modules/@gsd/native/package.json +24 -23
  58. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.d.ts +11 -5
  59. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  60. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.js +19 -142
  61. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  62. package/node_modules/@gsd/pi-coding-agent/src/core/tools/edit-diff.ts +23 -157
  63. package/package.json +4 -2
  64. package/packages/native/dist/ast/index.d.ts +4 -0
  65. package/packages/native/dist/ast/index.js +7 -0
  66. package/packages/native/dist/ast/types.d.ts +69 -0
  67. package/packages/native/dist/ast/types.js +1 -0
  68. package/packages/native/dist/clipboard/index.d.ts +28 -0
  69. package/packages/native/dist/clipboard/index.js +33 -0
  70. package/packages/native/dist/clipboard/types.d.ts +7 -0
  71. package/packages/native/dist/clipboard/types.js +1 -0
  72. package/packages/native/dist/diff/index.d.ts +33 -0
  73. package/packages/native/dist/diff/index.js +38 -0
  74. package/packages/native/dist/diff/types.d.ts +23 -0
  75. package/packages/native/dist/diff/types.js +1 -0
  76. package/packages/native/dist/fd/index.d.ts +25 -0
  77. package/packages/native/dist/fd/index.js +26 -0
  78. package/packages/native/dist/fd/types.d.ts +29 -0
  79. package/packages/native/dist/fd/types.js +1 -0
  80. package/packages/native/dist/glob/index.d.ts +28 -0
  81. package/packages/native/dist/glob/index.js +31 -0
  82. package/packages/native/dist/glob/types.d.ts +50 -0
  83. package/packages/native/dist/glob/types.js +1 -0
  84. package/packages/native/dist/grep/index.d.ts +20 -0
  85. package/packages/native/dist/grep/index.js +23 -0
  86. package/packages/native/dist/grep/types.d.ts +99 -0
  87. package/packages/native/dist/grep/types.js +1 -0
  88. package/packages/native/dist/gsd-parser/index.d.ts +45 -0
  89. package/packages/native/dist/gsd-parser/index.js +54 -0
  90. package/packages/native/dist/gsd-parser/types.d.ts +55 -0
  91. package/packages/native/dist/gsd-parser/types.js +7 -0
  92. package/packages/native/dist/highlight/index.d.ts +28 -0
  93. package/packages/native/dist/highlight/index.js +33 -0
  94. package/packages/native/dist/highlight/types.d.ts +25 -0
  95. package/packages/native/dist/highlight/types.js +1 -0
  96. package/packages/native/dist/html/index.d.ts +15 -0
  97. package/packages/native/dist/html/index.js +16 -0
  98. package/packages/native/dist/html/types.d.ts +7 -0
  99. package/packages/native/dist/html/types.js +1 -0
  100. package/packages/native/dist/image/index.d.ts +15 -0
  101. package/packages/native/dist/image/index.js +18 -0
  102. package/packages/native/dist/image/types.d.ts +35 -0
  103. package/packages/native/dist/image/types.js +26 -0
  104. package/packages/native/dist/index.d.ts +40 -0
  105. package/packages/native/dist/index.js +28 -0
  106. package/packages/native/dist/native.d.ts +44 -0
  107. package/packages/native/dist/native.js +34 -0
  108. package/packages/native/dist/ps/index.d.ts +38 -0
  109. package/packages/native/dist/ps/index.js +47 -0
  110. package/packages/native/dist/ps/types.d.ts +5 -0
  111. package/packages/native/dist/ps/types.js +1 -0
  112. package/packages/native/dist/text/index.d.ts +55 -0
  113. package/packages/native/dist/text/index.js +66 -0
  114. package/packages/native/dist/text/types.d.ts +27 -0
  115. package/packages/native/dist/text/types.js +10 -0
  116. package/packages/native/dist/ttsr/index.d.ts +27 -0
  117. package/packages/native/dist/ttsr/index.js +32 -0
  118. package/packages/native/dist/ttsr/types.d.ts +9 -0
  119. package/packages/native/dist/ttsr/types.js +1 -0
  120. package/packages/native/package.json +24 -23
  121. package/packages/native/src/__tests__/diff.test.mjs +189 -0
  122. package/packages/native/src/__tests__/ttsr.test.mjs +135 -0
  123. package/packages/native/src/diff/index.ts +61 -0
  124. package/packages/native/src/diff/types.ts +24 -0
  125. package/packages/native/src/gsd-parser/index.ts +98 -0
  126. package/packages/native/src/gsd-parser/types.ts +62 -0
  127. package/packages/native/src/index.ts +23 -0
  128. package/packages/native/src/native.ts +8 -0
  129. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +11 -5
  130. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +19 -142
  132. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  133. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +23 -157
  134. package/src/resources/extensions/gsd/files.ts +9 -0
  135. package/src/resources/extensions/gsd/native-parser-bridge.ts +135 -0
  136. package/src/resources/extensions/ttsr/ttsr-manager.ts +86 -0
  137. package/node_modules/@gsd/native/src/__tests__/clipboard.test.mjs +0 -79
  138. package/node_modules/@gsd/native/src/__tests__/fd.test.mjs +0 -164
  139. package/node_modules/@gsd/native/src/__tests__/glob.test.mjs +0 -237
  140. package/node_modules/@gsd/native/src/__tests__/grep.test.mjs +0 -162
  141. package/node_modules/@gsd/native/src/__tests__/highlight.test.mjs +0 -156
  142. package/node_modules/@gsd/native/src/__tests__/html.test.mjs +0 -98
  143. package/node_modules/@gsd/native/src/__tests__/image.test.mjs +0 -137
  144. package/node_modules/@gsd/native/src/__tests__/ps.test.mjs +0 -109
  145. package/node_modules/@gsd/native/src/__tests__/text.test.mjs +0 -262
  146. package/node_modules/@gsd/native/src/ast/index.ts +0 -12
  147. package/node_modules/@gsd/native/src/ast/types.ts +0 -75
  148. package/node_modules/@gsd/native/src/clipboard/types.ts +0 -7
  149. package/node_modules/@gsd/native/src/fd/types.ts +0 -31
  150. package/node_modules/@gsd/native/src/glob/types.ts +0 -53
  151. package/node_modules/@gsd/native/src/grep/index.ts +0 -48
  152. package/node_modules/@gsd/native/src/grep/types.ts +0 -105
  153. package/node_modules/@gsd/native/src/highlight/types.ts +0 -25
  154. package/node_modules/@gsd/native/src/html/types.ts +0 -7
  155. package/node_modules/@gsd/native/src/image/types.ts +0 -41
  156. package/node_modules/@gsd/native/src/native.ts +0 -94
  157. package/node_modules/@gsd/native/src/text/types.ts +0 -29
@@ -19,6 +19,7 @@ import type {
19
19
 
20
20
  import { checkExistingEnvKeys } from '../get-secrets-from-user.ts';
21
21
  import { parseRoadmapSlices } from './roadmap-slices.ts';
22
+ import { nativeParseRoadmap, nativeExtractSection, NATIVE_UNAVAILABLE } from './native-parser-bridge.ts';
22
23
 
23
24
  // ─── Helpers ───────────────────────────────────────────────────────────────
24
25
 
@@ -130,6 +131,10 @@ export function parseFrontmatterMap(lines: string[]): Record<string, unknown> {
130
131
 
131
132
  /** Extract the text after a heading at a given level, up to the next heading of same or higher level. */
132
133
  export function extractSection(body: string, heading: string, level: number = 2): string | null {
134
+ // Try native parser first for better performance on large files
135
+ const nativeResult = nativeExtractSection(body, heading, level);
136
+ if (nativeResult !== NATIVE_UNAVAILABLE) return nativeResult as string | null;
137
+
133
138
  const prefix = '#'.repeat(level) + ' ';
134
139
  const regex = new RegExp(`^${prefix}${escapeRegex(heading)}\\s*$`, 'm');
135
140
  const match = regex.exec(body);
@@ -182,6 +187,10 @@ export function extractBoldField(text: string, key: string): string | null {
182
187
  // ─── Roadmap Parser ────────────────────────────────────────────────────────
183
188
 
184
189
  export function parseRoadmap(content: string): Roadmap {
190
+ // Try native parser first for better performance
191
+ const nativeResult = nativeParseRoadmap(content);
192
+ if (nativeResult) return nativeResult;
193
+
185
194
  const lines = content.split('\n');
186
195
 
187
196
  const h1 = lines.find(l => l.startsWith('# '));
@@ -0,0 +1,135 @@
1
+ // Native GSD Parser Bridge
2
+ // Provides drop-in replacements for the JS parsing functions in files.ts,
3
+ // backed by the Rust native parser for better performance on large projects.
4
+ //
5
+ // Functions fall back to JS implementations if the native module is unavailable.
6
+
7
+ import type { Roadmap, BoundaryMapEntry, RoadmapSliceEntry, RiskLevel } from './types.ts';
8
+
9
+ let nativeModule: {
10
+ parseFrontmatter: (content: string) => { metadata: string; body: string };
11
+ extractSection: (content: string, heading: string, level?: number) => { content: string; found: boolean };
12
+ extractAllSections: (content: string, level?: number) => string;
13
+ batchParseGsdFiles: (directory: string) => { files: Array<{ path: string; metadata: string; body: string; sections: string }>; count: number };
14
+ parseRoadmapFile: (content: string) => {
15
+ title: string;
16
+ vision: string;
17
+ successCriteria: string[];
18
+ slices: Array<{ id: string; title: string; risk: string; depends: string[]; done: boolean; demo: string }>;
19
+ boundaryMap: Array<{ fromSlice: string; toSlice: string; produces: string; consumes: string }>;
20
+ };
21
+ } | null = null;
22
+
23
+ let loadAttempted = false;
24
+
25
+ function loadNative(): typeof nativeModule {
26
+ if (loadAttempted) return nativeModule;
27
+ loadAttempted = true;
28
+
29
+ try {
30
+ // Dynamic import to avoid hard dependency - fails gracefully if native module not built
31
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
32
+ const mod = require('@gsd/native');
33
+ if (mod.parseFrontmatter && mod.extractSection && mod.batchParseGsdFiles) {
34
+ nativeModule = mod;
35
+ }
36
+ } catch {
37
+ // Native module not available - all functions fall back to JS
38
+ }
39
+
40
+ return nativeModule;
41
+ }
42
+
43
+ /**
44
+ * Native-backed frontmatter splitting.
45
+ * Returns [parsedMetadata, body] where parsedMetadata is the parsed key-value map.
46
+ */
47
+ export function nativeSplitFrontmatter(content: string): { metadata: Record<string, unknown>; body: string } | null {
48
+ const native = loadNative();
49
+ if (!native) return null;
50
+
51
+ const result = native.parseFrontmatter(content);
52
+ return {
53
+ metadata: JSON.parse(result.metadata) as Record<string, unknown>,
54
+ body: result.body,
55
+ };
56
+ }
57
+
58
+ /** Sentinel value indicating the native module is not available. */
59
+ const NATIVE_UNAVAILABLE = Symbol('native-unavailable');
60
+
61
+ /**
62
+ * Native-backed section extraction.
63
+ * Returns section content, null if not found, or NATIVE_UNAVAILABLE symbol
64
+ * if the native module isn't loaded.
65
+ */
66
+ export function nativeExtractSection(content: string, heading: string, level: number = 2): string | null | typeof NATIVE_UNAVAILABLE {
67
+ const native = loadNative();
68
+ if (!native) return NATIVE_UNAVAILABLE;
69
+
70
+ const result = native.extractSection(content, heading, level);
71
+ return result.found ? result.content : null;
72
+ }
73
+
74
+ export { NATIVE_UNAVAILABLE };
75
+
76
+ /**
77
+ * Native-backed roadmap parsing.
78
+ * Returns a Roadmap object or null if native module unavailable.
79
+ */
80
+ export function nativeParseRoadmap(content: string): Roadmap | null {
81
+ const native = loadNative();
82
+ if (!native) return null;
83
+
84
+ const result = native.parseRoadmapFile(content);
85
+ return {
86
+ title: result.title,
87
+ vision: result.vision,
88
+ successCriteria: result.successCriteria,
89
+ slices: result.slices.map(s => ({
90
+ id: s.id,
91
+ title: s.title,
92
+ risk: s.risk as RiskLevel,
93
+ depends: s.depends,
94
+ done: s.done,
95
+ demo: s.demo,
96
+ })),
97
+ boundaryMap: result.boundaryMap.map(b => ({
98
+ fromSlice: b.fromSlice,
99
+ toSlice: b.toSlice,
100
+ produces: b.produces,
101
+ consumes: b.consumes,
102
+ })),
103
+ };
104
+ }
105
+
106
+ export interface BatchParsedFile {
107
+ path: string;
108
+ metadata: Record<string, unknown>;
109
+ body: string;
110
+ sections: Record<string, string>;
111
+ }
112
+
113
+ /**
114
+ * Batch-parse all .md files in a .gsd/ directory tree using the native parser.
115
+ * Returns null if native module unavailable.
116
+ */
117
+ export function nativeBatchParseGsdFiles(directory: string): BatchParsedFile[] | null {
118
+ const native = loadNative();
119
+ if (!native) return null;
120
+
121
+ const result = native.batchParseGsdFiles(directory);
122
+ return result.files.map(f => ({
123
+ path: f.path,
124
+ metadata: JSON.parse(f.metadata) as Record<string, unknown>,
125
+ body: f.body,
126
+ sections: JSON.parse(f.sections) as Record<string, string>,
127
+ }));
128
+ }
129
+
130
+ /**
131
+ * Check if the native parser is available.
132
+ */
133
+ export function isNativeParserAvailable(): boolean {
134
+ return loadNative() !== null;
135
+ }
@@ -4,9 +4,34 @@
4
4
  * Manages rules that get injected mid-stream when their condition pattern matches
5
5
  * the agent's output. When a match occurs, the stream is aborted, the rule is
6
6
  * injected as a system reminder, and the request is retried.
7
+ *
8
+ * The regex hot-path is delegated to a native Rust RegexSet engine when
9
+ * available, testing all patterns in a single DFA pass. Falls back to
10
+ * per-rule JS RegExp iteration when the native module is not loaded.
7
11
  */
8
12
  import picomatch from "picomatch";
9
13
 
14
+ // ── Native TTSR engine (optional) ─────────────────────────────────────
15
+ let nativeTtsr: {
16
+ ttsrCompileRules: (rules: { name: string; conditions: string[] }[]) => number;
17
+ ttsrCheckBuffer: (handle: number, buffer: string) => string[];
18
+ ttsrFreeRules: (handle: number) => void;
19
+ } | null = null;
20
+
21
+ try {
22
+ // Dynamic import to avoid hard dependency — gracefully degrades to JS.
23
+ const native = await import("@gsd/native");
24
+ if (native.ttsrCompileRules && native.ttsrCheckBuffer && native.ttsrFreeRules) {
25
+ nativeTtsr = {
26
+ ttsrCompileRules: native.ttsrCompileRules,
27
+ ttsrCheckBuffer: native.ttsrCheckBuffer,
28
+ ttsrFreeRules: native.ttsrFreeRules,
29
+ };
30
+ }
31
+ } catch {
32
+ // Native module not available — JS fallback will be used.
33
+ }
34
+
10
35
  export type TtsrMatchSource = "text" | "thinking" | "tool";
11
36
 
12
37
  /** Context about the stream content currently being checked against TTSR rules. */
@@ -86,6 +111,8 @@ export class TtsrManager {
86
111
  readonly #injectionRecords = new Map<string, InjectionRecord>();
87
112
  readonly #buffers = new Map<string, string>();
88
113
  #messageCount = 0;
114
+ #nativeHandle: number | null = null;
115
+ #nativeDirty = false;
89
116
 
90
117
  constructor(settings?: TtsrSettings) {
91
118
  this.#settings = { ...DEFAULT_SETTINGS, ...settings };
@@ -245,6 +272,40 @@ export class TtsrManager {
245
272
  return false;
246
273
  }
247
274
 
275
+ /** Compile (or recompile) the native RegexSet from all current rules. */
276
+ #compileNative(): void {
277
+ if (!nativeTtsr || !this.#nativeDirty) return;
278
+
279
+ // Free previous handle if any.
280
+ if (this.#nativeHandle !== null) {
281
+ try {
282
+ nativeTtsr.ttsrFreeRules(this.#nativeHandle);
283
+ } catch { /* ignore */ }
284
+ this.#nativeHandle = null;
285
+ }
286
+
287
+ const ruleInputs: { name: string; conditions: string[] }[] = [];
288
+ for (const [, entry] of this.#rules) {
289
+ ruleInputs.push({
290
+ name: entry.rule.name,
291
+ conditions: entry.rule.condition,
292
+ });
293
+ }
294
+
295
+ if (ruleInputs.length === 0) {
296
+ this.#nativeDirty = false;
297
+ return;
298
+ }
299
+
300
+ try {
301
+ this.#nativeHandle = nativeTtsr.ttsrCompileRules(ruleInputs);
302
+ } catch (err) {
303
+ console.warn(`[ttsr] Native compilation failed, using JS fallback: ${(err as Error).message}`);
304
+ this.#nativeHandle = null;
305
+ }
306
+ this.#nativeDirty = false;
307
+ }
308
+
248
309
  /** Add a TTSR rule to be monitored. */
249
310
  addRule(rule: Rule): boolean {
250
311
  if (this.#rules.has(rule.name)) return false;
@@ -257,6 +318,7 @@ export class TtsrManager {
257
318
 
258
319
  const globalPathMatchers = this.#compileGlobalPathMatchers(rule.globs);
259
320
  this.#rules.set(rule.name, { rule, conditions, scope, globalPathMatchers });
321
+ this.#nativeDirty = true;
260
322
  return true;
261
323
  }
262
324
 
@@ -265,6 +327,10 @@ export class TtsrManager {
265
327
  *
266
328
  * Buffers are isolated by source/tool key so matches don't bleed across
267
329
  * assistant prose, thinking text, and unrelated tool argument streams.
330
+ *
331
+ * When the native Rust engine is available, all regex conditions are tested
332
+ * in a single DFA pass via RegexSet. Scope, glob, and repeat-gate checks
333
+ * remain in JS as they are lightweight and context-dependent.
268
334
  */
269
335
  checkDelta(delta: string, context: TtsrMatchContext): Rule[] {
270
336
  const bufferKey = this.#bufferKey(context);
@@ -275,6 +341,26 @@ export class TtsrManager {
275
341
  }
276
342
  this.#buffers.set(bufferKey, nextBuffer);
277
343
 
344
+ // Lazily compile native engine if rules changed.
345
+ if (this.#nativeDirty) this.#compileNative();
346
+
347
+ // ── Native path: single-pass RegexSet match ───────────────────────
348
+ if (nativeTtsr && this.#nativeHandle !== null) {
349
+ const regexMatchedNames = nativeTtsr.ttsrCheckBuffer(this.#nativeHandle, nextBuffer);
350
+ const regexMatchedSet = new Set(regexMatchedNames);
351
+
352
+ const matches: Rule[] = [];
353
+ for (const [name, entry] of this.#rules) {
354
+ if (!regexMatchedSet.has(name)) continue;
355
+ if (!this.#canTrigger(name)) continue;
356
+ if (!this.#matchesScope(entry, context)) continue;
357
+ if (!this.#matchesGlobalPaths(entry, context)) continue;
358
+ matches.push(entry.rule);
359
+ }
360
+ return matches;
361
+ }
362
+
363
+ // ── JS fallback: per-rule regex iteration ─────────────────────────
278
364
  const matches: Rule[] = [];
279
365
  for (const [name, entry] of this.#rules) {
280
366
  if (!this.#canTrigger(name)) continue;
@@ -1,79 +0,0 @@
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
- const addonDir = path.resolve(__dirname, "..", "..", "..", "..", "native", "addon");
11
- const platformTag = `${process.platform}-${process.arch}`;
12
- const candidates = [
13
- path.join(addonDir, `gsd_engine.${platformTag}.node`),
14
- path.join(addonDir, "gsd_engine.dev.node"),
15
- ];
16
-
17
- let native;
18
- for (const candidate of candidates) {
19
- try {
20
- native = require(candidate);
21
- break;
22
- } catch {
23
- // try next
24
- }
25
- }
26
-
27
- if (!native) {
28
- console.error("Native addon not found. Run build:native first.");
29
- process.exit(1);
30
- }
31
-
32
- describe("native clipboard: copyToClipboard()", () => {
33
- test("copies text without throwing", () => {
34
- assert.doesNotThrow(() => {
35
- native.copyToClipboard("GSD clipboard test");
36
- });
37
- });
38
-
39
- test("accepts empty string", () => {
40
- assert.doesNotThrow(() => {
41
- native.copyToClipboard("");
42
- });
43
- });
44
-
45
- test("accepts unicode text", () => {
46
- assert.doesNotThrow(() => {
47
- native.copyToClipboard("Hello 世界");
48
- });
49
- });
50
- });
51
-
52
- describe("native clipboard: readTextFromClipboard()", () => {
53
- test("reads back text that was copied", () => {
54
- const testText = `GSD clipboard roundtrip ${Date.now()}`;
55
- native.copyToClipboard(testText);
56
- const result = native.readTextFromClipboard();
57
- assert.equal(result, testText);
58
- });
59
-
60
- test("returns a string or null", () => {
61
- const result = native.readTextFromClipboard();
62
- assert.ok(result === null || typeof result === "string");
63
- });
64
- });
65
-
66
- describe("native clipboard: readImageFromClipboard()", () => {
67
- test("returns a promise", () => {
68
- const result = native.readImageFromClipboard();
69
- assert.ok(result instanceof Promise);
70
- });
71
-
72
- test("resolves to ClipboardImage or null", async () => {
73
- const result = await native.readImageFromClipboard();
74
- if (result !== null) {
75
- assert.ok(result.data instanceof Uint8Array, "data should be Uint8Array");
76
- assert.equal(result.mimeType, "image/png");
77
- }
78
- });
79
- });
@@ -1,164 +0,0 @@
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
- import * as fs from "node:fs";
7
- import * as os from "node:os";
8
-
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const require = createRequire(import.meta.url);
11
-
12
- // Load the native addon directly
13
- const addonDir = path.resolve(__dirname, "..", "..", "..", "..", "native", "addon");
14
- const platformTag = `${process.platform}-${process.arch}`;
15
- const candidates = [
16
- path.join(addonDir, `gsd_engine.${platformTag}.node`),
17
- path.join(addonDir, "gsd_engine.dev.node"),
18
- ];
19
-
20
- let native;
21
- for (const candidate of candidates) {
22
- try {
23
- native = require(candidate);
24
- break;
25
- } catch {
26
- // try next
27
- }
28
- }
29
-
30
- if (!native) {
31
- console.error("Native addon not found. Run `npm run build:native -w @gsd/native` first.");
32
- process.exit(1);
33
- }
34
-
35
- describe("native fd: fuzzyFind()", () => {
36
- test("finds files matching a query", (t) => {
37
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
38
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
39
-
40
- fs.writeFileSync(path.join(tmpDir, "main.rs"), "fn main() {}");
41
- fs.writeFileSync(path.join(tmpDir, "lib.rs"), "pub mod lib;");
42
- fs.writeFileSync(path.join(tmpDir, "utils.ts"), "export {}");
43
- fs.mkdirSync(path.join(tmpDir, "src"));
44
- fs.writeFileSync(path.join(tmpDir, "src", "helper.rs"), "fn helper() {}");
45
-
46
- const result = native.fuzzyFind({ query: "main", path: tmpDir });
47
-
48
- assert.ok(result.matches.length > 0, "Should find at least one match");
49
- assert.equal(result.matches[0].path, "main.rs");
50
- assert.equal(result.matches[0].isDirectory, false);
51
- assert.ok(result.matches[0].score > 0);
52
- });
53
-
54
- test("returns empty results for non-matching query", (t) => {
55
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
56
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
57
-
58
- fs.writeFileSync(path.join(tmpDir, "hello.txt"), "hello");
59
-
60
- const result = native.fuzzyFind({
61
- query: "zzzznotexist",
62
- path: tmpDir,
63
- });
64
-
65
- assert.equal(result.matches.length, 0);
66
- assert.equal(result.totalMatches, 0);
67
- });
68
-
69
- test("respects maxResults limit", (t) => {
70
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
71
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
72
-
73
- for (let i = 0; i < 10; i++) {
74
- fs.writeFileSync(path.join(tmpDir, `file${i}.txt`), "content");
75
- }
76
-
77
- const result = native.fuzzyFind({
78
- query: "file",
79
- path: tmpDir,
80
- maxResults: 3,
81
- });
82
-
83
- assert.equal(result.matches.length, 3);
84
- assert.ok(result.totalMatches >= 3);
85
- });
86
-
87
- test("directories have trailing slash and bonus score", (t) => {
88
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
89
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
90
-
91
- fs.mkdirSync(path.join(tmpDir, "models"));
92
- fs.writeFileSync(path.join(tmpDir, "models.ts"), "export {}");
93
-
94
- const result = native.fuzzyFind({ query: "models", path: tmpDir });
95
-
96
- const dirMatch = result.matches.find((m) => m.isDirectory);
97
- const fileMatch = result.matches.find((m) => !m.isDirectory);
98
-
99
- assert.ok(dirMatch, "Should find a directory match");
100
- assert.ok(fileMatch, "Should find a file match");
101
- assert.ok(dirMatch.path.endsWith("/"), "Directory should have trailing slash");
102
- assert.ok(dirMatch.score > fileMatch.score, "Directory should score higher");
103
- });
104
-
105
- test("empty query returns all entries", (t) => {
106
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
107
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
108
-
109
- fs.writeFileSync(path.join(tmpDir, "a.txt"), "a");
110
- fs.writeFileSync(path.join(tmpDir, "b.txt"), "b");
111
- fs.writeFileSync(path.join(tmpDir, "c.txt"), "c");
112
-
113
- const result = native.fuzzyFind({ query: "", path: tmpDir });
114
-
115
- assert.equal(result.matches.length, 3);
116
- });
117
-
118
- test("errors on non-existent path", () => {
119
- assert.throws(
120
- () => native.fuzzyFind({ query: "test", path: "/nonexistent/path" }),
121
- { message: /Path not found/ },
122
- );
123
- });
124
-
125
- test("fuzzy subsequence matching works", (t) => {
126
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
127
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
128
-
129
- fs.writeFileSync(path.join(tmpDir, "MyComponentFile.tsx"), "export {}");
130
- fs.writeFileSync(path.join(tmpDir, "other.txt"), "other");
131
-
132
- // "mcf" should fuzzy-match "MyComponentFile" via subsequence
133
- const result = native.fuzzyFind({ query: "mcf", path: tmpDir });
134
-
135
- assert.ok(result.matches.length > 0, "Fuzzy subsequence should match");
136
- assert.ok(
137
- result.matches.some((m) => m.path.includes("MyComponentFile")),
138
- "Should find MyComponentFile via fuzzy match",
139
- );
140
- });
141
-
142
- test("results are sorted by score descending", (t) => {
143
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-fd-test-"));
144
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
145
-
146
- fs.writeFileSync(path.join(tmpDir, "main.ts"), "");
147
- fs.writeFileSync(path.join(tmpDir, "my_main.ts"), "");
148
- fs.mkdirSync(path.join(tmpDir, "src"));
149
- fs.writeFileSync(path.join(tmpDir, "src", "main.rs"), "");
150
-
151
- const result = native.fuzzyFind({
152
- query: "main",
153
- path: tmpDir,
154
- maxResults: 100,
155
- });
156
-
157
- for (let i = 1; i < result.matches.length; i++) {
158
- assert.ok(
159
- result.matches[i - 1].score >= result.matches[i].score,
160
- `Match ${i - 1} (score ${result.matches[i - 1].score}) should be >= match ${i} (score ${result.matches[i].score})`,
161
- );
162
- }
163
- });
164
- });