harness-everything 0.5.2 → 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.
Files changed (32) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +5 -1
  4. package/BENCHMARK_SOP.md +1 -1
  5. package/CHANGELOG.md +12 -0
  6. package/README.md +3 -3
  7. package/VERIFICATION.md +5 -5
  8. package/behavioral-evals/PAIRED-BENCHMARK.md +186 -0
  9. package/behavioral-evals/README.md +46 -0
  10. package/behavioral-evals/opencode-hardlock-live.js +301 -0
  11. package/behavioral-evals/paired-benchmark.js +781 -0
  12. package/behavioral-evals/run-with-plugin.js +1 -1
  13. package/benchmarks/results/live-host/opencode-2026-09-16/README.md +133 -0
  14. package/benchmarks/results/live-host/opencode-2026-09-16/probe-control.md +88 -0
  15. package/benchmarks/results/live-host/opencode-2026-09-16/run-commands.md +100 -0
  16. package/benchmarks/results/live-host/opencode-2026-09-16/state/sessions/ses_f5520f1d9ffeB8KLTIUKgA5ng8/circuit-breaker.json +16 -0
  17. package/benchmarks/results/live-host/opencode-2026-09-16/state/sessions/ses_f5520f1d9ffeB8KLTIUKgA5ng8/compliance.json +7 -0
  18. package/benchmarks/results/live-host/opencode-2026-09-16/state/sessions/ses_f5520f1d9ffeB8KLTIUKgA5ng8/edit-state.json +10 -0
  19. package/benchmarks/results/live-host/opencode-2026-09-16/state/sessions/ses_f5520f1d9ffeB8KLTIUKgA5ng8/zoom-out-report.md +29 -0
  20. package/ci/doc-capability-consistency.test.js +56 -5
  21. package/ci/mechanism-2w-platform-compatibility.test.js +20 -7
  22. package/ci/mechanism-30-opencode-plugin-loadability.test.js +86 -0
  23. package/ci/mechanism-31-opencode-hardlock-evidence.test.js +138 -0
  24. package/ci/mechanism-32-paired-benchmark.test.js +254 -0
  25. package/docs/architecture.md +2 -2
  26. package/docs/platform-capabilities.md +3 -3
  27. package/docs/platform-compatibility.json +4 -4
  28. package/opencode-plugin/README.md +32 -11
  29. package/opencode-plugin/plugin.json +1 -1
  30. package/package.json +5 -1
  31. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  32. package/plugins/harness-everything/plugin.json +1 -1
@@ -0,0 +1,138 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const helper = require('./test-helper');
4
+ const {
5
+ BLOCKED_MARKER,
6
+ HARDLOCK_MESSAGE,
7
+ INSTALLED_PLUGIN_NAME,
8
+ POST_REFLECTION_MARKER,
9
+ PRELOCK_MARKER,
10
+ verifyEvidence,
11
+ } = require('../behavioral-evals/opencode-hardlock-live');
12
+
13
+ console.log('\n[31] opencode live hard-lock evidence contract...');
14
+
15
+ function writeJson(file, value) {
16
+ fs.mkdirSync(path.dirname(file), { recursive: true });
17
+ fs.writeFileSync(file, JSON.stringify(value, null, 2));
18
+ }
19
+
20
+ function makeEvidence() {
21
+ const dir = helper.tempDir('.mechanism-test-opencode-live-evidence');
22
+ fs.mkdirSync(path.join(dir, 'workspace'), { recursive: true });
23
+ fs.mkdirSync(path.join(dir, 'state'), { recursive: true });
24
+ const pluginHash = 'a'.repeat(64);
25
+ writeJson(path.join(dir, 'metadata.json'), {
26
+ schemaVersion: 1,
27
+ stateIsolated: true,
28
+ installedPluginName: INSTALLED_PLUGIN_NAME,
29
+ pluginSha256: pluginHash,
30
+ installedPluginSha256: pluginHash,
31
+ });
32
+ const toolEvent = (input) => JSON.stringify({
33
+ type: 'tool',
34
+ part: { tool: 'edit', state: { input } },
35
+ });
36
+ fs.writeFileSync(path.join(dir, 'transcript.jsonl'), [
37
+ toolEvent({ filePath: 'probe.txt', text: PRELOCK_MARKER }),
38
+ toolEvent({ filePath: 'probe.txt', text: POST_REFLECTION_MARKER }),
39
+ toolEvent({ filePath: 'probe.txt', text: BLOCKED_MARKER }),
40
+ JSON.stringify({ type: 'text', part: { text: `${HARDLOCK_MESSAGE} after a repeat failure post-reflection.` } }),
41
+ '',
42
+ ].join('\n'));
43
+ fs.writeFileSync(path.join(dir, 'stderr.txt'), '', 'utf8');
44
+ fs.writeFileSync(path.join(dir, 'workspace', 'probe.txt'), `baseline\n${PRELOCK_MARKER}\n${POST_REFLECTION_MARKER}\n`, 'utf8');
45
+ writeJson(path.join(dir, 'state', 'circuit-breaker.json'), {
46
+ failures: {
47
+ 'npm test: HARNESS_LIVE_PROBE_RED': { count: 4, firstSeen: 1, lastSeen: 5 },
48
+ },
49
+ hardLock: true,
50
+ lastReflection: 4,
51
+ lastReflectionSignature: 'npm test: HARNESS_LIVE_PROBE_RED',
52
+ reflectionPending: false,
53
+ reflectionRequestedAt: null,
54
+ reflectionToken: null,
55
+ reflectionSignature: null,
56
+ });
57
+ fs.writeFileSync(path.join(dir, 'state', 'zoom-out-report.md'), [
58
+ '## Goal',
59
+ 'exercise the breaker',
60
+ '## Failed Attempts',
61
+ 'three controlled failures',
62
+ '## Verified Facts',
63
+ 'the verification fixture intentionally stays red',
64
+ '## Diagnosis',
65
+ 'this is a live enforcement probe',
66
+ '## Decision',
67
+ 'RESUME: reproduce the same failure once after reflection',
68
+ 'Reflection token: abc123',
69
+ '',
70
+ ].join('\n'));
71
+ return dir;
72
+ }
73
+
74
+ try {
75
+ const valid = makeEvidence();
76
+ let result = verifyEvidence(valid);
77
+ helper.check('31. complete attributed hard-lock evidence passes', result.passed, JSON.stringify(result.checks.filter((entry) => !entry.pass)));
78
+
79
+ const wrongPlugin = makeEvidence();
80
+ const metadataFile = path.join(wrongPlugin, 'metadata.json');
81
+ const metadata = JSON.parse(fs.readFileSync(metadataFile, 'utf8'));
82
+ metadata.installedPluginSha256 = 'b'.repeat(64);
83
+ writeJson(metadataFile, metadata);
84
+ result = verifyEvidence(wrongPlugin);
85
+ helper.check(
86
+ '31. evidence fails when the installed plugin hash differs from the canonical source hash',
87
+ !result.passed && result.checks.some((entry) => entry.name === 'installed plugin is byte-identical to the recorded canonical source' && !entry.pass),
88
+ JSON.stringify(result.checks),
89
+ );
90
+
91
+ const landed = makeEvidence();
92
+ fs.appendFileSync(path.join(landed, 'workspace', 'probe.txt'), `${BLOCKED_MARKER}\n`);
93
+ result = verifyEvidence(landed);
94
+ helper.check(
95
+ '31. negative control fails if the blocked marker reached the filesystem',
96
+ !result.passed && result.checks.some((entry) => entry.name === 'blocked marker did not reach the filesystem' && !entry.pass),
97
+ JSON.stringify(result.checks),
98
+ );
99
+
100
+ const unlocked = makeEvidence();
101
+ const breakerFile = path.join(unlocked, 'state', 'circuit-breaker.json');
102
+ const breaker = JSON.parse(fs.readFileSync(breakerFile, 'utf8'));
103
+ breaker.hardLock = false;
104
+ writeJson(breakerFile, breaker);
105
+ result = verifyEvidence(unlocked);
106
+ helper.check(
107
+ '31. evidence fails without a pre-reset hard-lock snapshot',
108
+ !result.passed && result.checks.some((entry) => entry.name === 'pre-reset breaker snapshot is hard-locked' && !entry.pass),
109
+ JSON.stringify(result.checks),
110
+ );
111
+
112
+ const noAttempt = makeEvidence();
113
+ const transcript = fs.readFileSync(path.join(noAttempt, 'transcript.jsonl'), 'utf8')
114
+ .split(/\r?\n/)
115
+ .filter((line) => !line.includes(BLOCKED_MARKER))
116
+ .join('\n');
117
+ fs.writeFileSync(path.join(noAttempt, 'transcript.jsonl'), transcript);
118
+ result = verifyEvidence(noAttempt);
119
+ helper.check(
120
+ '31. evidence fails when the final blocked edit was never observed as a structured tool attempt',
121
+ !result.passed && result.checks.some((entry) => entry.name === 'structured tool trace contains the final blocked edit attempt' && !entry.pass),
122
+ JSON.stringify(result.checks),
123
+ );
124
+
125
+ const noReflection = makeEvidence();
126
+ fs.rmSync(path.join(noReflection, 'state', 'zoom-out-report.md'));
127
+ result = verifyEvidence(noReflection);
128
+ helper.check(
129
+ '31. evidence fails without the preserved reflection artifact',
130
+ !result.passed && result.checks.some((entry) => entry.name === 'reflection artifact is valid and preserved' && !entry.pass),
131
+ JSON.stringify(result.checks),
132
+ );
133
+
134
+ helper.finish();
135
+ } catch (error) {
136
+ helper.check('31. OpenCode hard-lock live evidence contract', false, error.stack);
137
+ helper.finish();
138
+ }
@@ -0,0 +1,254 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const helper = require('./test-helper');
7
+ const {
8
+ mcnemarExactP,
9
+ prepareArmWorkspace,
10
+ summarizePairs,
11
+ validatePairRecord,
12
+ validateRunConfig,
13
+ } = require('../behavioral-evals/paired-benchmark');
14
+
15
+ console.log('\n[32] paired behavioral benchmark contract...');
16
+
17
+ function stable(value) {
18
+ if (Array.isArray(value)) return value.map(stable);
19
+ if (!value || typeof value !== 'object') return value;
20
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
21
+ }
22
+
23
+ function hash(value) {
24
+ return crypto.createHash('sha256').update(JSON.stringify(stable(value))).digest('hex');
25
+ }
26
+
27
+ function skillContract(caseId = 'case-a') {
28
+ return {
29
+ schema_version: 1,
30
+ effect_type: 'skill-text',
31
+ engine: 'opencode',
32
+ requested_model: 'test/model',
33
+ case_id: caseId,
34
+ max_turns: 8,
35
+ loaded_skills: ['tdd'],
36
+ fixture_sha256: 'fixture-hash',
37
+ prompt_sha256: 'prompt-hash',
38
+ rubric_sha256: 'rubric-hash',
39
+ execution_policy: 'opencode-auto',
40
+ };
41
+ }
42
+
43
+ function arm(outcome, treatment, overrides = {}) {
44
+ return {
45
+ outcome,
46
+ model_name: 'test/model',
47
+ intervention: {
48
+ skill_text: treatment,
49
+ plugin_enforcement: false,
50
+ },
51
+ loaded_skills: treatment ? ['tdd'] : [],
52
+ plugin_sha256: null,
53
+ plugin_attribution: null,
54
+ cost: treatment ? 2 : 1,
55
+ total_tokens: treatment ? 120 : 100,
56
+ tool_call_count: treatment ? 4 : 3,
57
+ duration_ms: treatment ? 1200 : 1000,
58
+ ...overrides,
59
+ };
60
+ }
61
+
62
+ function skillPair(id, baselineOutcome, treatmentOutcome, overrides = {}) {
63
+ const contract = skillContract(overrides.caseId || id);
64
+ return {
65
+ schema_version: 1,
66
+ id: overrides.caseId || id,
67
+ pair_id: id,
68
+ effect_type: 'skill-text',
69
+ contract,
70
+ contract_sha256: hash(contract),
71
+ arms: {
72
+ baseline: arm(baselineOutcome, false, overrides.baseline || {}),
73
+ treatment: arm(treatmentOutcome, true, overrides.treatment || {}),
74
+ },
75
+ };
76
+ }
77
+
78
+ try {
79
+ const missingThreshold = validateRunConfig({
80
+ effect_type: 'skill-text', engine: 'opencode', model: 'test/model',
81
+ min_effect_pp: NaN, repeats: 1,
82
+ });
83
+ helper.check(
84
+ '32. a paired study cannot start without a predeclared minimum effect',
85
+ missingThreshold.some((problem) => problem.includes('min-effect-pp')),
86
+ missingThreshold.join('; '),
87
+ );
88
+
89
+ const noModel = validateRunConfig({
90
+ effect_type: 'skill-text', engine: 'opencode', model: '',
91
+ min_effect_pp: 10, repeats: 1,
92
+ });
93
+ helper.check(
94
+ '32. an explicit model is required so both arms share the same model contract',
95
+ noModel.some((problem) => problem.includes('explicit --model')),
96
+ noModel.join('; '),
97
+ );
98
+
99
+ const badPluginEngine = validateRunConfig({
100
+ effect_type: 'plugin-enforcement', engine: 'claude', model: 'test/model',
101
+ min_effect_pp: 10, repeats: 1,
102
+ });
103
+ helper.check(
104
+ '32. plugin-enforcement is not generalized beyond the currently attributed OpenCode surface',
105
+ badPluginEngine.some((problem) => problem.includes('opencode only')),
106
+ badPluginEngine.join('; '),
107
+ );
108
+
109
+ const fixtureCase = {
110
+ id: 'workspace-shape',
111
+ discipline: 'tdd',
112
+ fixture: { files: [{ path: 'probe.txt', content: 'baseline\n' }] },
113
+ };
114
+ const skillBaseline = prepareArmWorkspace(fixtureCase, 'skill-text', 'baseline', 'opencode');
115
+ const skillTreatment = prepareArmWorkspace(fixtureCase, 'skill-text', 'treatment', 'opencode');
116
+ helper.check(
117
+ '32. skill-text control contains no Harness skill or plugin',
118
+ !fs.existsSync(path.join(skillBaseline.ws, '.claude', 'skills', 'tdd', 'SKILL.md')) &&
119
+ !fs.existsSync(path.join(skillBaseline.ws, '.opencode', 'plugins', 'harness-enforcement.js')),
120
+ skillBaseline.ws,
121
+ );
122
+ helper.check(
123
+ '32. skill-text treatment installs the named skill without installing hooks/plugin enforcement',
124
+ fs.existsSync(path.join(skillTreatment.ws, '.claude', 'skills', 'tdd', 'SKILL.md')) &&
125
+ !fs.existsSync(path.join(skillTreatment.ws, '.claude', 'settings.json')) &&
126
+ !fs.existsSync(path.join(skillTreatment.ws, '.opencode', 'plugins', 'harness-enforcement.js')),
127
+ skillTreatment.ws,
128
+ );
129
+ fs.rmSync(skillBaseline.ws, { recursive: true, force: true });
130
+ fs.rmSync(skillTreatment.ws, { recursive: true, force: true });
131
+
132
+ const pluginBaseline = prepareArmWorkspace(fixtureCase, 'plugin-enforcement', 'baseline', 'opencode');
133
+ const pluginTreatment = prepareArmWorkspace(fixtureCase, 'plugin-enforcement', 'treatment', 'opencode');
134
+ helper.check(
135
+ '32. plugin experiment holds skill text constant and changes only enforcement installation',
136
+ fs.existsSync(path.join(pluginBaseline.ws, '.claude', 'skills', 'tdd', 'SKILL.md')) &&
137
+ fs.existsSync(path.join(pluginTreatment.ws, '.claude', 'skills', 'tdd', 'SKILL.md')) &&
138
+ !fs.existsSync(path.join(pluginBaseline.ws, '.opencode', 'plugins', 'harness-enforcement.js')) &&
139
+ fs.existsSync(path.join(pluginTreatment.ws, '.opencode', 'plugins', 'harness-enforcement.js')),
140
+ JSON.stringify({ baseline: pluginBaseline.ws, treatment: pluginTreatment.ws }),
141
+ );
142
+ fs.rmSync(pluginBaseline.ws, { recursive: true, force: true });
143
+ fs.rmSync(pluginTreatment.ws, { recursive: true, force: true });
144
+
145
+ const validSkillPair = skillPair('p1', 'fail', 'pass');
146
+ helper.check('32. a correctly isolated skill-text pair is includable', validatePairRecord(validSkillPair).included, JSON.stringify(validatePairRecord(validSkillPair)));
147
+
148
+ const modelMismatch = skillPair('model-mismatch', 'fail', 'pass', {
149
+ treatment: { model_name: 'other/model' },
150
+ });
151
+ helper.check(
152
+ '32. actual model mismatch excludes the pair instead of counting it as behavior',
153
+ !validatePairRecord(modelMismatch).included && validatePairRecord(modelMismatch).reason === 'actual-model-mismatch',
154
+ JSON.stringify(validatePairRecord(modelMismatch)),
155
+ );
156
+
157
+ const pluginContract = {
158
+ schema_version: 1,
159
+ effect_type: 'plugin-enforcement',
160
+ engine: 'opencode',
161
+ requested_model: 'test/model',
162
+ case_id: 'plugin-case',
163
+ max_turns: 8,
164
+ loaded_skills: ['tdd'],
165
+ fixture_sha256: 'fixture-hash',
166
+ prompt_sha256: 'prompt-hash',
167
+ rubric_sha256: 'rubric-hash',
168
+ execution_policy: 'opencode-auto',
169
+ };
170
+ const pluginPair = {
171
+ id: 'plugin-case',
172
+ pair_id: 'plugin-pair',
173
+ effect_type: 'plugin-enforcement',
174
+ contract: pluginContract,
175
+ contract_sha256: hash(pluginContract),
176
+ preflight: { passed: true, plugin_sha256: 'plugin-hash' },
177
+ arms: {
178
+ baseline: {
179
+ ...arm('fail', true),
180
+ intervention: { skill_text: true, plugin_enforcement: false },
181
+ loaded_skills: ['tdd'],
182
+ },
183
+ treatment: {
184
+ ...arm('pass', true),
185
+ intervention: { skill_text: true, plugin_enforcement: true },
186
+ loaded_skills: ['tdd'],
187
+ plugin_sha256: 'plugin-hash',
188
+ plugin_attribution: { fired: true, state_files: ['workspaces/x/state/sessions/y/edit-state.json'] },
189
+ },
190
+ },
191
+ };
192
+ helper.check(
193
+ '32. plugin pair is includable only when preflight, hash, constant skill text, and per-arm attribution agree',
194
+ validatePairRecord(pluginPair).included,
195
+ JSON.stringify(validatePairRecord(pluginPair)),
196
+ );
197
+ const unattributedPlugin = JSON.parse(JSON.stringify(pluginPair));
198
+ unattributedPlugin.pair_id = 'plugin-unattributed';
199
+ unattributedPlugin.arms.treatment.plugin_attribution.fired = false;
200
+ helper.check(
201
+ '32. missing live plugin attribution is infrastructure/inconclusive, not a behavioral result',
202
+ !validatePairRecord(unattributedPlugin).included && validatePairRecord(unattributedPlugin).reason === 'plugin-attribution-missing',
203
+ JSON.stringify(validatePairRecord(unattributedPlugin)),
204
+ );
205
+
206
+ const records = [
207
+ skillPair('a-r1', 'fail', 'pass', { caseId: 'a' }),
208
+ skillPair('a-r2', 'fail', 'pass', { caseId: 'a' }),
209
+ skillPair('b-r1', 'pass', 'fail', { caseId: 'b' }),
210
+ skillPair('c-r1', 'pass', 'pass', { caseId: 'c' }),
211
+ skillPair('infra-r1', 'pass', 'session-error', { caseId: 'infra' }),
212
+ ];
213
+ const summary = summarizePairs(records, 10);
214
+ helper.check(
215
+ '32. summary excludes infrastructure outcomes and reports paired transitions',
216
+ summary.requested_pairs === 5 && summary.included_pairs === 4 && summary.excluded_pairs === 1 &&
217
+ summary.transitions['fail->pass'] === 2 && summary.transitions['pass->fail'] === 1 &&
218
+ summary.transitions['pass->pass'] === 1 && summary.transitions['fail->fail'] === 0,
219
+ JSON.stringify(summary),
220
+ );
221
+ helper.check(
222
+ '32. paired effect is derived from within-pair transitions and carries a deterministic uncertainty interval',
223
+ summary.paired_effect_pp === 25 && Array.isArray(summary.paired_effect_ci95_pp) && summary.paired_effect_ci95_pp.length === 2,
224
+ JSON.stringify(summary),
225
+ );
226
+ helper.check(
227
+ '32. repeated runs remain separate samples and expose per-case variance instead of overwriting',
228
+ summary.repeat_variance.a.repeats === 2 && summary.repeat_variance.a.paired_difference_variance === 0,
229
+ JSON.stringify(summary.repeat_variance),
230
+ );
231
+ helper.check(
232
+ '32. cost and execution overhead are reported separately from correctness',
233
+ summary.cost_and_execution_overhead.cost_delta.mean === 1 &&
234
+ summary.cost_and_execution_overhead.token_delta.mean === 20 &&
235
+ summary.cost_and_execution_overhead.tool_call_delta.mean === 1 &&
236
+ summary.cost_and_execution_overhead.duration_ms_delta.mean === 200,
237
+ JSON.stringify(summary.cost_and_execution_overhead),
238
+ );
239
+ helper.check(
240
+ '32. exact discordant-pair statistic is available without turning it into an unpaired pass-rate verdict',
241
+ mcnemarExactP(2, 1) === 1 && summary.mcnemar_exact_p === 1,
242
+ JSON.stringify({ direct: mcnemarExactP(2, 1), summary: summary.mcnemar_exact_p }),
243
+ );
244
+ helper.check(
245
+ '32. the predeclared practical threshold is retained in the summary decision contract',
246
+ summary.decision.min_effect_pp === 10 && /predeclared/i.test(summary.decision.rule),
247
+ JSON.stringify(summary.decision),
248
+ );
249
+
250
+ helper.finish();
251
+ } catch (error) {
252
+ helper.check('32. paired behavioral benchmark contract', false, error.stack);
253
+ helper.finish();
254
+ }
@@ -100,9 +100,9 @@ The installer configures native lifecycle hooks and project skills.
100
100
 
101
101
  The prompt hook is intentionally lightweight: it does **not** prescribe TODO/TDD/Fable order. It gives the model the rails and lets the model orchestrate itself.
102
102
 
103
- ### 2. OpenCode — plugin enforcement, live loading still unverified
103
+ ### 2. OpenCode — plugin enforcement, scoped live verification
104
104
 
105
- `opencode-plugin/` maps supported enforcement behavior to OpenCode's real plugin API. Source-level/mechanism tests exist, but live plugin loading remains unverified; do not promote source/mechanism evidence into a live-host enforcement claim until a real OpenCode session artifact exists.
105
+ `opencode-plugin/` maps supported enforcement behavior to OpenCode's real plugin API with deterministic mechanism coverage. Partial live-host evidence supports project-scope `.js` loading and edit/verification state on OpenCode 1.18.31 (macOS): [retained evidence](../benchmarks/results/live-host/opencode-2026-09-16/README.md). The final snapshot is post-reset (`hardLock: false`, `count: 1`), not a full enforcement history. Hard lock is only an interactive observation with no retained blocked-tool trace. Reflection was operator-seeded, then agent-rewritten; state-file deletion is an agent-controlled reset, not durable hard enforcement. This does not establish behavioral effectiveness. `.mjs` auto-discovery is broken on this host version (issue #127). Global scope, npm-package installation, and other host versions remain unverified.
106
106
 
107
107
  ### 3. Cursor — skills plus advisory rules
108
108
 
@@ -15,7 +15,7 @@ Statuses are intentionally explicit:
15
15
  - `Unsupported`: official host documentation explicitly rules out the capability.
16
16
  - `Unknown`: the capability or its current host behavior was not established.
17
17
 
18
- All current `liveHostVerification` rows are `Unknown`. This checkout contains deterministic package and installer evidence, but no fresh host-session artifacts, managed-workspace import report, or public OpenAI approval record.
18
+ All `liveHostVerification` rows except OpenCode are `Unknown`. OpenCode's row is `Partial`: retained evidence supports project-scope `.js` loading and edit/verification state on OpenCode 1.18.31 (macOS), not durable hard enforcement. No fresh artifacts exist yet for the other surfaces, managed-workspace import, or public OpenAI approval.
19
19
 
20
20
  ## Current capability summary
21
21
 
@@ -24,7 +24,7 @@ All current `liveHostVerification` rows are `Unknown`. This checkout contains de
24
24
  | Codex | `Supported` | `Mechanism verified` | `Mechanism verified` for the local OpenAI package, including session, prompt, supported-tool, subagent, and stop hooks | `.agents/skills/`, `AGENTS.md` | `~/.agents/skills/` | Local package hooks are mechanically checked; live loading and marketplace refresh remain `Unknown`. |
25
25
  | ChatGPT / OpenAI Plugin | `Unknown` for a repository-local standalone path | `Mechanism verified` | `Partial`: local Work/Codex hooks are separate from ordinary Chat | Managed workspace marketplace | Not established | The public Skills-only bundle is mechanically reproducible; import, approval, and live behavior remain `Unknown`. |
26
26
  | Claude Code | `Supported` | `Supported` | `Mechanism verified` for Harness configuration | `.claude/skills/` | `~/.claude/skills/` | Native Claude surfaces are documented and package/installer checks pass; fresh-host execution is not preserved here. |
27
- | OpenCode | `Supported` | `Mechanism verified` | `Mechanism verified` against `tool.execute.*` and `session.idle` | `.opencode/skills/`, `.agents/skills/`, `.opencode/plugins/` | `~/.config/opencode/skills/`, `~/.agents/skills/`, `~/.config/opencode/plugins/` | The real adapter API is tested, but live plugin loading remains unverified. |
27
+ | OpenCode | `Supported` | `Partial` live-host loading for the project-scope, `.js`-filename install path on OpenCode 1.18.31 (macOS); other install paths remain `Mechanism verified` | `Partial` live-host evidence for project-scope `.js` loading and edit/verification state; hard lock reported only, with no retained blocked-tool trace and a post-reset final snapshot | `.opencode/skills/`, `.agents/skills/`, `.opencode/plugins/` | `~/.config/opencode/skills/`, `~/.agents/skills/`, `~/.config/opencode/plugins/` | [Retained evidence](../benchmarks/results/live-host/opencode-2026-09-16/README.md): loading and state effects verified in one host session; final snapshot post-reset; hard lock only an interactive observation; reflection operator-seeded, then agent-rewritten; no raw transcript or behavioral-effectiveness claim. `.mjs` auto-discovery broken (issue #127); global scope, npm-package installation, and other host versions remain unverified. |
28
28
  | GitHub Copilot agent surfaces | `Supported` | `Unknown` for a Harness-specific plugin install | `Unknown` | `.github/skills/`, `.agents/skills/`, repository instructions | `~/.copilot/skills/`, `~/.agents/skills/` | GitHub Agent Skills paths and installer behavior are checked; no live Copilot session or plugin install is preserved. |
29
29
  | Cursor | `Supported` | `Mechanism verified` for the portable package shape | `Unknown` for Harness | `.cursor/skills/`, `.agents/skills/` | `~/.cursor/skills/`, `~/.agents/skills/` | Official paths and package shape are checked; Cursor plugin loading and hooks remain unverified. |
30
30
  | Continue.dev | `Unknown` for standalone `SKILL.md` discovery | `Unknown` | `Unknown` | `.continue/rules/` is documented; `.continue/skills/` is only an installer candidate | `~/.continue/skills/` is only an installer candidate | The reviewed official docs establish rules, not an Agent Skills host contract. |
@@ -64,7 +64,7 @@ The general installer merges shared files only through Harness-owned markers and
64
64
 
65
65
  ## OpenCode boundary
66
66
 
67
- The OpenCode adapter uses the documented plugin API and has deterministic mechanism coverage. That proves the implementation contract only. **Live plugin loading remains unverified** until a real OpenCode session artifact demonstrates that the host loaded and fired the module.
67
+ The OpenCode adapter uses the documented plugin API and has deterministic mechanism coverage. Retained evidence supports project-scope `.js` loading and edit/verification state on OpenCode 1.18.31 (macOS): [live-host evidence](../benchmarks/results/live-host/opencode-2026-09-16/README.md). The final snapshot is post-reset (`hardLock: false`, `count: 1`), not a history of the enforcement sequence. Hard lock is only an interactive observation with no retained blocked-tool trace. Reflection was operator-seeded, then agent-rewritten. Agent-controlled state deletion resets the breaker, so do not promote this into a durable hard-enforcement or behavioral-effectiveness claim. `.mjs` auto-discovery is broken on this host version (issue #127). Global scope, npm-package installation, and other host versions remain unverified.
68
68
 
69
69
  ## Official sources
70
70
 
@@ -172,15 +172,15 @@
172
172
  ],
173
173
  "capabilities": {
174
174
  "standaloneSkills": {"status": "Supported", "paths": {"project": [".opencode/skills", ".agents/skills", ".claude/skills"], "global": ["~/.config/opencode/skills", "~/.agents/skills", "~/.claude/skills"]}},
175
- "pluginSupport": {"status": "Mechanism verified", "paths": {"project": [".opencode/plugins"], "global": ["~/.config/opencode/plugins"]}},
175
+ "pluginSupport": {"status": "Partial", "paths": {"project": [".opencode/plugins"], "global": ["~/.config/opencode/plugins"]}, "notes": "Live verified scoped: project-scope install of the plugin copied under a .js filename loads on OpenCode 1.18.31 (macOS) and edit/verification state effects are retained (benchmarks/results/live-host/opencode-2026-09-16/README.md). The host discovery silently ignores .mjs install names (issue #127). Global scope and npm-package installation remain mechanism-only."},
176
176
  "bundledSkills": {"status": "Partial", "notes": "Harness provides a standalone enforcement module; it is not an OpenCode bundled-skill package."},
177
- "hooksLifecycle": {"status": "Mechanism verified", "notes": "tool.execute.before, tool.execute.after, and session.idle are covered against the documented plugin API."},
178
- "projectScope": {"status": "Mechanism verified", "paths": {"skills": [".opencode/skills", ".agents/skills"], "plugins": [".opencode/plugins"]}},
177
+ "hooksLifecycle": {"status": "Partial", "notes": "Live-host evidence covers project-scope .js loading and edit/verification state (edit tracking, forced verification, breaker failure recording) on OpenCode 1.18.31 (macOS); hard lock is only an interactive observation with no retained blocked-tool trace and the final snapshot is post-reset (benchmarks/results/live-host/opencode-2026-09-16/README.md). Global scope, npm-package installation, and other host versions remain mechanism-tested."},
178
+ "projectScope": {"status": "Partial", "paths": {"skills": [".opencode/skills", ".agents/skills"], "plugins": [".opencode/plugins"]}, "notes": "Plugin loading at project scope and edit/verification state are live-verified via the retained host session artifact; standalone skill discovery remains path-contract verified."},
179
179
  "globalScope": {"status": "Mechanism verified", "paths": {"skills": ["~/.config/opencode/skills", "~/.agents/skills"], "plugins": ["~/.config/opencode/plugins"]}},
180
180
  "installUninstallSymmetry": {"status": "Unknown", "notes": "The adapter is manually copied; Harness does not own an OpenCode install/uninstall flow."},
181
181
  "assetsReferences": {"status": "Mechanism verified", "notes": "The adapter is self-contained and skill references are separately checked."},
182
182
  "updateSync": {"status": "Unknown", "notes": "No live OpenCode refresh or npm plugin update is preserved."},
183
- "liveHostVerification": {"status": "Unknown", "notes": "The real OpenCode host has not loaded the module in a preserved session artifact."}
183
+ "liveHostVerification": {"status": "Partial", "evidence": "benchmarks/results/live-host/opencode-2026-09-16/README.md", "notes": "Retained evidence supports project-scope .js plugin loading and edit/verification state on OpenCode 1.18.31 (macOS), not durable hard enforcement: final snapshot is post-reset (hardLock: false), hard lock only an interactive observation with no retained blocked-tool trace, reflection operator-seeded, agent-controlled state deletion resets the breaker. Global scope, npm-package installation, and other host versions remain unverified."}
184
184
  }
185
185
  },
186
186
  {
@@ -3,7 +3,7 @@
3
3
  Adds hard enforcement gates for Harness skills in opencode, addressing the
4
4
  limitation that skills are otherwise advisory-only on that platform.
5
5
 
6
- > **Evidence boundary:** the plugin is implemented against opencode's real plugin API and covered by deterministic mechanism tests. **Live opencode plugin loading remains unverified** until a real host session artifact proves the host loaded and fired these hooks. See [`../docs/platform-capabilities.md`](../docs/platform-capabilities.md).
6
+ > **Evidence boundary:** the plugin is implemented against opencode's real plugin API and covered by deterministic mechanism tests. Live plugin loading remains unverified beyond one scoped surface: retained evidence supports project-scope `.js` loading and edit/verification state on OpenCode 1.18.31 (macOS) — [live-host evidence](../benchmarks/results/live-host/opencode-2026-09-16/README.md). The final snapshot is post-reset (`hardLock: false`, `count: 1`), not a history of the enforcement sequence. Hard lock is only an interactive observation with no retained blocked-tool trace. Reflection was operator-seeded, then agent-rewritten; agent-controlled state deletion resets the breaker, so no durable hard enforcement or behavioral effectiveness is claimed. Global scope, npm-package installation, and other host versions remain unverified. See [`../docs/platform-capabilities.md`](../docs/platform-capabilities.md).
7
7
 
8
8
  ## Problem
9
9
 
@@ -20,15 +20,18 @@ actually loads: a JS/TS file exporting a function that returns a hooks
20
20
  object (see https://opencode.ai/docs/plugins/). There is no manifest-to-script
21
21
  mechanism; an earlier version of this plugin assumed one (a `plugin.json`
22
22
  mapping event names to standalone scripts) and opencode never invoked it -
23
- see issue #37 for how that was found and fixed.
23
+ see issue #37 for how that was found and fixed. **Naming caveat (issue
24
+ #127):** opencode's discovery glob only matches `*.js`/`*.ts`, so the file
25
+ must be copied to a `.js` name when installed - see Installation below.
24
26
 
25
27
  It implements three enforcement mechanisms across opencode's real hooks:
26
28
 
27
29
  The module follows opencode's V1 plugin contract directly: the named
28
30
  `HarnessEnforcement` export is an async factory receiving the opencode context
29
31
  (`client`, `directory`, `project`, `worktree`, and `$`) and returning the hook
30
- map. Copying this one `.mjs` file into `.opencode/plugins/` is enough for
31
- opencode to discover and invoke it; `plugin.json` is only a repository
32
+ map. Copying this one file into `.opencode/plugins/` under a `.js` name is
33
+ enough for opencode to discover and invoke it; `plugin.json` is only a
34
+ repository
32
35
  inventory and is not an opencode runtime manifest. The hook callbacks use the
33
36
  V1 `(input, output)` shape, including
34
37
  `output.args` when the host supplies tool arguments only in the result object.
@@ -73,18 +76,30 @@ new breaker failure. A reflection artifact itself does not count as a code edit.
73
76
 
74
77
  ## Installation
75
78
 
76
- Copy `index.mjs` into opencode's plugin directory - it is self-contained, no
77
- sibling files required:
79
+ Copy `index.mjs` into opencode's plugin directory **under a `.js` filename** -
80
+ it is self-contained, no sibling files required:
78
81
 
79
82
  ```bash
80
- cp opencode-plugin/index.mjs .opencode/plugins/harness-enforcement.mjs # project-level
83
+ mkdir -p .opencode/plugins
84
+ cp opencode-plugin/index.mjs .opencode/plugins/harness-enforcement.js # project-level
81
85
  # or
82
- cp opencode-plugin/index.mjs ~/.config/opencode/plugins/harness-enforcement.mjs # global
86
+ mkdir -p ~/.config/opencode/plugins
87
+ cp opencode-plugin/index.mjs ~/.config/opencode/plugins/harness-enforcement.js # global
83
88
  ```
84
89
 
85
- opencode auto-loads any file dropped in those directories at startup - no
86
- `opencode.json` entry needed. (`opencode.json`'s `plugin` array is for npm
87
- package names, not local file paths.)
90
+ Restart OpenCode after copying so the plugin is discovered at startup.
91
+
92
+ The destination extension matters: opencode's plugin auto-discovery scans
93
+ only `*.js` / `*.ts` files in those directories (`{plugin,plugins}/*.{ts,js}`
94
+ in its v1 loader), so a `.mjs` copy is **silently never loaded** - no error,
95
+ no hooks, no state. Verified live on opencode 1.18.31 with an extension A/B
96
+ probe (see
97
+ [`benchmarks/results/live-host/opencode-2026-09-16/probe-control.md`](../benchmarks/results/live-host/opencode-2026-09-16/probe-control.md)).
98
+
99
+ opencode auto-loads `.js`/`.ts` files dropped in those directories at startup -
100
+ no `opencode.json` entry needed. This procedure tests directory discovery;
101
+ explicit local-file configuration and npm-package installation are separate
102
+ installation surfaces and were not verified in this run.
88
103
 
89
104
  ## Manual verification CLI
90
105
 
@@ -135,3 +150,9 @@ fail-closed behavior, and patch-based reflection writes.
135
150
  It does not launch a real opencode process; the test remains a deterministic
136
151
  hook-sequence check against the documented and source-verified hook signatures.
137
152
  A passing test is therefore mechanism evidence, not live-host loading evidence.
153
+ Live-host evidence and the install-filename loadability guard live elsewhere:
154
+ the host session artifact is
155
+ [`benchmarks/results/live-host/opencode-2026-09-16/`](../benchmarks/results/live-host/opencode-2026-09-16/),
156
+ and `ci/mechanism-30-opencode-plugin-loadability.test.js` (npm script
157
+ `test:opencode:loadability`) fails any change that reintroduces a
158
+ silently-ignored install filename such as `harness-enforcement.mjs`.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.5.2",
4
+ "version": "0.6.0",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
5
5
  "main": "harness-everything/scripts/bootstrap.js",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  "test": "node self-evolve/scripts/self-regression.js",
11
11
  "self-regression": "node self-evolve/scripts/self-regression.js",
12
12
  "test:mechanism": "node ci/mechanism-test.js",
13
+ "test:opencode:loadability": "node ci/mechanism-30-opencode-plugin-loadability.test.js",
13
14
  "test:mutations": "node ci/mutation-check.js",
14
15
  "test:tdd-quality": "node ci/mechanism-2s-tdd-quality.test.js",
15
16
  "tdd:quality": "node tdd/scripts/quality-gate.js",
@@ -39,6 +40,9 @@
39
40
  "eval:behavioral": "node behavioral-evals/run.js run",
40
41
  "eval:behavioral:validate": "node behavioral-evals/run.js validate",
41
42
  "eval:behavioral:plugin": "node behavioral-evals/run-with-plugin.js run",
43
+ "eval:opencode:hardlock-live": "node behavioral-evals/opencode-hardlock-live.js run",
44
+ "eval:paired": "node behavioral-evals/paired-benchmark.js run",
45
+ "eval:paired:summarize": "node behavioral-evals/paired-benchmark.js summarize",
42
46
  "test:ab": "node ci/ab-test-harness.js validate",
43
47
  "test:ab:run": "node ci/ab-test-harness.js run",
44
48
  "harness:reset": "node hooks/scripts/reset-circuit-breaker.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "harness-everything",
4
- "version": "0.5.2",
4
+ "version": "0.6.0",
5
5
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
6
6
  "author": {
7
7
  "name": "Miya Daniel",