opencode-swarm 7.83.0 → 7.85.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 (67) hide show
  1. package/.opencode/skills/codebase-review-swarm/references/review-protocol-v8.2.md +4 -0
  2. package/.opencode/skills/council/SKILL.md +6 -1
  3. package/.opencode/skills/deep-dive/SKILL.md +2 -0
  4. package/.opencode/skills/deep-research/SKILL.md +6 -0
  5. package/.opencode/skills/swarm-pr-feedback/SKILL.md +6 -0
  6. package/.opencode/skills/swarm-pr-review/SKILL.md +4 -0
  7. package/README.md +3 -1
  8. package/dist/background/lane-output-store.d.ts +72 -0
  9. package/dist/background/pending-delegations.d.ts +6 -0
  10. package/dist/cli/capability-probe-jevmgwmf.js +18 -0
  11. package/dist/cli/config-doctor-zejarrr6.js +35 -0
  12. package/dist/cli/dispatch-k86d928w.js +477 -0
  13. package/dist/cli/evidence-summary-service-g2znnd33.js +320 -0
  14. package/dist/cli/explorer-gz70sm9b.js +16 -0
  15. package/dist/cli/gate-evidence-y8zn7fe2.js +29 -0
  16. package/dist/cli/guardrail-explain-w4txg349.js +30 -0
  17. package/dist/cli/guardrail-log-80116wmz.js +15 -0
  18. package/dist/cli/index-0sxvwjt0.js +1241 -0
  19. package/dist/cli/index-293f68mj.js +13538 -0
  20. package/dist/cli/index-5cb86007.js +110 -0
  21. package/dist/cli/index-a76rekgs.js +67 -0
  22. package/dist/cli/index-b9v501fr.js +371 -0
  23. package/dist/cli/index-bcp79s17.js +1673 -0
  24. package/dist/cli/index-ckntc5gf.js +91 -0
  25. package/dist/cli/index-d9fbxaqd.js +2314 -0
  26. package/dist/cli/index-e7h9bb6v.js +233 -0
  27. package/dist/cli/index-e8pk68cc.js +540 -0
  28. package/dist/cli/index-eb85wtx9.js +242 -0
  29. package/dist/cli/index-f8r50m3h.js +14505 -0
  30. package/dist/cli/index-fjwwrwr5.js +37 -0
  31. package/dist/cli/index-hw9b2xng.js +2046 -0
  32. package/dist/cli/index-hz59hg4h.js +452 -0
  33. package/dist/cli/index-jtqkh8jf.js +119 -0
  34. package/dist/cli/index-p0arc26j.js +28 -0
  35. package/dist/cli/index-p0ye10nd.js +222 -0
  36. package/dist/cli/index-qqabjns2.js +412 -0
  37. package/dist/cli/index-red8fm8p.js +2914 -0
  38. package/dist/cli/index-vq2321gg.js +2391 -0
  39. package/dist/cli/index-x7qck34v.js +583 -0
  40. package/dist/cli/index-yhqt45de.js +29027 -0
  41. package/dist/cli/index-yhsmmv2z.js +339 -0
  42. package/dist/cli/index-yx44zd0p.js +40 -0
  43. package/dist/cli/index-zfsbaaqh.js +29 -0
  44. package/dist/cli/index.js +73 -69708
  45. package/dist/cli/knowledge-store-n4x6zyk7.js +73 -0
  46. package/dist/cli/pending-delegations-rd40tv9s.js +261 -0
  47. package/dist/cli/pr-subscriptions-y1nn36e5.js +33 -0
  48. package/dist/cli/schema-8d32b2v6.js +168 -0
  49. package/dist/cli/skill-generator-a5ehggyg.js +55 -0
  50. package/dist/cli/task-envelope-qn0qtnh0.js +90 -0
  51. package/dist/cli/telemetry-9bbyxrvn.js +20 -0
  52. package/dist/cli/workspace-snapshot-w58jr2ga.js +90 -0
  53. package/dist/commands/guardrail-explain.d.ts +1 -0
  54. package/dist/commands/guardrail-log.d.ts +1 -0
  55. package/dist/commands/index.d.ts +2 -0
  56. package/dist/commands/registry.d.ts +14 -0
  57. package/dist/hooks/guardrails/audit-log.d.ts +114 -0
  58. package/dist/index.js +4005 -2432
  59. package/dist/services/diagnose-service.d.ts +5 -0
  60. package/dist/services/guardrail-explain-service.d.ts +42 -0
  61. package/dist/services/guardrail-log-service.d.ts +10 -0
  62. package/dist/tools/dispatch-lanes.d.ts +14 -3
  63. package/dist/tools/index.d.ts +1 -0
  64. package/dist/tools/manifest.d.ts +1 -0
  65. package/dist/tools/retrieve-lane-output.d.ts +2 -0
  66. package/dist/tools/tool-metadata.d.ts +4 -0
  67. package/package.json +2 -2
@@ -0,0 +1,583 @@
1
+ // @bun
2
+ import {
3
+ DC_SAFE_TARGETS,
4
+ HUMAN_ONLY_SWARM_COMMANDS,
5
+ dcCheckJunctionCreation,
6
+ dcExtractPowerShellTargets,
7
+ dcExtractWindowsCmdTargets,
8
+ dcNormalizeCommand,
9
+ dcSplitSegments,
10
+ dcUnwrapWrappers,
11
+ dcValidateTargets,
12
+ detectPosixWrites,
13
+ detectWindowsWrites,
14
+ resolveWriteTargets
15
+ } from "./index-yhqt45de.js";
16
+ import {
17
+ checkFileAuthority,
18
+ classifyFile,
19
+ isInDeclaredScope,
20
+ redactPath,
21
+ redactShellCommand
22
+ } from "./index-vq2321gg.js";
23
+
24
+ // src/services/guardrail-explain-service.ts
25
+ import path from "path";
26
+ function parseGuardrailExplainArgs(args) {
27
+ let agent = "coder";
28
+ let scope = null;
29
+ const writeTargets = [];
30
+ const positional = [];
31
+ let i = 0;
32
+ while (i < args.length) {
33
+ const token = args[i];
34
+ if (token === "--agent" && i + 1 < args.length) {
35
+ agent = args[i + 1];
36
+ i += 2;
37
+ } else if (token === "--scope" && i + 1 < args.length) {
38
+ scope = args[i + 1];
39
+ i += 2;
40
+ } else if (token === "--write" && i + 1 < args.length) {
41
+ writeTargets.push(args[i + 1]);
42
+ i += 2;
43
+ } else {
44
+ positional.push(token);
45
+ i++;
46
+ }
47
+ }
48
+ return {
49
+ agent,
50
+ scope,
51
+ writeTargets,
52
+ shellCommand: positional.join(" ")
53
+ };
54
+ }
55
+ function resolveShellType(command) {
56
+ if (/^(powershell|ps1|\.\s*\\|Remove-Item|Copy-Item|Move-Item|Start-Process|New-Object|Get-ChildItem|Set-Content|Add-Content|Out-File|Invoke-WebRequest|Invoke-RestMethod|IEX|iex)\b/i.test(command) || command.includes("$PSVersionTable") || command.includes("$env:") || /-EncodedCommand|-ExecutionPolicy|Enable-PSRemoting/i.test(command)) {
57
+ return "powershell";
58
+ }
59
+ if (/^(cmd|cmd\.exe|c:\/|set \w+=|\.\d+|del \/|rd \/|mkdir|chdir|echo |copy |move |ren |fc |diskpart)/i.test(command) || /%[^%\s]+%/.test(command) || /\b(set|echo|if|exist)\s+/i.test(command)) {
60
+ return "cmd";
61
+ }
62
+ return "posix";
63
+ }
64
+ function evaluateDestructiveDecision(rawCommand, cwd, declaredScope) {
65
+ if (!rawCommand) {
66
+ return { decision: "allow", firingRule: "allow: no rule matched" };
67
+ }
68
+ const command = dcNormalizeCommand(rawCommand);
69
+ if (/:\s*\(\s*\)\s*\{[^}]*\|[^}]*:/.test(command)) {
70
+ return {
71
+ decision: "block",
72
+ firingRule: "destructive_block: fork bomb pattern"
73
+ };
74
+ }
75
+ const unwrapped = dcUnwrapWrappers(command);
76
+ const outerSegments = dcSplitSegments(command);
77
+ const innerSegments = dcSplitSegments(unwrapped);
78
+ const perSegmentUnwrapped = outerSegments.map((s) => dcUnwrapWrappers(s));
79
+ const allSegments = [
80
+ ...new Set([...outerSegments, ...innerSegments, ...perSegmentUnwrapped])
81
+ ];
82
+ for (const segment of allSegments) {
83
+ const seg = segment.trim();
84
+ if (!seg)
85
+ continue;
86
+ const junctionBlock = dcCheckJunctionCreation(seg, cwd);
87
+ if (junctionBlock) {
88
+ return {
89
+ decision: "block",
90
+ firingRule: "destructive_block: junction creation"
91
+ };
92
+ }
93
+ const rmShortMatch = seg.match(/^rm\s+(-[rRfF]+(?:\s+-[rRfF]+)*|-r\s+-f|-f\s+-r)\s+(.+)$/);
94
+ const rmLongMatch = seg.match(/^rm\s+(?:--(?:recursive|force)\s+){1,2}(.+)$/);
95
+ const rmAnyMatch = rmShortMatch ?? rmLongMatch;
96
+ if (rmAnyMatch) {
97
+ const targetPart = rmAnyMatch[rmShortMatch ? 2 : 1].trim();
98
+ const targets = targetPart.split(/\s+/);
99
+ const validateBlock = dcValidateTargets(targets, cwd);
100
+ if (validateBlock) {
101
+ return {
102
+ decision: "block",
103
+ firingRule: `destructive_block: rm with unsafe target: ${redactPath(targetPart)}`
104
+ };
105
+ }
106
+ const allSafe = targets.every((t) => DC_SAFE_TARGETS.has(t.replace(/^["']|["']$/g, "").trim()));
107
+ if (!allSafe) {
108
+ const scopeExempt = declaredScope != null && declaredScope.length > 0 && targets.every((t) => isInDeclaredScope(t.replace(/^["']|["']$/g, "").trim(), declaredScope, cwd));
109
+ if (!scopeExempt) {
110
+ return {
111
+ decision: "block",
112
+ firingRule: `destructive_block: rm recursive/force on unsafe path: ${redactPath(targetPart)}`
113
+ };
114
+ }
115
+ }
116
+ }
117
+ if (/^(?:rmdir|rd)(?:\.exe)?\s+.*\/[sS]/i.test(seg)) {
118
+ const targets = dcExtractWindowsCmdTargets(seg);
119
+ if (targets.length === 0) {
120
+ return {
121
+ decision: "block",
122
+ firingRule: "destructive_block: Windows recursive directory delete (rmdir /s)"
123
+ };
124
+ }
125
+ const validateBlock = dcValidateTargets(targets, cwd);
126
+ if (validateBlock) {
127
+ return {
128
+ decision: "block",
129
+ firingRule: `destructive_block: rmdir /s unsafe: ${redactPath(targets.join(", "))}`
130
+ };
131
+ }
132
+ const allSafe = targets.every((t) => DC_SAFE_TARGETS.has(t.trim()));
133
+ if (!allSafe) {
134
+ const scopeExempt = declaredScope != null && declaredScope.length > 0 && targets.every((t) => isInDeclaredScope(t.trim(), declaredScope, cwd));
135
+ if (!scopeExempt) {
136
+ return {
137
+ decision: "block",
138
+ firingRule: `destructive_block: rmdir /s on unsafe path: ${redactPath(targets.join(", "))}`
139
+ };
140
+ }
141
+ }
142
+ }
143
+ if (/^del(?:\.exe)?\s+.*\/[sS]/i.test(seg)) {
144
+ const targets = dcExtractWindowsCmdTargets(seg);
145
+ if (targets.length > 0) {
146
+ const validateBlock = dcValidateTargets(targets, cwd);
147
+ if (validateBlock) {
148
+ return {
149
+ decision: "block",
150
+ firingRule: `destructive_block: del /s unsafe: ${redactPath(targets.join(", "))}`
151
+ };
152
+ }
153
+ const allSafe = targets.every((t) => DC_SAFE_TARGETS.has(t.trim()));
154
+ if (!allSafe) {
155
+ const scopeExempt = declaredScope != null && declaredScope.length > 0 && targets.every((t) => isInDeclaredScope(t.trim(), declaredScope, cwd));
156
+ if (!scopeExempt) {
157
+ return {
158
+ decision: "block",
159
+ firingRule: `destructive_block: del /s on unsafe path: ${redactPath(targets.join(", "))}`
160
+ };
161
+ }
162
+ }
163
+ }
164
+ }
165
+ if (/^(?:Remove-Item|ri|rm|rmdir|del|erase|rd)\b.*-[Rr]ecurse\b/i.test(seg) || /^(?:Remove-Item|ri|rm|rmdir|del|erase|rd)\b.*-[Rr]\b/i.test(seg)) {
166
+ const targets = dcExtractPowerShellTargets(seg);
167
+ if (targets.length > 0) {
168
+ const validateBlock = dcValidateTargets(targets, cwd);
169
+ if (validateBlock) {
170
+ return {
171
+ decision: "block",
172
+ firingRule: `destructive_block: PowerShell recursive delete unsafe: ${redactPath(targets.join(", "))}`
173
+ };
174
+ }
175
+ const allSafe = targets.every((t) => DC_SAFE_TARGETS.has(t.trim()));
176
+ if (!allSafe) {
177
+ const scopeExempt = declaredScope != null && declaredScope.length > 0 && targets.every((t) => isInDeclaredScope(t.trim(), declaredScope, cwd));
178
+ if (!scopeExempt) {
179
+ return {
180
+ decision: "block",
181
+ firingRule: `destructive_block: PowerShell recursive delete on unsafe path: ${redactPath(targets.join(", "))}`
182
+ };
183
+ }
184
+ }
185
+ } else {
186
+ return {
187
+ decision: "block",
188
+ firingRule: "destructive_block: PowerShell Remove-Item -Recurse (target unverifiable)"
189
+ };
190
+ }
191
+ }
192
+ if (/Get-ChildItem\b.*\|\s*Remove-Item\b.*-[Rr]ecurse/i.test(seg) || /gci\b.*\|\s*ri\b.*-[Rr]ecurse/i.test(seg)) {
193
+ return {
194
+ decision: "block",
195
+ firingRule: "destructive_block: PowerShell pipeline Get-ChildItem | Remove-Item -Recurse"
196
+ };
197
+ }
198
+ if (/^vssadmin(?:\.exe)?\s+delete\b/i.test(seg)) {
199
+ return {
200
+ decision: "block",
201
+ firingRule: "destructive_block: vssadmin delete (ransomware-grade)"
202
+ };
203
+ }
204
+ if (/^wbadmin(?:\.exe)?\s+delete\b/i.test(seg)) {
205
+ return {
206
+ decision: "block",
207
+ firingRule: "destructive_block: wbadmin delete (backup catalog deletion)"
208
+ };
209
+ }
210
+ if (/^diskpart(?:\.exe)?$/i.test(seg)) {
211
+ return {
212
+ decision: "block",
213
+ firingRule: "destructive_block: diskpart (interactive disk partitioning)"
214
+ };
215
+ }
216
+ if (/^bcdedit(?:\.exe)?\s+\/delete\b/i.test(seg)) {
217
+ return {
218
+ decision: "block",
219
+ firingRule: "destructive_block: bcdedit /delete (boot config modification)"
220
+ };
221
+ }
222
+ if (/^sdelete(?:\.exe)?\s+/i.test(seg)) {
223
+ return {
224
+ decision: "block",
225
+ firingRule: "destructive_block: sdelete (secure file deletion)"
226
+ };
227
+ }
228
+ if (/^fsutil(?:\.exe)?\s+reparsepoint\s+delete\b/i.test(seg) || /^fsutil(?:\.exe)?\s+file\s+setzerodata\b/i.test(seg)) {
229
+ return {
230
+ decision: "block",
231
+ firingRule: "destructive_block: fsutil destructive subcommand"
232
+ };
233
+ }
234
+ if (/^takeown(?:\.exe)?\s+.*\/[rR]\b/i.test(seg)) {
235
+ return {
236
+ decision: "block",
237
+ firingRule: "destructive_block: takeown /R (recursive ownership takeover)"
238
+ };
239
+ }
240
+ if (/^cipher(?:\.exe)?\s+\/[wW]\b/i.test(seg)) {
241
+ return {
242
+ decision: "block",
243
+ firingRule: "destructive_block: cipher /w (free disk space wipe)"
244
+ };
245
+ }
246
+ if (/^format\s+[A-Za-z]:/i.test(seg)) {
247
+ return {
248
+ decision: "block",
249
+ firingRule: "destructive_block: Windows disk format command"
250
+ };
251
+ }
252
+ if (/^robocopy(?:\.exe)?\s+.*\/(?:MIR|mir)\b/.test(seg)) {
253
+ return {
254
+ decision: "block",
255
+ firingRule: "destructive_block: robocopy /MIR (mirror delete)"
256
+ };
257
+ }
258
+ if (/^chmod\s+.*-[rR]\b.*000\b/.test(seg)) {
259
+ return {
260
+ decision: "block",
261
+ firingRule: "destructive_block: chmod -R 000 (permission wipe)"
262
+ };
263
+ }
264
+ if (/^chattr\s+.*\+i\b/.test(seg)) {
265
+ return {
266
+ decision: "block",
267
+ firingRule: "destructive_block: chattr +i (immutable flag)"
268
+ };
269
+ }
270
+ if (/^icacls(?:\.exe)?\s+.*\/deny\b/i.test(seg)) {
271
+ return {
272
+ decision: "block",
273
+ firingRule: "destructive_block: icacls /deny (permission denial)"
274
+ };
275
+ }
276
+ if (/^dd\b.*\bif=\/dev\/(zero|null|urandom)\b/.test(seg)) {
277
+ return {
278
+ decision: "block",
279
+ firingRule: "destructive_block: dd with /dev/zero|null|urandom (data wipe)"
280
+ };
281
+ }
282
+ if (/^git\s+push\b.*?(--force|-f)\b/.test(seg)) {
283
+ return {
284
+ decision: "block",
285
+ firingRule: "destructive_block: git push --force"
286
+ };
287
+ }
288
+ if (/^git\s+reset\s+--hard/.test(seg)) {
289
+ return {
290
+ decision: "block",
291
+ firingRule: "destructive_block: git reset --hard"
292
+ };
293
+ }
294
+ if (/^git\s+reset\s+--mixed\s+\S+/.test(seg)) {
295
+ return {
296
+ decision: "block",
297
+ firingRule: "destructive_block: git reset --mixed with target"
298
+ };
299
+ }
300
+ if (/^git\s+clean\s+.*-[fF].*[dD]/.test(seg)) {
301
+ return {
302
+ decision: "block",
303
+ firingRule: "destructive_block: git clean -fd"
304
+ };
305
+ }
306
+ if (/^git\s+worktree\s+remove\s+.*--force\b/i.test(seg)) {
307
+ return {
308
+ decision: "block",
309
+ firingRule: "destructive_block: git worktree remove --force"
310
+ };
311
+ }
312
+ if (/^rsync\b.*--delete(?:-after|-before|-during|-delay)?\b/.test(seg)) {
313
+ const rsyncArgs = seg.split(/\s+/).slice(1);
314
+ const rsyncTarget = rsyncArgs.filter((a) => !a.startsWith("-") && !a.includes("@")).pop();
315
+ const scopeExempt = rsyncTarget != null && declaredScope != null && declaredScope.length > 0 && isInDeclaredScope(rsyncTarget, declaredScope, cwd);
316
+ if (!scopeExempt) {
317
+ return {
318
+ decision: "block",
319
+ firingRule: `destructive_block: rsync --delete (target: ${redactPath(rsyncTarget ?? "unknown")})`
320
+ };
321
+ }
322
+ }
323
+ if (/^kubectl\s+delete\b/.test(seg)) {
324
+ return {
325
+ decision: "block",
326
+ firingRule: "destructive_block: kubectl delete"
327
+ };
328
+ }
329
+ if (/^docker\s+system\s+prune\b/.test(seg)) {
330
+ return {
331
+ decision: "block",
332
+ firingRule: "destructive_block: docker system prune"
333
+ };
334
+ }
335
+ if (/^\s*DROP\s+(TABLE|DATABASE|SCHEMA)\b/i.test(seg)) {
336
+ return {
337
+ decision: "block",
338
+ firingRule: "destructive_block: SQL DROP statement"
339
+ };
340
+ }
341
+ if (/^\s*TRUNCATE\s+TABLE\b/i.test(seg)) {
342
+ return {
343
+ decision: "block",
344
+ firingRule: "destructive_block: SQL TRUNCATE TABLE statement"
345
+ };
346
+ }
347
+ if (/^\\?mv\s/i.test(seg)) {
348
+ const mvMatch = seg.match(/^\\?mv\s+(.+)$/i);
349
+ if (mvMatch) {
350
+ const argsStr = mvMatch[1].replace(/["']/g, "");
351
+ if (/\.swarm(?:[\x5c/\s]|$)/.test(argsStr)) {
352
+ return {
353
+ decision: "block",
354
+ firingRule: 'destructive_block: "mv" targeting .swarm/ detected \u2014 move operations under .swarm/ are not allowed from shell commands'
355
+ };
356
+ }
357
+ }
358
+ }
359
+ if (/^\\?(?:move|ren)(?:\.exe)?\s/i.test(seg)) {
360
+ const moveMatch = seg.match(/^\\?(?:move|ren)(?:\.exe)?\s+(.+)$/i);
361
+ if (moveMatch) {
362
+ const argsStr = moveMatch[1].replace(/["']/g, "");
363
+ if (/\.swarm(?:[\x5c/\s]|$)/i.test(argsStr)) {
364
+ return {
365
+ decision: "block",
366
+ firingRule: 'destructive_block: "move" or "ren" targeting .swarm/ detected \u2014 move/rename operations under .swarm/ are not allowed from shell commands'
367
+ };
368
+ }
369
+ }
370
+ }
371
+ if (/^\\?(?:Move-Item|Rename-Item|move|mi|mv|ren|rni)\b.*\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
372
+ return {
373
+ decision: "block",
374
+ firingRule: "destructive_block: PowerShell Move-Item or Rename-Item targeting .swarm/ detected \u2014 move/rename operations under .swarm/ are not allowed from shell commands"
375
+ };
376
+ }
377
+ if (/^\\?rm\b/i.test(seg) && !/^\\?rm\s+(?:-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
378
+ return {
379
+ decision: "block",
380
+ firingRule: 'destructive_block: "rm" targeting .swarm/ detected \u2014 deleting files under .swarm/ is not allowed from shell commands'
381
+ };
382
+ }
383
+ if (/\bcp\b.*\.swarm(?:[\x5c/\s]|$)/i.test(seg) && /\brm\b.*\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
384
+ return {
385
+ decision: "block",
386
+ firingRule: 'destructive_block: "cp" of .swarm/ file followed by "rm" of .swarm/ source detected \u2014 copy-and-delete bypass is not allowed'
387
+ };
388
+ }
389
+ if (/^rsync\b.*--remove-source-files\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
390
+ return {
391
+ decision: "block",
392
+ firingRule: 'destructive_block: "rsync" with delete-source flag targeting .swarm/ detected \u2014 archive with source deletion under .swarm/ is not allowed'
393
+ };
394
+ }
395
+ if (/^tar\b.*--remove-files\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
396
+ return {
397
+ decision: "block",
398
+ firingRule: 'destructive_block: "tar" with delete-source flag targeting .swarm/ detected \u2014 archive with source deletion under .swarm/ is not allowed'
399
+ };
400
+ }
401
+ if (/^zip\b.*\s-m\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
402
+ return {
403
+ decision: "block",
404
+ firingRule: 'destructive_block: "zip" with delete-source flag targeting .swarm/ detected \u2014 archive with source deletion under .swarm/ is not allowed'
405
+ };
406
+ }
407
+ if (/^7z\b.*\s-sdel\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
408
+ return {
409
+ decision: "block",
410
+ firingRule: 'destructive_block: "7z" with delete-source flag targeting .swarm/ detected \u2014 archive with source deletion under .swarm/ is not allowed'
411
+ };
412
+ }
413
+ if (/^mkfs[./]/.test(seg)) {
414
+ return {
415
+ decision: "block",
416
+ firingRule: "destructive_block: mkfs (filesystem format)"
417
+ };
418
+ }
419
+ {
420
+ let probe = seg.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)+/, "").replace(/^eval(?:\s+--)?\s+["']?/, "").replace(/["']\s*$/, "").replace(/^\$\(\s*/, "").replace(/^\(\s*/, "").replace(/\s*\)$/, "").replace(/^`/, "").replace(/`$/, "").trim();
421
+ for (let i = 0;i < 4; i++) {
422
+ const before = probe;
423
+ probe = probe.replace(/^env\s+(?:-i\b|--ignore-environment\b|-u\s+\S+|-[a-zA-Z]+\s+)*\s*/, "").replace(/^command\s+(?:-[pvV]\s+)*/, "").replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)+/, "").trim();
424
+ if (probe === before)
425
+ break;
426
+ }
427
+ const swarmCliMatches = [
428
+ probe.match(/^\\?(?:bunx|npx|pnpx|npm(?:\s+(?:exec|x)(?:\s+--)?)?|pnpm(?:\s+(?:dlx|exec))?|yarn(?:\s+(?:dlx|exec))?|bun(?:\s+x)?|node|deno\s+run|tsx|ts-node)\b[^|;&]*?\bopencode-swarm\b[^|;&]*?\brun\s+([A-Za-z0-9_-]+(?:\s+(?!-)[A-Za-z0-9_-]+)?)/i),
429
+ probe.match(/^\\?opencode-swarm\b[^|;&]*?\brun\s+([A-Za-z0-9_-]+(?:\s+(?!-)[A-Za-z0-9_-]+)?)/i),
430
+ probe.match(/\bcli[/\\]+index\.[mc]?(?:js|ts)\b[^|;&]*?\brun\s+([A-Za-z0-9_-]+(?:\s+(?!-)[A-Za-z0-9_-]+)?)/i)
431
+ ];
432
+ for (const m of swarmCliMatches) {
433
+ if (!m)
434
+ continue;
435
+ const captured = m[1];
436
+ const normalized = captured.trim().split(/\s+/).join(" ");
437
+ const firstToken = normalized.includes(" ") ? normalized.split(" ")[0] : normalized;
438
+ if (HUMAN_ONLY_SWARM_COMMANDS.has(normalized) || HUMAN_ONLY_SWARM_COMMANDS.has(firstToken)) {
439
+ const cmdName = HUMAN_ONLY_SWARM_COMMANDS.has(normalized) ? normalized : firstToken;
440
+ return {
441
+ decision: "block",
442
+ firingRule: `human_only_command: "${cmdName}" is a human-only swarm command (not invocable from shell by an agent)`
443
+ };
444
+ }
445
+ }
446
+ }
447
+ {
448
+ const normForPathCheck = seg.replace(/\\/g, "/").replace(/\/(?:\.\/+)+/g, "/").replace(/\/{2,}/g, "/");
449
+ if (/\.swarm\/spec-staleness\.json\b/i.test(normForPathCheck)) {
450
+ const trimmed = seg.trim();
451
+ const looksReadOnly = /^(?:cat|less|more|head|tail|file|stat|ls|dir|Get-Content|gc|Get-Item|gi|type)\b/i.test(trimmed);
452
+ const hasWriteRedirect = />{1,2}\s*[^\s>]/.test(trimmed);
453
+ if (!looksReadOnly || hasWriteRedirect) {
454
+ return {
455
+ decision: "block",
456
+ firingRule: "system_file_tampering: shell command targeting .swarm/spec-staleness.json (system-managed file)"
457
+ };
458
+ }
459
+ }
460
+ }
461
+ }
462
+ return {
463
+ decision: "allow",
464
+ firingRule: "allow: no destructive/scope rule matched"
465
+ };
466
+ }
467
+ async function handleGuardrailExplain(directory, args) {
468
+ const { agent, scope, writeTargets, shellCommand } = parseGuardrailExplainArgs(args);
469
+ const resolvedScope = scope ?? directory;
470
+ const scopeEntries = scope != null && scope.length > 0 ? [scope] : [resolvedScope];
471
+ if (writeTargets.length > 0) {
472
+ const results = [];
473
+ for (const rawPath of writeTargets) {
474
+ const absolutePath = path.resolve(directory, rawPath);
475
+ const decision2 = checkFileAuthority(agent, absolutePath, directory, undefined, { declaredScope: scope != null ? [scope] : null });
476
+ const zoneResult = classifyFile(absolutePath);
477
+ const allowed = decision2.allowed === true;
478
+ const zone = allowed ? zoneResult.zone : decision2.zone ?? zoneResult.zone;
479
+ results.push({
480
+ path: rawPath,
481
+ decision: allowed ? "allow" : "block",
482
+ firingRule: allowed ? "allow: no rule matched" : decision2.reason,
483
+ resolvedScope,
484
+ zone
485
+ });
486
+ }
487
+ const redactedResolvedScope = redactPath(resolvedScope);
488
+ const lines2 = [
489
+ "## Guardrail Explain (dry-run)",
490
+ "",
491
+ `**Mode**: write-target | **Agent**: ${agent} | **Resolved scope**: ${redactedResolvedScope}`,
492
+ "",
493
+ "| Path | Decision | Firing Rule | Resolved Scope | Zone |",
494
+ "| --- | --- | --- | --- | --- |",
495
+ ...results.map((r) => `| ${redactPath(r.path)} | ${r.decision} | ${r.firingRule} | ${redactPath(r.resolvedScope)} | ${r.zone} |`),
496
+ ""
497
+ ];
498
+ return lines2.join(`
499
+ `);
500
+ }
501
+ if (!shellCommand) {
502
+ return [
503
+ "## Guardrail Explain (dry-run)",
504
+ "",
505
+ "Provide a shell command to analyze, or use `--write <path>` for write-target mode.",
506
+ "",
507
+ "```",
508
+ "/swarm guardrail explain <command>",
509
+ "/swarm guardrail explain --agent <role> --scope <path> <command>",
510
+ "/swarm guardrail explain --write <path> [--write <path2> ...]",
511
+ "```",
512
+ ""
513
+ ].join(`
514
+ `);
515
+ }
516
+ let decision = "allow";
517
+ let firingRule = "allow: no rule matched";
518
+ const writeCategories = new Set;
519
+ const destructiveEval = evaluateDestructiveDecision(shellCommand, directory, scopeEntries);
520
+ if (destructiveEval.decision === "block") {
521
+ decision = "block";
522
+ firingRule = destructiveEval.firingRule;
523
+ }
524
+ if (decision === "allow") {
525
+ const shellType = resolveShellType(shellCommand);
526
+ const analysis = shellType === "powershell" || shellType === "cmd" ? detectWindowsWrites(shellCommand, shellType) : detectPosixWrites(shellCommand);
527
+ if (analysis.parseError) {
528
+ decision = "block";
529
+ firingRule = "parse_error: write detection failed to parse command \u2014 rejecting for safety";
530
+ } else if (analysis.hasWrites) {
531
+ const resolvedWrites = resolveWriteTargets(shellCommand, analysis.writes, directory);
532
+ for (const write of resolvedWrites) {
533
+ if (write.resolvedPath) {
534
+ if (agent !== "architect") {
535
+ const inScope = isInDeclaredScope(write.resolvedPath, scopeEntries, directory);
536
+ if (!inScope) {
537
+ decision = "block";
538
+ firingRule = `scope_violation: write outside declared scope: ${redactPath(write.resolvedPath)}`;
539
+ break;
540
+ }
541
+ }
542
+ writeCategories.add(write.original.category);
543
+ } else {
544
+ writeCategories.add(write.original.category);
545
+ }
546
+ }
547
+ }
548
+ } else {
549
+ const shellType = resolveShellType(shellCommand);
550
+ const analysis = shellType === "powershell" || shellType === "cmd" ? detectWindowsWrites(shellCommand, shellType) : detectPosixWrites(shellCommand);
551
+ if (!analysis.parseError && analysis.hasWrites) {
552
+ for (const write of analysis.writes) {
553
+ writeCategories.add(write.category);
554
+ }
555
+ }
556
+ }
557
+ const redactedCommand = redactShellCommand(shellCommand);
558
+ const redactedResolvedScopeShell = redactPath(resolvedScope);
559
+ const shellResult = {
560
+ decision,
561
+ firingRule,
562
+ resolvedScope: redactedResolvedScopeShell,
563
+ writeCategories: writeCategories.size > 0 ? [...writeCategories] : [],
564
+ command: redactedCommand
565
+ };
566
+ const lines = [
567
+ "## Guardrail Explain (dry-run)",
568
+ "",
569
+ `**Mode**: shell | **Agent**: ${agent} | **Resolved scope**: ${redactedResolvedScopeShell}`,
570
+ "",
571
+ "| Field | Value |",
572
+ "| --- | --- |",
573
+ `| Command | \`${shellResult.command}\` |`,
574
+ `| Decision | ${shellResult.decision} |`,
575
+ `| Firing Rule | ${shellResult.firingRule} |`,
576
+ `| Resolved Scope | ${shellResult.resolvedScope} |`,
577
+ `| Write Categories | ${shellResult.writeCategories.length > 0 ? shellResult.writeCategories.join(", ") : "(none)"} |`,
578
+ ""
579
+ ];
580
+ return lines.join(`
581
+ `);
582
+ }
583
+ export { handleGuardrailExplain };