icopilot 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,299 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { confirm } from '@inquirer/prompts';
4
+ import { createPatch } from 'diff';
5
+ import { config } from '../config.js';
6
+ import { hookManager } from '../hooks/lifecycle.js';
7
+ import { theme } from '../ui/theme.js';
8
+ import { formatAutoCheckResult, runAutoLint } from './auto-check.js';
9
+ import { toolMemory } from './memory.js';
10
+ import { loadPolicy, writePathAllowed } from './policy.js';
11
+ import { assertSandbox } from './sandbox.js';
12
+ import { ensureWriteAllowed } from './file-ops.js';
13
+ const rollbackContexts = new WeakMap();
14
+ export const multiEditSchema = {
15
+ type: 'function',
16
+ function: {
17
+ name: 'multi_edit',
18
+ description: 'Preview and atomically apply coordinated text replacements across multiple files, rolling back all changes if any file write fails.',
19
+ parameters: {
20
+ type: 'object',
21
+ properties: {
22
+ description: {
23
+ type: 'string',
24
+ description: 'Short description of the coordinated edit operation.',
25
+ },
26
+ rollbackable: {
27
+ type: 'boolean',
28
+ description: 'Whether a successful edit should remain eligible for manual rollback.',
29
+ },
30
+ files: {
31
+ type: 'array',
32
+ items: {
33
+ type: 'object',
34
+ properties: {
35
+ file: {
36
+ type: 'string',
37
+ description: 'Repository-relative file path to edit.',
38
+ },
39
+ edits: {
40
+ type: 'array',
41
+ items: {
42
+ type: 'object',
43
+ properties: {
44
+ oldText: { type: 'string' },
45
+ newText: { type: 'string' },
46
+ },
47
+ required: ['oldText', 'newText'],
48
+ },
49
+ },
50
+ },
51
+ required: ['file', 'edits'],
52
+ },
53
+ },
54
+ },
55
+ required: ['description', 'files', 'rollbackable'],
56
+ },
57
+ },
58
+ };
59
+ export function planMultiEdit(plan) {
60
+ return prepareMultiEdit(plan).preview;
61
+ }
62
+ export function applyMultiEdit(plan) {
63
+ const { prepared } = prepareMultiEdit(plan);
64
+ return applyPreparedMultiEdit(prepared, plan.rollbackable);
65
+ }
66
+ export function rollbackMultiEdit(result) {
67
+ const context = rollbackContexts.get(result);
68
+ if (!context?.active)
69
+ return;
70
+ const failed = result.failed ? [...result.failed] : [];
71
+ const applied = new Set(result.applied);
72
+ for (const file of [...context.files].reverse()) {
73
+ if (!applied.has(file.path))
74
+ continue;
75
+ try {
76
+ if (file.existed) {
77
+ fs.mkdirSync(path.dirname(file.absPath), { recursive: true });
78
+ fs.writeFileSync(file.absPath, file.originalContent, 'utf8');
79
+ }
80
+ else if (fs.existsSync(file.absPath)) {
81
+ fs.unlinkSync(file.absPath);
82
+ }
83
+ }
84
+ catch (error) {
85
+ failed.push({
86
+ file: file.path,
87
+ error: error instanceof Error ? error.message : String(error),
88
+ });
89
+ }
90
+ }
91
+ context.active = false;
92
+ rollbackContexts.delete(result);
93
+ result.applied = [];
94
+ result.failed = failed.length ? failed : undefined;
95
+ }
96
+ export async function multiEditTool(rawPlan) {
97
+ try {
98
+ const plan = normalizePlan(rawPlan);
99
+ const { prepared, preview } = prepareMultiEdit(plan);
100
+ displayPreview(plan.description, preview);
101
+ const approved = await confirmMultiEdit(prepared.map((file) => file.absPath));
102
+ if (!approved) {
103
+ return JSON.stringify({
104
+ success: false,
105
+ applied: [],
106
+ failed: [{ file: '*', error: 'multi_edit cancelled' }],
107
+ preview,
108
+ });
109
+ }
110
+ const result = applyPreparedMultiEdit(prepared, plan.rollbackable);
111
+ const autoLint = result.success ? await maybeRunAutoLint(result.applied) : undefined;
112
+ return JSON.stringify({ ...result, preview, ...(autoLint ? { autoLint } : {}) });
113
+ }
114
+ catch (error) {
115
+ return JSON.stringify({
116
+ success: false,
117
+ applied: [],
118
+ failed: [{ file: '*', error: error instanceof Error ? error.message : String(error) }],
119
+ });
120
+ }
121
+ }
122
+ function normalizePlan(rawPlan) {
123
+ return {
124
+ description: String(rawPlan.description ?? ''),
125
+ rollbackable: Boolean(rawPlan.rollbackable),
126
+ files: Array.isArray(rawPlan.files)
127
+ ? rawPlan.files.map((file) => ({
128
+ file: String(file.file ?? ''),
129
+ edits: Array.isArray(file.edits)
130
+ ? file.edits.map((edit) => ({
131
+ oldText: String(edit.oldText ?? ''),
132
+ newText: String(edit.newText ?? ''),
133
+ }))
134
+ : [],
135
+ }))
136
+ : [],
137
+ };
138
+ }
139
+ function prepareMultiEdit(plan) {
140
+ if (!plan.files.length) {
141
+ throw new Error('multi_edit requires at least one file');
142
+ }
143
+ const policy = loadPolicy(config.cwd);
144
+ const prepared = plan.files.map((filePlan) => {
145
+ if (!filePlan.file.trim()) {
146
+ throw new Error('multi_edit file path must not be empty');
147
+ }
148
+ if (!filePlan.edits.length) {
149
+ throw new Error(`multi_edit requires at least one edit for ${filePlan.file}`);
150
+ }
151
+ const absPath = path.resolve(config.cwd, filePlan.file);
152
+ const denied = ensureWriteAllowed(absPath);
153
+ if (denied)
154
+ throw new Error(`${denied}: ${filePlan.file}`);
155
+ assertSandbox(absPath, config.cwd);
156
+ if (!writePathAllowed(absPath, policy, config.cwd))
157
+ throw new Error(`policy denied: ${filePlan.file}`);
158
+ if (!fs.existsSync(absPath)) {
159
+ throw new Error(`file not found: ${filePlan.file}`);
160
+ }
161
+ const originalContent = fs.readFileSync(absPath, 'utf8');
162
+ const nextContent = applyEdits(filePlan.file, originalContent, filePlan.edits);
163
+ const diff = createPatch(filePlan.file, originalContent, nextContent, 'current', 'proposed');
164
+ return {
165
+ path: filePlan.file,
166
+ absPath,
167
+ existed: true,
168
+ originalContent,
169
+ nextContent,
170
+ diff,
171
+ };
172
+ });
173
+ return {
174
+ prepared,
175
+ preview: {
176
+ files: prepared.map((file) => ({ path: file.path, diff: file.diff })),
177
+ totalChanges: plan.files.reduce((total, file) => total + file.edits.length, 0),
178
+ },
179
+ };
180
+ }
181
+ function applyPreparedMultiEdit(prepared, rollbackable) {
182
+ const result = { success: false, applied: [] };
183
+ const rollbackContext = { files: prepared, active: true };
184
+ rollbackContexts.set(result, rollbackContext);
185
+ for (const file of prepared) {
186
+ try {
187
+ fs.mkdirSync(path.dirname(file.absPath), { recursive: true });
188
+ fs.writeFileSync(file.absPath, file.nextContent, 'utf8');
189
+ void hookManager.emit('fileChanged', {
190
+ cwd: config.cwd,
191
+ path: file.path,
192
+ absolutePath: file.absPath,
193
+ bytes: Buffer.byteLength(file.nextContent),
194
+ });
195
+ result.applied.push(file.path);
196
+ }
197
+ catch (error) {
198
+ result.failed = [
199
+ {
200
+ file: file.path,
201
+ error: error instanceof Error ? error.message : String(error),
202
+ },
203
+ ];
204
+ rollbackMultiEdit(result);
205
+ return result;
206
+ }
207
+ }
208
+ result.success = true;
209
+ if (!rollbackable) {
210
+ rollbackContext.active = false;
211
+ rollbackContexts.delete(result);
212
+ }
213
+ return result;
214
+ }
215
+ function applyEdits(file, source, edits) {
216
+ let next = source;
217
+ edits.forEach((edit, index) => {
218
+ if (!edit.oldText) {
219
+ throw new Error(`edit ${index + 1} for ${file} must include a non-empty oldText`);
220
+ }
221
+ const matches = countOccurrences(next, edit.oldText);
222
+ if (matches === 0) {
223
+ throw new Error(`edit ${index + 1} for ${file} could not find oldText`);
224
+ }
225
+ if (matches > 1) {
226
+ throw new Error(`edit ${index + 1} for ${file} matched multiple locations`);
227
+ }
228
+ next = next.replace(edit.oldText, edit.newText);
229
+ });
230
+ return next;
231
+ }
232
+ function countOccurrences(source, target) {
233
+ if (!target)
234
+ return 0;
235
+ let count = 0;
236
+ let offset = 0;
237
+ while (offset <= source.length) {
238
+ const index = source.indexOf(target, offset);
239
+ if (index === -1)
240
+ break;
241
+ count += 1;
242
+ offset = index + target.length;
243
+ }
244
+ return count;
245
+ }
246
+ async function confirmMultiEdit(paths) {
247
+ const remembered = paths.every((file) => toolMemory.isWriteRemembered(file));
248
+ const approved = config.autoApprove ||
249
+ remembered ||
250
+ (await confirm({ message: 'Apply all patches?', default: false }).catch(() => false));
251
+ if (!approved) {
252
+ if (!config.jsonOutput)
253
+ process.stdout.write(theme.warn(' skipped.\n'));
254
+ return false;
255
+ }
256
+ if (!config.autoApprove && !remembered) {
257
+ const remember = await confirm({
258
+ message: 'Remember these write paths for the session?',
259
+ default: false,
260
+ }).catch(() => false);
261
+ if (remember)
262
+ paths.forEach((file) => toolMemory.rememberWrite(file));
263
+ }
264
+ return true;
265
+ }
266
+ function displayPreview(description, preview) {
267
+ if (config.quiet || config.jsonOutput)
268
+ return;
269
+ process.stdout.write(`\n${theme.badge('MULTI EDIT')} ${description || 'Coordinated multi-file edit'}\n`);
270
+ process.stdout.write(theme.dim(`${preview.files.length} files, ${preview.totalChanges} text changes\n`));
271
+ for (const file of preview.files) {
272
+ process.stdout.write(colorizePatch(file.diff) + '\n');
273
+ }
274
+ }
275
+ async function maybeRunAutoLint(changedFiles) {
276
+ if (!config.autoLint || changedFiles.length === 0)
277
+ return undefined;
278
+ const result = await runAutoLint(changedFiles);
279
+ if (!config.quiet && !config.jsonOutput) {
280
+ process.stdout.write(`${theme.dim(formatAutoCheckResult('lint', result, changedFiles))}\n`);
281
+ }
282
+ return result;
283
+ }
284
+ function colorizePatch(patch) {
285
+ return patch
286
+ .split('\n')
287
+ .map((line) => {
288
+ if (line.startsWith('+++') || line.startsWith('---'))
289
+ return theme.dim(line);
290
+ if (line.startsWith('+'))
291
+ return theme.ok(line);
292
+ if (line.startsWith('-'))
293
+ return theme.err(line);
294
+ if (line.startsWith('@@'))
295
+ return theme.hl(line);
296
+ return line;
297
+ })
298
+ .join('\n');
299
+ }
@@ -0,0 +1,95 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ export function loadPolicy(cwd) {
5
+ const globalPolicy = readPolicy(path.join(os.homedir(), '.icopilot', 'policy.json'));
6
+ const localPolicy = readPolicy(path.join(cwd, '.icopilot', 'policy.json'));
7
+ return mergePolicy(globalPolicy, localPolicy);
8
+ }
9
+ export function shellCommandAllowed(cmd, policy) {
10
+ const full = cmd.trim();
11
+ const first = full.split(/\s+/)[0] || '';
12
+ if (!hasPolicy(policy.denyShell) && !hasPolicy(policy.allowShell))
13
+ return true;
14
+ if (matchesAny(policy.denyShell, full) || matchesAny(policy.denyShell, first))
15
+ return false;
16
+ if (hasPolicy(policy.allowShell)) {
17
+ return matchesAny(policy.allowShell, full) || matchesAny(policy.allowShell, first);
18
+ }
19
+ return true;
20
+ }
21
+ export function writePathAllowed(absPath, policy, cwd) {
22
+ const rel = normalize(path.relative(cwd, absPath));
23
+ const normalizedAbs = normalize(path.resolve(absPath));
24
+ if (!hasPolicy(policy.denyWrite) && !hasPolicy(policy.allowWrite))
25
+ return true;
26
+ if (matchesAny(policy.denyWrite, rel) || matchesAny(policy.denyWrite, normalizedAbs))
27
+ return false;
28
+ if (hasPolicy(policy.allowWrite)) {
29
+ return matchesAny(policy.allowWrite, rel) || matchesAny(policy.allowWrite, normalizedAbs);
30
+ }
31
+ return true;
32
+ }
33
+ export function matches(pattern, str) {
34
+ const source = normalize(pattern);
35
+ const target = normalize(str);
36
+ if (source === target || target.startsWith(source.endsWith('/') ? source : `${source}/`)) {
37
+ return true;
38
+ }
39
+ const re = new RegExp(`^${globToRegex(source)}$`, process.platform === 'win32' ? 'i' : '');
40
+ return re.test(target);
41
+ }
42
+ function readPolicy(file) {
43
+ try {
44
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ }
50
+ function mergePolicy(base, override) {
51
+ return {
52
+ allowShell: mergeArray(base.allowShell, override.allowShell),
53
+ denyShell: mergeArray(base.denyShell, override.denyShell),
54
+ allowWrite: mergeArray(base.allowWrite, override.allowWrite),
55
+ denyWrite: mergeArray(base.denyWrite, override.denyWrite),
56
+ sandbox: override.sandbox ?? base.sandbox,
57
+ };
58
+ }
59
+ function mergeArray(a, b) {
60
+ const merged = [...(a || []), ...(b || [])];
61
+ return merged.length ? merged : undefined;
62
+ }
63
+ function hasPolicy(patterns) {
64
+ return Boolean(patterns?.length);
65
+ }
66
+ function matchesAny(patterns, value) {
67
+ return Boolean(patterns?.some((pattern) => matches(pattern, value)));
68
+ }
69
+ function normalize(value) {
70
+ return value.replace(/\\/g, '/').replace(/^\.\//, '');
71
+ }
72
+ function globToRegex(pattern) {
73
+ let out = '';
74
+ for (let i = 0; i < pattern.length; i += 1) {
75
+ const c = pattern[i];
76
+ const next = pattern[i + 1];
77
+ if (c === '*' && next === '*') {
78
+ out += '.*';
79
+ i += 1;
80
+ }
81
+ else if (c === '*') {
82
+ out += '[^/]*';
83
+ }
84
+ else if (c === '?') {
85
+ out += '[^/]';
86
+ }
87
+ else {
88
+ out += escapeRegex(c);
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+ function escapeRegex(value) {
94
+ return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
95
+ }