@heretek-ai/epistemic-swarm 0.6.0 → 0.7.2
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/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +4 -2
- package/MARKETPLACE.md +28 -0
- package/bin/cli.js +1 -0
- package/extensions/pi/index.js +7 -1
- package/install.sh +1 -1
- package/package.json +1 -1
- package/plugins/darkharvest/.claude-plugin/plugin.json +15 -0
- package/plugins/darkharvest/agents/harvest-proponent.md +22 -0
- package/plugins/darkharvest/agents/harvest-redteam.md +20 -0
- package/plugins/darkharvest/evals/teardown-verdict/graders/license-line.md +6 -0
- package/plugins/darkharvest/evals/teardown-verdict/graders/skill-fired.md +5 -0
- package/plugins/darkharvest/evals/teardown-verdict/prompt.md +6 -0
- package/plugins/darkharvest/skills/darkharvest/SKILL.md +70 -0
- package/plugins/darkharvest/skills/darkharvest/scripts/harvest.py +289 -0
- package/plugins/factory/.claude-plugin/plugin.json +15 -0
- package/plugins/factory/agents/factory-manager.md +22 -0
- package/plugins/factory/agents/programmer.md +16 -0
- package/plugins/factory/agents/qa-adversarial.md +17 -0
- package/plugins/factory/agents/qa-functional.md +17 -0
- package/plugins/factory/evals/gate-halt/graders/gates-first.md +6 -0
- package/plugins/factory/evals/gate-halt/graders/skill-fired.md +5 -0
- package/plugins/factory/evals/gate-halt/prompt.md +6 -0
- package/plugins/factory/skills/factory/SKILL.md +51 -0
- package/plugins/factory/skills/factory/scripts/factory.py +212 -0
- package/plugins/opencode/index.js +41 -1
- package/plugins/opencode/tui.js +66 -38
- 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/claim_store.py +15 -4
- package/runner/living_dossiers.py +74 -56
- package/runner/mcp_server.py +37 -2
- package/runner/pcrb.py +53 -29
- package/runner/refinement.py +70 -27
- package/runner/research_swarm.py +336 -127
- package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_backends.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_claude_plugin.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_backends.py +280 -0
- package/runner/tests/test_claude_plugin.py +170 -0
- package/runner/tests/test_sweep_regressions.py +71 -38
- 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 +87 -9
- package/skills/darkharvest/scripts/harvest.py +76 -39
- package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
- package/skills/epistemic_search/scripts/search.py +29 -19
- package/skills/epistemic_search/scripts/webcache.py +29 -17
- package/skills/factory/scripts/factory.py +1 -1
- 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 +70 -37
|
@@ -36,6 +36,26 @@
|
|
|
36
36
|
"category": "research",
|
|
37
37
|
"source": "./plugins/research-cache",
|
|
38
38
|
"homepage": "https://github.com/Heretek-AI/IUMBTEMS"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "darkharvest",
|
|
42
|
+
"description": "Product-level competitor teardown and clean-room harvest engine.",
|
|
43
|
+
"author": {
|
|
44
|
+
"name": "Heretek AI"
|
|
45
|
+
},
|
|
46
|
+
"category": "research",
|
|
47
|
+
"source": "./plugins/darkharvest",
|
|
48
|
+
"homepage": "https://github.com/Heretek-AI/IUMBTEMS"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "factory",
|
|
52
|
+
"description": "Coding-factory Manager loop with grill-gated phased builds and dual QA.",
|
|
53
|
+
"author": {
|
|
54
|
+
"name": "Heretek AI"
|
|
55
|
+
},
|
|
56
|
+
"category": "agents",
|
|
57
|
+
"source": "./plugins/factory",
|
|
58
|
+
"homepage": "https://github.com/Heretek-AI/IUMBTEMS"
|
|
39
59
|
}
|
|
40
60
|
]
|
|
41
61
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epistemic-swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.2",
|
|
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",
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
"./skills/swarm_config",
|
|
70
70
|
"./skills/code_audit",
|
|
71
71
|
"./skills/oss_scout",
|
|
72
|
-
"./skills/brainstorming"
|
|
72
|
+
"./skills/brainstorming",
|
|
73
|
+
"./skills/darkharvest",
|
|
74
|
+
"./skills/factory"
|
|
73
75
|
]
|
|
74
76
|
}
|
package/MARKETPLACE.md
CHANGED
|
@@ -30,6 +30,8 @@ claude plugin marketplace list
|
|
|
30
30
|
| **`epistemic-swarm`** | `research` | **Flagship Swarm Harness**: Multi-agent dialectic research harness pairing Agent Alpha (Thesis) against Agent Beta (Red Team), audited by an Epistemic Auditor with verbatim empirical quote validation. | `claude plugin install epistemic-swarm@heretek-official` |
|
|
31
31
|
| **`socratic-grilling`** | `agents` | **Socratic Ideation**: Matt Pocock-style Socratic interrogation, premise inversion, and lateral exploration skill. | `claude plugin install socratic-grilling@heretek-official` |
|
|
32
32
|
| **`research-cache`** | `research` | **Source Hasher**: Content-addressed SHA256 Markdown source hashing and verbatim quote verification engine. | `claude plugin install research-cache@heretek-official` |
|
|
33
|
+
| **`darkharvest`** | `research` | **Competitor Teardown**: Product-level teardown with per-feature `depend\|vendor\|clean-room\|skip` verdicts and SPDX attribution. | `claude plugin install darkharvest@heretek-official` |
|
|
34
|
+
| **`factory`** | `agents` | **Coding Factory**: Manager loop with grill-gated phases, programmer spawns, and dual QA. | `claude plugin install factory@heretek-official` |
|
|
33
35
|
|
|
34
36
|
---
|
|
35
37
|
|
|
@@ -72,6 +74,32 @@ claude plugin marketplace list
|
|
|
72
74
|
claude plugin install research-cache@heretek-official
|
|
73
75
|
```
|
|
74
76
|
|
|
77
|
+
### 4. `darkharvest` (Modular Tool)
|
|
78
|
+
- **Manifest**: [`plugins/darkharvest/.claude-plugin/plugin.json`](file:///plugins/darkharvest/.claude-plugin/plugin.json)
|
|
79
|
+
- **Components**:
|
|
80
|
+
- **Skill**: `skills/darkharvest` (competitor × capability teardown).
|
|
81
|
+
- **Agents**: `harvest-proponent` (per-feature verdicts), `harvest-redteam` (license/Bloat/CVE vetting).
|
|
82
|
+
- **Evals**: `plugins/darkharvest/evals/teardown-verdict` (skill fires + license-line rubric).
|
|
83
|
+
- **Install**:
|
|
84
|
+
```bash
|
|
85
|
+
claude plugin install darkharvest@heretek-official
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 5. `factory` (Modular Agent Pack)
|
|
89
|
+
- **Manifest**: [`plugins/factory/.claude-plugin/plugin.json`](file:///plugins/factory/.claude-plugin/plugin.json)
|
|
90
|
+
- **Components**:
|
|
91
|
+
- **Skill**: `skills/factory` (Manager loop, gates, QA bounds).
|
|
92
|
+
- **Agents**: `factory-manager`, `programmer`, `qa-functional`, `qa-adversarial`.
|
|
93
|
+
- **Evals**: `plugins/factory/evals/gate-halt` (skill fires + gates-first rubric).
|
|
94
|
+
- **Install**:
|
|
95
|
+
```bash
|
|
96
|
+
claude plugin install factory@heretek-official
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Flagship agents & evals
|
|
100
|
+
- **Agents** (repo-root `agents/`): `alpha-thesis`, `beta-antithesis`, `epistemic-auditor` — condensed from `prompts/agent_alpha_thesis.md`, `prompts/agent_beta_antithesis.md`, `prompts/epistemic_auditor.md`.
|
|
101
|
+
- **Evals** (repo-root `evals/`): `grill-fires`, `darkharvest-fires`, `factory-gate` — each `prompt.md` plus `tool_used: Skill` and `llm` graders. Run `claude plugin eval .` (billable model calls); CI gates via `.github/workflows/plugin-evals.yml` on release/dispatch.
|
|
102
|
+
|
|
75
103
|
---
|
|
76
104
|
|
|
77
105
|
## 💡 Lateral Brainstorming (`/brainstorming`)
|
package/bin/cli.js
CHANGED
|
@@ -74,6 +74,7 @@ Options:
|
|
|
74
74
|
--mode <mode> Operating mode (research, audit, scout, hybrid, brainstorm)
|
|
75
75
|
--engine <engine> Search engine (duckduckgo, brave, firecrawl, searxng)
|
|
76
76
|
--depth <n> Max dialectic iterations (1-4)
|
|
77
|
+
--backend <b> Agent runtime: auto (host-native), claude, opencode
|
|
77
78
|
--dir <path> Path to .research workspace directory (default: .research)
|
|
78
79
|
|
|
79
80
|
Examples:
|
package/extensions/pi/index.js
CHANGED
|
@@ -49,7 +49,13 @@ function callMcp(tool, args = {}, ctx = {}) {
|
|
|
49
49
|
{
|
|
50
50
|
encoding: 'utf-8',
|
|
51
51
|
cwd: ctx.cwd || process.cwd(),
|
|
52
|
-
|
|
52
|
+
// Project root: evidence defaults under <project>/.research instead
|
|
53
|
+
// of the server process cwd.
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
PYTHONPATH: PKG_ROOT,
|
|
57
|
+
IUMBTEMS_PROJECT_DIR: ctx.cwd || process.cwd(),
|
|
58
|
+
},
|
|
53
59
|
}
|
|
54
60
|
);
|
|
55
61
|
return {
|
package/install.sh
CHANGED
|
@@ -21,7 +21,7 @@ echo "✅ Core prerequisites detected (Python $(python3 --version | cut -d' ' -f
|
|
|
21
21
|
mkdir -p "$CLAUDE_DIR/skills"
|
|
22
22
|
|
|
23
23
|
echo "🔗 Linking skills into $CLAUDE_DIR/skills/..."
|
|
24
|
-
for skill in grilling research_cache epistemic_search swarm_config code_audit oss_scout brainstorming; do
|
|
24
|
+
for skill in grilling research_cache epistemic_search swarm_config code_audit oss_scout brainstorming darkharvest factory; do
|
|
25
25
|
# legacy research-cache dir name kept as alias for older configs
|
|
26
26
|
ln -sfn "$REPO_DIR/skills/$skill" "$CLAUDE_DIR/skills/$skill"
|
|
27
27
|
echo " - $CLAUDE_DIR/skills/$skill -> $REPO_DIR/skills/$skill"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heretek-ai/epistemic-swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.2",
|
|
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": {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "darkharvest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Product-level competitor teardown and clean-room harvest engine for Claude Code.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Heretek AI",
|
|
7
|
+
"email": "dev@heretek.ai"
|
|
8
|
+
},
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"repository": "https://github.com/Heretek-AI/IUMBTEMS",
|
|
11
|
+
"homepage": "https://github.com/Heretek-AI/IUMBTEMS#readme",
|
|
12
|
+
"skills": [
|
|
13
|
+
"./skills/darkharvest"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harvest-proponent
|
|
3
|
+
description: Competitor-teardown proponent. Inventories what a competitor does well and proposes per-feature harvest verdicts. Use per competitor scope in darkharvest runs.
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the Harvest Proponent in the darkharvest plugin.
|
|
8
|
+
|
|
9
|
+
Per assigned competitor: build the capability inventory (present / missing /
|
|
10
|
+
partial per capability with `[VERIFIED: <hash>]` evidence from
|
|
11
|
+
`.research/sources/<sha256>.md`), then propose per-feature verdicts:
|
|
12
|
+
`depend | vendor | clean-room-rebuild | skip(reason)`, ranked by
|
|
13
|
+
Impact x Effort x Differentiation.
|
|
14
|
+
|
|
15
|
+
Legal guardrails (final): permissive licenses only (MIT, Apache-2.0, BSD, ISC)
|
|
16
|
+
may be `depend`/`vendor`. GPL / AGPL / UNKNOWN license means
|
|
17
|
+
`clean-room-rebuild` spec only — never copy code. Workflows clonable;
|
|
18
|
+
copy-text, UI assets, and brand are never copied. Every `vendor` item emits an
|
|
19
|
+
SPDX attribution block (license + upstream URL + files). Closed targets get
|
|
20
|
+
metadata-only rows plus `[NEGATIVE_KNOWLEDGE: <query>]`, never a failed run.
|
|
21
|
+
|
|
22
|
+
Full specification: `skills/darkharvest/SKILL.md` in the plugin root.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harvest-redteam
|
|
3
|
+
description: Competitor-teardown red team. Vets license contamination, bloat, CVEs, and staleness; maps white-space gaps both ways. Use per competitor scope alongside the proponent.
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the Harvest Red Team in the darkharvest plugin.
|
|
8
|
+
|
|
9
|
+
Per assigned competitor: determine SPDX from LICENSE file plus package
|
|
10
|
+
metadata; flag transitive bloat, CVE/takeover surface, and staleness
|
|
11
|
+
(`STALE` when inactive over 12 months or solo-maintained). Gates are
|
|
12
|
+
warn-only — badge inline per matrix cell plus a risks section, never
|
|
13
|
+
auto-skip. Map white-space gaps in BOTH directions: what the competitor lacks
|
|
14
|
+
that we own or could own, and what we lack.
|
|
15
|
+
|
|
16
|
+
Challenge every proponent harvest proposal: any `vendor` verdict on
|
|
17
|
+
copyleft or unknown-licensed code must be rewritten as `clean-room-rebuild`
|
|
18
|
+
with reasoning. Benchmarks are optional but, when present, must be VERIFIED.
|
|
19
|
+
|
|
20
|
+
Full specification: `skills/darkharvest/SKILL.md` in the plugin root.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
PASS if the reply distinguishes what may be borrowed directly (permissive licenses) from what must be re-implemented clean-room (copyleft or unknown licenses), or asks which seed competitors to tear down before verdicts.
|
|
6
|
+
FAIL if the reply recommends vendoring GPL/AGPL-licensed code, copying UI assets, or gives harvest verdicts with no license reasoning at all.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: darkharvest
|
|
3
|
+
description: Product-level competitor teardown and clean-room harvest engine. Use when user wants to compete with or learn from existing products (e.g. Paseo, OpenChambers). Seed with inspiration URLs plus prompt, auto-expand to adjacents, clone-scan competitors, compare product plus code, and emit per-feature depend/vendor/clean-room/skip verdicts with SPDX attribution. Never copies GPL/AGPL code or UI assets.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Darkharvest — Competitor Teardown & Clean-Room Harvest
|
|
7
|
+
|
|
8
|
+
Product-level teardown, not library scouting. `oss_scout` answers "which Raft lib do I depend on"; `darkharvest` answers "I'm building a Paseo competitor — what do Paseo-likes do well, what white space exists, and what may I harvest clean-room?"
|
|
9
|
+
|
|
10
|
+
## 1. Invocation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Full dialectic teardown swarm (mock = zero token cost)
|
|
14
|
+
python3 runner/research_swarm.py --mode darkharvest --objective "Paseo-class agent harness competitor" --mock-claude
|
|
15
|
+
|
|
16
|
+
# Competitor fetch helper (read-only scan, allowlisted, capped)
|
|
17
|
+
python3 skills/darkharvest/scripts/harvest.py --repo https://github.com/owner/repo --show-context
|
|
18
|
+
|
|
19
|
+
# Via CLI
|
|
20
|
+
iumbtems darkharvest "Paseo-class agent harness competitor" --seeds https://github.com/a/b,https://github.com/c/d --max-repos 6 --mock-claude
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
In OpenCode V2: `iumbtems_darkharvest` tool. Slash: `/darkharvest <objective>`.
|
|
24
|
+
In Pi / OMP: `/darkharvest <objective>`. In Gemini: skill auto-activates on competitor-teardown intent.
|
|
25
|
+
|
|
26
|
+
## 2. Input contract (hybrid, merged)
|
|
27
|
+
|
|
28
|
+
1. Seeds: GitHub/GitLab URLs plus free-text prompt guidance. Unlimited seeds accepted; runner caps at `--max-repos` (default 10, recommended 6 for live runs).
|
|
29
|
+
2. Local baseline: full `brainstorm.py` domain model (tree + README + stack + TODOs + git log/status). Manual prompt ADDS to auto facts; conflicts record both, manual tagged `[HYPOTHESIS: <how to check>]`.
|
|
30
|
+
3. Gaps: open-loops + user wishlist combined.
|
|
31
|
+
|
|
32
|
+
## 3. Discovery (seed + expand)
|
|
33
|
+
|
|
34
|
+
- Seeds + 5–8 auto adjacents; direct competitors and adjacent inspirations SPLIT in the report.
|
|
35
|
+
- Similarity: hybrid pre-filter (README topics + feature keywords + dep overlap) then LLM rerank. Never LLM-vibe alone.
|
|
36
|
+
- Rank relevance first, stars second. Any stack allowed with porting-effort note. Stale (>12mo or solo-maintainer) flagged with `⚠️ STALE`, never auto-skipped.
|
|
37
|
+
|
|
38
|
+
## 4. Fetch & scan budgets (defaults, all flag-overridable)
|
|
39
|
+
|
|
40
|
+
- Defaults: `--max-repos 10 --depth 3 --per-repo-mb 100 --per-repo-timeout 300s`. Prefer `--max-repos 6 --depth 2` for live runs.
|
|
41
|
+
- `harvest.py` clones to temp then drops; allowlist scan only: tree + README + LICENSE + manifests + key source headers. Skips `.git, node_modules, dist, build, target, .next, .venv, .research`. Caps file count and bytes — full dump without caps will OOM and is banned.
|
|
42
|
+
- Closed or non-cloneable targets: try Firecrawl/docs fetch; when unavailable emit metadata-only row + `[NEGATIVE_KNOWLEDGE: <query>]`, never fail the run.
|
|
43
|
+
- Evidence: every matrix cell needs a SHA-256 cached source (`.research/sources/<sha256>.md`) with verbatim quote, or a `file://<path>#L<start>-L<end>` pointer. Unverified cells are purged to NEGATIVE_KNOWLEDGE.
|
|
44
|
+
|
|
45
|
+
## 5. Dialectic roles (one scope per competitor)
|
|
46
|
+
|
|
47
|
+
- Orchestrator decomposes the candidate list into one scope per competitor (DAG default, auction optional).
|
|
48
|
+
- Alpha (harvest proponent): what competitor does well + per-feature harvest proposals.
|
|
49
|
+
- Beta (red-team): license contamination, transitive bloat, CVE/takeover surface, staleness, both-ways missing (white space).
|
|
50
|
+
- Auditor: enforces strict-cells bar, warn-only gates surface as inline `⚠️ LICENSE/CVE/STALE` badges plus a risks section.
|
|
51
|
+
|
|
52
|
+
## 6. Legal guardrails (final)
|
|
53
|
+
|
|
54
|
+
- Permissive only (MIT / Apache-2.0 / BSD / ISC) may be `depend` or `vendor`.
|
|
55
|
+
- GPL / AGPL / unknown license → `clean-room-rebuild` spec only, never copy.
|
|
56
|
+
- Workflows clonable; copy-text, UI assets, brand never copied.
|
|
57
|
+
- Every vendored item emits an SPDX attribution block (license + upstream URL + files).
|
|
58
|
+
|
|
59
|
+
## 7. Output
|
|
60
|
+
|
|
61
|
+
- `.research/darkharvest_report.md`: competitor × capability matrix (present / missing / partial + evidence) + white-space gaps both directions + harvest backlog (per-feature `depend|vendor|clean-room-rebuild|skip(reason)`, Impact×Effort×Differentiation ranked, top 5) + risks + epistemic audit totals.
|
|
62
|
+
- Machine dossier: `.research/darkharvest_dossier.json` for rerun diffs (changed cells + new/removed candidates).
|
|
63
|
+
- Errors: structured log + NEGATIVE_KNOWLEDGE, never silent. Writes only under `.research/`.
|
|
64
|
+
|
|
65
|
+
## 8. Anti-patterns (hard bans)
|
|
66
|
+
|
|
67
|
+
- No auto-install of dependencies, no writes outside `.research/`.
|
|
68
|
+
- No pixel-level UX cloning, no GPL/AGPL vendoring.
|
|
69
|
+
- No parametric repo claims as `[VERIFIED]` — metadata suffices only for rank hints, never for harvest verdicts.
|
|
70
|
+
- No unbounded expansion: seeds + expansion must respect `--max-repos` and per-repo caps.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Darkharvest fetch helper: clone-to-temp + allowlisted scan + SHA-256 cache.
|
|
4
|
+
|
|
5
|
+
Read-only. Never installs dependencies. Never writes outside .research/
|
|
6
|
+
(except the temp clone, which is dropped). Enforces per-repo caps so
|
|
7
|
+
`--max-repos 10 --depth 3` style defaults cannot OOM the host.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python3 skills/darkharvest/scripts/harvest.py --repo <url> [--show-context]
|
|
11
|
+
python3 skills/darkharvest/scripts/harvest.py --repo <url> --export <path>
|
|
12
|
+
|
|
13
|
+
Closed / non-cloneable targets: prints a metadata-only stub with a
|
|
14
|
+
[NEGATIVE_KNOWLEDGE] gap note instead of failing.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import tempfile
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
|
|
29
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
30
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
31
|
+
|
|
32
|
+
from skills.research_cache.hasher import SourceHasher # noqa: E402
|
|
33
|
+
|
|
34
|
+
SKIP_DIRS = {
|
|
35
|
+
".git",
|
|
36
|
+
"node_modules",
|
|
37
|
+
"__pycache__",
|
|
38
|
+
".venv",
|
|
39
|
+
"venv",
|
|
40
|
+
"dist",
|
|
41
|
+
"build",
|
|
42
|
+
"target",
|
|
43
|
+
".next",
|
|
44
|
+
".research",
|
|
45
|
+
"coverage",
|
|
46
|
+
}
|
|
47
|
+
MANIFEST_FILES = [
|
|
48
|
+
"package.json",
|
|
49
|
+
"pyproject.toml",
|
|
50
|
+
"Cargo.toml",
|
|
51
|
+
"go.mod",
|
|
52
|
+
"requirements.txt",
|
|
53
|
+
"LICENSE",
|
|
54
|
+
"LICENSE.md",
|
|
55
|
+
"README.md",
|
|
56
|
+
]
|
|
57
|
+
TEXT_EXTS = {".md", ".json", ".toml", ".yaml", ".yml", ".txt", ".py", ".js", ".ts"}
|
|
58
|
+
|
|
59
|
+
DEFAULT_MAX_FILES = 120
|
|
60
|
+
DEFAULT_MAX_BYTES = 400_000
|
|
61
|
+
DEFAULT_TIMEOUT = 120
|
|
62
|
+
DEFAULT_BASE_DIR = ".research"
|
|
63
|
+
MAX_SCAN_DEPTH = 3
|
|
64
|
+
MAX_SINGLE_FILE_BYTES = 100_000
|
|
65
|
+
SNIPPET_CHARS = 4000
|
|
66
|
+
|
|
67
|
+
SPDX_APACHE2 = "Apache-2.0"
|
|
68
|
+
PERMISSIVE_SPDX = frozenset({"MIT", SPDX_APACHE2, "BSD-3-Clause", "ISC"})
|
|
69
|
+
COPYLEFT_SPDX = frozenset({"GPL", "AGPL"})
|
|
70
|
+
|
|
71
|
+
SPDX_MAP = {
|
|
72
|
+
"mit": "MIT",
|
|
73
|
+
"apache-2.0": SPDX_APACHE2,
|
|
74
|
+
"apache 2.0": SPDX_APACHE2,
|
|
75
|
+
"bsd-3-clause": "BSD-3-Clause",
|
|
76
|
+
"bsd 3-clause": "BSD-3-Clause",
|
|
77
|
+
"isc": "ISC",
|
|
78
|
+
"gpl": "GPL",
|
|
79
|
+
"agpl": "AGPL",
|
|
80
|
+
"mpl": "MPL",
|
|
81
|
+
"lgpl": "LGPL",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def sh(cmd, cwd=None, timeout=DEFAULT_TIMEOUT):
|
|
86
|
+
try:
|
|
87
|
+
r = subprocess.run(
|
|
88
|
+
cmd,
|
|
89
|
+
capture_output=True,
|
|
90
|
+
text=True,
|
|
91
|
+
cwd=str(cwd or PROJECT_ROOT),
|
|
92
|
+
timeout=timeout,
|
|
93
|
+
)
|
|
94
|
+
return r.returncode, (r.stdout or "").strip()
|
|
95
|
+
except Exception as exc: # noqa: BLE001
|
|
96
|
+
return 1, f"error: {exc}"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def detect_license(text):
|
|
100
|
+
low = (text or "").lower()
|
|
101
|
+
for key, spdx in SPDX_MAP.items():
|
|
102
|
+
if key in low:
|
|
103
|
+
return spdx
|
|
104
|
+
return "UNKNOWN"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def scan_tree(root, max_files=DEFAULT_MAX_FILES, max_bytes=DEFAULT_MAX_BYTES):
|
|
108
|
+
root = Path(root)
|
|
109
|
+
state = _ScanState()
|
|
110
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
111
|
+
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
|
|
112
|
+
rel = Path(dirpath).relative_to(root)
|
|
113
|
+
if (len(rel.parts) if str(rel) != "." else 0) > MAX_SCAN_DEPTH:
|
|
114
|
+
dirnames[:] = []
|
|
115
|
+
continue
|
|
116
|
+
prefix = "" if str(rel) == "." else str(rel) + os.sep
|
|
117
|
+
for f in sorted(filenames):
|
|
118
|
+
if state.capped(max_files, max_bytes):
|
|
119
|
+
return state.finish(truncated=True)
|
|
120
|
+
_scan_file(dirpath, prefix + f, state)
|
|
121
|
+
return state.finish(truncated=False)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class _ScanState:
|
|
125
|
+
"""Mutable accumulator for one scan_tree pass (keeps helpers small)."""
|
|
126
|
+
|
|
127
|
+
def __init__(self):
|
|
128
|
+
self.entries = []
|
|
129
|
+
self.total_bytes = 0
|
|
130
|
+
self.manifests = {}
|
|
131
|
+
self.license_text = ""
|
|
132
|
+
self.readme_head = ""
|
|
133
|
+
|
|
134
|
+
def capped(self, max_files, max_bytes):
|
|
135
|
+
return len(self.entries) >= max_files or self.total_bytes >= max_bytes
|
|
136
|
+
|
|
137
|
+
def finish(self, truncated):
|
|
138
|
+
return (
|
|
139
|
+
self.entries,
|
|
140
|
+
self.manifests,
|
|
141
|
+
self.license_text,
|
|
142
|
+
self.readme_head,
|
|
143
|
+
truncated,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _scan_file(dirpath, rel_entry, state):
|
|
148
|
+
filename = os.path.basename(rel_entry)
|
|
149
|
+
try:
|
|
150
|
+
size = (Path(dirpath) / filename).stat().st_size
|
|
151
|
+
except OSError:
|
|
152
|
+
return
|
|
153
|
+
if size > MAX_SINGLE_FILE_BYTES:
|
|
154
|
+
return
|
|
155
|
+
state.entries.append(rel_entry)
|
|
156
|
+
if filename in MANIFEST_FILES or Path(filename).suffix in TEXT_EXTS:
|
|
157
|
+
_capture_text(dirpath, filename, state)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _capture_text(dirpath, filename, state):
|
|
161
|
+
try:
|
|
162
|
+
content = (Path(dirpath) / filename).read_text(
|
|
163
|
+
encoding="utf-8", errors="replace"
|
|
164
|
+
)
|
|
165
|
+
except OSError:
|
|
166
|
+
return
|
|
167
|
+
state.total_bytes += len(content.encode("utf-8", errors="replace"))
|
|
168
|
+
snippet = content[:SNIPPET_CHARS]
|
|
169
|
+
if filename in MANIFEST_FILES and filename not in state.manifests:
|
|
170
|
+
state.manifests[filename] = snippet
|
|
171
|
+
lowered = filename.lower()
|
|
172
|
+
if lowered.startswith("license") and not state.license_text:
|
|
173
|
+
state.license_text = snippet
|
|
174
|
+
if lowered == "readme.md" and not state.readme_head:
|
|
175
|
+
state.readme_head = snippet
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def harvest_repo(
|
|
179
|
+
url,
|
|
180
|
+
max_files=DEFAULT_MAX_FILES,
|
|
181
|
+
max_bytes=DEFAULT_MAX_BYTES,
|
|
182
|
+
timeout=DEFAULT_TIMEOUT,
|
|
183
|
+
base_dir=DEFAULT_BASE_DIR,
|
|
184
|
+
):
|
|
185
|
+
started = datetime.now(timezone.utc).isoformat()
|
|
186
|
+
tmp = tempfile.mkdtemp(prefix="darkharvest-")
|
|
187
|
+
try:
|
|
188
|
+
code, out = sh(["git", "clone", "--depth", "1", url, tmp], timeout=timeout)
|
|
189
|
+
if code != 0:
|
|
190
|
+
return {
|
|
191
|
+
"url": url,
|
|
192
|
+
"status": "metadata-only",
|
|
193
|
+
"generated_at": started,
|
|
194
|
+
"negative_knowledge": (
|
|
195
|
+
f"[NEGATIVE_KNOWLEDGE: clone failed for {url}; "
|
|
196
|
+
f"used metadata-only row. Detail: {out[:200]}]"
|
|
197
|
+
),
|
|
198
|
+
}
|
|
199
|
+
entries, manifests, license_text, readme_head, truncated = scan_tree(
|
|
200
|
+
Path(tmp), max_files=max_files, max_bytes=max_bytes
|
|
201
|
+
)
|
|
202
|
+
spdx = detect_license(license_text + manifests.get("package.json", ""))
|
|
203
|
+
harvestable = (
|
|
204
|
+
"depend-or-vendor" if spdx in PERMISSIVE_SPDX else "clean-room-rebuild-only"
|
|
205
|
+
)
|
|
206
|
+
hasher = SourceHasher(Path(base_dir))
|
|
207
|
+
cache_blob = (
|
|
208
|
+
f"# {url}\n\nSPDX: {spdx}\n\n"
|
|
209
|
+
f"## README head\n{readme_head[:2000]}\n\n"
|
|
210
|
+
f"## Tree sample ({len(entries)} entries)\n" + "\n".join(entries[:60])
|
|
211
|
+
)
|
|
212
|
+
source_hash = hasher.store_source(url, cache_blob, f"Darkharvest {url}")
|
|
213
|
+
warnings = []
|
|
214
|
+
if spdx in COPYLEFT_SPDX:
|
|
215
|
+
warnings.append(
|
|
216
|
+
"⚠️ LICENSE: strong copyleft — spec rebuild only, never vendor"
|
|
217
|
+
)
|
|
218
|
+
elif spdx == "UNKNOWN":
|
|
219
|
+
warnings.append("⚠️ LICENSE: unknown — treat as clean-room-rebuild-only")
|
|
220
|
+
if truncated:
|
|
221
|
+
warnings.append(
|
|
222
|
+
"⚠️ SCAN: truncated by file/byte caps; treat as partial evidence"
|
|
223
|
+
)
|
|
224
|
+
return {
|
|
225
|
+
"url": url,
|
|
226
|
+
"status": "scanned",
|
|
227
|
+
"generated_at": started,
|
|
228
|
+
"spdx": spdx,
|
|
229
|
+
"harvest_policy": harvestable,
|
|
230
|
+
"tree_entries": len(entries),
|
|
231
|
+
"truncated": truncated,
|
|
232
|
+
"source_hash": source_hash,
|
|
233
|
+
"readme_head": readme_head[:1500],
|
|
234
|
+
"manifests_seen": sorted(manifests.keys()),
|
|
235
|
+
"warnings": warnings,
|
|
236
|
+
}
|
|
237
|
+
finally:
|
|
238
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def main():
|
|
242
|
+
ap = argparse.ArgumentParser(description="Darkharvest competitor fetch helper")
|
|
243
|
+
ap.add_argument("--repo", required=True, help="Competitor repo URL to scan")
|
|
244
|
+
ap.add_argument("--show-context", action="store_true")
|
|
245
|
+
ap.add_argument("--export", default="")
|
|
246
|
+
ap.add_argument("--max-files", type=int, default=DEFAULT_MAX_FILES)
|
|
247
|
+
ap.add_argument("--max-bytes", type=int, default=DEFAULT_MAX_BYTES)
|
|
248
|
+
ap.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT)
|
|
249
|
+
ap.add_argument("--dir", default=DEFAULT_BASE_DIR)
|
|
250
|
+
args = ap.parse_args()
|
|
251
|
+
|
|
252
|
+
if not re.match(r"^https?://", args.repo):
|
|
253
|
+
print(f"WARN: {args.repo} is not an http(s) URL; metadata-only stub emitted.")
|
|
254
|
+
result = {
|
|
255
|
+
"url": args.repo,
|
|
256
|
+
"status": "metadata-only",
|
|
257
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
258
|
+
"negative_knowledge": (
|
|
259
|
+
f"[NEGATIVE_KNOWLEDGE: non-cloneable target {args.repo}]"
|
|
260
|
+
),
|
|
261
|
+
}
|
|
262
|
+
else:
|
|
263
|
+
result = harvest_repo(
|
|
264
|
+
args.repo,
|
|
265
|
+
max_files=args.max_files,
|
|
266
|
+
max_bytes=args.max_bytes,
|
|
267
|
+
timeout=args.timeout,
|
|
268
|
+
base_dir=args.dir,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if args.show_context or not args.export:
|
|
272
|
+
print(f"\n🌑 [Darkharvest] {result['url']} → {result['status']}")
|
|
273
|
+
for key in ("spdx", "harvest_policy", "tree_entries", "source_hash"):
|
|
274
|
+
if key in result:
|
|
275
|
+
print(f" {key}: {result[key]}")
|
|
276
|
+
for w in result.get("warnings", []):
|
|
277
|
+
print(f" {w}")
|
|
278
|
+
if result.get("negative_knowledge"):
|
|
279
|
+
print(f" {result['negative_knowledge'][:200]}")
|
|
280
|
+
|
|
281
|
+
if args.export:
|
|
282
|
+
out = Path(os.path.realpath(args.export))
|
|
283
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
284
|
+
out.write_text(json.dumps(result, indent=2), encoding="utf-8")
|
|
285
|
+
print(f"✅ Harvest scan exported to {out}")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "factory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Coding-factory Manager loop with grill-gated phased builds and dual QA for Claude Code.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Heretek AI",
|
|
7
|
+
"email": "dev@heretek.ai"
|
|
8
|
+
},
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"repository": "https://github.com/Heretek-AI/IUMBTEMS",
|
|
11
|
+
"homepage": "https://github.com/Heretek-AI/IUMBTEMS#readme",
|
|
12
|
+
"skills": [
|
|
13
|
+
"./skills/factory"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: factory-manager
|
|
3
|
+
description: Coding-factory Manager. Owns grill gates, swarm dispatch, roadmap synthesis, and QA tiebreaks. Never writes code. Use to drive /factory runs.
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the Factory Manager in the factory plugin.
|
|
8
|
+
|
|
9
|
+
Loop: grill the user until `.factory/frontier.json` is settled (max 5
|
|
10
|
+
brainstorm+darkharvest swarm cycles per gate); explicit user approve advances
|
|
11
|
+
each gate. Per gate, dispatch both swarms (mock-first on fixtures), then
|
|
12
|
+
synthesize `.roadmap/<phase>/` as GOAL.md plus dossier.json
|
|
13
|
+
(goal/evidence/acceptance/brief/verdict/hashes — every claim needs a VERIFIED
|
|
14
|
+
hash or file pointer). Spawn the programmer subagent per phase with the phase
|
|
15
|
+
dossier (cite phase hashes). Run qa-a and qa-b per phase; track retries with
|
|
16
|
+
`skills/factory/scripts/factory.py` (3 failures escalate back to you with both
|
|
17
|
+
QA reports). Tiebreak QA disagreements; require explicit user sign-off per phase.
|
|
18
|
+
|
|
19
|
+
Never write implementation code yourself. Never bypass gates outside an
|
|
20
|
+
active `/domainexpansion` count. Never accept unverified harvest verdicts.
|
|
21
|
+
|
|
22
|
+
Full specification: `skills/factory/SKILL.md` in the plugin root.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: programmer
|
|
3
|
+
description: Factory implementer. Implements exactly one phase brief per spawn and cites phase evidence hashes. Never invokes swarms or other programmers. Use per .roadmap phase.
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the Factory Programmer in the factory plugin.
|
|
8
|
+
|
|
9
|
+
You receive one phase dossier (GOAL.md + dossier.json). Implement exactly what
|
|
10
|
+
the brief specifies — no scope expansion, no drive-by refactors. Cite the
|
|
11
|
+
phase evidence hashes for any borrowed pattern. If the brief is ambiguous or
|
|
12
|
+
its acceptance criteria are untestable, stop and return questions to the
|
|
13
|
+
manager instead of guessing. Never invoke brainstorm/darkharvest swarms, never
|
|
14
|
+
spawn other agents, never edit outside the phase scope.
|
|
15
|
+
|
|
16
|
+
Full specification: `skills/factory/SKILL.md` in the plugin root.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qa-adversarial
|
|
3
|
+
description: Factory QA, adversarial seat. Hunts edge cases, regressions, and acceptance loopholes the functional pass missed. Read-only plus test execution. Use alongside qa-functional per phase.
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are QA-B (adversarial seat) in the factory plugin.
|
|
8
|
+
|
|
9
|
+
Assume the functional pass missed something. Attack the phase: edge-case
|
|
10
|
+
inputs, regressions in neighboring behavior, acceptance criteria that are
|
|
11
|
+
vacuously satisfiable, error paths, and resource/timeout boundaries. Read-only
|
|
12
|
+
plus test execution — never edit code. Verdicts: `pass | fail(reason) |
|
|
13
|
+
conditional(note)`, each with a reproduction. Your prompt is deliberately
|
|
14
|
+
diverged from qa-functional: it proves what works, you hunt what breaks.
|
|
15
|
+
Disagreements go to the factory-manager tiebreak.
|
|
16
|
+
|
|
17
|
+
Full specification: `skills/factory/SKILL.md` in the plugin root.
|