@heretek-ai/epistemic-swarm 0.7.21 → 0.7.23
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/plugin.json +1 -1
- package/README.md +1 -1
- package/bin/cli.js +3 -2
- package/extensions/pi/index.js +3 -2
- package/package.json +5 -1
- package/plugins/antigravity/plugin.json +1 -1
- package/plugins/factory/skills/factory/SKILL.md +1 -1
- package/plugins/opencode/analysis-bridge.js +566 -0
- package/plugins/opencode/config-io.js +598 -31
- package/plugins/opencode/dep-health.js +1039 -0
- package/plugins/opencode/hook-bus.js +434 -0
- package/plugins/opencode/index.js +252 -19
- package/plugins/opencode/tui.js +230 -17
- package/plugins/opencode/weight-baseline.json +16 -0
- package/plugins/opencode/weight-signals.js +769 -0
- package/plugins/research-cache/skills/research_cache/hasher.py +33 -8
- 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__/darkharvest_claims.cpython-311.pyc +0 -0
- package/runner/__pycache__/errors.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__/preflight.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__/retrieval_log.cpython-311.pyc +0 -0
- package/runner/__pycache__/schema_validate.cpython-311.pyc +0 -0
- package/runner/__pycache__/schemas.cpython-311.pyc +0 -0
- package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
- package/runner/mcp_server.py +249 -81
- package/runner/refinement.py +5 -0
- package/runner/schema_validate.py +63 -1
- package/runner/schemas.py +9 -4
- package/runner/tests/__pycache__/test_analysis_bridge.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_antigravity_plugin.cpython-311.pyc +0 -0
- 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_config_schema.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_contract_tooling.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_darkharvest_audit.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_dep_health.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_dossier_contract.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_frontier_concurrency.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_frontier_mirror_standalone.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_grounding.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_hardening_contract.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_hook_bus.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_manifest_concurrency.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_opencode_v2_registrars.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_phase08_waiver_reopens.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_search_anomaly.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_search_provider.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_search_provider_plugin.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_swarm_config.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/__pycache__/test_weight_signals.cpython-311.pyc +0 -0
- package/runner/tests/fixtures/config_validation_cases.json +60 -0
- package/runner/tests/test_analysis_bridge.py +356 -0
- package/runner/tests/test_config_schema.py +119 -15
- package/runner/tests/test_dep_health.py +484 -0
- package/runner/tests/test_hardening_contract.py +391 -0
- package/runner/tests/test_hook_bus.py +802 -0
- package/runner/tests/test_opencode_ux.py +232 -0
- package/runner/tests/test_opencode_v2_registrars.py +548 -16
- package/runner/tests/test_phase08_waiver_reopens.py +539 -0
- package/runner/tests/test_weight_signals.py +361 -0
- package/schemas/config.schema.json +4 -3
- package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
- package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
- package/scripts/__pycache__/check_docs.cpython-311.pyc +0 -0
- package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
- package/scripts/__pycache__/gen_schemas.cpython-311.pyc +0 -0
- package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
- package/skills/factory/SKILL.md +1 -1
- package/skills/grilling/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/grilling/__pycache__/socratic_tree.cpython-311.pyc +0 -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/research_cache/hasher.py +33 -8
- package/skills/swarm_config/SKILL.md +6 -1
- 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 +254 -17
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Tests for Phase 04-coverage-treeshake-signals: coverage deltas + bundle weight.
|
|
3
|
+
|
|
4
|
+
Mock-host only (no live TUI, no real esbuild spawn unless noted): every build
|
|
5
|
+
is an injected mock or a precomputed metafile, every budget is tens of
|
|
6
|
+
milliseconds, at most zero MCP spawns. Pins: blowup-still-allows (+500%
|
|
7
|
+
fixture), missing-esbuild unavailable-skip, malformed-metafile tolerated,
|
|
8
|
+
annotation key completeness (always-present keys on both record shapes),
|
|
9
|
+
delta math (up/down/flat/unknown), stale-baseline annotates-without-blocking,
|
|
10
|
+
and the setup adopt/dispose wiring in plugins/opencode/index.js.
|
|
11
|
+
|
|
12
|
+
Phase evidence cited in-code per brief:
|
|
13
|
+
[VERIFIED: sha256:f42787d2ce436bbd6d55e90a15d45b656290a1a44451fff3318b094a4582ce01]
|
|
14
|
+
[VERIFIED: sha256:f2c0843b598ba6f19cc0e208f8d02e0250c70615e3c484474d50635d0b5cdef8]
|
|
15
|
+
[VERIFIED: sha256:cd66253fec923d8210d2863f98d9c5669662ac70c3a1939f2bd8ea30ed007644]
|
|
16
|
+
[VERIFIED: sha256:da194d498bc256f2b619c406015a3fb600712ec5b8c8ab9001fc38f0a6f5c8cc]
|
|
17
|
+
[VERIFIED: sha256:7690b878a654ee4dee2b649b60740bc95d4b746cf786051887153b6f656f2e5e]
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import unittest
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
27
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
28
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_node(code):
|
|
32
|
+
return subprocess.run(
|
|
33
|
+
["node", "--input-type=module", "-e", code],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
cwd=str(PROJECT_ROOT),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def last_json_object(stdout):
|
|
41
|
+
lines = [
|
|
42
|
+
line.strip()
|
|
43
|
+
for line in stdout.strip().split("\n")
|
|
44
|
+
if line.strip().startswith("{")
|
|
45
|
+
]
|
|
46
|
+
return json.loads(lines[-1])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
WEIGHT_IMPORT = (
|
|
50
|
+
'import { createHookBus } from "./plugins/opencode/hook-bus.js";\n'
|
|
51
|
+
"import { measureBundle, coverageDelta, parseMetafile, isBaselineStale, loadBaseline,"
|
|
52
|
+
" createWeightSignalsHandler, registerWeightSignals, toWeightRecord, toCoverageRecord,"
|
|
53
|
+
' WEIGHT_RECORD_KEYS, COVERAGE_RECORD_KEYS } from "./plugins/opencode/weight-signals.js";\n'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Metafile fixture builder: outputs sum to totalBytes over two outputs.
|
|
57
|
+
META_BUILDER = """
|
|
58
|
+
const meta = (totalBytes) => ({
|
|
59
|
+
inputs: {
|
|
60
|
+
"plugins/opencode/index.js": { bytes: Math.floor(totalBytes / 2), imports: [] },
|
|
61
|
+
"plugins/opencode/hook-bus.js": { bytes: totalBytes - Math.floor(totalBytes / 2), imports: [] },
|
|
62
|
+
},
|
|
63
|
+
outputs: {
|
|
64
|
+
"out/bundle.js": { bytes: Math.floor(totalBytes / 2), inputs: {} },
|
|
65
|
+
"out/bundle2.js": { bytes: totalBytes - Math.floor(totalBytes / 2), inputs: {} },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const freshBaseline = (bytes) => ({ bytes, measuredAt: new Date().toISOString() });
|
|
69
|
+
const staleBaseline = (bytes) => ({ bytes, measuredAt: "2020-01-01T00:00:00.000Z" });
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
SETUP_WEIGHT = """
|
|
73
|
+
const bus = createHookBus();
|
|
74
|
+
const notices = [];
|
|
75
|
+
const notify = (s) => { notices.push(s); };
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestNeverBlocks(unittest.TestCase):
|
|
80
|
+
def test_plus_500pct_blowup_still_allows(self):
|
|
81
|
+
res = run_node(
|
|
82
|
+
WEIGHT_IMPORT
|
|
83
|
+
+ META_BUILDER
|
|
84
|
+
+ SETUP_WEIGHT
|
|
85
|
+
+ """
|
|
86
|
+
const reg = await registerWeightSignals(null, {
|
|
87
|
+
bus, metafile: meta(6000), baseline: freshBaseline(1000),
|
|
88
|
+
notify, timeoutMs: 200,
|
|
89
|
+
});
|
|
90
|
+
const direct = await measureBundle({ metafile: meta(6000), baseline: freshBaseline(1000) });
|
|
91
|
+
const rec = direct.annotations[0];
|
|
92
|
+
const flat = JSON.stringify(direct);
|
|
93
|
+
let threw = false;
|
|
94
|
+
let r = null;
|
|
95
|
+
try {
|
|
96
|
+
r = await bus.emit("pre-commit", {tool: "edit", eventId: "e-blowup"});
|
|
97
|
+
} catch { threw = true; }
|
|
98
|
+
console.log(JSON.stringify({
|
|
99
|
+
threw,
|
|
100
|
+
allowed: direct.allowed,
|
|
101
|
+
hasDeny: flat.includes("deny"),
|
|
102
|
+
emitAllowed: r && r.allowed,
|
|
103
|
+
bytes: rec.bytes,
|
|
104
|
+
deltaBytes: rec.deltaVsBaseline && rec.deltaVsBaseline.bytes,
|
|
105
|
+
deltaPct: rec.deltaVsBaseline && rec.deltaVsBaseline.pct,
|
|
106
|
+
notices: notices.length,
|
|
107
|
+
}));
|
|
108
|
+
"""
|
|
109
|
+
)
|
|
110
|
+
self.assertEqual(res.returncode, 0, f"never-blocks test failed: {res.stderr}")
|
|
111
|
+
data = last_json_object(res.stdout)
|
|
112
|
+
self.assertFalse(data["threw"], "blowup must never throw")
|
|
113
|
+
self.assertTrue(
|
|
114
|
+
data["allowed"], "+500% blowup must still allow (advisory-only)"
|
|
115
|
+
)
|
|
116
|
+
self.assertFalse(data["hasDeny"], "no deny verdict anywhere in the result")
|
|
117
|
+
self.assertTrue(data["emitAllowed"], "bus emit must allow on blowup")
|
|
118
|
+
self.assertEqual(data["bytes"], 6000)
|
|
119
|
+
self.assertEqual(data["deltaBytes"], 5000)
|
|
120
|
+
self.assertEqual(data["deltaPct"], 500.0)
|
|
121
|
+
self.assertEqual(data["notices"], 1, "exactly one counts-only toast")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class TestMissingEsbuild(unittest.TestCase):
|
|
125
|
+
def test_missing_esbuild_unavailable_skip(self):
|
|
126
|
+
res = run_node(
|
|
127
|
+
WEIGHT_IMPORT
|
|
128
|
+
+ SETUP_WEIGHT
|
|
129
|
+
+ """
|
|
130
|
+
const runBuild = async () => ({ status: "missing" });
|
|
131
|
+
const reg = await registerWeightSignals(null, { bus, runBuild, notify, timeoutMs: 200 });
|
|
132
|
+
const mark = bus.getAuditLog().length;
|
|
133
|
+
let threw = false;
|
|
134
|
+
let r = null;
|
|
135
|
+
try {
|
|
136
|
+
r = await bus.emit("pre-commit", {tool: "edit", eventId: "e-miss"});
|
|
137
|
+
} catch { threw = true; }
|
|
138
|
+
const fresh = bus.getAuditLog().slice(mark);
|
|
139
|
+
const direct = await measureBundle({ runBuild });
|
|
140
|
+
console.log(JSON.stringify({
|
|
141
|
+
threw, allowed: r && r.allowed,
|
|
142
|
+
status: direct.annotations[0].status,
|
|
143
|
+
keys: Object.keys(direct.annotations[0]).sort(),
|
|
144
|
+
auditEntries: fresh.length,
|
|
145
|
+
notices: notices.length,
|
|
146
|
+
}));
|
|
147
|
+
"""
|
|
148
|
+
)
|
|
149
|
+
self.assertEqual(res.returncode, 0, f"missing test failed: {res.stderr}")
|
|
150
|
+
data = last_json_object(res.stdout)
|
|
151
|
+
self.assertFalse(data["threw"], "missing esbuild must never throw")
|
|
152
|
+
self.assertTrue(data["allowed"], "missing esbuild must fail open")
|
|
153
|
+
self.assertEqual(data["status"], "unavailable")
|
|
154
|
+
self.assertGreaterEqual(
|
|
155
|
+
data["auditEntries"], 1, "unavailable entry must be audit-logged"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TestMalformedMetafile(unittest.TestCase):
|
|
160
|
+
def test_malformed_variants_tolerated(self):
|
|
161
|
+
res = run_node(
|
|
162
|
+
WEIGHT_IMPORT
|
|
163
|
+
+ """
|
|
164
|
+
const variants = [
|
|
165
|
+
"garbage-string", null, 42, [], {},
|
|
166
|
+
{ inputs: {}, outputs: 42 },
|
|
167
|
+
{ inputs: {}, outputs: {} },
|
|
168
|
+
{ inputs: {}, outputs: { "o.js": { bytes: "nan" } } },
|
|
169
|
+
];
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const m of variants) {
|
|
172
|
+
let threw = false;
|
|
173
|
+
let rec = null;
|
|
174
|
+
try {
|
|
175
|
+
const r = await measureBundle({ metafile: m === null ? { outputs: null } : m });
|
|
176
|
+
rec = r.annotations[0];
|
|
177
|
+
out.push({ allowed: r.allowed, status: rec && rec.status });
|
|
178
|
+
} catch { threw = true; out.push({ threw: true }); }
|
|
179
|
+
}
|
|
180
|
+
const parsed = parseMetafile("nope");
|
|
181
|
+
console.log(JSON.stringify({ out, parsedStatus: parsed.status }));
|
|
182
|
+
"""
|
|
183
|
+
)
|
|
184
|
+
self.assertEqual(res.returncode, 0, f"malformed test failed: {res.stderr}")
|
|
185
|
+
data = last_json_object(res.stdout)
|
|
186
|
+
for entry in data["out"]:
|
|
187
|
+
self.assertFalse(
|
|
188
|
+
entry.get("threw", False), "malformed metafile must never throw"
|
|
189
|
+
)
|
|
190
|
+
self.assertTrue(entry["allowed"], "malformed metafile must fail open")
|
|
191
|
+
self.assertEqual(entry["status"], "unavailable")
|
|
192
|
+
self.assertEqual(data["parsedStatus"], "malformed")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestKeyCompleteness(unittest.TestCase):
|
|
196
|
+
def test_every_record_carries_complete_keys(self):
|
|
197
|
+
res = run_node(
|
|
198
|
+
WEIGHT_IMPORT
|
|
199
|
+
+ META_BUILDER
|
|
200
|
+
+ """
|
|
201
|
+
const scanned = toWeightRecord({
|
|
202
|
+
tool: "edit", eventId: "e1", status: "scanned", entry: "x.js",
|
|
203
|
+
bytes: 10, files: 1, topHeaviest: [{file: "x.js", bytes: 10}],
|
|
204
|
+
deltaVsBaseline: {bytes: 1, pct: 10}, baselineStale: false, message: "m",
|
|
205
|
+
});
|
|
206
|
+
const bare = toWeightRecord({});
|
|
207
|
+
const cov = toCoverageRecord({
|
|
208
|
+
tool: "edit", eventId: "e2", scope: "s",
|
|
209
|
+
passedBefore: 1, totalBefore: 2, passedAfter: 2, totalAfter: 2, message: "m",
|
|
210
|
+
});
|
|
211
|
+
const covBare = toCoverageRecord({});
|
|
212
|
+
const cd = coverageDelta({ before: {passed: 1, total: 2}, after: {passed: 2, total: 2}, scope: "s" });
|
|
213
|
+
console.log(JSON.stringify({
|
|
214
|
+
weightKeys: Object.keys(scanned).sort(),
|
|
215
|
+
bareKeys: Object.keys(bare).sort(),
|
|
216
|
+
covKeys: Object.keys(cov).sort(),
|
|
217
|
+
covBareKeys: Object.keys(covBare).sort(),
|
|
218
|
+
expectedWeight: [...WEIGHT_RECORD_KEYS].sort(),
|
|
219
|
+
expectedCov: [...COVERAGE_RECORD_KEYS].sort(),
|
|
220
|
+
cdKeys: Object.keys(cd).sort(),
|
|
221
|
+
cdDelta: cd.delta, cdDirection: cd.direction, cdAllowed: cd.allowed,
|
|
222
|
+
}));
|
|
223
|
+
"""
|
|
224
|
+
)
|
|
225
|
+
self.assertEqual(res.returncode, 0, f"keys test failed: {res.stderr}")
|
|
226
|
+
data = last_json_object(res.stdout)
|
|
227
|
+
self.assertEqual(data["weightKeys"], data["expectedWeight"])
|
|
228
|
+
self.assertEqual(
|
|
229
|
+
data["bareKeys"], data["expectedWeight"], "no silent drops on empty input"
|
|
230
|
+
)
|
|
231
|
+
self.assertEqual(data["covKeys"], data["expectedCov"])
|
|
232
|
+
self.assertEqual(data["covBareKeys"], data["expectedCov"])
|
|
233
|
+
self.assertIn("delta", data["cdKeys"])
|
|
234
|
+
self.assertIn("direction", data["cdKeys"])
|
|
235
|
+
self.assertTrue(data["cdAllowed"])
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TestDeltaMath(unittest.TestCase):
|
|
239
|
+
def test_up_down_flat_unknown(self):
|
|
240
|
+
res = run_node(
|
|
241
|
+
WEIGHT_IMPORT
|
|
242
|
+
+ """
|
|
243
|
+
const up = coverageDelta({ before: {passed: 80, total: 100}, after: {passed: 90, total: 100}, scope: "s" });
|
|
244
|
+
const down = coverageDelta({ before: {passed: 90, total: 100}, after: {passed: 70, total: 100}, scope: "s" });
|
|
245
|
+
const flat = coverageDelta({ before: {passed: 80, total: 100}, after: {passed: 80, total: 100}, scope: "s" });
|
|
246
|
+
const empty = coverageDelta({ before: {passed: 0, total: 0}, after: {passed: 0, total: 0}, scope: "s" });
|
|
247
|
+
const missing = coverageDelta({});
|
|
248
|
+
console.log(JSON.stringify({
|
|
249
|
+
up: [up.delta, up.direction], down: [down.delta, down.direction],
|
|
250
|
+
flat: [flat.delta, flat.direction],
|
|
251
|
+
empty: [empty.delta, empty.direction, empty.record.status],
|
|
252
|
+
missing: [missing.delta, missing.direction, missing.record.status],
|
|
253
|
+
}));
|
|
254
|
+
"""
|
|
255
|
+
)
|
|
256
|
+
self.assertEqual(res.returncode, 0, f"delta test failed: {res.stderr}")
|
|
257
|
+
data = last_json_object(res.stdout)
|
|
258
|
+
self.assertEqual(data["up"], [10.0, "up"])
|
|
259
|
+
self.assertEqual(data["down"], [-20.0, "down"])
|
|
260
|
+
self.assertEqual(data["flat"], [0.0, "flat"])
|
|
261
|
+
self.assertEqual(data["empty"][1], "unknown")
|
|
262
|
+
self.assertEqual(data["empty"][2], "unavailable")
|
|
263
|
+
self.assertEqual(data["missing"][1], "unknown")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class TestStaleBaseline(unittest.TestCase):
|
|
267
|
+
def test_stale_baseline_annotates_without_blocking(self):
|
|
268
|
+
res = run_node(
|
|
269
|
+
WEIGHT_IMPORT
|
|
270
|
+
+ META_BUILDER
|
|
271
|
+
+ SETUP_WEIGHT
|
|
272
|
+
+ """
|
|
273
|
+
const reg = await registerWeightSignals(null, {
|
|
274
|
+
bus, metafile: meta(1100), baseline: staleBaseline(1000),
|
|
275
|
+
notify, timeoutMs: 200,
|
|
276
|
+
});
|
|
277
|
+
const stale = await measureBundle({ metafile: meta(1100), baseline: staleBaseline(1000) });
|
|
278
|
+
const fresh = await measureBundle({ metafile: meta(1100), baseline: freshBaseline(1000) });
|
|
279
|
+
let threw = false;
|
|
280
|
+
let r = null;
|
|
281
|
+
try {
|
|
282
|
+
r = await bus.emit("pre-commit", {tool: "edit", eventId: "e-stale"});
|
|
283
|
+
} catch { threw = true; }
|
|
284
|
+
const staleNote = stale.annotations.find((a) => a.baselineStale && a.bytes === null);
|
|
285
|
+
console.log(JSON.stringify({
|
|
286
|
+
threw,
|
|
287
|
+
allowed: stale.allowed,
|
|
288
|
+
emitAllowed: r && r.allowed,
|
|
289
|
+
count: stale.annotations.length,
|
|
290
|
+
firstStale: stale.annotations[0].baselineStale,
|
|
291
|
+
deltaBytes: stale.annotations[0].deltaVsBaseline.bytes,
|
|
292
|
+
hasStaleNote: !!staleNote,
|
|
293
|
+
staleMsg: staleNote && staleNote.message,
|
|
294
|
+
freshCount: fresh.annotations.length,
|
|
295
|
+
freshStale: fresh.annotations[0].baselineStale,
|
|
296
|
+
}));
|
|
297
|
+
"""
|
|
298
|
+
)
|
|
299
|
+
self.assertEqual(res.returncode, 0, f"stale test failed: {res.stderr}")
|
|
300
|
+
data = last_json_object(res.stdout)
|
|
301
|
+
self.assertFalse(data["threw"])
|
|
302
|
+
self.assertTrue(data["allowed"], "stale baseline must still allow")
|
|
303
|
+
self.assertTrue(data["emitAllowed"])
|
|
304
|
+
self.assertEqual(
|
|
305
|
+
data["count"], 2, "stale baseline appends a stale-baseline annotation"
|
|
306
|
+
)
|
|
307
|
+
self.assertTrue(data["firstStale"])
|
|
308
|
+
self.assertEqual(
|
|
309
|
+
data["deltaBytes"], 100, "deltas still compute against a stale baseline"
|
|
310
|
+
)
|
|
311
|
+
self.assertTrue(data["hasStaleNote"])
|
|
312
|
+
self.assertIn("stale-baseline", data["staleMsg"])
|
|
313
|
+
self.assertIn("user-confirmed", data["staleMsg"])
|
|
314
|
+
self.assertEqual(data["freshCount"], 1, "fresh baseline adds no stale note")
|
|
315
|
+
self.assertFalse(data["freshStale"])
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class TestSetupAdoptsWeightSignals(unittest.TestCase):
|
|
319
|
+
def test_setup_adopts_and_disposes_weight_signals(self):
|
|
320
|
+
res = run_node(
|
|
321
|
+
"""
|
|
322
|
+
import plugin, { hookBus } from "./plugins/opencode/index.js";
|
|
323
|
+
const before = hookBus.handlerCount("pre-commit");
|
|
324
|
+
const host = {
|
|
325
|
+
options: {},
|
|
326
|
+
command: {
|
|
327
|
+
list: async () => ({data: []}),
|
|
328
|
+
transform: async (fn) => { fn({add: () => {}}); return {dispose: async () => {}}; }
|
|
329
|
+
},
|
|
330
|
+
tool: {
|
|
331
|
+
transform: async (fn) => { fn({add: () => {}}); return {dispose: async () => {}}; }
|
|
332
|
+
},
|
|
333
|
+
session: { prompt: async () => ({}) }
|
|
334
|
+
};
|
|
335
|
+
const cleanup = await plugin.setup(host);
|
|
336
|
+
const after = hookBus.handlerCount("pre-commit");
|
|
337
|
+
const attached = !!host.iumbtemsWeightSignals;
|
|
338
|
+
const events = host.iumbtemsWeightSignals && host.iumbtemsWeightSignals.events;
|
|
339
|
+
await cleanup();
|
|
340
|
+
const closed = hookBus.handlerCount("pre-commit");
|
|
341
|
+
console.log(JSON.stringify({before, after, attached, events, closed}));
|
|
342
|
+
"""
|
|
343
|
+
)
|
|
344
|
+
self.assertEqual(res.returncode, 0, f"adopt test failed: {res.stderr}")
|
|
345
|
+
data = last_json_object(res.stdout)
|
|
346
|
+
self.assertTrue(data["attached"], "weight-signals must be adopted on setup")
|
|
347
|
+
self.assertEqual(data["events"], ["pre-commit"])
|
|
348
|
+
self.assertGreater(
|
|
349
|
+
data["after"],
|
|
350
|
+
data["before"],
|
|
351
|
+
"pre-commit weight-signals handler subscribed",
|
|
352
|
+
)
|
|
353
|
+
self.assertEqual(
|
|
354
|
+
data["closed"],
|
|
355
|
+
data["before"],
|
|
356
|
+
"cleanup must dispose weight-signals subscriptions",
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
if __name__ == "__main__":
|
|
361
|
+
unittest.main()
|
|
@@ -104,12 +104,12 @@
|
|
|
104
104
|
"string",
|
|
105
105
|
"null"
|
|
106
106
|
],
|
|
107
|
-
"pattern": "^https?://",
|
|
107
|
+
"pattern": "^https?://[^\\s/]+\\S*$",
|
|
108
108
|
"default": null,
|
|
109
109
|
"x-label": "SearXNG URL",
|
|
110
110
|
"x-scope": "retrieval",
|
|
111
111
|
"x-restart": "next-run",
|
|
112
|
-
"description": "Base URL of a self-hosted SearXNG; exported to spawned children as SEARXNG_URL when set (preflight also reads the env var). Must include an http:// or https:// scheme; null leaves the environment untouched."
|
|
112
|
+
"description": "Base URL of a self-hosted SearXNG; exported to spawned children as SEARXNG_URL when set (preflight also reads the env var). Must include an http:// or https:// scheme AND a host (`http://` with no host is rejected); null leaves the environment untouched."
|
|
113
113
|
},
|
|
114
114
|
"license_whitelist": {
|
|
115
115
|
"type": "array",
|
|
@@ -154,11 +154,12 @@
|
|
|
154
154
|
"null"
|
|
155
155
|
],
|
|
156
156
|
"minLength": 1,
|
|
157
|
+
"pattern": "^\\S(.*\\S)?$",
|
|
157
158
|
"default": null,
|
|
158
159
|
"x-label": "Domain Pack",
|
|
159
160
|
"x-scope": "audit",
|
|
160
161
|
"x-restart": "next-run",
|
|
161
|
-
"description": "Regulated Domain Pack id (config/domain_packs/) or null for the legacy constitution. The empty string
|
|
162
|
+
"description": "Regulated Domain Pack id (config/domain_packs/) or null for the legacy constitution. The empty string, whitespace-only strings, and ids with leading/trailing whitespace are rejected; a pack id that does not resolve fails loudly when the constitution loads. UI entry points (iumbtems_set_domain_pack) strip surrounding whitespace before resolving."
|
|
162
163
|
},
|
|
163
164
|
"verify": {
|
|
164
165
|
"type": "object",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/skills/factory/SKILL.md
CHANGED
|
@@ -21,7 +21,7 @@ npm cache in consuming projects, and relative paths broke live: the manager
|
|
|
21
21
|
agent ran `find / -name factory.py`). The tool wraps the same helper and
|
|
22
22
|
resolves the project via `project_dir` argument, `IUMBTEMS_PROJECT_DIR`, or
|
|
23
23
|
the session cwd. It supports `init`, `phase-add`, `qa-record`, `expansion`,
|
|
24
|
-
`stop`, and returns `status: escalated` (exit 2) on the 3rd QA failure.
|
|
24
|
+
`stop`, `gate` (`--action open|settle|approve|waive|escalate|count`), and returns `status: escalated` (exit 2) on the 3rd QA failure.
|
|
25
25
|
|
|
26
26
|
## 2. Gate protocol (max 5 swarm cycles per gate)
|
|
27
27
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -17,6 +17,35 @@ from typing import Dict, Any, Optional, Tuple
|
|
|
17
17
|
DEFAULT_RESEARCH_DIR = ".research"
|
|
18
18
|
BASE_DIR_HELP = "Base .research directory"
|
|
19
19
|
|
|
20
|
+
#: Historical decision boundary for the fuzzy quote fallback (config
|
|
21
|
+
#: `verify.min_fuzzy_confidence`). This is the default whenever no threshold
|
|
22
|
+
#: is configured or the configured value is malformed.
|
|
23
|
+
DEFAULT_MIN_FUZZY_CONFIDENCE = 0.88
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def coerce_min_fuzzy_confidence(value: Any) -> float:
|
|
27
|
+
"""Coerce a `verify.min_fuzzy_confidence` candidate into [0, 1].
|
|
28
|
+
|
|
29
|
+
SINGLE-ENFORCEMENT-POINT CONTRACT (Phase 05 H5): this function is the one
|
|
30
|
+
definition of the coercion rule — string values are parsed with `float()`
|
|
31
|
+
(so a hand-edited `"0.95"` behaves like `0.95`), the result is clamped
|
|
32
|
+
into [0, 1], and anything malformed (None, non-numeric strings, NaN)
|
|
33
|
+
falls back to `DEFAULT_MIN_FUZZY_CONFIDENCE` (0.88, the strict
|
|
34
|
+
historical boundary). `SourceHasher.verify_quote` enforces it at call
|
|
35
|
+
time; every other layer (MCP `iumbtems_verify_quote`, the auditor, the
|
|
36
|
+
swarm runner) forwards its configured value untouched and must NOT
|
|
37
|
+
re-implement this rule. The persisted schema stays strict
|
|
38
|
+
(`type: number`): coercion exists only for hand-edited files and direct
|
|
39
|
+
library callers, never as an excuse to loosen validation.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
threshold = float(value)
|
|
43
|
+
except (TypeError, ValueError):
|
|
44
|
+
return DEFAULT_MIN_FUZZY_CONFIDENCE
|
|
45
|
+
if threshold != threshold: # NaN: float("nan") parses but matches nothing
|
|
46
|
+
return DEFAULT_MIN_FUZZY_CONFIDENCE
|
|
47
|
+
return max(0.0, min(1.0, threshold))
|
|
48
|
+
|
|
20
49
|
|
|
21
50
|
def _log_retrieval(base_dir, kind: str, **fields) -> None:
|
|
22
51
|
"""Best-effort retrieval telemetry (see runner/retrieval_log.py)."""
|
|
@@ -146,15 +175,11 @@ class SourceHasher:
|
|
|
146
175
|
the historical decision boundary; callers that pass a value opt into a
|
|
147
176
|
stricter/looser match. Exact and normalized matches are unaffected.
|
|
148
177
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
178
|
+
Coercion is NOT done here inline: `coerce_min_fuzzy_confidence` (the
|
|
179
|
+
single enforcement point — see its contract) parses, clamps, and
|
|
180
|
+
defaults the threshold.
|
|
152
181
|
"""
|
|
153
|
-
|
|
154
|
-
threshold = float(min_fuzzy_confidence)
|
|
155
|
-
except (TypeError, ValueError):
|
|
156
|
-
threshold = 0.88
|
|
157
|
-
threshold = max(0.0, min(1.0, threshold))
|
|
182
|
+
threshold = coerce_min_fuzzy_confidence(min_fuzzy_confidence)
|
|
158
183
|
|
|
159
184
|
source_text = self.get_source_content(content_hash)
|
|
160
185
|
if not source_text:
|
|
@@ -122,7 +122,12 @@ and enforced by `iumbtems_config` before any write. The write guard
|
|
|
122
122
|
prefix of **at least 8 hex characters**; conflicting spellings are rejected with
|
|
123
123
|
`CONFLICTING_EXPECTED_HASH`. `heal_config` refuses to persist a file that fails
|
|
124
124
|
canonical validation (the legacy-pin migration stays in memory until the
|
|
125
|
-
invalid values are fixed).
|
|
125
|
+
invalid values are fixed). Non-healing reads (phase-08 R3 truth): no production
|
|
126
|
+
read path calls `heal_config` — `--show` / bare CLI, the MCP `show` leg, and
|
|
127
|
+
`load_config` itself never write. The migration is applied in memory on every
|
|
128
|
+
load and persists opportunistically when a later write leg merges the loaded
|
|
129
|
+
config; `heal_config` remains an exported, tested, explicit repair helper for
|
|
130
|
+
persisting the migration outside any other write.
|
|
126
131
|
|
|
127
132
|
## 4. TUI Settings Surface (OpenCode V2)
|
|
128
133
|
|
|
Binary file
|
|
Binary file
|