@heretek-ai/epistemic-swarm 0.4.6 → 0.4.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 (74) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/cli.js +59 -7
  3. package/config/mcp_launcher.py +75 -48
  4. package/extensions/pi/index.js +25 -5
  5. package/install.sh +5 -5
  6. package/package.json +1 -1
  7. package/plugins/antigravity/plugin.json +1 -1
  8. package/plugins/gemini/gemini-extension.json +1 -1
  9. package/plugins/opencode/index.js +310 -134
  10. package/plugins/opencode/tui.js +7 -9
  11. package/plugins/research-cache/skills/research_cache/hasher.py +27 -9
  12. package/plugins/socratic-grilling/skills/grilling/socratic_tree.py +26 -18
  13. package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
  14. package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
  15. package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
  16. package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
  17. package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
  18. package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
  19. package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
  20. package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
  21. package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
  22. package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
  23. package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
  24. package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
  25. package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
  26. package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
  27. package/runner/auditor_engine.py +122 -102
  28. package/runner/claim_store.py +5 -4
  29. package/runner/living_dossiers.py +32 -10
  30. package/runner/mcp_protocol.py +1 -1
  31. package/runner/mcp_server.py +23 -11
  32. package/runner/path_safety.py +62 -0
  33. package/runner/pcrb.py +68 -39
  34. package/runner/pcrb_verify.py +82 -52
  35. package/runner/refinement.py +61 -47
  36. package/runner/research_swarm.py +61 -7
  37. package/runner/state_machine.py +9 -6
  38. package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
  39. package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
  40. package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
  41. package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
  42. package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
  43. package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
  44. package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
  45. package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
  46. package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
  47. package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
  48. package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
  49. package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
  50. package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
  51. package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
  52. package/runner/tests/test_mcp_server.py +0 -1
  53. package/runner/tests/test_opencode_ux.py +398 -33
  54. package/runner/tests/test_swarm.py +20 -9
  55. package/runner/tests/test_sweep_regressions.py +373 -0
  56. package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
  57. package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
  58. package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
  59. package/scripts/auction_experiment.py +38 -30
  60. package/scripts/bet1_advisory_spike.py +41 -25
  61. package/scripts/build_adapters.py +52 -37
  62. package/scripts/divergence_experiment.py +61 -31
  63. package/skills/brainstorming/scripts/brainstorm.py +3 -2
  64. package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
  65. package/skills/epistemic_search/scripts/fetch.py +0 -1
  66. package/skills/epistemic_search/scripts/search.py +202 -73
  67. package/skills/epistemic_search/scripts/webcache.py +1 -1
  68. package/skills/grilling/socratic_tree.py +26 -18
  69. package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  70. package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
  71. package/skills/research_cache/hasher.py +27 -9
  72. package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
  73. package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
  74. package/skills/swarm_config/configure.py +32 -28
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epistemic-swarm",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "High-Integrity Dialectic Research Agent Harness for Claude Code enforcing empirical evidence over parametric hallucination.",
5
5
  "author": {
6
6
  "name": "Heretek AI",
package/bin/cli.js CHANGED
@@ -5,9 +5,28 @@
5
5
  * High-Integrity Dialectic Research Harness for Claude Code, Pi, OMP, OpenCode V2, Gemini CLI, Codex CLI, AntiGravity.
6
6
  */
7
7
 
8
- const { spawnSync } = require('child_process');
9
- const path = require('path');
10
- const fs = require('fs');
8
+ const { spawnSync } = require('node:child_process');
9
+ const path = require('node:path');
10
+ const fs = require('node:fs');
11
+
12
+ function resolveBinary(name) {
13
+ const pathDirs = (process.env.PATH || '').split(path.delimiter);
14
+ for (const dir of pathDirs) {
15
+ if (!dir || dir === '.' || dir.startsWith('./') || dir.startsWith('../')) continue;
16
+ const full = path.join(dir, name);
17
+ try {
18
+ // Match Node's own PATH search: skip non-files and non-executables so a
19
+ // directory or a non-executable named `python3` earlier on PATH cannot
20
+ // shadow the real binary.
21
+ if (!fs.statSync(full).isFile()) continue;
22
+ fs.accessSync(full, fs.constants.X_OK);
23
+ return fs.realpathSync(full);
24
+ } catch {
25
+ /* not present, not a file, or not executable — keep searching */
26
+ }
27
+ }
28
+ return name;
29
+ }
11
30
 
12
31
  const PKG_ROOT = path.resolve(__dirname, '..');
13
32
  const args = process.argv.slice(2);
@@ -74,7 +93,7 @@ function runPython(scriptRelPath, extraArgs = []) {
74
93
  const scriptPath = path.join(PKG_ROOT, scriptRelPath);
75
94
  finalArgs = [scriptPath, ...extraArgs];
76
95
  }
77
- const result = spawnSync('python3', finalArgs, {
96
+ const result = spawnSync(resolveBinary('python3'), finalArgs, {
78
97
  stdio: 'inherit',
79
98
  cwd: process.cwd(),
80
99
  env: { ...process.env, PYTHONPATH: PKG_ROOT }
@@ -84,7 +103,7 @@ function runPython(scriptRelPath, extraArgs = []) {
84
103
 
85
104
  function runBash(scriptRelPath, extraArgs = []) {
86
105
  const scriptPath = path.join(PKG_ROOT, scriptRelPath);
87
- const result = spawnSync('bash', [scriptPath, ...extraArgs], {
106
+ const result = spawnSync(resolveBinary('bash'), [scriptPath, ...extraArgs], {
88
107
  stdio: 'inherit',
89
108
  cwd: process.cwd()
90
109
  });
@@ -186,18 +205,51 @@ switch (command) {
186
205
 
187
206
  case 'doctor': {
188
207
  console.log('🔍 Checking Epistemic Swarm Environment:\n');
189
- const claudeCheck = spawnSync('claude', ['--version'], { encoding: 'utf-8' });
208
+
209
+ let pkgVersion = 'unknown';
210
+ try {
211
+ pkgVersion = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version || 'unknown';
212
+ } catch { /* keep 'unknown' */ }
213
+ console.log(`✅ epistemic-swarm: ${pkgVersion}`);
214
+
215
+ const claudeCheck = spawnSync(resolveBinary('claude'), ['--version'], { encoding: 'utf-8' });
190
216
  if (claudeCheck.status === 0) {
191
217
  console.log(`✅ Claude Code: ${claudeCheck.stdout.trim()}`);
192
218
  } else {
193
219
  console.log('⚠️ Claude Code CLI not found in PATH (Install via npm i -g @anthropic-ai/claude-code)');
194
220
  }
195
221
 
196
- const pyCheck = spawnSync('python3', ['--version'], { encoding: 'utf-8' });
222
+ const pyCheck = spawnSync(resolveBinary('python3'), ['--version'], { encoding: 'utf-8' });
197
223
  console.log(`✅ Python: ${pyCheck.stdout ? pyCheck.stdout.trim() : 'python3 missing'}`);
198
224
 
199
225
  console.log(`✅ Node.js: ${process.version}`);
200
226
  console.log(`📁 Package Root: ${PKG_ROOT}`);
227
+
228
+ // opencode resolves this package as `@latest` into a per-fetch cache and
229
+ // keeps every generation. A session started against an old generation keeps
230
+ // running the old code until it is refreshed, so report what is cached.
231
+ try {
232
+ const cacheRoot = path.join(process.env.HOME || '', '.cache', 'opencode', 'npm', '@heretek-ai');
233
+ const pkgDir = path.join(cacheRoot, 'epistemic-swarm@latest');
234
+ if (fs.existsSync(pkgDir)) {
235
+ const generations = fs.readdirSync(pkgDir).filter((d) => /^\d+$/.test(d)).sort();
236
+ const newest = generations[generations.length - 1];
237
+ console.log(`\n🧩 OpenCode plugin cache: ${generations.length} generation(s) in ${pkgDir}`);
238
+ for (const g of generations.slice(-5)) {
239
+ const marker = g === newest ? ' <- newest' : '';
240
+ console.log(` - ${g}${marker}`);
241
+ }
242
+ if (generations.length > 1) {
243
+ console.log(' ⚠️ Multiple generations cached. If slash commands vanish or the plugin');
244
+ console.log(' misbehaves, the running session may hold an older one — refresh with');
245
+ console.log(' `opencode plugin update` (or clear the cache dir) and relaunch.');
246
+ }
247
+ } else {
248
+ console.log('\n🧩 OpenCode plugin cache: not present (plugin not yet fetched)');
249
+ }
250
+ } catch (err) {
251
+ console.log(`\n🧩 OpenCode plugin cache: could not inspect (${err.message})`);
252
+ }
201
253
  break;
202
254
  }
203
255
 
@@ -8,12 +8,11 @@ Safely detects API keys and endpoints:
8
8
  Prevents startup crashes and missing environment variable errors in Claude Code.
9
9
  """
10
10
 
11
- import sys
12
- import os
13
11
  import json
14
- import subprocess
12
+ import os
15
13
  import shutil
16
- from typing import Dict, Any, List
14
+ import sys
15
+ from typing import Any, Dict, List, Optional
17
16
 
18
17
  # Add parent dir to path for imports
19
18
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -21,32 +20,30 @@ PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
21
20
  if PROJECT_ROOT not in sys.path:
22
21
  sys.path.insert(0, PROJECT_ROOT)
23
22
 
23
+
24
24
  def get_config_val(key: str, default: str = "") -> str:
25
25
  """Retrieve config from env, userConfig, or defaults."""
26
26
  env_val = os.environ.get(key)
27
27
  if env_val:
28
28
  return env_val
29
- # Also check user_config format
30
29
  user_cfg_val = os.environ.get(f"CLAUDE_PLUGIN_OPTION_{key.upper()}")
31
30
  if user_cfg_val:
32
31
  return user_cfg_val
33
32
  return default
34
33
 
35
- def run_zero_key_mcp_server(service: str):
36
- """Run lightweight JSON-RPC MCP server handling search and fetch without API keys."""
34
+
35
+ def _build_brave_tools() -> list:
36
+ from runner.mcp_protocol import ToolSpec
37
37
  from skills.epistemic_search.scripts.search import search_duckduckgo
38
- from skills.epistemic_search.scripts.fetch import fetch_and_cache
39
- from runner.mcp_protocol import StdioJsonRpcServer, ToolSpec
40
38
 
41
- tools = []
42
- if service == "brave-search":
43
- def handle_brave_search(args: Dict[str, Any]) -> str:
44
- results = search_duckduckgo(
45
- query=args.get("query", ""), max_results=args.get("count", 10)
46
- )
47
- return json.dumps(results, indent=2)
39
+ def handle_brave_search(args: Dict[str, Any]) -> str:
40
+ results = search_duckduckgo(
41
+ query=args.get("query", ""), max_results=args.get("count", 10)
42
+ )
43
+ return json.dumps(results, indent=2)
48
44
 
49
- tools.append(ToolSpec(
45
+ return [
46
+ ToolSpec(
50
47
  name="brave_web_search",
51
48
  description="Web search with DuckDuckGo fallback (zero API key required). Returns verifiable search results with titles, URLs, and snippets.",
52
49
  input_schema={
@@ -59,12 +56,19 @@ def run_zero_key_mcp_server(service: str):
59
56
  },
60
57
  handler=handle_brave_search,
61
58
  aliases=("search",),
62
- ))
63
- elif service == "firecrawl":
64
- def handle_firecrawl_scrape(args: Dict[str, Any]) -> str:
65
- return fetch_and_cache(url=args.get("url", ""))
59
+ )
60
+ ]
61
+
62
+
63
+ def _build_firecrawl_tools() -> list:
64
+ from runner.mcp_protocol import ToolSpec
65
+ from skills.epistemic_search.scripts.fetch import fetch_and_cache
66
66
 
67
- tools.append(ToolSpec(
67
+ def handle_firecrawl_scrape(args: Dict[str, Any]) -> str:
68
+ return fetch_and_cache(url=args.get("url", ""))
69
+
70
+ return [
71
+ ToolSpec(
68
72
  name="firecrawl_scrape",
69
73
  description="Scrape and extract clean markdown with automatic content-addressed SHA-256 caching into .research/sources/.",
70
74
  input_schema={
@@ -76,7 +80,20 @@ def run_zero_key_mcp_server(service: str):
76
80
  },
77
81
  handler=handle_firecrawl_scrape,
78
82
  aliases=("scrape", "fetch"),
79
- ))
83
+ )
84
+ ]
85
+
86
+
87
+ def run_zero_key_mcp_server(service: str):
88
+ """Run lightweight JSON-RPC MCP server handling search and fetch without API keys."""
89
+ from runner.mcp_protocol import StdioJsonRpcServer
90
+
91
+ if service == "brave-search":
92
+ tools = _build_brave_tools()
93
+ elif service == "firecrawl":
94
+ tools = _build_firecrawl_tools()
95
+ else:
96
+ tools = []
80
97
 
81
98
  server = StdioJsonRpcServer(
82
99
  server_name=f"epistemic-{service}-fallback",
@@ -86,41 +103,51 @@ def run_zero_key_mcp_server(service: str):
86
103
  )
87
104
  server.serve_forever()
88
105
 
106
+
107
+ def _launch_brave(npx_bin: Optional[str]) -> None:
108
+ api_key = get_config_val("BRAVE_SEARCH_API_KEY")
109
+ if api_key and npx_bin:
110
+ os.environ["BRAVE_SEARCH_API_KEY"] = api_key
111
+ os.execvp(npx_bin, [npx_bin, "-y", "@modelcontextprotocol/server-brave-search"])
112
+ else:
113
+ run_zero_key_mcp_server("brave-search")
114
+
115
+
116
+ def _launch_firecrawl(npx_bin: Optional[str]) -> None:
117
+ api_key = get_config_val("FIRECRAWL_API_KEY")
118
+ api_url = get_config_val("FIRECRAWL_API_URL", "https://api.firecrawl.dev")
119
+ if api_key and npx_bin:
120
+ os.environ["FIRECRAWL_API_KEY"] = api_key
121
+ os.environ["FIRECRAWL_API_URL"] = api_url
122
+ os.execvp(npx_bin, [npx_bin, "-y", "firecrawl-mcp"])
123
+ else:
124
+ run_zero_key_mcp_server("firecrawl")
125
+
126
+
127
+ def _launch_searxng() -> None:
128
+ searxng_url = get_config_val("SEARXNG_URL", "http://localhost:8080")
129
+ searxng_script = os.path.join(SCRIPT_DIR, "searxng_mcp.py")
130
+ if os.path.exists(searxng_script):
131
+ os.environ["SEARXNG_URL"] = searxng_url
132
+ os.execvp("python3", ["python3", searxng_script])
133
+ else:
134
+ run_zero_key_mcp_server("searxng")
135
+
136
+
89
137
  def main():
90
138
  service = sys.argv[1] if len(sys.argv) > 1 else "brave-search"
91
139
  npx_bin = shutil.which("npx")
92
140
 
93
141
  if service == "brave-search":
94
- api_key = get_config_val("BRAVE_SEARCH_API_KEY")
95
- if api_key and npx_bin:
96
- os.environ["BRAVE_SEARCH_API_KEY"] = api_key
97
- os.execvp(npx_bin, [npx_bin, "-y", "@modelcontextprotocol/server-brave-search"])
98
- else:
99
- # Fall back to zero-key search proxy
100
- run_zero_key_mcp_server("brave-search")
101
-
142
+ _launch_brave(npx_bin)
102
143
  elif service == "firecrawl":
103
- api_key = get_config_val("FIRECRAWL_API_KEY")
104
- api_url = get_config_val("FIRECRAWL_API_URL", "https://api.firecrawl.dev")
105
- if api_key and npx_bin:
106
- os.environ["FIRECRAWL_API_KEY"] = api_key
107
- os.environ["FIRECRAWL_API_URL"] = api_url
108
- os.execvp(npx_bin, [npx_bin, "-y", "firecrawl-mcp"])
109
- else:
110
- # Fall back to zero-key readability fetcher + SHA-256 hasher
111
- run_zero_key_mcp_server("firecrawl")
112
-
144
+ _launch_firecrawl(npx_bin)
113
145
  elif service == "searxng":
114
- searxng_url = get_config_val("SEARXNG_URL", "http://localhost:8080")
115
- searxng_script = os.path.join(SCRIPT_DIR, "searxng_mcp.py")
116
- if os.path.exists(searxng_script):
117
- os.environ["SEARXNG_URL"] = searxng_url
118
- os.execvp("python3", ["python3", searxng_script])
119
- else:
120
- run_zero_key_mcp_server("searxng")
146
+ _launch_searxng()
121
147
  else:
122
148
  sys.stderr.write(f"Unknown service: {service}\n")
123
149
  sys.exit(1)
124
150
 
151
+
125
152
  if __name__ == "__main__":
126
153
  main()
@@ -12,19 +12,39 @@
12
12
  * not a duplicate tool implementation.
13
13
  */
14
14
 
15
- import { spawnSync } from 'child_process';
16
- import path from 'path';
17
- import { fileURLToPath } from 'url';
15
+ import { spawnSync } from 'node:child_process';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
18
19
 
19
20
  const __filename = fileURLToPath(import.meta.url);
20
21
  const __dirname = path.dirname(__filename);
21
22
  const PKG_ROOT = path.resolve(__dirname, '../..');
22
23
  const MCP_SERVER = path.join(PKG_ROOT, 'runner', 'mcp_server.py');
23
24
 
25
+ function resolveBinary(name) {
26
+ const pathDirs = (process.env.PATH || '').split(path.delimiter);
27
+ for (const dir of pathDirs) {
28
+ if (!dir || dir === '.' || dir.startsWith('./') || dir.startsWith('../')) continue;
29
+ const full = path.join(dir, name);
30
+ try {
31
+ // Match Node's own PATH search: skip non-files and non-executables so a
32
+ // directory or a non-executable named `python3` earlier on PATH cannot
33
+ // shadow the real binary.
34
+ if (!fs.statSync(full).isFile()) continue;
35
+ fs.accessSync(full, fs.constants.X_OK);
36
+ return fs.realpathSync(full);
37
+ } catch {
38
+ /* not present, not a file, or not executable — keep searching */
39
+ }
40
+ }
41
+ return name;
42
+ }
43
+
24
44
  /** Uniform dispatch: one-shot MCP call. Returns {ok, text}. */
25
45
  function callMcp(tool, args = {}, ctx = {}) {
26
46
  const res = spawnSync(
27
- 'python3',
47
+ resolveBinary('python3'),
28
48
  [MCP_SERVER, 'call', tool, JSON.stringify(args || {})],
29
49
  {
30
50
  encoding: 'utf-8',
@@ -154,7 +174,7 @@ export default function initPiExtension(pi) {
154
174
  ctx.output?.(`💡 [IUMBTEMS Brainstorm] Diverging on: "${objective}"...`);
155
175
  // Skill-local context scaffold (not a tool duplicate) -> then canonical tool.
156
176
  const scaffold = path.join(PKG_ROOT, 'skills/brainstorming/scripts/brainstorm.py');
157
- const pre = spawnSync('python3', [scaffold, '--objective', objective, '--show-context'], {
177
+ const pre = spawnSync(resolveBinary('python3'), [scaffold, '--objective', objective, '--show-context'], {
158
178
  encoding: 'utf-8',
159
179
  cwd: ctx.cwd || process.cwd()
160
180
  });
package/install.sh CHANGED
@@ -23,16 +23,16 @@ mkdir -p "$CLAUDE_DIR/skills"
23
23
  echo "🔗 Linking skills into $CLAUDE_DIR/skills/..."
24
24
  for skill in grilling research_cache epistemic_search swarm_config code_audit oss_scout brainstorming; do
25
25
  # legacy research-cache dir name kept as alias for older configs
26
- ln -sf "$REPO_DIR/skills/$skill" "$CLAUDE_DIR/skills/$skill"
26
+ ln -sfn "$REPO_DIR/skills/$skill" "$CLAUDE_DIR/skills/$skill"
27
27
  echo " - $CLAUDE_DIR/skills/$skill -> $REPO_DIR/skills/$skill"
28
28
  done
29
- if [ ! -e "$CLAUDE_DIR/skills/research-cache" ]; then
30
- ln -sf "$REPO_DIR/skills/research_cache" "$CLAUDE_DIR/skills/research-cache"
29
+ if [[ ! -e "$CLAUDE_DIR/skills/research-cache" ]]; then
30
+ ln -sfn "$REPO_DIR/skills/research_cache" "$CLAUDE_DIR/skills/research-cache"
31
31
  echo " - $CLAUDE_DIR/skills/research-cache -> $REPO_DIR/skills/research_cache (legacy alias)"
32
32
  fi
33
33
 
34
34
  # 3. Patch ~/.claude.json mcpServers non-destructively
35
- if [ -f "$CLAUDE_JSON" ]; then
35
+ if [[ -f "$CLAUDE_JSON" ]]; then
36
36
  echo "🔧 Merging research MCP servers into $CLAUDE_JSON..."
37
37
  python3 - <<EOF
38
38
  import json
@@ -77,7 +77,7 @@ fi
77
77
 
78
78
  # 4. Patch ~/.claude/settings.json
79
79
  SETTINGS_JSON="$CLAUDE_DIR/settings.json"
80
- if [ -f "$SETTINGS_JSON" ]; then
80
+ if [[ -f "$SETTINGS_JSON" ]]; then
81
81
  echo "🔧 Merging plugin settings into $SETTINGS_JSON..."
82
82
  python3 - <<EOF
83
83
  import json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heretek-ai/epistemic-swarm",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "IUMBTEMS: I Use My Brain To Express My Self — High-Integrity Dialectic Research Agent Harness for Claude Code, OpenCode V2, Pi, OMP (oh-my-pi), Gemini CLI, Codex CLI, and AntiGravity",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "epistemic-swarm",
4
- "version": "0.4.6",
4
+ "version": "0.4.8",
5
5
  "description": "IUMBTEMS Epistemic Swarm: dialectic research, code audit, OSS scout, and lateral brainstorming for AntiGravity.",
6
6
  "author": "Heretek AI",
7
7
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epistemic-swarm",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "IUMBTEMS Epistemic Swarm: dialectic research, code audit, OSS scout, and lateral brainstorming for Gemini CLI.",
5
5
  "mcpServers": {
6
6
  "brave-search": {