harness-everything 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/ci/mechanism-2z-claude-plugin-manifest.test.js +109 -5
- package/docs/claude-plugin-certification.md +178 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +4 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/scripts/stage-claude-plugin.js +79 -0
- package/scripts/validate-claude-plugin.js +72 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
|
+
"description": "Harness Everything plugin marketplace for Claude Code.",
|
|
3
4
|
"owner": {
|
|
4
5
|
"name": "dyphn1",
|
|
5
6
|
"url": "https://github.com/dyphn1"
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
"name": "harness-everything",
|
|
10
11
|
"source": "./",
|
|
11
12
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
12
|
-
"version": "0.
|
|
13
|
+
"version": "0.5.0",
|
|
13
14
|
"author": {
|
|
14
15
|
"name": "Miya Daniel"
|
|
15
16
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
3
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.0](https://github.com/dyphn1/Harness-everything/compare/v0.4.4...v0.5.0) (2026-09-16)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **claude:** add plugin-only staging helper ([64e619b](https://github.com/dyphn1/Harness-everything/commit/64e619ba1add2678fac26152fe9e95a82517c03b))
|
|
10
|
+
* **claude:** add strict host validation helper ([8d4ba18](https://github.com/dyphn1/Harness-everything/commit/8d4ba18396bd9ed305f26849f937f3e4e0d4d1ad))
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **claude:** add strict marketplace description ([61129e5](https://github.com/dyphn1/Harness-everything/commit/61129e5078352ad8e0af343cba10255e9d31a0f5))
|
|
15
|
+
* **claude:** correct hook validation diagnostic ([dd6681b](https://github.com/dyphn1/Harness-everything/commit/dd6681b5a073dcef19c17c9eda7ffbeb1cc5687f))
|
|
16
|
+
* **claude:** keep staging output outside checkout ([10ac0da](https://github.com/dyphn1/Harness-everything/commit/10ac0da0c0e62537490d4051bb20c9095c601831))
|
|
17
|
+
|
|
5
18
|
## [0.4.4](https://github.com/dyphn1/Harness-everything/compare/v0.4.3...v0.4.4) (2026-09-16)
|
|
6
19
|
|
|
7
20
|
### Bug Fixes
|
|
@@ -2,28 +2,132 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('assert');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const projectRoot = path.resolve(__dirname, '..');
|
|
8
9
|
const manifestPath = path.join(projectRoot, '.claude-plugin', 'plugin.json');
|
|
10
|
+
const marketplacePath = path.join(projectRoot, '.claude-plugin', 'marketplace.json');
|
|
9
11
|
const conventionalHooksPath = path.join(projectRoot, 'hooks', 'hooks.json');
|
|
10
12
|
|
|
13
|
+
function insideProject(target) {
|
|
14
|
+
const relative = path.relative(projectRoot, target);
|
|
15
|
+
return relative !== '' && !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolvePluginPath(reference, label) {
|
|
19
|
+
assert.strictEqual(typeof reference, 'string', `${label} must be a string path`);
|
|
20
|
+
assert.ok(reference.startsWith('./'), `${label} must be explicitly plugin-root relative: ${reference}`);
|
|
21
|
+
const resolved = path.resolve(projectRoot, reference);
|
|
22
|
+
assert.ok(insideProject(resolved), `${label} must stay inside the plugin root: ${reference}`);
|
|
23
|
+
return resolved;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateCommandTarget(command, label) {
|
|
27
|
+
assert.strictEqual(typeof command, 'string', `${label} command must be a string`);
|
|
28
|
+
|
|
29
|
+
// Harness deliberately uses direct Node hook commands. This avoids the known
|
|
30
|
+
// Windows shell-wrapper/path failure class while keeping plugin-root paths portable.
|
|
31
|
+
assert.match(
|
|
32
|
+
command,
|
|
33
|
+
/^node\s+["']?\$\{CLAUDE_PLUGIN_ROOT\}\//,
|
|
34
|
+
`${label} must invoke Node directly from \${CLAUDE_PLUGIN_ROOT}: ${command}`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const targetPattern = /\$\{CLAUDE_PLUGIN_ROOT\}\/([^"'\s]+)/g;
|
|
38
|
+
const targets = [...command.matchAll(targetPattern)].map(match => match[1]);
|
|
39
|
+
assert.ok(targets.length > 0, `${label} must reference at least one plugin-root target`);
|
|
40
|
+
|
|
41
|
+
for (const relativeTarget of targets) {
|
|
42
|
+
const resolved = path.resolve(projectRoot, relativeTarget);
|
|
43
|
+
assert.ok(insideProject(resolved), `${label} target escapes plugin root: ${relativeTarget}`);
|
|
44
|
+
assert.ok(fs.existsSync(resolved), `${label} references missing file: ${relativeTarget}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
11
48
|
assert.ok(fs.existsSync(manifestPath), '.claude-plugin/plugin.json must exist');
|
|
49
|
+
assert.ok(fs.existsSync(marketplacePath), '.claude-plugin/marketplace.json must exist');
|
|
12
50
|
assert.ok(fs.existsSync(conventionalHooksPath), 'hooks/hooks.json must remain available for convention-based discovery');
|
|
13
51
|
|
|
14
52
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
15
|
-
const
|
|
53
|
+
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
|
|
54
|
+
const hookConfig = JSON.parse(fs.readFileSync(conventionalHooksPath, 'utf8'));
|
|
55
|
+
|
|
56
|
+
assert.ok(
|
|
57
|
+
typeof marketplace.description === 'string' && marketplace.description.trim().length > 0,
|
|
58
|
+
'Claude marketplace must have a top-level description so strict host validation does not regress'
|
|
59
|
+
);
|
|
16
60
|
|
|
17
|
-
|
|
18
|
-
|
|
61
|
+
const marketplaceEntry = marketplace.plugins.find(entry => entry && entry.name === manifest.name);
|
|
62
|
+
assert.ok(marketplaceEntry, `marketplace must contain plugin entry ${manifest.name}`);
|
|
63
|
+
assert.strictEqual(marketplaceEntry.source, './', 'Claude marketplace entry must continue to point at the repository plugin root');
|
|
64
|
+
assert.strictEqual(marketplaceEntry.version, manifest.version, 'Claude marketplace and plugin manifest versions must stay synchronized');
|
|
65
|
+
assert.ok(
|
|
66
|
+
!Object.prototype.hasOwnProperty.call(marketplaceEntry, 'hooks'),
|
|
67
|
+
'Do not duplicate hook component registration in the marketplace entry; use hooks/hooks.json convention discovery'
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const manifestHooks = manifest.hooks;
|
|
71
|
+
if (manifestHooks !== undefined) {
|
|
72
|
+
const refs = Array.isArray(manifestHooks) ? manifestHooks : [manifestHooks];
|
|
19
73
|
const normalized = refs
|
|
20
74
|
.filter(ref => typeof ref === 'string')
|
|
21
75
|
.map(ref => ref.replace(/\\/g, '/').replace(/^\.\//, ''));
|
|
22
76
|
|
|
23
77
|
assert.ok(
|
|
24
78
|
!normalized.includes('hooks/hooks.json'),
|
|
25
|
-
'Do not explicitly register hooks/hooks.json in .claude-plugin/plugin.json; Claude Code auto-discovers the conventional hook file and duplicate registration
|
|
79
|
+
'Do not explicitly register hooks/hooks.json in .claude-plugin/plugin.json; Claude Code auto-discovers the conventional hook file and duplicate registration has caused plugin-load regressions.'
|
|
26
80
|
);
|
|
27
81
|
}
|
|
28
82
|
|
|
29
|
-
|
|
83
|
+
assert.ok(Array.isArray(manifest.skills) && manifest.skills.length > 0, 'Claude plugin must declare packaged skills');
|
|
84
|
+
for (const skillRef of manifest.skills) {
|
|
85
|
+
const skillDir = resolvePluginPath(skillRef, `skill ${skillRef}`);
|
|
86
|
+
assert.ok(fs.statSync(skillDir).isDirectory(), `skill path must be a directory: ${skillRef}`);
|
|
87
|
+
assert.ok(fs.existsSync(path.join(skillDir, 'SKILL.md')), `skill path must contain SKILL.md: ${skillRef}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
assert.ok(Array.isArray(manifest.agents) && manifest.agents.length > 0, 'Claude plugin must declare packaged agents');
|
|
91
|
+
for (const agentRef of manifest.agents) {
|
|
92
|
+
const agentPath = resolvePluginPath(agentRef, `agent ${agentRef}`);
|
|
93
|
+
assert.ok(fs.statSync(agentPath).isFile(), `agent path must exist as a file: ${agentRef}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
assert.ok(hookConfig && typeof hookConfig.hooks === 'object', 'hooks/hooks.json must contain a hooks object');
|
|
97
|
+
let commandHookCount = 0;
|
|
98
|
+
for (const [eventName, registrations] of Object.entries(hookConfig.hooks)) {
|
|
99
|
+
assert.ok(Array.isArray(registrations), `${eventName} registrations must be an array`);
|
|
100
|
+
for (const registration of registrations) {
|
|
101
|
+
assert.ok(Array.isArray(registration.hooks), `${eventName} registration must contain a hooks array`);
|
|
102
|
+
for (const hook of registration.hooks) {
|
|
103
|
+
if (hook.type !== 'command') continue;
|
|
104
|
+
commandHookCount += 1;
|
|
105
|
+
validateCommandTarget(hook.command, `${eventName}/${registration.id || 'unnamed'}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
assert.ok(commandHookCount > 0, 'Claude plugin should contain command hooks to validate');
|
|
110
|
+
|
|
111
|
+
// Negative control for anthropics/claude-code#77912: the host validator has
|
|
112
|
+
// historically accepted dangling hook command targets, so the repository gate must not.
|
|
113
|
+
assert.throws(
|
|
114
|
+
() => validateCommandTarget('node "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/DOES-NOT-EXIST.js"', 'negative-control'),
|
|
115
|
+
/references missing file/,
|
|
116
|
+
'missing hook command targets must fail repository validation'
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Ensure the optional staging helper can produce a plugin-only tree for host validation
|
|
120
|
+
// without a marketplace file masking per-plugin manifest problems.
|
|
121
|
+
const { stageClaudePlugin } = require('../scripts/stage-claude-plugin');
|
|
122
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-claude-plugin-'));
|
|
123
|
+
const stagedRoot = path.join(tempRoot, 'plugin');
|
|
124
|
+
try {
|
|
125
|
+
stageClaudePlugin(stagedRoot, { quiet: true });
|
|
126
|
+
assert.ok(fs.existsSync(path.join(stagedRoot, '.claude-plugin', 'plugin.json')), 'staged plugin must contain plugin.json');
|
|
127
|
+
assert.ok(fs.existsSync(path.join(stagedRoot, 'hooks', 'hooks.json')), 'staged plugin must contain hooks/hooks.json');
|
|
128
|
+
assert.ok(!fs.existsSync(path.join(stagedRoot, '.claude-plugin', 'marketplace.json')), 'plugin-only stage must exclude marketplace.json');
|
|
129
|
+
} finally {
|
|
130
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(`PASS: Claude plugin manifest, marketplace, ${manifest.skills.length} skills, ${manifest.agents.length} agents, and ${commandHookCount} hook commands are internally consistent.`);
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Claude Code plugin certification
|
|
2
|
+
|
|
3
|
+
This document separates **repository/mechanism validation** from **live Claude Code evidence** for the Harness Everything plugin.
|
|
4
|
+
|
|
5
|
+
## Current design boundary
|
|
6
|
+
|
|
7
|
+
Harness uses Claude Code's conventional plugin hook location:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
hooks/hooks.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Do not add `"hooks": "./hooks/hooks.json"` to `.claude-plugin/plugin.json` or to the marketplace entry. Current Claude Code documentation explicitly supports convention-based discovery from `hooks/hooks.json`, and Harness already had a duplicate-registration load failure when the conventional file was also registered explicitly.
|
|
14
|
+
|
|
15
|
+
Harness command hooks intentionally invoke Node directly through `${CLAUDE_PLUGIN_ROOT}` rather than routing through `.sh`, PowerShell, or another shell wrapper. This reduces Windows-specific hook-dispatch/path problems reported upstream.
|
|
16
|
+
|
|
17
|
+
## What repository validation proves
|
|
18
|
+
|
|
19
|
+
Run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run test:plugin:claude
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The repository gate verifies:
|
|
26
|
+
|
|
27
|
+
- the marketplace has current strict-validation metadata;
|
|
28
|
+
- marketplace and plugin versions agree;
|
|
29
|
+
- the plugin does not duplicate-register the conventional hook file;
|
|
30
|
+
- every declared skill path exists and contains `SKILL.md`;
|
|
31
|
+
- every declared agent file exists;
|
|
32
|
+
- every command hook uses a direct Node + `${CLAUDE_PLUGIN_ROOT}` form;
|
|
33
|
+
- every plugin-root file referenced by a command hook exists;
|
|
34
|
+
- a negative control proves a missing hook target fails the repository gate.
|
|
35
|
+
|
|
36
|
+
This last check is intentional because Claude Code issue `anthropics/claude-code#77912` reports that `claude plugin validate` can accept a hook command whose target file does not exist.
|
|
37
|
+
|
|
38
|
+
## Strict host validation
|
|
39
|
+
|
|
40
|
+
When Claude Code is installed, run:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run plugin:claude:validate
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The helper runs the repository gate, then performs two separate strict host validations:
|
|
47
|
+
|
|
48
|
+
1. the repository marketplace;
|
|
49
|
+
2. a temporary **plugin-only** staged tree with `.claude-plugin/marketplace.json` removed.
|
|
50
|
+
|
|
51
|
+
The second validation prevents a marketplace-level success from being mistaken for validation of the plugin manifest itself. This is also a regression boundary for the historical `anthropics/claude-code#60725` validator behavior.
|
|
52
|
+
|
|
53
|
+
`--json` validation output requires Claude Code 2.1.259 or later. `claude plugin list --json` diagnostics are richer on 2.1.268 or later.
|
|
54
|
+
|
|
55
|
+
To inspect the staged plugin manually:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run plugin:claude:stage
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
By default this writes a temporary staged tree under the operating-system temp directory and excludes only the marketplace manifest.
|
|
62
|
+
|
|
63
|
+
## Known upstream constraints that affect live testing
|
|
64
|
+
|
|
65
|
+
### Directory-source marketplace hooks
|
|
66
|
+
|
|
67
|
+
`anthropics/claude-code#86809` reports a reproducible case where a plugin installed from a **directory-source marketplace** is enabled and cached but its bundled hooks never run, while the same plugin installed from a GitHub-source marketplace works.
|
|
68
|
+
|
|
69
|
+
Therefore:
|
|
70
|
+
|
|
71
|
+
- use `--plugin-dir <plugin-only-stage>` only as a direct development/load probe;
|
|
72
|
+
- use a **GitHub-source marketplace install** as the distribution/live-hook certification path;
|
|
73
|
+
- do not treat a directory-source marketplace failure as a Harness runtime defect without reproducing it through the GitHub-source path.
|
|
74
|
+
|
|
75
|
+
### Validator does not prove hook targets execute
|
|
76
|
+
|
|
77
|
+
A green `claude plugin validate` result is schema/mechanism evidence only. It is not proof that hooks were registered or fired. Preserve observable runtime evidence from a fresh session.
|
|
78
|
+
|
|
79
|
+
### Windows shell-wrapper issues
|
|
80
|
+
|
|
81
|
+
Several upstream reports describe `.sh`, Bash/PowerShell wrapper, path-format, or shell-spawn problems for plugin hooks on Windows. Harness command hooks use direct Node invocations, so certification should retain that shape unless a future change has explicit Windows evidence.
|
|
82
|
+
|
|
83
|
+
## Narrow live-certification checklist
|
|
84
|
+
|
|
85
|
+
Do this after the implementation PR is merged and released.
|
|
86
|
+
|
|
87
|
+
### A. Record environment
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
claude --version
|
|
91
|
+
node --version
|
|
92
|
+
npm --version
|
|
93
|
+
git rev-parse HEAD
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Record Windows version as well.
|
|
97
|
+
|
|
98
|
+
### B. Mechanism/schema checks
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm run test:plugin:claude
|
|
102
|
+
npm run plugin:claude:validate
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Both must pass. These checks still do **not** make the platform `Live verified`.
|
|
106
|
+
|
|
107
|
+
### C. Direct plugin-only development probe
|
|
108
|
+
|
|
109
|
+
Create the stage:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run plugin:claude:stage
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Start a fresh Claude Code session using the printed directory:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
claude --plugin-dir <staged-plugin-directory>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
In another terminal, this form can inspect a session-only plugin:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
claude --plugin-dir <staged-plugin-directory> plugin list --json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Verify the plugin has no `errors` and no unexpected `notes`, and confirm the component inventory includes representative skills, agents, and hooks.
|
|
128
|
+
|
|
129
|
+
### D. GitHub-source marketplace certification
|
|
130
|
+
|
|
131
|
+
Use the repository's normal GitHub marketplace install/update path, not a directory-source marketplace.
|
|
132
|
+
|
|
133
|
+
After installation or update:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
claude plugin list --json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Then start a **fresh session**. Preserve evidence for:
|
|
140
|
+
|
|
141
|
+
1. the installed plugin version/source;
|
|
142
|
+
2. a representative bundled skill being discoverable without a separate standalone skill install;
|
|
143
|
+
3. `SessionStart` / `UserPromptSubmit` Harness behavior being observable;
|
|
144
|
+
4. a harmless tool call such as `git status` running normally;
|
|
145
|
+
5. a safe destructive **no-op** probe producing the expected Harness audit/gate behavior without changing real data;
|
|
146
|
+
6. restart/update/reload retaining plugin discovery.
|
|
147
|
+
|
|
148
|
+
Use `claude --debug` if component or hook registration is unclear; current official documentation says debug output includes plugin loading and skill/agent/hook registration.
|
|
149
|
+
|
|
150
|
+
## Permission-mode scope
|
|
151
|
+
|
|
152
|
+
Harness's Claude action gate now defers matched actions to Claude Code's native permission flow by default rather than forcing its own `ask` decision. Claude Code itself owns the semantics of `default`, `acceptEdits`, `plan`, `auto`, `dontAsk`, and `bypassPermissions`.
|
|
153
|
+
|
|
154
|
+
Because of that boundary, Claude certification does **not** need to re-prove every host permission mode before Harness can establish that its hook loaded and deferred correctly. The live minimum is:
|
|
155
|
+
|
|
156
|
+
- one normal/manual-style session;
|
|
157
|
+
- one `auto` session where available;
|
|
158
|
+
- observable Harness audit evidence showing the hook ran without silently authorizing the command.
|
|
159
|
+
|
|
160
|
+
Additional permission-mode testing is useful as a host-regression matrix, but it is not a prerequisite for validating Harness plugin discovery.
|
|
161
|
+
|
|
162
|
+
## Evidence levels
|
|
163
|
+
|
|
164
|
+
- **Repository verified**: Harness static/mechanism checks pass.
|
|
165
|
+
- **Host schema verified**: current `claude plugin validate --strict --json` passes for marketplace and plugin-only stage.
|
|
166
|
+
- **Live verified**: a fresh real Claude Code session proves bundled components loaded and the relevant hooks actually fired.
|
|
167
|
+
|
|
168
|
+
Never promote repository or schema evidence to `Live verified` without the live-session artifacts above.
|
|
169
|
+
|
|
170
|
+
## References
|
|
171
|
+
|
|
172
|
+
- Claude Code plugins reference: https://code.claude.com/docs/en/plugins-reference
|
|
173
|
+
- Claude Code plugin marketplaces: https://code.claude.com/docs/en/plugin-marketplaces
|
|
174
|
+
- Claude Code permissions: https://code.claude.com/docs/en/permissions
|
|
175
|
+
- Directory-source marketplace hook issue: https://github.com/anthropics/claude-code/issues/86809
|
|
176
|
+
- Missing hook target validator issue: https://github.com/anthropics/claude-code/issues/77912
|
|
177
|
+
- Historical per-plugin validation gap: https://github.com/anthropics/claude-code/issues/60725
|
|
178
|
+
- Windows hook wrapper issue: https://github.com/anthropics/claude-code/issues/54772
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": {
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"test:routing": "node ci/routing-check.js",
|
|
27
27
|
"test:routing:invariants": "node ci/invariant-routing.test.js && node ci/router-workflow-plan.test.js",
|
|
28
28
|
"plugin:sync": "node scripts/sync-openai-plugin.js",
|
|
29
|
+
"test:plugin:claude": "node ci/mechanism-2z-claude-plugin-manifest.test.js",
|
|
30
|
+
"plugin:claude:stage": "node scripts/stage-claude-plugin.js",
|
|
31
|
+
"plugin:claude:validate": "node scripts/validate-claude-plugin.js",
|
|
29
32
|
"test:plugin:openai": "node ci/openai-plugin-package.test.js && node ci/openai-plugin-runtime.test.js",
|
|
30
33
|
"plugin:submission:build": "node scripts/build-openai-submission.js",
|
|
31
34
|
"test:plugin:submission": "node ci/openai-plugin-submission.test.js",
|
|
@@ -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.
|
|
4
|
+
"version": "0.5.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",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const MARKETPLACE_PATH = '.claude-plugin/marketplace.json';
|
|
10
|
+
|
|
11
|
+
function containsPath(parent, target) {
|
|
12
|
+
const relative = path.relative(parent, target);
|
|
13
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function trackedFiles() {
|
|
17
|
+
const output = execFileSync('git', ['ls-files', '-z'], {
|
|
18
|
+
cwd: projectRoot,
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return output.split('\0').filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stageClaudePlugin(outputDir, options = {}) {
|
|
27
|
+
const outputRoot = path.resolve(outputDir);
|
|
28
|
+
|
|
29
|
+
// The staging operation replaces its destination recursively. Keep it entirely
|
|
30
|
+
// outside the checkout so an accidental argument can never delete repo content.
|
|
31
|
+
if (containsPath(projectRoot, outputRoot) || containsPath(outputRoot, projectRoot)) {
|
|
32
|
+
throw new Error(`Refusing to stage inside the repository or one of its parent directories: ${outputRoot}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fs.rmSync(outputRoot, { recursive: true, force: true });
|
|
36
|
+
fs.mkdirSync(outputRoot, { recursive: true });
|
|
37
|
+
|
|
38
|
+
let copied = 0;
|
|
39
|
+
for (const relativePath of trackedFiles()) {
|
|
40
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
41
|
+
if (normalized === MARKETPLACE_PATH) continue;
|
|
42
|
+
|
|
43
|
+
const source = path.join(projectRoot, relativePath);
|
|
44
|
+
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) continue;
|
|
45
|
+
|
|
46
|
+
const destination = path.join(outputRoot, relativePath);
|
|
47
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
48
|
+
fs.copyFileSync(source, destination);
|
|
49
|
+
copied += 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pluginManifest = path.join(outputRoot, '.claude-plugin', 'plugin.json');
|
|
53
|
+
const hookManifest = path.join(outputRoot, 'hooks', 'hooks.json');
|
|
54
|
+
if (!fs.existsSync(pluginManifest) || !fs.existsSync(hookManifest)) {
|
|
55
|
+
fs.rmSync(outputRoot, { recursive: true, force: true });
|
|
56
|
+
throw new Error('Staged Claude plugin is incomplete: plugin.json or hooks/hooks.json is missing');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!options.quiet) {
|
|
60
|
+
console.log(`Staged ${copied} tracked files for Claude plugin validation at: ${outputRoot}`);
|
|
61
|
+
console.log('Excluded .claude-plugin/marketplace.json so host validation targets the plugin itself.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return outputRoot;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (require.main === module) {
|
|
68
|
+
const outputDir = process.argv[2] || path.join(os.tmpdir(), 'harness-everything-claude-plugin');
|
|
69
|
+
try {
|
|
70
|
+
stageClaudePlugin(outputDir);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(error.message || error);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
stageClaudePlugin,
|
|
79
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { stageClaudePlugin } = require('./stage-claude-plugin');
|
|
8
|
+
|
|
9
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
10
|
+
const staticTest = path.join(projectRoot, 'ci', 'mechanism-2z-claude-plugin-manifest.test.js');
|
|
11
|
+
|
|
12
|
+
function run(command, args, options = {}) {
|
|
13
|
+
const result = spawnSync(command, args, {
|
|
14
|
+
cwd: options.cwd || projectRoot,
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
shell: process.platform === 'win32' && command === 'claude',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
20
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requireSuccess(result, label) {
|
|
25
|
+
if (result.error) {
|
|
26
|
+
throw new Error(`${label} failed to start: ${result.error.message}`);
|
|
27
|
+
}
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
throw new Error(`${label} exited with status ${result.status}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function main() {
|
|
34
|
+
requireSuccess(run(process.execPath, [staticTest]), 'repository Claude plugin validation');
|
|
35
|
+
|
|
36
|
+
const version = run('claude', ['--version']);
|
|
37
|
+
if (version.error || version.status !== 0) {
|
|
38
|
+
console.error('INCONCLUSIVE: Claude Code CLI is unavailable or failed to start. Static repository validation passed, but host validation did not run.');
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-claude-cert-'));
|
|
43
|
+
const stagedPlugin = path.join(tempRoot, 'plugin');
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
stageClaudePlugin(stagedPlugin, { quiet: true });
|
|
47
|
+
|
|
48
|
+
console.log('\n[1/2] Strict marketplace validation');
|
|
49
|
+
requireSuccess(
|
|
50
|
+
run('claude', ['plugin', 'validate', projectRoot, '--strict', '--json']),
|
|
51
|
+
'Claude marketplace validation'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
console.log('\n[2/2] Strict plugin-only validation');
|
|
55
|
+
requireSuccess(
|
|
56
|
+
run('claude', ['plugin', 'validate', stagedPlugin, '--strict', '--json']),
|
|
57
|
+
'Claude plugin-only validation'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
console.log('\nPASS: repository, marketplace, and plugin-only Claude validation completed successfully.');
|
|
61
|
+
console.log('This is mechanism/schema evidence only; it does not prove plugin hooks fired in a live Claude Code session.');
|
|
62
|
+
} finally {
|
|
63
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
main();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`FAIL: ${error.message || error}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|