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.
@@ -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.4.4",
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.4",
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 hooks = manifest.hooks;
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
- if (hooks !== undefined) {
18
- const refs = Array.isArray(hooks) ? hooks : [hooks];
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 prevents the plugin from loading.'
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
- console.log('PASS: Claude plugin manifest does not duplicate the conventional hooks/hooks.json registration.');
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
@@ -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.4.4",
4
+ "version": "0.5.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.4.4",
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,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.4.4",
3
+ "version": "0.5.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.4.4",
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
+ }