opencode-swarm 7.114.7 → 7.114.8

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 (42) hide show
  1. package/.opencode/skills/engineering-conventions/SKILL.md +33 -0
  2. package/README.md +15 -14
  3. package/dist/agents/template.d.ts +40 -20
  4. package/dist/cli/{curator-llm-factory-2xgznxyw.js → curator-llm-factory-ez48eq02.js} +7 -7
  5. package/dist/cli/{curator-qshyr2tc.js → curator-qj970412.js} +7 -7
  6. package/dist/cli/{evidence-summary-service-8g594znj.js → evidence-summary-service-fehwj116.js} +1 -1
  7. package/dist/cli/{guardrail-explain-4t8fnysh.js → guardrail-explain-x2vaxp6s.js} +8 -8
  8. package/dist/cli/{hive-promoter-5ngbmnrw.js → hive-promoter-ysk5edzw.js} +7 -7
  9. package/dist/cli/{index-7stsmndb.js → index-6f6y2rbp.js} +3 -3
  10. package/dist/cli/{index-kjbfry6m.js → index-8f2270hc.js} +1 -1
  11. package/dist/cli/{index-6ctejje2.js → index-cs5765s2.js} +86 -63
  12. package/dist/cli/{index-qrnhvhmg.js → index-n18yy0z1.js} +26 -2
  13. package/dist/cli/{index-91qesget.js → index-rk6qhyng.js} +2 -2
  14. package/dist/cli/{index-gmjrwz34.js → index-ry8nwsq6.js} +1 -1
  15. package/dist/cli/{index-vjvdvjd3.js → index-x12hvfpf.js} +3 -3
  16. package/dist/cli/{index-f6y341yk.js → index-yrrqs4b0.js} +82 -21
  17. package/dist/cli/{index-z01r5483.js → index-z718bgxe.js} +8 -8
  18. package/dist/cli/index.d.ts +34 -0
  19. package/dist/cli/index.js +105 -28
  20. package/dist/cli/{knowledge-escalator-qn7ew687.js → knowledge-escalator-5gwp1ar8.js} +3 -3
  21. package/dist/cli/{knowledge-events-k2xsz5bh.js → knowledge-events-m304swh6.js} +1 -1
  22. package/dist/cli/{knowledge-store-ksa1dr2z.js → knowledge-store-9babt8rd.js} +1 -1
  23. package/dist/cli/{knowledge-validator-2knz0d2t.js → knowledge-validator-t9sym59g.js} +4 -2
  24. package/dist/cli/{skill-generator-w6qd9mde.js → skill-generator-tav44xpm.js} +4 -4
  25. package/dist/config/skill-mirrors.d.ts +12 -0
  26. package/dist/hooks/adversarial-detector.d.ts +6 -0
  27. package/dist/hooks/delegation-gate.d.ts +19 -0
  28. package/dist/hooks/guardrails/index.d.ts +2 -2
  29. package/dist/hooks/knowledge-validator.d.ts +22 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.js +74 -70
  32. package/dist/plan/ledger.d.ts +83 -20
  33. package/dist/types/delegation.d.ts +24 -0
  34. package/package.json +1 -1
  35. package/dist/graph/graph-builder.d.ts +0 -39
  36. package/dist/graph/graph-query.d.ts +0 -42
  37. package/dist/graph/graph-store.d.ts +0 -27
  38. package/dist/graph/import-extractor.d.ts +0 -44
  39. package/dist/graph/index.d.ts +0 -16
  40. package/dist/graph/symbol-extractor.d.ts +0 -17
  41. package/dist/graph/types.d.ts +0 -84
  42. package/dist/sandbox/win32/restricted-token-executor.d.ts +0 -9
@@ -15,6 +15,40 @@ export declare function isSafeCachePath(p: string): boolean;
15
15
  * grandparent structure validation to prevent misconfigured nested paths.
16
16
  */
17
17
  export declare function isSafeLockFilePath(p: string): boolean;
18
+ /**
19
+ * Safety guard for the `uninstall --clean` deletion of PROMPTS_DIR
20
+ * (`<CONFIG_DIR>/opencode-swarm`, a DIRECTORY removed recursively).
21
+ *
22
+ * This is a SEPARATE guard from isSafeCachePath — it is NOT reused because
23
+ * isSafeCachePath's layer-4 requires parent ∈ {packages, node_modules} and
24
+ * grandparent === 'opencode', which the prompts directory (parent basename is
25
+ * the config dir's basename, typically 'opencode') does not satisfy, so
26
+ * isSafeCachePath would false-REJECT a legitimate PROMPTS_DIR.
27
+ *
28
+ * Defense in depth:
29
+ * 1. Canonicalize via safeRealpathSync (realpathSync with ENOENT→fallback)
30
+ * so the path a symlinked leaf actually resolves to is what we validate.
31
+ * 2. Refuse root, home, or shorter-than-home paths.
32
+ * 3. Require ≥ 3 non-empty segments (rejects pathological
33
+ * XDG_CONFIG_HOME='/' which yields '/opencode/opencode-swarm', 2 segments).
34
+ * 4. Require basename === 'opencode-swarm' AND the parent directory's
35
+ * basename to equal the config dir's basename (canonical layout is
36
+ * `<CONFIG_DIR>/opencode-swarm`).
37
+ */
38
+ export declare function isSafePromptsDir(p: string): boolean;
39
+ /**
40
+ * Safety guard for the `uninstall --clean` deletion of PLUGIN_CONFIG_PATH
41
+ * (`<CONFIG_DIR>/opencode-swarm.json`, a FILE removed with unlinkSync).
42
+ *
43
+ * File-oriented sibling of isSafePromptsDir. isSafePromptsDir would
44
+ * false-REJECT this path because the leaf here is the `.json` FILE, not the
45
+ * `opencode-swarm` directory — hence a dedicated guard.
46
+ *
47
+ * Defense in depth: canonicalize via safeRealpathSync; refuse root/home/
48
+ * shorter-than-home; require basename === 'opencode-swarm.json' AND the
49
+ * parent directory's basename to equal the config dir's basename.
50
+ */
51
+ export declare function isSafePluginConfigPath(p: string): boolean;
18
52
  /**
19
53
  * Recursively delete every known opencode plugin cache location for
20
54
  * opencode-swarm. Returns paths actually cleared and paths that errored.
package/dist/cli/index.js CHANGED
@@ -7,22 +7,22 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-6ctejje2.js";
10
+ } from "./index-cs5765s2.js";
11
11
  import"./index-yfedche9.js";
12
12
  import"./index-c8s9a3zh.js";
13
13
  import"./index-9bsmzfk3.js";
14
14
  import {
15
15
  DEFAULT_AGENT_CONFIGS
16
16
  } from "./index-a45jq4b7.js";
17
- import"./index-f6y341yk.js";
17
+ import"./index-yrrqs4b0.js";
18
18
  import"./index-scww5b77.js";
19
19
  import"./index-ga4ta3cr.js";
20
20
  import"./index-80jkseqw.js";
21
- import"./index-vjvdvjd3.js";
22
- import"./index-qrnhvhmg.js";
23
- import"./index-7stsmndb.js";
24
- import"./index-kjbfry6m.js";
25
- import"./index-91qesget.js";
21
+ import"./index-x12hvfpf.js";
22
+ import"./index-n18yy0z1.js";
23
+ import"./index-6f6y2rbp.js";
24
+ import"./index-8f2270hc.js";
25
+ import"./index-rk6qhyng.js";
26
26
  import"./index-bfwt3abw.js";
27
27
  import"./index-g40r5d08.js";
28
28
  import"./index-vxv732ex.js";
@@ -49,6 +49,21 @@ import"./index-a76rekgs.js";
49
49
  import * as fs from "fs";
50
50
  import * as os from "os";
51
51
  import * as path from "path";
52
+
53
+ // src/tools/repo-graph/safe-realpath.ts
54
+ import { realpathSync } from "fs";
55
+ function safeRealpathSync(targetPath, fallback, realpathResolver = realpathSync) {
56
+ try {
57
+ return realpathResolver(targetPath);
58
+ } catch (error) {
59
+ if (error instanceof Error && error.code === "ENOENT") {
60
+ return fallback;
61
+ }
62
+ return null;
63
+ }
64
+ }
65
+
66
+ // src/cli/index.ts
52
67
  var { version } = package_default;
53
68
  var CONFIG_DIR = getPluginConfigDir();
54
69
  var OPENCODE_CONFIG_PATH = path.join(CONFIG_DIR, "opencode.json");
@@ -56,14 +71,17 @@ var PLUGIN_CONFIG_PATH = path.join(CONFIG_DIR, "opencode-swarm.json");
56
71
  var PROMPTS_DIR = path.join(CONFIG_DIR, "opencode-swarm");
57
72
  var OPENCODE_PLUGIN_CACHE_PATHS = getPluginCachePaths();
58
73
  var OPENCODE_PLUGIN_LOCK_FILE_PATHS = getPluginLockFilePaths();
74
+ function segmentDepthBelowRoot(resolved) {
75
+ const { root } = path.parse(resolved);
76
+ return resolved.slice(root.length).split(path.sep).filter((s) => s.length > 0).length;
77
+ }
59
78
  function isSafeCachePath(p) {
60
79
  const resolved = path.resolve(p);
61
80
  const home = path.resolve(os.homedir());
62
81
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
63
82
  return false;
64
83
  }
65
- const segments = resolved.split(path.sep).filter((s) => s.length > 0);
66
- if (segments.length < 4) {
84
+ if (segmentDepthBelowRoot(resolved) < 4) {
67
85
  return false;
68
86
  }
69
87
  const leaf = path.basename(resolved);
@@ -86,8 +104,7 @@ function isSafeLockFilePath(p) {
86
104
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
87
105
  return false;
88
106
  }
89
- const segments = resolved.split(path.sep).filter((s) => s.length > 0);
90
- if (segments.length < 4) {
107
+ if (segmentDepthBelowRoot(resolved) < 4) {
91
108
  return false;
92
109
  }
93
110
  const leaf = path.basename(resolved);
@@ -104,6 +121,48 @@ function isSafeLockFilePath(p) {
104
121
  }
105
122
  return true;
106
123
  }
124
+ function isSafePromptsDir(p) {
125
+ const canonical = safeRealpathSync(p, p);
126
+ if (canonical === null) {
127
+ return false;
128
+ }
129
+ const resolved = path.resolve(canonical);
130
+ const home = path.resolve(os.homedir());
131
+ if (resolved === "/" || resolved === home || resolved.length <= home.length) {
132
+ return false;
133
+ }
134
+ if (segmentDepthBelowRoot(resolved) < 3) {
135
+ return false;
136
+ }
137
+ if (path.basename(resolved) !== "opencode-swarm") {
138
+ return false;
139
+ }
140
+ if (path.basename(path.dirname(resolved)) !== path.basename(CONFIG_DIR)) {
141
+ return false;
142
+ }
143
+ return true;
144
+ }
145
+ function isSafePluginConfigPath(p) {
146
+ const canonical = safeRealpathSync(p, p);
147
+ if (canonical === null) {
148
+ return false;
149
+ }
150
+ const resolved = path.resolve(canonical);
151
+ const home = path.resolve(os.homedir());
152
+ if (resolved === "/" || resolved === home || resolved.length <= home.length) {
153
+ return false;
154
+ }
155
+ if (segmentDepthBelowRoot(resolved) < 3) {
156
+ return false;
157
+ }
158
+ if (path.basename(resolved) !== "opencode-swarm.json") {
159
+ return false;
160
+ }
161
+ if (path.basename(path.dirname(resolved)) !== path.basename(CONFIG_DIR)) {
162
+ return false;
163
+ }
164
+ return true;
165
+ }
107
166
  function ensureDir(dir) {
108
167
  if (!fs.existsSync(dir)) {
109
168
  fs.mkdirSync(dir, { recursive: true });
@@ -270,15 +329,18 @@ function evictPluginCaches() {
270
329
  for (const cachePath of OPENCODE_PLUGIN_CACHE_PATHS) {
271
330
  if (!fs.existsSync(cachePath))
272
331
  continue;
273
- if (!isSafeCachePath(cachePath)) {
274
- failed.push(`${cachePath} (refused: failed safety check)`);
332
+ const canonical = safeRealpathSync(cachePath, cachePath);
333
+ if (canonical === null || !fs.existsSync(canonical))
334
+ continue;
335
+ if (!isSafeCachePath(canonical)) {
336
+ failed.push(`${canonical} (refused: failed safety check)`);
275
337
  continue;
276
338
  }
277
339
  try {
278
- fs.rmSync(cachePath, { recursive: true, force: true });
279
- cleared.push(cachePath);
340
+ fs.rmSync(canonical, { recursive: true, force: true });
341
+ cleared.push(canonical);
280
342
  } catch (err) {
281
- failed.push(`${cachePath} (${err instanceof Error ? err.message : String(err)})`);
343
+ failed.push(`${canonical} (${err instanceof Error ? err.message : String(err)})`);
282
344
  }
283
345
  }
284
346
  return { cleared, failed };
@@ -289,19 +351,22 @@ function evictLockFiles() {
289
351
  for (const lockPath of OPENCODE_PLUGIN_LOCK_FILE_PATHS) {
290
352
  if (!fs.existsSync(lockPath))
291
353
  continue;
292
- if (!isSafeLockFilePath(lockPath)) {
293
- failed.push(`${lockPath} (refused: failed safety check)`);
354
+ const canonical = safeRealpathSync(lockPath, lockPath);
355
+ if (canonical === null || !fs.existsSync(canonical))
356
+ continue;
357
+ if (!isSafeLockFilePath(canonical)) {
358
+ failed.push(`${canonical} (refused: failed safety check)`);
294
359
  continue;
295
360
  }
296
361
  try {
297
- fs.unlinkSync(lockPath);
298
- cleared.push(lockPath);
362
+ fs.unlinkSync(canonical);
363
+ cleared.push(canonical);
299
364
  } catch (err) {
300
365
  const code = err?.code;
301
366
  if (code === "EISDIR") {
302
- failed.push(`${lockPath} (path is a directory, not a file)`);
367
+ failed.push(`${canonical} (path is a directory, not a file)`);
303
368
  } else {
304
- failed.push(`${lockPath} (${err instanceof Error ? err.message : String(err)})`);
369
+ failed.push(`${canonical} (${err instanceof Error ? err.message : String(err)})`);
305
370
  }
306
371
  }
307
372
  }
@@ -346,14 +411,24 @@ async function uninstall() {
346
411
  if (process.argv.includes("--clean")) {
347
412
  let cleaned = false;
348
413
  if (fs.existsSync(PLUGIN_CONFIG_PATH)) {
349
- fs.unlinkSync(PLUGIN_CONFIG_PATH);
350
- console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
351
- cleaned = true;
414
+ const canonical = safeRealpathSync(PLUGIN_CONFIG_PATH, PLUGIN_CONFIG_PATH);
415
+ if (canonical === null || !isSafePluginConfigPath(canonical)) {
416
+ console.log(`\u2717 Refused to remove plugin config (failed safety check): ${canonical ?? PLUGIN_CONFIG_PATH}`);
417
+ } else {
418
+ fs.unlinkSync(canonical);
419
+ console.log(`\u2713 Removed plugin config: ${canonical}`);
420
+ cleaned = true;
421
+ }
352
422
  }
353
423
  if (fs.existsSync(PROMPTS_DIR)) {
354
- fs.rmSync(PROMPTS_DIR, { recursive: true });
355
- console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
356
- cleaned = true;
424
+ const canonical = safeRealpathSync(PROMPTS_DIR, PROMPTS_DIR);
425
+ if (canonical === null || !isSafePromptsDir(canonical)) {
426
+ console.log(`\u2717 Refused to remove custom prompts (failed safety check): ${canonical ?? PROMPTS_DIR}`);
427
+ } else {
428
+ fs.rmSync(canonical, { recursive: true });
429
+ console.log(`\u2713 Removed custom prompts: ${canonical}`);
430
+ cleaned = true;
431
+ }
357
432
  }
358
433
  if (!cleaned) {
359
434
  console.log("\u2713 No config files to clean up");
@@ -474,6 +549,8 @@ Valid commands: ${VALID_COMMANDS.join(", ")}`);
474
549
  }
475
550
  export {
476
551
  run,
552
+ isSafePromptsDir,
553
+ isSafePluginConfigPath,
477
554
  isSafeLockFilePath,
478
555
  isSafeCachePath,
479
556
  evictPluginCaches,
@@ -8,9 +8,9 @@ import {
8
8
  maybeEscalateOnViolation,
9
9
  maybeQuarantineOnContradiction,
10
10
  readRecentEscalations
11
- } from "./index-7stsmndb.js";
12
- import"./index-kjbfry6m.js";
13
- import"./index-91qesget.js";
11
+ } from "./index-6f6y2rbp.js";
12
+ import"./index-8f2270hc.js";
13
+ import"./index-rk6qhyng.js";
14
14
  import"./index-bfwt3abw.js";
15
15
  import"./index-g40r5d08.js";
16
16
  import"./index-jtqkh8jf.js";
@@ -25,7 +25,7 @@ import {
25
25
  resolveKnowledgeCounterBaselinePath,
26
26
  resolveKnowledgeEventsPath,
27
27
  resolveLegacyApplicationLogPath
28
- } from "./index-kjbfry6m.js";
28
+ } from "./index-8f2270hc.js";
29
29
  import"./index-g40r5d08.js";
30
30
  import"./index-zgwm4ryv.js";
31
31
  import"./index-0p660a87.js";
@@ -34,7 +34,7 @@ import {
34
34
  transactFile,
35
35
  transactKnowledge,
36
36
  wordBigrams
37
- } from "./index-91qesget.js";
37
+ } from "./index-rk6qhyng.js";
38
38
  import"./index-bfwt3abw.js";
39
39
  import"./index-g40r5d08.js";
40
40
  import"./index-jtqkh8jf.js";
@@ -16,14 +16,15 @@ import {
16
16
  quarantineEntry,
17
17
  resolveUnactionablePath,
18
18
  restoreEntry,
19
+ scanContentSafety,
19
20
  unarchiveEntry,
20
21
  validateActionability,
21
22
  validateActionableFields,
22
23
  validateLesson,
23
24
  validateSkillCandidatePath,
24
25
  validateSkillPath
25
- } from "./index-qrnhvhmg.js";
26
- import"./index-91qesget.js";
26
+ } from "./index-n18yy0z1.js";
27
+ import"./index-rk6qhyng.js";
27
28
  import"./index-bfwt3abw.js";
28
29
  import"./index-g40r5d08.js";
29
30
  import"./index-jtqkh8jf.js";
@@ -41,6 +42,7 @@ export {
41
42
  validateActionableFields,
42
43
  validateActionability,
43
44
  unarchiveEntry,
45
+ scanContentSafety,
44
46
  restoreEntry,
45
47
  resolveUnactionablePath,
46
48
  quarantineEntry,
@@ -33,10 +33,10 @@ import {
33
33
  sanitizeSlug,
34
34
  selectCandidateEntries,
35
35
  writeEvalStub
36
- } from "./index-vjvdvjd3.js";
37
- import"./index-qrnhvhmg.js";
38
- import"./index-kjbfry6m.js";
39
- import"./index-91qesget.js";
36
+ } from "./index-x12hvfpf.js";
37
+ import"./index-n18yy0z1.js";
38
+ import"./index-8f2270hc.js";
39
+ import"./index-rk6qhyng.js";
40
40
  import"./index-bfwt3abw.js";
41
41
  import"./index-g40r5d08.js";
42
42
  import"./index-jtqkh8jf.js";
@@ -88,6 +88,18 @@ export declare const ADDITIONAL_SKILL_MIRROR_CONTRACTS: Array<{
88
88
  kind: 'identical' | 'divergent' | 'opencode-only';
89
89
  canonical?: '.claude' | '.opencode';
90
90
  extraIdenticalPaths?: string[];
91
+ /**
92
+ * For `divergent` pairs only: a set of Markdown section headings that must be
93
+ * present in BOTH trees (`.opencode` and `.claude`). Divergence is still
94
+ * allowed for genuinely runtime-specific prose (title line, `effort:`
95
+ * frontmatter, "(Claude Code)" phrasing), but these designated safety
96
+ * sections are parity-enforced by scripts/drift-check.ts so a whole safety
97
+ * guard cannot silently exist in one tree and be absent from the other.
98
+ * Match is a substring test, so a heading may omit a trailing parenthetical.
99
+ * Fixes the M13 skill-mirror safety-drift defect: the divergent branch
100
+ * previously did existence-only checks with no content comparison.
101
+ */
102
+ sharedSafetyHeadings?: string[];
91
103
  reason: string;
92
104
  }>;
93
105
  /**
@@ -38,6 +38,12 @@ export declare function handleDebuggingSpiral(match: AdversarialPatternMatch, ta
38
38
  checkpointCreated: boolean;
39
39
  message: string;
40
40
  }>;
41
+ /**
42
+ * Test seam: current number of distinct sessions tracked for spiral detection.
43
+ * Used by unit tests to assert the FIFO key-count cap holds; the map itself stays
44
+ * module-private so production callers cannot mutate it.
45
+ */
46
+ export declare function recentToolCallSessionCount(): number;
41
47
  /**
42
48
  * Record a tool call for debugging spiral detection.
43
49
  * Call this from toolAfter to track repetitive patterns.
@@ -68,6 +68,25 @@ interface ValidationContext {
68
68
  * Returns { valid: true } on success, or { valid: false; reason: string } on failure.
69
69
  */
70
70
  export declare function validateDelegationEnvelope(envelope: unknown, context: ValidationContext): EnvelopeValidationResult;
71
+ /**
72
+ * M15 advisory-only delegation-envelope check. NON-BLOCKING by contract: it
73
+ * NEVER throws and NEVER rejects a delegation. It runs
74
+ * {@link validateDelegationEnvelope} (previously reachable only from tests) on a
75
+ * delegation prompt that ALREADY parses as a structured `DelegationEnvelope`,
76
+ * and on validation failure appends a concise advisory to
77
+ * `session.pendingAdvisoryMessages` — the existing non-blocking channel.
78
+ *
79
+ * A real, minimal free-text delegation (TASK:/FILE:/... with no structured
80
+ * envelope) is a no-op: {@link parseDelegationEnvelope} returns `null`, so no
81
+ * advisory is produced and nothing is rejected. The new optional `specCriteria`
82
+ * field is OPTIONAL in the validator — a missing field passes silently; only a
83
+ * populated-but-malformed field surfaces an advisory.
84
+ *
85
+ * @returns the validation result when an envelope was parsed, or `null` when the
86
+ * prompt was not a structured envelope. Return value is for tests/telemetry;
87
+ * the side effect (advisory push) is the production purpose.
88
+ */
89
+ export declare function appendDelegationEnvelopeAdvisory(session: AgentSessionState, promptText: string, context: ValidationContext): EnvelopeValidationResult | null;
71
90
  interface MessageInfo {
72
91
  role: string;
73
92
  agent?: string;
@@ -38,8 +38,9 @@ export declare function enforceSpecDriftGate(directory: string | undefined, tool
38
38
  declare function extractErrorSignal(errorContent: unknown): string;
39
39
  /**
40
40
  * Redacts sensitive values from a shell command string before audit logging.
41
+ * Single-sourced from ./helpers (which additionally redacts home paths).
41
42
  */
42
- export declare function redactShellCommand(cmd: string): string;
43
+ export { redactShellCommand } from './helpers';
43
44
  /**
44
45
  * Creates guardrails hooks for circuit breaker protection
45
46
  * @param directory Working directory from plugin init context (required)
@@ -80,4 +81,3 @@ export declare function createGuardrailsHooks(directory: string, directoryOrConf
80
81
  }>;
81
82
  }) => Promise<void>;
82
83
  };
83
- export {};
@@ -12,6 +12,28 @@ export declare const DANGEROUS_COMMAND_PATTERNS: RegExp[];
12
12
  export declare const SECURITY_DEGRADING_PATTERNS: RegExp[];
13
13
  export declare const INVISIBLE_FORMAT_CHARS: RegExp;
14
14
  export declare const INJECTION_PATTERNS: RegExp[];
15
+ /**
16
+ * Layer-2 content-safety scan for a single string value. Runs the same
17
+ * *blocking* pattern sets {@link validateLesson}'s Layer-2 block uses —
18
+ * dangerous-command errors, security-degrading instructions, and
19
+ * prompt-injection patterns — over an arbitrary short string (e.g. a
20
+ * directive-action field value such as `triggers` / `required_actions` /
21
+ * `forbidden_actions` / `verification_checks`).
22
+ *
23
+ * Returns a blocking reason string on the first match, or `null` when the value
24
+ * is content-safe. Mirrors validateLesson's normalization exactly: NFKC +
25
+ * invisible-format-char stripping + whitespace collapse + lowercase for the
26
+ * dangerous/security scans, and tests the RAW string for the injection patterns
27
+ * (normalization would strip the very control/invisible characters those
28
+ * patterns are meant to detect).
29
+ *
30
+ * The warning-severity dangerous-command patterns are intentionally NOT applied
31
+ * here: validateLesson treats them as non-blocking warnings (they match benign
32
+ * shell snippets like backticks and `$(...)` that legitimately appear in
33
+ * actionable directives), so promoting them to hard rejections on field values
34
+ * would create false positives without a corresponding safety gain.
35
+ */
36
+ export declare function scanContentSafety(text: string): string | null;
15
37
  /**
16
38
  * Extract context words around each occurrence of a target word (single tokens within a window).
17
39
  * Context window is 3 words before and after, excluding the target word itself.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  import type { Plugin } from '@opencode-ai/plugin';
2
+ /**
3
+ * FIFO-cap a session-keyed Map to at most `max` entries, evicting oldest first.
4
+ * Values tracked by these maps are plain data (timestamps/usage snapshots), never
5
+ * timer handles, so eviction requires no clearInterval/clearTimeout. Exported for
6
+ * unit testing of the cap invariant; used by the heartbeat throttle path below.
7
+ */
8
+ export declare function capSessionMap<K, V>(map: Map<K, V>, max: number): void;
2
9
  declare const _default: {
3
10
  id: "opencode-swarm";
4
11
  server: Plugin;