@diegopetrucci/pi-extensions 0.1.74 → 0.1.78

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 (108) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +17 -12
  3. package/extensions/agent-workflow-audit/README.md +1 -1
  4. package/extensions/agent-workflow-audit/index.ts +1631 -1009
  5. package/extensions/agent-workflow-audit/package.json +1 -1
  6. package/extensions/annotate-git-diff/README.md +3 -1
  7. package/extensions/annotate-git-diff/clipboard.ts +109 -100
  8. package/extensions/annotate-git-diff/git.ts +846 -768
  9. package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
  10. package/extensions/annotate-git-diff/index.ts +712 -671
  11. package/extensions/annotate-git-diff/package.json +1 -1
  12. package/extensions/annotate-git-diff/prompt.ts +45 -45
  13. package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
  14. package/extensions/annotate-git-diff/review-server.ts +337 -311
  15. package/extensions/annotate-git-diff/types.ts +132 -132
  16. package/extensions/annotate-git-diff/ui.ts +27 -27
  17. package/extensions/annotate-git-diff/watch.ts +74 -74
  18. package/extensions/annotate-git-diff/web/app.js +1947 -1884
  19. package/extensions/annotate-git-diff/web/index.html +5 -6
  20. package/extensions/annotate-last-message/README.md +1 -1
  21. package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
  22. package/extensions/annotate-last-message/index.ts +210 -206
  23. package/extensions/annotate-last-message/package.json +1 -1
  24. package/extensions/annotate-last-message/prompt.ts +87 -66
  25. package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
  26. package/extensions/annotate-last-message/session.ts +94 -89
  27. package/extensions/annotate-last-message/theme.ts +199 -193
  28. package/extensions/annotate-last-message/types.ts +23 -23
  29. package/extensions/annotate-last-message/ui.ts +27 -25
  30. package/extensions/annotate-last-message/web/app.js +4 -7
  31. package/extensions/annotate-last-message/web/index.html +3 -3
  32. package/extensions/annotate-last-message/web/md-renderer.js +1 -1
  33. package/extensions/brrr/README.md +1 -1
  34. package/extensions/brrr/index.ts +213 -191
  35. package/extensions/brrr/package.json +1 -1
  36. package/extensions/claude-fast/README.md +5 -79
  37. package/extensions/claude-fast/index.ts +31 -289
  38. package/extensions/claude-fast/package.json +2 -3
  39. package/extensions/code-reviewer/README.md +6 -6
  40. package/extensions/code-reviewer/index.ts +1825 -1373
  41. package/extensions/code-reviewer/package.json +1 -1
  42. package/extensions/confirm-destructive/README.md +1 -1
  43. package/extensions/confirm-destructive/index.ts +50 -52
  44. package/extensions/confirm-destructive/package.json +1 -1
  45. package/extensions/context-cap/README.md +9 -9
  46. package/extensions/context-cap/index.ts +313 -305
  47. package/extensions/context-cap/package.json +2 -2
  48. package/extensions/context-inspector/index.ts +1211 -1064
  49. package/extensions/context-inspector/package.json +1 -1
  50. package/extensions/contrarian/README.md +4 -3
  51. package/extensions/contrarian/index.ts +1863 -1737
  52. package/extensions/contrarian/package.json +1 -1
  53. package/extensions/dirty-repo-guard/index.ts +37 -41
  54. package/extensions/dirty-repo-guard/package.json +1 -1
  55. package/extensions/dynamic-context-pruning/README.md +11 -43
  56. package/extensions/dynamic-context-pruning/index.ts +2131 -2105
  57. package/extensions/dynamic-context-pruning/package.json +1 -1
  58. package/extensions/fast/README.md +52 -14
  59. package/extensions/fast/index.ts +513 -317
  60. package/extensions/fast/package.json +1 -1
  61. package/extensions/git-footer/README.md +1 -1
  62. package/extensions/git-footer/index.ts +458 -411
  63. package/extensions/git-footer/package.json +2 -3
  64. package/extensions/gnosis/index.ts +180 -130
  65. package/extensions/gnosis/package.json +1 -1
  66. package/extensions/inline-bash/index.ts +87 -103
  67. package/extensions/inline-bash/package.json +1 -1
  68. package/extensions/librarian/README.md +1 -1
  69. package/extensions/librarian/index.ts +1877 -1419
  70. package/extensions/librarian/package.json +1 -1
  71. package/extensions/minimal-footer/README.md +5 -5
  72. package/extensions/minimal-footer/index.ts +987 -1012
  73. package/extensions/minimal-footer/minimal-footer.example.json +1 -1
  74. package/extensions/minimal-footer/openai-usage.ts +195 -190
  75. package/extensions/minimal-footer/package.json +1 -1
  76. package/extensions/notify/index.ts +222 -230
  77. package/extensions/notify/package.json +1 -1
  78. package/extensions/openai-fast/README.md +5 -73
  79. package/extensions/openai-fast/index.ts +32 -272
  80. package/extensions/openai-fast/package.json +2 -3
  81. package/extensions/oracle/README.md +4 -3
  82. package/extensions/oracle/index.ts +1740 -1627
  83. package/extensions/oracle/package.json +1 -1
  84. package/extensions/permission-gate/README.md +3 -0
  85. package/extensions/permission-gate/index.ts +785 -612
  86. package/extensions/permission-gate/package.json +1 -1
  87. package/extensions/permission-gate/powershell-safety.ts +840 -793
  88. package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
  89. package/extensions/project-mcp-json/README.md +199 -0
  90. package/extensions/project-mcp-json/config.ts +564 -0
  91. package/extensions/project-mcp-json/index.ts +119 -0
  92. package/extensions/project-mcp-json/package.json +38 -0
  93. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  94. package/extensions/quiet-tools/README.md +30 -19
  95. package/extensions/quiet-tools/index.ts +376 -291
  96. package/extensions/quiet-tools/package.json +2 -2
  97. package/extensions/review/README.md +2 -2
  98. package/extensions/review/index.ts +1745 -1872
  99. package/extensions/review/package.json +1 -1
  100. package/extensions/todo/index.ts +280 -280
  101. package/extensions/todo/package.json +1 -1
  102. package/extensions/triage-comments/README.md +6 -4
  103. package/extensions/triage-comments/index.ts +2620 -1888
  104. package/extensions/triage-comments/package.json +1 -1
  105. package/package.json +9 -6
  106. package/assets/social-preview.png +0 -0
  107. package/extensions/claude-fast/claude-fast.example.json +0 -4
  108. package/extensions/openai-fast/openai-fast.example.json +0 -4
@@ -7,202 +7,202 @@ export type ReviewFileKind = "text" | "binary" | "image";
7
7
  export type ReviewCommitKind = "commit" | "working-tree";
8
8
 
9
9
  export interface ReviewFileComparison {
10
- status: ChangeStatus;
11
- oldPath: string | null;
12
- newPath: string | null;
13
- displayPath: string;
14
- hasOriginal: boolean;
15
- hasModified: boolean;
10
+ status: ChangeStatus;
11
+ oldPath: string | null;
12
+ newPath: string | null;
13
+ displayPath: string;
14
+ hasOriginal: boolean;
15
+ hasModified: boolean;
16
16
  }
17
17
 
18
18
  export interface ReviewFile {
19
- id: string;
20
- path: string;
21
- worktreeStatus: ChangeStatus | null;
22
- hasWorkingTreeFile: boolean;
23
- /** True when the file is touched by the current branch diff (merge-base/base ref vs HEAD). */
24
- inGitDiff: boolean;
25
- /** Diff comparison to render for the file in the current scope.
26
- * - branch scope: merge-base/base ref vs HEAD
27
- * - commits scope: commit vs commit^ (populated when loaded via request-commit) */
28
- gitDiff: ReviewFileComparison | null;
29
- kind: ReviewFileKind;
30
- mimeType: string | null;
19
+ id: string;
20
+ path: string;
21
+ worktreeStatus: ChangeStatus | null;
22
+ hasWorkingTreeFile: boolean;
23
+ /** True when the file is touched by the current branch diff (merge-base/base ref vs HEAD). */
24
+ inGitDiff: boolean;
25
+ /** Diff comparison to render for the file in the current scope.
26
+ * - branch scope: merge-base/base ref vs HEAD
27
+ * - commits scope: commit vs commit^ (populated when loaded via request-commit) */
28
+ gitDiff: ReviewFileComparison | null;
29
+ kind: ReviewFileKind;
30
+ mimeType: string | null;
31
31
  }
32
32
 
33
33
  export interface ReviewCommitInfo {
34
- sha: string;
35
- shortSha: string;
36
- subject: string;
37
- authorName: string;
38
- authorDate: string;
39
- kind: ReviewCommitKind;
34
+ sha: string;
35
+ shortSha: string;
36
+ subject: string;
37
+ authorName: string;
38
+ authorDate: string;
39
+ kind: ReviewCommitKind;
40
40
  }
41
41
 
42
42
  export interface ReviewFileContents {
43
- originalContent: string;
44
- modifiedContent: string;
45
- kind: ReviewFileKind;
46
- mimeType: string | null;
47
- originalExists: boolean;
48
- modifiedExists: boolean;
49
- originalPreviewUrl: string | null;
50
- modifiedPreviewUrl: string | null;
43
+ originalContent: string;
44
+ modifiedContent: string;
45
+ kind: ReviewFileKind;
46
+ mimeType: string | null;
47
+ originalExists: boolean;
48
+ modifiedExists: boolean;
49
+ originalPreviewUrl: string | null;
50
+ modifiedPreviewUrl: string | null;
51
51
  }
52
52
 
53
53
  export type CommentSide = "original" | "modified" | "file";
54
54
 
55
55
  export interface DiffReviewComment {
56
- id: string;
57
- fileId: string;
58
- scope: ReviewScope;
59
- /** Commit SHA when scope === "commits". */
60
- commitSha?: string | null;
61
- /** Short commit identifier to render in the prompt (e.g. first 7 chars of SHA). */
62
- commitShort?: string | null;
63
- commitKind?: ReviewCommitKind | null;
64
- side: CommentSide;
65
- startLine: number | null;
66
- endLine: number | null;
67
- body: string;
56
+ id: string;
57
+ fileId: string;
58
+ scope: ReviewScope;
59
+ /** Commit SHA when scope === "commits". */
60
+ commitSha?: string | null;
61
+ /** Short commit identifier to render in the prompt (e.g. first 7 chars of SHA). */
62
+ commitShort?: string | null;
63
+ commitKind?: ReviewCommitKind | null;
64
+ side: CommentSide;
65
+ startLine: number | null;
66
+ endLine: number | null;
67
+ body: string;
68
68
  }
69
69
 
70
70
  export interface ReviewSubmitPayload {
71
- type: "submit";
72
- overallComment: string;
73
- comments: DiffReviewComment[];
74
- /**
75
- * True means this is a close-recovery draft and must stay editor-only.
76
- * Only literal false permits an explicit submission to trigger an agent turn;
77
- * malformed or missing values are normalized to true by the host.
78
- */
79
- draft: boolean;
71
+ type: "submit";
72
+ overallComment: string;
73
+ comments: DiffReviewComment[];
74
+ /**
75
+ * True means this is a close-recovery draft and must stay editor-only.
76
+ * Only literal false permits an explicit submission to trigger an agent turn;
77
+ * malformed or missing values are normalized to true by the host.
78
+ */
79
+ draft: boolean;
80
80
  }
81
81
 
82
82
  export interface ReviewCancelPayload {
83
- type: "cancel";
83
+ type: "cancel";
84
84
  }
85
85
 
86
86
  export interface ReviewRequestFilePayload {
87
- type: "request-file";
88
- requestId: string;
89
- fileId: string;
90
- scope: ReviewScope;
91
- commitSha?: string | null;
87
+ type: "request-file";
88
+ requestId: string;
89
+ fileId: string;
90
+ scope: ReviewScope;
91
+ commitSha?: string | null;
92
92
  }
93
93
 
94
94
  export interface ReviewRequestCommitPayload {
95
- type: "request-commit";
96
- requestId: string;
97
- sha: string;
95
+ type: "request-commit";
96
+ requestId: string;
97
+ sha: string;
98
98
  }
99
99
 
100
100
  export interface ReviewRequestReviewDataPayload {
101
- type: "request-review-data";
102
- requestId: string;
101
+ type: "request-review-data";
102
+ requestId: string;
103
103
  }
104
104
 
105
105
  export interface ReviewClipboardReadPayload {
106
- type: "clipboard-read";
107
- requestId: string;
106
+ type: "clipboard-read";
107
+ requestId: string;
108
108
  }
109
109
 
110
110
  export interface ReviewClipboardWritePayload {
111
- type: "clipboard-write";
112
- text: string;
111
+ type: "clipboard-write";
112
+ text: string;
113
113
  }
114
114
 
115
115
  export type ReviewWindowMessage =
116
- | ReviewSubmitPayload
117
- | ReviewCancelPayload
118
- | ReviewRequestFilePayload
119
- | ReviewRequestCommitPayload
120
- | ReviewRequestReviewDataPayload
121
- | ReviewClipboardReadPayload
122
- | ReviewClipboardWritePayload;
116
+ | ReviewSubmitPayload
117
+ | ReviewCancelPayload
118
+ | ReviewRequestFilePayload
119
+ | ReviewRequestCommitPayload
120
+ | ReviewRequestReviewDataPayload
121
+ | ReviewClipboardReadPayload
122
+ | ReviewClipboardWritePayload;
123
123
 
124
124
  export interface ReviewFileDataMessage {
125
- type: "file-data";
126
- requestId: string;
127
- fileId: string;
128
- scope: ReviewScope;
129
- commitSha?: string | null;
130
- originalContent: string;
131
- modifiedContent: string;
132
- kind: ReviewFileKind;
133
- mimeType: string | null;
134
- originalExists: boolean;
135
- modifiedExists: boolean;
136
- originalPreviewUrl: string | null;
137
- modifiedPreviewUrl: string | null;
125
+ type: "file-data";
126
+ requestId: string;
127
+ fileId: string;
128
+ scope: ReviewScope;
129
+ commitSha?: string | null;
130
+ originalContent: string;
131
+ modifiedContent: string;
132
+ kind: ReviewFileKind;
133
+ mimeType: string | null;
134
+ originalExists: boolean;
135
+ modifiedExists: boolean;
136
+ originalPreviewUrl: string | null;
137
+ modifiedPreviewUrl: string | null;
138
138
  }
139
139
 
140
140
  export interface ReviewFileErrorMessage {
141
- type: "file-error";
142
- requestId: string;
143
- fileId: string;
144
- scope: ReviewScope;
145
- commitSha?: string | null;
146
- message: string;
141
+ type: "file-error";
142
+ requestId: string;
143
+ fileId: string;
144
+ scope: ReviewScope;
145
+ commitSha?: string | null;
146
+ message: string;
147
147
  }
148
148
 
149
149
  export interface ReviewCommitDataMessage {
150
- type: "commit-data";
151
- requestId: string;
152
- sha: string;
153
- files: ReviewFile[];
150
+ type: "commit-data";
151
+ requestId: string;
152
+ sha: string;
153
+ files: ReviewFile[];
154
154
  }
155
155
 
156
156
  export interface ReviewCommitErrorMessage {
157
- type: "commit-error";
158
- requestId: string;
159
- sha: string;
160
- message: string;
157
+ type: "commit-error";
158
+ requestId: string;
159
+ sha: string;
160
+ message: string;
161
161
  }
162
162
 
163
163
  export interface ReviewReviewDataMessage {
164
- type: "review-data";
165
- requestId: string;
166
- files: ReviewFile[];
167
- commits: ReviewCommitInfo[];
168
- branchBaseRef: string | null;
169
- branchMergeBaseSha: string | null;
170
- repositoryHasHead: boolean;
164
+ type: "review-data";
165
+ requestId: string;
166
+ files: ReviewFile[];
167
+ commits: ReviewCommitInfo[];
168
+ branchBaseRef: string | null;
169
+ branchMergeBaseSha: string | null;
170
+ repositoryHasHead: boolean;
171
171
  }
172
172
 
173
173
  export interface ReviewReviewDataErrorMessage {
174
- type: "review-data-error";
175
- requestId: string;
176
- message: string;
174
+ type: "review-data-error";
175
+ requestId: string;
176
+ message: string;
177
177
  }
178
178
 
179
179
  export interface ReviewClipboardDataMessage {
180
- type: "clipboard-data";
181
- requestId: string;
182
- text: string;
183
- message?: string;
180
+ type: "clipboard-data";
181
+ requestId: string;
182
+ text: string;
183
+ message?: string;
184
184
  }
185
185
 
186
186
  export interface ReviewWorkingTreeChangedMessage {
187
- type: "working-tree-changed";
188
- changedAt: number;
187
+ type: "working-tree-changed";
188
+ changedAt: number;
189
189
  }
190
190
 
191
191
  export type ReviewHostMessage =
192
- | ReviewFileDataMessage
193
- | ReviewFileErrorMessage
194
- | ReviewCommitDataMessage
195
- | ReviewCommitErrorMessage
196
- | ReviewReviewDataMessage
197
- | ReviewReviewDataErrorMessage
198
- | ReviewClipboardDataMessage
199
- | ReviewWorkingTreeChangedMessage;
192
+ | ReviewFileDataMessage
193
+ | ReviewFileErrorMessage
194
+ | ReviewCommitDataMessage
195
+ | ReviewCommitErrorMessage
196
+ | ReviewReviewDataMessage
197
+ | ReviewReviewDataErrorMessage
198
+ | ReviewClipboardDataMessage
199
+ | ReviewWorkingTreeChangedMessage;
200
200
 
201
201
  export interface ReviewWindowData {
202
- repoRoot: string;
203
- files: ReviewFile[];
204
- commits: ReviewCommitInfo[];
205
- branchBaseRef: string | null;
206
- branchMergeBaseSha: string | null;
207
- repositoryHasHead: boolean;
202
+ repoRoot: string;
203
+ files: ReviewFile[];
204
+ commits: ReviewCommitInfo[];
205
+ branchBaseRef: string | null;
206
+ branchMergeBaseSha: string | null;
207
+ repositoryHasHead: boolean;
208
208
  }
@@ -7,41 +7,41 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const webDir = join(__dirname, "web");
8
8
 
9
9
  function escapeForInlineScript(value: string): string {
10
- return value.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
10
+ return value.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
11
11
  }
12
12
 
13
13
  function escapeInlineScriptSource(value: string): string {
14
- return value.replace(/<\/(script)/gi, "<\\/$1");
14
+ return value.replace(/<\/(script)/gi, "<\\/$1");
15
15
  }
16
16
 
17
17
  export interface ReviewUiAssets {
18
- tailwindBrowserJs: string;
19
- monacoLoaderJs: string;
20
- monacoVsBaseUrl: string;
18
+ tailwindBrowserJs: string;
19
+ monacoLoaderJs: string;
20
+ monacoVsBaseUrl: string;
21
21
  }
22
22
 
23
23
  export function buildReviewHtml(data: ReviewWindowData, assets: ReviewUiAssets): string {
24
- const templateHtml = readFileSync(join(webDir, "index.html"), "utf8");
25
- const appJs = escapeInlineScriptSource(readFileSync(join(webDir, "app.js"), "utf8"));
26
- const payload = escapeForInlineScript(JSON.stringify(data));
27
- const assetConfig = escapeForInlineScript(
28
- JSON.stringify({
29
- monacoVsBaseUrl: assets.monacoVsBaseUrl,
30
- bootstrapError: null,
31
- }),
32
- );
33
- const replacements = new Map([
34
- ['"__INLINE_DATA__"', payload],
35
- ["__INLINE_ASSET_CONFIG__", assetConfig],
36
- ["__INLINE_TAILWIND_JS__", escapeInlineScriptSource(assets.tailwindBrowserJs)],
37
- ["__INLINE_MONACO_LOADER_JS__", escapeInlineScriptSource(assets.monacoLoaderJs)],
38
- ["__INLINE_JS__", appJs],
39
- ]);
24
+ const templateHtml = readFileSync(join(webDir, "index.html"), "utf8");
25
+ const appJs = escapeInlineScriptSource(readFileSync(join(webDir, "app.js"), "utf8"));
26
+ const payload = escapeForInlineScript(JSON.stringify(data));
27
+ const assetConfig = escapeForInlineScript(
28
+ JSON.stringify({
29
+ monacoVsBaseUrl: assets.monacoVsBaseUrl,
30
+ bootstrapError: null,
31
+ }),
32
+ );
33
+ const replacements = new Map([
34
+ ['"__INLINE_DATA__"', payload],
35
+ ["__INLINE_ASSET_CONFIG__", assetConfig],
36
+ ["__INLINE_TAILWIND_JS__", escapeInlineScriptSource(assets.tailwindBrowserJs)],
37
+ ["__INLINE_MONACO_LOADER_JS__", escapeInlineScriptSource(assets.monacoLoaderJs)],
38
+ ["__INLINE_JS__", appJs],
39
+ ]);
40
40
 
41
- // Replace the original template in one pass so marker-like repository data
42
- // or runtime source is never scanned as another template placeholder.
43
- return templateHtml.replace(
44
- /"__INLINE_DATA__"|__INLINE_ASSET_CONFIG__|__INLINE_TAILWIND_JS__|__INLINE_MONACO_LOADER_JS__|__INLINE_JS__/g,
45
- (marker) => replacements.get(marker) ?? marker,
46
- );
41
+ // Replace the original template in one pass so marker-like repository data
42
+ // or runtime source is never scanned as another template placeholder.
43
+ return templateHtml.replace(
44
+ /"__INLINE_DATA__"|__INLINE_ASSET_CONFIG__|__INLINE_TAILWIND_JS__|__INLINE_MONACO_LOADER_JS__|__INLINE_JS__/g,
45
+ (marker) => replacements.get(marker) ?? marker,
46
+ );
47
47
  }
@@ -1,104 +1,104 @@
1
1
  import { type FSWatcher, watch } from "node:fs";
2
2
 
3
3
  export interface RepoChangeWatcher {
4
- dispose(): void;
4
+ dispose(): void;
5
5
  }
6
6
 
7
7
  interface RepoChangeWatcherOptions {
8
- debounceMs?: number;
9
- onError?: (error: Error) => void;
8
+ debounceMs?: number;
9
+ onError?: (error: Error) => void;
10
10
  }
11
11
 
12
12
  const DEFAULT_DEBOUNCE_MS = 2000;
13
13
 
14
14
  const ignoredPathSegments = new Set([
15
- ".cache",
16
- ".git",
17
- ".hg",
18
- ".next",
19
- ".nuxt",
20
- ".svn",
21
- ".turbo",
22
- "build",
23
- "coverage",
24
- "dist",
25
- "node_modules",
26
- "out",
27
- "target",
28
- "tmp",
15
+ ".cache",
16
+ ".git",
17
+ ".hg",
18
+ ".next",
19
+ ".nuxt",
20
+ ".svn",
21
+ ".turbo",
22
+ "build",
23
+ "coverage",
24
+ "dist",
25
+ "node_modules",
26
+ "out",
27
+ "target",
28
+ "tmp",
29
29
  ]);
30
30
 
31
31
  const ignoredFileNames = new Set([".DS_Store"]);
32
32
 
33
33
  function normalizeWatchPath(path: string): string {
34
- return path.replace(/\\/g, "/").replace(/^\/+/, "");
34
+ return path.replace(/\\/g, "/").replace(/^\/+/, "");
35
35
  }
36
36
 
37
37
  export function isIgnoredWatchPath(path: string | Buffer | null | undefined): boolean {
38
- if (path == null) return false;
39
- const normalized = normalizeWatchPath(path.toString());
40
- if (normalized.length === 0) return false;
38
+ if (path == null) return false;
39
+ const normalized = normalizeWatchPath(path.toString());
40
+ if (normalized.length === 0) return false;
41
41
 
42
- const segments = normalized.split("/").filter((segment) => segment.length > 0);
43
- if (segments.some((segment) => ignoredPathSegments.has(segment))) return true;
42
+ const segments = normalized.split("/").filter((segment) => segment.length > 0);
43
+ if (segments.some((segment) => ignoredPathSegments.has(segment))) return true;
44
44
 
45
- const fileName = segments.at(-1) ?? normalized;
46
- if (ignoredFileNames.has(fileName)) return true;
47
- if (fileName.endsWith("~") || fileName.endsWith(".swp") || fileName.endsWith(".tmp")) return true;
45
+ const fileName = segments.at(-1) ?? normalized;
46
+ if (ignoredFileNames.has(fileName)) return true;
47
+ if (fileName.endsWith("~") || fileName.endsWith(".swp") || fileName.endsWith(".tmp")) return true;
48
48
 
49
- return false;
49
+ return false;
50
50
  }
51
51
 
52
52
  export function createRepoChangeWatcher(
53
- repoRoot: string,
54
- onChange: () => void,
55
- options: RepoChangeWatcherOptions = {},
53
+ repoRoot: string,
54
+ onChange: () => void,
55
+ options: RepoChangeWatcherOptions = {},
56
56
  ): RepoChangeWatcher {
57
- const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
58
- let disposed = false;
59
- let timer: ReturnType<typeof setTimeout> | null = null;
60
- let watcher: FSWatcher | null = null;
61
-
62
- const clearPending = (): void => {
63
- if (timer == null) return;
64
- clearTimeout(timer);
65
- timer = null;
66
- };
67
-
68
- const scheduleChange = (path: string | Buffer | null): void => {
69
- if (disposed || isIgnoredWatchPath(path)) return;
70
- clearPending();
71
- timer = setTimeout(() => {
72
- timer = null;
73
- if (!disposed) onChange();
74
- }, debounceMs);
75
- };
76
-
77
- try {
78
- watcher = watch(repoRoot, { recursive: true }, (_eventType, fileName) => {
79
- scheduleChange(fileName);
80
- });
81
- watcher.on("error", (error) => {
82
- if (!disposed) options.onError?.(error);
83
- });
84
- } catch (error) {
85
- options.onError?.(error instanceof Error ? error : new Error(String(error)));
86
- }
87
-
88
- return {
89
- dispose() {
90
- disposed = true;
91
- clearPending();
92
- try {
93
- watcher?.close();
94
- } catch {
95
- // Ignore watcher shutdown races.
96
- }
97
- watcher = null;
98
- },
99
- };
57
+ const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
58
+ let disposed = false;
59
+ let timer: ReturnType<typeof setTimeout> | null = null;
60
+ let watcher: FSWatcher | null = null;
61
+
62
+ const clearPending = (): void => {
63
+ if (timer == null) return;
64
+ clearTimeout(timer);
65
+ timer = null;
66
+ };
67
+
68
+ const scheduleChange = (path: string | Buffer | null): void => {
69
+ if (disposed || isIgnoredWatchPath(path)) return;
70
+ clearPending();
71
+ timer = setTimeout(() => {
72
+ timer = null;
73
+ if (!disposed) onChange();
74
+ }, debounceMs);
75
+ };
76
+
77
+ try {
78
+ watcher = watch(repoRoot, { recursive: true }, (_eventType, fileName) => {
79
+ scheduleChange(fileName);
80
+ });
81
+ watcher.on("error", (error) => {
82
+ if (!disposed) options.onError?.(error);
83
+ });
84
+ } catch (error) {
85
+ options.onError?.(error instanceof Error ? error : new Error(String(error)));
86
+ }
87
+
88
+ return {
89
+ dispose() {
90
+ disposed = true;
91
+ clearPending();
92
+ try {
93
+ watcher?.close();
94
+ } catch {
95
+ // Ignore watcher shutdown races.
96
+ }
97
+ watcher = null;
98
+ },
99
+ };
100
100
  }
101
101
 
102
102
  export const __testing = {
103
- isIgnoredWatchPath,
103
+ isIgnoredWatchPath,
104
104
  };