@heretek-ai/epistemic-swarm 0.4.8 → 0.6.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.
- package/.agents/skills/darkharvest/SKILL.md +13 -0
- package/.agents/skills/factory/SKILL.md +13 -0
- package/.omp/commands/darkharvest.md +11 -0
- package/.omp/commands/domainexpansion.md +9 -0
- package/.omp/commands/factory.md +9 -0
- package/README.md +13 -0
- package/bin/cli.js +35 -0
- package/config/opencode-snippet.json +101 -1
- package/extensions/pi/index.js +47 -1
- package/package.json +7 -3
- package/plugins/antigravity/skills/darkharvest/SKILL.md +13 -0
- package/plugins/antigravity/skills/factory/SKILL.md +13 -0
- package/plugins/codex/skills/darkharvest/SKILL.md +13 -0
- package/plugins/codex/skills/factory/SKILL.md +13 -0
- package/plugins/gemini/commands/darkharvest.toml +8 -0
- package/plugins/gemini/commands/domainexpansion.toml +7 -0
- package/plugins/gemini/commands/factory.toml +8 -0
- package/plugins/gemini/skills/darkharvest/SKILL.md +13 -0
- package/plugins/gemini/skills/factory/SKILL.md +13 -0
- package/plugins/opencode/index.js +84 -2
- package/prompts/agent_darkharvest.md +91 -0
- package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
- package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
- package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
- package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
- package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
- package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
- package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
- package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
- package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
- package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
- package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
- package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
- package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
- package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
- package/runner/mcp_server.py +202 -62
- package/runner/research_swarm.py +138 -16
- package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
- package/runner/tests/test_factory.py +199 -0
- package/runner/tests/test_mcp_server.py +80 -37
- package/runner/tests/test_opencode_ux.py +35 -8
- package/runner/tests/test_swarm.py +14 -0
- package/runner/tests/test_sweep_regressions.py +47 -0
- package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
- package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
- package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
- package/scripts/build_adapters.py +16 -2
- package/skills/darkharvest/SKILL.md +70 -0
- package/skills/darkharvest/scripts/harvest.py +252 -0
- package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
- package/skills/factory/SKILL.md +51 -0
- package/skills/factory/scripts/factory.py +212 -0
- package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
- package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
- package/skills/swarm_config/configure.py +12 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# AGENT DARKHARVEST: COMPETITOR TEARDOWN & CLEAN-ROOM HARVEST SPECIFICATION
|
|
2
|
+
|
|
3
|
+
You are the **Darkharvest Engine** within the IUMBTEMS harness. Your job is
|
|
4
|
+
product-level competitor teardown: given a local repo plus seed inspirations
|
|
5
|
+
(e.g. Paseo, OpenChambers) plus prompt guidance, find same-vein projects and
|
|
6
|
+
report what they do well, what white space exists in BOTH directions, and what
|
|
7
|
+
may be harvested per feature as `depend | vendor | clean-room-rebuild |
|
|
8
|
+
skip(reason)`.
|
|
9
|
+
|
|
10
|
+
Trigger: `/darkharvest <objective>` with `--seeds <urls>`.
|
|
11
|
+
Calibration: `temperature 0.2-0.4`, `top_p 0.9`, `max_iterations 2-3`.
|
|
12
|
+
Budget: per-competitor scan MUST respect `--max-repos`, `--per-repo-mb`,
|
|
13
|
+
`--per-repo-timeout`, and the harvest.py allowlist. NEVER full-dump without caps.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. SCOPE DECOMPOSITION (one scope per competitor)
|
|
18
|
+
|
|
19
|
+
The Orchestrator emits one scope per competitor (seeds + expanded adjacents,
|
|
20
|
+
capped at `--max-repos`, default 10, recommended 6 live). Direct competitors
|
|
21
|
+
and adjacent inspirations are labeled separately.
|
|
22
|
+
|
|
23
|
+
- `affirmative_targets`: harvest surface Alpha must prove (features, UX flows,
|
|
24
|
+
arch patterns, deps, data model).
|
|
25
|
+
- `adversarial_targets`: Beta must probe (license contamination, transitive
|
|
26
|
+
bloat, CVE/takeover surface, staleness, both-ways missing).
|
|
27
|
+
|
|
28
|
+
## 2. DIALECTIC ROLES
|
|
29
|
+
|
|
30
|
+
### Alpha — Harvest Proponent
|
|
31
|
+
For the assigned competitor: capability inventory (present / missing /
|
|
32
|
+
partial per capability), benchmark/API quotes, and per-feature harvest
|
|
33
|
+
proposals. Every cell needs `[VERIFIED: <hash>]` from
|
|
34
|
+
`.research/sources/<sha256>.md` or `file://<path>#L<start>-L<end>`.
|
|
35
|
+
|
|
36
|
+
### Beta — Red Team
|
|
37
|
+
License gate (LICENSE file + package metadata → SPDX), transitive weight,
|
|
38
|
+
CVE/takeover notes, staleness (`⚠️ STALE` when >12mo or solo-maintainer),
|
|
39
|
+
and white-space gaps BOTH ways (what competitor lacks that we own or could own
|
|
40
|
+
AND what we lack). Gates are warn-only: badge inline, never auto-skip.
|
|
41
|
+
|
|
42
|
+
## 3. LEGAL HARVEST RULE (FINAL)
|
|
43
|
+
|
|
44
|
+
- Permissive only (MIT, Apache-2.0, BSD-3-Clause, ISC) → `depend` or `vendor`.
|
|
45
|
+
- GPL / AGPL / UNKNOWN → `clean-room-rebuild` spec only. NEVER copy code.
|
|
46
|
+
- Workflows clonable; copy-text, UI assets, brand NEVER copied.
|
|
47
|
+
- Every `vendor` item emits an SPDX attribution block (license + upstream URL
|
|
48
|
+
+ files). Closed targets get metadata-only rows +
|
|
49
|
+
`[NEGATIVE_KNOWLEDGE: <query>]`, never a failed run.
|
|
50
|
+
|
|
51
|
+
## 4. OUTPUT SPECIFICATION
|
|
52
|
+
|
|
53
|
+
Per scope write `alpha_dossier.json` / `beta_dossier.json` with
|
|
54
|
+
`candidate_repositories[]` entries:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"repo_id": "DH-01",
|
|
59
|
+
"name": "owner/repo",
|
|
60
|
+
"url": "https://github.com/owner/repo",
|
|
61
|
+
"source_hash": "<sha256>",
|
|
62
|
+
"license": "Apache-2.0",
|
|
63
|
+
"license_risk": "SAFE | COPYLEFT_WARNING | PROHIBITIVE",
|
|
64
|
+
"harvest_policy": "depend-or-vendor | clean-room-rebuild-only",
|
|
65
|
+
"verdicts": [
|
|
66
|
+
{
|
|
67
|
+
"feature": "<capability>",
|
|
68
|
+
"verdict": "depend | vendor | clean-room-rebuild | skip",
|
|
69
|
+
"reason": "<evidence-backed>",
|
|
70
|
+
"effort": "S|M|L",
|
|
71
|
+
"impact": "1-5",
|
|
72
|
+
"differentiation": "1-5"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"warnings": ["⚠️ LICENSE: ...", "⚠️ STALE: ...", "⚠️ CVE: ..."],
|
|
76
|
+
"attribution": {"spdx": "Apache-2.0", "upstream": "<url>", "files": []}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Master synthesis (`darkharvest_report.md`): competitor × capability matrix +
|
|
81
|
+
white-space gaps + harvest backlog ranked Impact×Effort×Differentiation (top 5)
|
|
82
|
+
+ risks + epistemic audit totals. Reruns diff changed cells + new/removed
|
|
83
|
+
candidates.
|
|
84
|
+
|
|
85
|
+
## 5. HARD BANS
|
|
86
|
+
|
|
87
|
+
1. No writes outside `.research/`. No dependency installs.
|
|
88
|
+
2. No GPL/AGPL vendoring, no asset/brand copying, no pixel-clone guidance.
|
|
89
|
+
3. No unverified harvest verdicts. Metadata ranks; only VERIFIED harvests.
|
|
90
|
+
4. No unbounded expansion past `--max-repos` or per-repo caps without user
|
|
91
|
+
confirmation. Firecrawl missing → gap note, never abort.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/runner/mcp_server.py
CHANGED
|
@@ -44,6 +44,7 @@ SERVER_VERSION = "0.3.0"
|
|
|
44
44
|
# helpers
|
|
45
45
|
# ---------------------------------------------------------------------------
|
|
46
46
|
|
|
47
|
+
|
|
47
48
|
def _tool_text(payload: Any) -> str:
|
|
48
49
|
"""Normalize a handler return into the MCP text content block body."""
|
|
49
50
|
if isinstance(payload, str):
|
|
@@ -70,6 +71,7 @@ def _resolve_base_dir(args: Dict[str, Any]) -> Path:
|
|
|
70
71
|
raw = args.get("base_dir") or args.get("dir") or DEFAULT_RESEARCH_DIR
|
|
71
72
|
return Path(os.path.realpath(str(raw)))
|
|
72
73
|
|
|
74
|
+
|
|
73
75
|
def _handle_config(args: Dict[str, Any]) -> str:
|
|
74
76
|
"""Inspect or modify swarm parameters in .research/config.json."""
|
|
75
77
|
from skills.swarm_config.configure import (
|
|
@@ -82,7 +84,14 @@ def _handle_config(args: Dict[str, Any]) -> str:
|
|
|
82
84
|
cfg = load_config(base_dir)
|
|
83
85
|
|
|
84
86
|
updates = {}
|
|
85
|
-
for key in (
|
|
87
|
+
for key in (
|
|
88
|
+
"search_engine",
|
|
89
|
+
"engine",
|
|
90
|
+
"max_iterations",
|
|
91
|
+
"depth",
|
|
92
|
+
"mode",
|
|
93
|
+
"divergence_threshold",
|
|
94
|
+
):
|
|
86
95
|
if key in args and args[key] is not None:
|
|
87
96
|
canon = {
|
|
88
97
|
"engine": "search_engine",
|
|
@@ -94,7 +103,13 @@ def _handle_config(args: Dict[str, Any]) -> str:
|
|
|
94
103
|
if updates:
|
|
95
104
|
cfg.update(updates)
|
|
96
105
|
save_config(cfg, base_dir)
|
|
97
|
-
return _tool_text(
|
|
106
|
+
return _tool_text(
|
|
107
|
+
{
|
|
108
|
+
"status": "updated",
|
|
109
|
+
"config": cfg,
|
|
110
|
+
"written_to": str(Path(base_dir) / "config.json"),
|
|
111
|
+
}
|
|
112
|
+
)
|
|
98
113
|
|
|
99
114
|
return _tool_text({"status": "current", "config": cfg})
|
|
100
115
|
|
|
@@ -121,16 +136,21 @@ def _run_swarm_mode(mode: Optional[str], args: Dict[str, Any]) -> str:
|
|
|
121
136
|
runner.run_swarm(objective, frontier_file=Path(frontier) if frontier else None)
|
|
122
137
|
|
|
123
138
|
report = runner.base_dir / (
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
{
|
|
140
|
+
"audit": "code_audit_report.md",
|
|
141
|
+
"scout": "oss_scout_report.md",
|
|
142
|
+
"brainstorm": "brainstorm_report.md",
|
|
143
|
+
"darkharvest": "darkharvest_report.md",
|
|
144
|
+
}.get(mode or args.get("mode") or "research", "final_synthesis.md")
|
|
145
|
+
)
|
|
146
|
+
return _tool_text(
|
|
147
|
+
{
|
|
148
|
+
"status": "completed",
|
|
149
|
+
"mode": mode or args.get("mode") or "research",
|
|
150
|
+
"report": str(report),
|
|
151
|
+
"log": buf.getvalue()[-4000:],
|
|
152
|
+
}
|
|
127
153
|
)
|
|
128
|
-
return _tool_text({
|
|
129
|
-
"status": "completed",
|
|
130
|
-
"mode": mode or args.get("mode") or "research",
|
|
131
|
-
"report": str(report),
|
|
132
|
-
"log": buf.getvalue()[-4000:],
|
|
133
|
-
})
|
|
134
154
|
|
|
135
155
|
|
|
136
156
|
def _handle_swarm_research(args: Dict[str, Any]) -> str:
|
|
@@ -153,23 +173,32 @@ def _handle_brainstorm(args: Dict[str, Any]) -> str:
|
|
|
153
173
|
return _run_swarm_mode("brainstorm", args)
|
|
154
174
|
|
|
155
175
|
|
|
176
|
+
def _handle_darkharvest(args: Dict[str, Any]) -> str:
|
|
177
|
+
"""Run product competitor teardown (seed + expand, harvest verdicts)."""
|
|
178
|
+
return _run_swarm_mode("darkharvest", args)
|
|
179
|
+
|
|
180
|
+
|
|
156
181
|
def _handle_verify_quote(args: Dict[str, Any]) -> str:
|
|
157
182
|
"""Verify a verbatim quote against the content-addressed source cache."""
|
|
158
183
|
from skills.research_cache.hasher import SourceHasher
|
|
159
184
|
|
|
160
|
-
content_hash =
|
|
185
|
+
content_hash = (
|
|
186
|
+
args.get("hash") or args.get("content_hash") or args.get("source_hash")
|
|
187
|
+
)
|
|
161
188
|
quote = args.get("quote")
|
|
162
189
|
if not content_hash or quote is None:
|
|
163
190
|
raise ValueError("hash and quote are required")
|
|
164
191
|
|
|
165
192
|
hasher = SourceHasher(_resolve_base_dir(args))
|
|
166
193
|
is_verified, confidence, message = hasher.verify_quote(content_hash, quote)
|
|
167
|
-
return _tool_text(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
return _tool_text(
|
|
195
|
+
{
|
|
196
|
+
"verified": bool(is_verified),
|
|
197
|
+
"confidence": float(confidence),
|
|
198
|
+
"message": message,
|
|
199
|
+
"content_hash": content_hash,
|
|
200
|
+
}
|
|
201
|
+
)
|
|
173
202
|
|
|
174
203
|
|
|
175
204
|
def _handle_report_retraction(args: Dict[str, Any]) -> str:
|
|
@@ -213,13 +242,15 @@ def _handle_set_domain_pack(args: Dict[str, Any]) -> str:
|
|
|
213
242
|
cfg = load_config(base_dir)
|
|
214
243
|
cfg["domain_pack"] = pack
|
|
215
244
|
save_config(cfg, base_dir)
|
|
216
|
-
return _tool_text(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
245
|
+
return _tool_text(
|
|
246
|
+
{
|
|
247
|
+
"status": "activated",
|
|
248
|
+
"domain_pack": pack,
|
|
249
|
+
"accept_threshold": constitution.accept_threshold,
|
|
250
|
+
"retraction_policy": constitution.retraction_policy,
|
|
251
|
+
"banned_domains": constitution.banned_domains,
|
|
252
|
+
}
|
|
253
|
+
)
|
|
223
254
|
|
|
224
255
|
|
|
225
256
|
def _handle_export_brief(args: Dict[str, Any]) -> str:
|
|
@@ -284,7 +315,9 @@ def _handle_socratic_frontier(args: Dict[str, Any]) -> str:
|
|
|
284
315
|
"""Advance or inspect the Socratic decision-tree frontier."""
|
|
285
316
|
from skills.grilling.socratic_tree import DesignTree
|
|
286
317
|
|
|
287
|
-
file_path = Path(
|
|
318
|
+
file_path = Path(
|
|
319
|
+
args.get("file") or args.get("frontier_file") or ".research/frontier.json"
|
|
320
|
+
)
|
|
288
321
|
|
|
289
322
|
if args.get("settle"):
|
|
290
323
|
node_id, answer = args["settle"][0], args["settle"][1]
|
|
@@ -294,32 +327,41 @@ def _handle_socratic_frontier(args: Dict[str, Any]) -> str:
|
|
|
294
327
|
tree = DesignTree(objective=args.get("objective", ""))
|
|
295
328
|
tree.settle_node(node_id, answer)
|
|
296
329
|
tree.export_frontier_json(file_path)
|
|
297
|
-
return _tool_text(
|
|
330
|
+
return _tool_text(
|
|
331
|
+
{"status": "settled", "node_id": node_id, "file": str(file_path)}
|
|
332
|
+
)
|
|
298
333
|
|
|
299
334
|
if not file_path.exists():
|
|
300
|
-
return _tool_text(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
335
|
+
return _tool_text(
|
|
336
|
+
{
|
|
337
|
+
"status": "no_frontier",
|
|
338
|
+
"message": f"No frontier file at {file_path}. Run /grilling first.",
|
|
339
|
+
"file": str(file_path),
|
|
340
|
+
}
|
|
341
|
+
)
|
|
305
342
|
|
|
306
343
|
tree = DesignTree.load_from_json(file_path)
|
|
307
344
|
frontier = tree.compute_frontier()
|
|
308
|
-
return _tool_text(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
345
|
+
return _tool_text(
|
|
346
|
+
{
|
|
347
|
+
"status": "frontier",
|
|
348
|
+
"objective": tree.objective,
|
|
349
|
+
"is_complete": tree.is_complete(),
|
|
350
|
+
"total_nodes": len(tree.nodes),
|
|
351
|
+
"settled": sum(1 for n in tree.nodes.values() if n.is_settled()),
|
|
352
|
+
"frontier": [
|
|
353
|
+
{"node_id": n.node_id, "question": n.question} for n in frontier
|
|
354
|
+
],
|
|
355
|
+
"file": str(file_path),
|
|
356
|
+
}
|
|
357
|
+
)
|
|
317
358
|
|
|
318
359
|
|
|
319
360
|
# ---------------------------------------------------------------------------
|
|
320
361
|
# registry
|
|
321
362
|
# ---------------------------------------------------------------------------
|
|
322
363
|
|
|
364
|
+
|
|
323
365
|
def build_tools() -> List[ToolSpec]:
|
|
324
366
|
"""The 7 canonical iumbtems_* tools, in-process wrappers only."""
|
|
325
367
|
return [
|
|
@@ -329,10 +371,29 @@ def build_tools() -> List[ToolSpec]:
|
|
|
329
371
|
input_schema={
|
|
330
372
|
"type": "object",
|
|
331
373
|
"properties": {
|
|
332
|
-
"base_dir": {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
374
|
+
"base_dir": {
|
|
375
|
+
"type": "string",
|
|
376
|
+
"description": "Path to .research workspace (default .research)",
|
|
377
|
+
},
|
|
378
|
+
"search_engine": {
|
|
379
|
+
"type": "string",
|
|
380
|
+
"enum": ["duckduckgo", "brave", "firecrawl", "searxng"],
|
|
381
|
+
},
|
|
382
|
+
"max_iterations": {
|
|
383
|
+
"type": "integer",
|
|
384
|
+
"description": "Dialectic depth 1-4",
|
|
385
|
+
},
|
|
386
|
+
"mode": {
|
|
387
|
+
"type": "string",
|
|
388
|
+
"enum": [
|
|
389
|
+
"research",
|
|
390
|
+
"audit",
|
|
391
|
+
"scout",
|
|
392
|
+
"hybrid",
|
|
393
|
+
"brainstorm",
|
|
394
|
+
"darkharvest",
|
|
395
|
+
],
|
|
396
|
+
},
|
|
336
397
|
"divergence_threshold": {"type": "number"},
|
|
337
398
|
},
|
|
338
399
|
},
|
|
@@ -344,12 +405,24 @@ def build_tools() -> List[ToolSpec]:
|
|
|
344
405
|
input_schema={
|
|
345
406
|
"type": "object",
|
|
346
407
|
"properties": {
|
|
347
|
-
"objective": {
|
|
408
|
+
"objective": {
|
|
409
|
+
"type": "string",
|
|
410
|
+
"description": "Research question or objective",
|
|
411
|
+
},
|
|
348
412
|
"base_dir": {"type": "string"},
|
|
349
|
-
"frontier_file": {
|
|
350
|
-
|
|
413
|
+
"frontier_file": {
|
|
414
|
+
"type": "string",
|
|
415
|
+
"description": "Settled frontier.json from grilling",
|
|
416
|
+
},
|
|
417
|
+
"engine": {
|
|
418
|
+
"type": "string",
|
|
419
|
+
"enum": ["duckduckgo", "brave", "firecrawl", "searxng"],
|
|
420
|
+
},
|
|
351
421
|
"depth": {"type": "integer"},
|
|
352
|
-
"mock_claude": {
|
|
422
|
+
"mock_claude": {
|
|
423
|
+
"type": "boolean",
|
|
424
|
+
"description": "Synthetic run, zero API cost",
|
|
425
|
+
},
|
|
353
426
|
},
|
|
354
427
|
"required": ["objective"],
|
|
355
428
|
},
|
|
@@ -361,7 +434,10 @@ def build_tools() -> List[ToolSpec]:
|
|
|
361
434
|
input_schema={
|
|
362
435
|
"type": "object",
|
|
363
436
|
"properties": {
|
|
364
|
-
"target": {
|
|
437
|
+
"target": {
|
|
438
|
+
"type": "string",
|
|
439
|
+
"description": "Codebase target or audit focus",
|
|
440
|
+
},
|
|
365
441
|
"base_dir": {"type": "string"},
|
|
366
442
|
"mock_claude": {"type": "boolean"},
|
|
367
443
|
},
|
|
@@ -375,7 +451,10 @@ def build_tools() -> List[ToolSpec]:
|
|
|
375
451
|
input_schema={
|
|
376
452
|
"type": "object",
|
|
377
453
|
"properties": {
|
|
378
|
-
"feature": {
|
|
454
|
+
"feature": {
|
|
455
|
+
"type": "string",
|
|
456
|
+
"description": "Feature or library to scout",
|
|
457
|
+
},
|
|
379
458
|
"base_dir": {"type": "string"},
|
|
380
459
|
"mock_claude": {"type": "boolean"},
|
|
381
460
|
},
|
|
@@ -389,7 +468,10 @@ def build_tools() -> List[ToolSpec]:
|
|
|
389
468
|
input_schema={
|
|
390
469
|
"type": "object",
|
|
391
470
|
"properties": {
|
|
392
|
-
"objective": {
|
|
471
|
+
"objective": {
|
|
472
|
+
"type": "string",
|
|
473
|
+
"description": "Ambiguous prompt to ideate on",
|
|
474
|
+
},
|
|
393
475
|
"base_dir": {"type": "string"},
|
|
394
476
|
"mock_claude": {"type": "boolean"},
|
|
395
477
|
},
|
|
@@ -397,14 +479,47 @@ def build_tools() -> List[ToolSpec]:
|
|
|
397
479
|
},
|
|
398
480
|
handler=_handle_brainstorm,
|
|
399
481
|
),
|
|
482
|
+
ToolSpec(
|
|
483
|
+
name="iumbtems_darkharvest",
|
|
484
|
+
description="Product competitor teardown: seed inspirations plus prompt, expand to adjacents, emit per-feature depend/vendor/clean-room/skip verdicts with SPDX attribution. Permissive-only vendoring; GPL/AGPL spec-rebuild only.",
|
|
485
|
+
input_schema={
|
|
486
|
+
"type": "object",
|
|
487
|
+
"properties": {
|
|
488
|
+
"objective": {
|
|
489
|
+
"type": "string",
|
|
490
|
+
"description": "Product arena to tear down (e.g. Paseo-class agent harness competitor)",
|
|
491
|
+
},
|
|
492
|
+
"seeds": {
|
|
493
|
+
"type": "array",
|
|
494
|
+
"items": {"type": "string"},
|
|
495
|
+
"description": "Seed inspiration repo URLs",
|
|
496
|
+
},
|
|
497
|
+
"maxRepos": {
|
|
498
|
+
"type": "integer",
|
|
499
|
+
"description": "Cap on total competitors (default 10)",
|
|
500
|
+
},
|
|
501
|
+
"depth": {"type": "integer", "description": "Dialectic depth 1-4"},
|
|
502
|
+
"base_dir": {"type": "string"},
|
|
503
|
+
"mock_claude": {"type": "boolean"},
|
|
504
|
+
},
|
|
505
|
+
"required": ["objective"],
|
|
506
|
+
},
|
|
507
|
+
handler=_handle_darkharvest,
|
|
508
|
+
),
|
|
400
509
|
ToolSpec(
|
|
401
510
|
name="iumbtems_verify_quote",
|
|
402
511
|
description="Audit a verbatim citation against the SHA-256 source cache (.research/sources/<hash>.md).",
|
|
403
512
|
input_schema={
|
|
404
513
|
"type": "object",
|
|
405
514
|
"properties": {
|
|
406
|
-
"hash": {
|
|
407
|
-
|
|
515
|
+
"hash": {
|
|
516
|
+
"type": "string",
|
|
517
|
+
"description": "Content-addressed SHA-256 of the cached source",
|
|
518
|
+
},
|
|
519
|
+
"quote": {
|
|
520
|
+
"type": "string",
|
|
521
|
+
"description": "Verbatim quote to verify",
|
|
522
|
+
},
|
|
408
523
|
"base_dir": {"type": "string"},
|
|
409
524
|
},
|
|
410
525
|
"required": ["hash", "quote"],
|
|
@@ -417,7 +532,10 @@ def build_tools() -> List[ToolSpec]:
|
|
|
417
532
|
input_schema={
|
|
418
533
|
"type": "object",
|
|
419
534
|
"properties": {
|
|
420
|
-
"base_dir": {
|
|
535
|
+
"base_dir": {
|
|
536
|
+
"type": "string",
|
|
537
|
+
"description": "Path to .research workspace",
|
|
538
|
+
},
|
|
421
539
|
},
|
|
422
540
|
},
|
|
423
541
|
handler=_handle_reindex_claims,
|
|
@@ -428,9 +546,15 @@ def build_tools() -> List[ToolSpec]:
|
|
|
428
546
|
input_schema={
|
|
429
547
|
"type": "object",
|
|
430
548
|
"properties": {
|
|
431
|
-
"hash": {
|
|
549
|
+
"hash": {
|
|
550
|
+
"type": "string",
|
|
551
|
+
"description": "Content-addressed SHA-256 of the affected source",
|
|
552
|
+
},
|
|
432
553
|
"event": {"type": "string", "enum": ["RETRACTED", "REVISED"]},
|
|
433
|
-
"note": {
|
|
554
|
+
"note": {
|
|
555
|
+
"type": "string",
|
|
556
|
+
"description": "Why the source was retracted/revised",
|
|
557
|
+
},
|
|
434
558
|
"base_dir": {"type": "string"},
|
|
435
559
|
},
|
|
436
560
|
"required": ["hash", "event"],
|
|
@@ -459,7 +583,10 @@ def build_tools() -> List[ToolSpec]:
|
|
|
459
583
|
input_schema={
|
|
460
584
|
"type": "object",
|
|
461
585
|
"properties": {
|
|
462
|
-
"pack": {
|
|
586
|
+
"pack": {
|
|
587
|
+
"type": "string",
|
|
588
|
+
"description": "Pack id: biopharma | quant | legal (or a path)",
|
|
589
|
+
},
|
|
463
590
|
"base_dir": {"type": "string"},
|
|
464
591
|
},
|
|
465
592
|
"required": ["pack"],
|
|
@@ -473,9 +600,15 @@ def build_tools() -> List[ToolSpec]:
|
|
|
473
600
|
"type": "object",
|
|
474
601
|
"properties": {
|
|
475
602
|
"base_dir": {"type": "string"},
|
|
476
|
-
"out": {
|
|
603
|
+
"out": {
|
|
604
|
+
"type": "string",
|
|
605
|
+
"description": "Output path (default <base_dir>/brief.pcrb.json)",
|
|
606
|
+
},
|
|
477
607
|
"objective": {"type": "string"},
|
|
478
|
-
"key": {
|
|
608
|
+
"key": {
|
|
609
|
+
"type": "string",
|
|
610
|
+
"description": "HMAC key (prefer key_file or env IUMBTEMS_PCRB_KEY)",
|
|
611
|
+
},
|
|
479
612
|
"key_file": {"type": "string"},
|
|
480
613
|
},
|
|
481
614
|
},
|
|
@@ -487,7 +620,10 @@ def build_tools() -> List[ToolSpec]:
|
|
|
487
620
|
input_schema={
|
|
488
621
|
"type": "object",
|
|
489
622
|
"properties": {
|
|
490
|
-
"brief": {
|
|
623
|
+
"brief": {
|
|
624
|
+
"type": "string",
|
|
625
|
+
"description": "Path to brief.pcrb.json",
|
|
626
|
+
},
|
|
491
627
|
"key": {"type": "string"},
|
|
492
628
|
"key_file": {"type": "string"},
|
|
493
629
|
},
|
|
@@ -529,13 +665,17 @@ def build_server() -> StdioJsonRpcServer:
|
|
|
529
665
|
# entry points
|
|
530
666
|
# ---------------------------------------------------------------------------
|
|
531
667
|
|
|
668
|
+
|
|
532
669
|
def call_once(tool_name: str, arguments: Dict[str, Any]) -> int:
|
|
533
670
|
"""One-shot dispatch: run a single tool and print its text payload."""
|
|
534
671
|
server = build_server()
|
|
535
672
|
spec = server._by_name.get(tool_name)
|
|
536
673
|
if spec is None:
|
|
537
674
|
json.dump(
|
|
538
|
-
{
|
|
675
|
+
{
|
|
676
|
+
"error": f"Tool {tool_name} not found",
|
|
677
|
+
"available": [t.name for t in server.tools],
|
|
678
|
+
},
|
|
539
679
|
sys.stdout,
|
|
540
680
|
indent=2,
|
|
541
681
|
)
|