From d565fc636af9e24db2a021de4a8a4d41a0c14394 Mon Sep 17 00:00:00 2001 From: Mario Aldayuz Date: Fri, 12 Jun 2026 19:56:36 -0400 Subject: [PATCH 1/5] chore: consolidate agent harnesses under harnesses/ Move claude-code, codex, openclaw, and pi into harnesses/, add cursor and hermes placeholders, and update build, CI, installers, docs, and tests for the new paths. --- .github/workflows/ci.yaml | 46 +++++++-------- .github/workflows/release.yaml | 28 +++++----- .gitignore | 8 +-- .jscpd.json | 4 +- README.md | 10 ++-- docs/ARCHITECTURE.md | 12 ++-- esbuild.config.mjs | 44 +++++++-------- .../claude-code}/.claude-plugin/plugin.json | 0 .../claude-code}/commands/login.md | 0 .../claude-code}/commands/update.md | 0 .../claude-code}/hooks/hooks.json | 0 .../skills/hivemind-goals/SKILL.md | 0 .../skills/hivemind-graph/SKILL.md | 0 .../skills/hivemind-memory/SKILL.md | 0 .../claude-code}/tsconfig.json | 0 .../codex}/.codex-plugin/plugin.json | 0 {codex => harnesses/codex}/INSTALL.md | 0 {codex => harnesses/codex}/commands/login.md | 0 {codex => harnesses/codex}/commands/update.md | 0 {codex => harnesses/codex}/hooks/hooks.json | 0 {codex => harnesses/codex}/install.sh | 0 {codex => harnesses/codex}/package.json | 0 .../codex}/skills/deeplake-memory/SKILL.md | 0 .../codex}/skills/hivemind-goals/SKILL.md | 0 .../codex}/skills/hivemind-graph/SKILL.md | 0 harnesses/cursor/.gitkeep | 0 harnesses/hermes/.gitkeep | 0 {openclaw => harnesses/openclaw}/README.md | 0 .../openclaw}/openclaw.plugin.json | 0 {openclaw => harnesses/openclaw}/package.json | 0 .../openclaw}/skills/SKILL.md | 0 .../openclaw}/skills/hivemind-goals/SKILL.md | 0 {openclaw => harnesses/openclaw}/src/index.ts | 28 +++++----- .../openclaw}/src/setup-config.ts | 0 .../pi}/extension-source/hivemind.ts | 4 +- .../private/architecture/system-overview.md | 14 ++--- .../infrastructure/monorepo-build-release.md | 22 ++++---- library/knowledge/private/overview.md | 2 +- .../private/plugins/hook-lifecycle.md | 8 +-- .../private/plugins/integration-model.md | 4 +- .../plugins/mcp-and-extension-surfaces.md | 22 ++++---- package.json | 16 +++--- scripts/audit-openclaw-bundle.mjs | 4 +- scripts/sync-versions.mjs | 8 +-- src/cli/embeddings.ts | 4 +- src/cli/index.ts | 2 +- src/cli/install-codex.ts | 6 +- src/cli/install-hermes.ts | 2 +- src/cli/install-openclaw.ts | 10 ++-- src/cli/install-pi.ts | 10 ++-- src/cli/skillify-spec.ts | 2 +- src/cli/util.ts | 2 +- src/graph/build-lock.ts | 2 +- src/hooks/cursor/session-end.ts | 2 +- src/hooks/graph-on-stop.ts | 2 +- src/hooks/hermes/session-end.ts | 2 +- src/hooks/hermes/session-start.ts | 2 +- src/hooks/pi/wiki-worker.ts | 2 +- src/hooks/session-notifications.ts | 2 +- src/hooks/shared/context-renderer.ts | 2 +- src/hooks/shared/skillopt-hook.ts | 2 +- src/skillify/agent-model.ts | 4 +- src/skillify/autopull-worker.ts | 2 +- src/skillify/gate-runner.ts | 6 +- src/skillify/skill-invocations.ts | 2 +- src/skillify/skillopt-worker.ts | 2 +- .../embeddings-bundle-scan.test.ts | 20 +++---- tests/claude-code/notifications.test.ts | 2 +- .../periodic-summary-bundles.test.ts | 16 +++--- .../claude-code/plugin-cache-bundles.test.ts | 2 +- ...plugin-cache-gc-bundle.integration.test.ts | 10 ++-- tests/claude-code/plugin-cache-gc.test.ts | 2 +- tests/claude-code/plugin-cache.test.ts | 2 +- .../plugin-version-resolution.test.ts | 24 ++++---- tests/claude-code/pre-tool-use.test.ts | 2 +- tests/claude-code/session-start.test.ts | 2 +- .../shell-bundle-sql-trace-silence.test.ts | 2 +- .../claude-code/skillify-bundle-scan.test.ts | 7 ++- .../skillify-session-start-injection.test.ts | 56 +++++++++---------- tests/claude-code/utils-version-check.test.ts | 6 +- tests/claude-code/version-check.test.ts | 20 +++---- .../version-define-bundles.test.ts | 6 +- .../wiki-next-steps-contract.test.ts | 2 +- .../wiki-worker-plugin-version.test.ts | 2 +- .../wiki-worker-upload-sql.test.ts | 4 +- tests/cli/cli-embeddings.test.ts | 4 +- tests/cli/cli-install-codex-fs.test.ts | 14 ++--- tests/cli/cli-install-hermes.test.ts | 12 ++-- tests/cli/cli-install-openclaw.test.ts | 18 +++--- tests/cli/cli-install-pi-fs.test.ts | 6 +- tests/cli/install-end-to-end.test.ts | 2 +- tests/codex/codex-capture.test.ts | 2 +- tests/codex/codex-hooks.test.ts | 2 +- tests/codex/codex-integration.test.ts | 4 +- tests/openclaw/auto-recall.test.ts | 2 +- tests/openclaw/hivemind-tools.test.ts | 8 +-- tests/openclaw/openclaw-embed-bundle.test.ts | 6 +- tests/openclaw/setup-command.test.ts | 8 +-- tests/pi/pi-extension-source.test.ts | 2 +- tests/pi/skillify-spec-drift.test.ts | 4 +- tests/scripts/sync-versions.test.ts | 4 +- tests/shared/agent-model.test.ts | 2 +- tests/shared/embeddings-schema.test.ts | 4 +- tests/shared/skillopt-hook.test.ts | 2 +- vitest.config.ts | 2 +- 105 files changed, 326 insertions(+), 325 deletions(-) rename {claude-code => harnesses/claude-code}/.claude-plugin/plugin.json (100%) rename {claude-code => harnesses/claude-code}/commands/login.md (100%) rename {claude-code => harnesses/claude-code}/commands/update.md (100%) rename {claude-code => harnesses/claude-code}/hooks/hooks.json (100%) rename {claude-code => harnesses/claude-code}/skills/hivemind-goals/SKILL.md (100%) rename {claude-code => harnesses/claude-code}/skills/hivemind-graph/SKILL.md (100%) rename {claude-code => harnesses/claude-code}/skills/hivemind-memory/SKILL.md (100%) rename {claude-code => harnesses/claude-code}/tsconfig.json (100%) rename {codex => harnesses/codex}/.codex-plugin/plugin.json (100%) rename {codex => harnesses/codex}/INSTALL.md (100%) rename {codex => harnesses/codex}/commands/login.md (100%) rename {codex => harnesses/codex}/commands/update.md (100%) rename {codex => harnesses/codex}/hooks/hooks.json (100%) rename {codex => harnesses/codex}/install.sh (100%) rename {codex => harnesses/codex}/package.json (100%) rename {codex => harnesses/codex}/skills/deeplake-memory/SKILL.md (100%) rename {codex => harnesses/codex}/skills/hivemind-goals/SKILL.md (100%) rename {codex => harnesses/codex}/skills/hivemind-graph/SKILL.md (100%) create mode 100644 harnesses/cursor/.gitkeep create mode 100644 harnesses/hermes/.gitkeep rename {openclaw => harnesses/openclaw}/README.md (100%) rename {openclaw => harnesses/openclaw}/openclaw.plugin.json (100%) rename {openclaw => harnesses/openclaw}/package.json (100%) rename {openclaw => harnesses/openclaw}/skills/SKILL.md (100%) rename {openclaw => harnesses/openclaw}/skills/hivemind-goals/SKILL.md (100%) rename {openclaw => harnesses/openclaw}/src/index.ts (98%) rename {openclaw => harnesses/openclaw}/src/setup-config.ts (100%) rename {pi => harnesses/pi}/extension-source/hivemind.ts (99%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c03109ea..c5abe7bd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,11 +116,11 @@ jobs: - name: Build (typecheck + emit bundle artefacts) # `build` runs `tsc && esbuild`, which is a strict superset of # `typecheck` (the bare `tsc --noEmit` we used to run here). It - # ALSO produces the per-agent bundles AND `openclaw/dist/`. The + # ALSO produces the per-agent bundles AND `harnesses/openclaw/dist/`. The # latter is gitignored, so it doesn't exist after a fresh # `actions/checkout` — and several bundle-scan tests under - # `claude-code/tests/skillify-session-start-injection.test.ts` read - # `openclaw/dist/index.js` and `openclaw/dist/skillify-worker.js` + # `harnesses/claude-code/tests/skillify-session-start-injection.test.ts` read + # `harnesses/openclaw/dist/index.js` and `harnesses/openclaw/dist/skillify-worker.js` # directly. Without this rebuild they fail with ENOENT (see PR #98 # — first CI run after the openclaw skillify wiring landed). run: npm run build @@ -294,19 +294,19 @@ jobs: set -e for f in \ bundle/cli.js \ - claude-code/bundle/capture.js \ - claude-code/bundle/session-start.js \ - claude-code/bundle/session-end.js \ - claude-code/bundle/plugin-cache-gc.js \ - claude-code/bundle/skillify-worker.js \ - codex/bundle/capture.js \ - codex/bundle/session-start.js \ + harnesses/claude-code/bundle/capture.js \ + harnesses/claude-code/bundle/session-start.js \ + harnesses/claude-code/bundle/session-end.js \ + harnesses/claude-code/bundle/plugin-cache-gc.js \ + harnesses/claude-code/bundle/skillify-worker.js \ + harnesses/codex/bundle/capture.js \ + harnesses/codex/bundle/session-start.js \ cursor/bundle/capture.js \ cursor/bundle/session-start.js \ - hermes/bundle/capture.js \ - hermes/bundle/session-start.js \ + harnesses/hermes/bundle/capture.js \ + harnesses/hermes/bundle/session-start.js \ mcp/bundle/server.js \ - pi/bundle/skillify-worker.js; do + harnesses/pi/bundle/skillify-worker.js; do if [ ! -f "$f" ]; then echo "::error::expected bundle entrypoint missing: $f" exit 1 @@ -417,19 +417,19 @@ jobs: set -e for f in \ bundle/cli.js \ - claude-code/bundle/capture.js \ - claude-code/bundle/session-start.js \ - claude-code/bundle/session-end.js \ - claude-code/bundle/plugin-cache-gc.js \ - claude-code/bundle/skillify-worker.js \ - codex/bundle/capture.js \ - codex/bundle/session-start.js \ + harnesses/claude-code/bundle/capture.js \ + harnesses/claude-code/bundle/session-start.js \ + harnesses/claude-code/bundle/session-end.js \ + harnesses/claude-code/bundle/plugin-cache-gc.js \ + harnesses/claude-code/bundle/skillify-worker.js \ + harnesses/codex/bundle/capture.js \ + harnesses/codex/bundle/session-start.js \ cursor/bundle/capture.js \ cursor/bundle/session-start.js \ - hermes/bundle/capture.js \ - hermes/bundle/session-start.js \ + harnesses/hermes/bundle/capture.js \ + harnesses/hermes/bundle/session-start.js \ mcp/bundle/server.js \ - pi/bundle/skillify-worker.js; do + harnesses/pi/bundle/skillify-worker.js; do if [ ! -f "$f" ]; then echo "::error::expected bundle entrypoint missing: $f" exit 1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 529df4dd..ece3faef 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -114,12 +114,12 @@ jobs: git add -A git add -f \ bundle \ - claude-code/bundle \ - codex/bundle \ + harnesses/claude-code/bundle \ + harnesses/codex/bundle \ cursor/bundle \ - hermes/bundle \ + harnesses/hermes/bundle \ mcp/bundle \ - pi/bundle + harnesses/pi/bundle git commit -m "release: v$VERSION" RELEASE_SHA=$(git rev-parse HEAD) echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" @@ -133,17 +133,17 @@ jobs: # them on disk for npm publish). git rm --cached -r \ bundle \ - claude-code/bundle \ - codex/bundle \ + harnesses/claude-code/bundle \ + harnesses/codex/bundle \ cursor/bundle \ - hermes/bundle \ + harnesses/hermes/bundle \ mcp/bundle \ - pi/bundle + harnesses/pi/bundle # Update marketplace.json's git-subdir source.sha to the release # commit. Users doing `claude plugin marketplace add activeloopai/hivemind` # clone main (HEAD = this cleanup commit), read marketplace.json, see # the sha → clone activeloopai/hivemind at that sha (which DOES have - # bundles tracked) → extract `claude-code/` → install. + # bundles tracked) → extract `harnesses/claude-code/` → install. node -e " const fs = require('fs'); const path = '.claude-plugin/marketplace.json'; @@ -263,9 +263,9 @@ jobs: - name: Build bundles # Must run BEFORE the quality gate. `npm run ci` includes vitest, # and the bundle-scan tests under - # claude-code/tests/skillify-session-start-injection.test.ts read - # openclaw/dist/index.js + openclaw/dist/skillify-worker.js - # directly. openclaw/dist/ is gitignored — it only exists after + # harnesses/claude-code/tests/skillify-session-start-injection.test.ts read + # harnesses/openclaw/dist/index.js + harnesses/openclaw/dist/skillify-worker.js + # directly. harnesses/openclaw/dist/ is gitignored — it only exists after # `npm run build`. Without this step before the gate, vitest # fails with ENOENT and the publish aborts. Same root cause as # 64cac0b in ci.yml; that fix never propagated to this job. @@ -336,8 +336,8 @@ jobs: run: clawhub login --token "$CLAWHUB_TOKEN" --no-browser - name: Publish openclaw bundle to ClawHub - # The release job above already synced openclaw/package.json and - # openclaw/openclaw.plugin.json versions to the bumped root + # The release job above already synced harnesses/openclaw/package.json and + # harnesses/openclaw/openclaw.plugin.json versions to the bumped root # version, so this publishes the same X.Y.Z that npm just got. # # --family code-plugin: the existing `hivemind` package on ClawHub diff --git a/.gitignore b/.gitignore index adfed883..da240bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,12 +24,12 @@ deploy-to-cache.sh # marketplace.json sha-pinned source can resolve to a bundle-containing # commit; the very next commit untracks them so main HEAD stays clean. bundle/ -claude-code/bundle/ -codex/bundle/ +harnesses/claude-code/bundle/ +harnesses/codex/bundle/ cursor/bundle/ -hermes/bundle/ +harnesses/hermes/bundle/ mcp/bundle/ -pi/bundle/ +harnesses/pi/bundle/ evals/ .cursor/ cursor_hivemind_environment_variables_c.md \ No newline at end of file diff --git a/.jscpd.json b/.jscpd.json index 7f22ee1e..d34d5b76 100644 --- a/.jscpd.json +++ b/.jscpd.json @@ -10,8 +10,8 @@ "**/*.test.ts", "**/tests/**", "**/fixtures/**", - "**/claude-code/.claude-plugin/**", - "**/codex/.codex-plugin/**", + "**/harnesses/claude-code/.claude-plugin/**", + "**/harnesses/codex/.codex-plugin/**", "**/src/hooks/cursor/wiki-worker.ts", "**/src/hooks/cursor/spawn-wiki-worker.ts", "**/src/hooks/cursor/capture.ts", diff --git a/README.md b/README.md index 8d40d9cf..60005624 100644 --- a/README.md +++ b/README.md @@ -165,14 +165,14 @@ Hivemind runs **alongside** OpenClaw's built-in `memory-core` plugin. It does ** Tell Codex to fetch and follow the install instructions: ``` -Fetch and follow instructions from https://raw.githubusercontent.com/activeloopai/hivemind/main/codex/INSTALL.md +Fetch and follow instructions from https://raw.githubusercontent.com/activeloopai/hivemind/main/harnesses/codex/INSTALL.md ``` Or run the installer script directly: ```bash git clone https://github.com/activeloopai/hivemind.git ~/.codex/hivemind -~/.codex/hivemind/codex/install.sh +~/.codex/hivemind/harnesses/codex/install.sh ``` Restart Codex to activate. @@ -342,12 +342,12 @@ hivemind rules list # latest 10 active hivemind rules edit "" # bumps version hivemind rules done # mark closed -# Cross-agent diagnostic / pi/openclaw fallback +# Cross-agent diagnostic / harnesses/pi/openclaw fallback hivemind context # print the injection block on demand ``` **What's injected at SessionStart** (claude-code, cursor, hermes. Codex is -deliberately excluded to keep its user-visible TUI clean; pi/openclaw +deliberately excluded to keep its user-visible TUI clean; harnesses/pi/openclaw fall back to `hivemind context`): ```text @@ -435,7 +435,7 @@ Setup, BYOC, agent integrations, or workflow. Come ask in the community: git clone https://github.com/activeloopai/hivemind.git cd hivemind npm install -npm run build # tsc + esbuild → claude-code/bundle/ + codex/bundle/ + cursor/bundle/ + openclaw/dist/ + mcp/bundle/ + bundle/cli.js +npm run build # tsc + esbuild → harnesses/claude-code/bundle/ + harnesses/codex/bundle/ + cursor/bundle/ + harnesses/openclaw/dist/ + mcp/bundle/ + bundle/cli.js npm test # vitest ``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0c482c42..00bb5509 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -20,17 +20,17 @@ hivemind/ │ ├── hooks/codex/ ← Codex hooks │ ├── hooks/cursor/ ← Cursor hooks │ ├── hooks/hermes/ ← Hermes shell hooks -│ ├── hooks/pi/ ← pi wiki-worker (extension lives in pi/extension-source/) +│ ├── hooks/pi/ ← pi wiki-worker (extension lives in harnesses/pi/extension-source/) │ ├── embeddings/ ← nomic embed-daemon + protocol + SQL helpers │ ├── mcp/ ← MCP server (used by Hermes; available to any future MCP-aware client) │ ├── commands/ ← auth, auth-creds, auth-login, session-prune │ └── cli/ ← unified `hivemind install` CLI + per-agent installers -├── claude-code/ ← Claude Code plugin source (marketplace-distributed) -├── codex/ ← Codex plugin build output (npm-distributed) +├── harnesses/claude-code/ ← Claude Code plugin source (marketplace-distributed) +├── harnesses/codex/ ← Codex plugin build output (npm-distributed) ├── cursor/ ← Cursor plugin build output (npm-distributed) -├── hermes/ ← Hermes plugin build output (npm-distributed) +├── harnesses/hermes/ ← Hermes plugin build output (npm-distributed) ├── mcp/ ← MCP server build output (shared by Hermes + future MCP clients) -├── openclaw/ ← OpenClaw plugin source + build output (ClawHub-distributed) -├── pi/ ← pi extension source (ships raw .ts; pi compiles at load) +├── harnesses/openclaw/ ← OpenClaw plugin source + build output (ClawHub-distributed) +├── harnesses/pi/ ← pi extension source (ships raw .ts; pi compiles at load) └── bundle/ ← unified `hivemind` CLI build output ``` diff --git a/esbuild.config.mjs b/esbuild.config.mjs index da1b7443..9ae1f5ba 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -3,8 +3,8 @@ import { chmodSync, writeFileSync, readFileSync } from "node:fs"; const esmPackageJson = '{"type":"module"}\n'; const hivemindVersion = JSON.parse(readFileSync("package.json", "utf-8")).version; -const openclawVersion = JSON.parse(readFileSync("openclaw/package.json", "utf-8")).version; -const openclawSkillBody = readFileSync("openclaw/skills/SKILL.md", "utf-8"); +const openclawVersion = JSON.parse(readFileSync("harnesses/openclaw/package.json", "utf-8")).version; +const openclawSkillBody = readFileSync("harnesses/openclaw/skills/SKILL.md", "utf-8"); // Claude Code plugin const ccHooks = [ @@ -54,7 +54,7 @@ await build({ bundle: true, platform: "node", format: "esm", - outdir: "claude-code/bundle", + outdir: "harnesses/claude-code/bundle", external: [ "node:*", "node-liblzma", @@ -82,9 +82,9 @@ await build({ }); for (const h of ccAll) { - chmodSync(`claude-code/bundle/${h.out}.js`, 0o755); + chmodSync(`harnesses/claude-code/bundle/${h.out}.js`, 0o755); } -writeFileSync("claude-code/bundle/package.json", esmPackageJson); +writeFileSync("harnesses/claude-code/bundle/package.json", esmPackageJson); // Codex plugin const codexHooks = [ @@ -122,7 +122,7 @@ await build({ bundle: true, platform: "node", format: "esm", - outdir: "codex/bundle", + outdir: "harnesses/codex/bundle", external: [ "node:*", "node-liblzma", @@ -149,9 +149,9 @@ await build({ }); for (const h of codexAll) { - chmodSync(`codex/bundle/${h.out}.js`, 0o755); + chmodSync(`harnesses/codex/bundle/${h.out}.js`, 0o755); } -writeFileSync("codex/bundle/package.json", esmPackageJson); +writeFileSync("harnesses/codex/bundle/package.json", esmPackageJson); // Cursor plugin (1.7+ hooks API). Same shell + commands as the other agents. const cursorHooks = [ @@ -253,7 +253,7 @@ await build({ bundle: true, platform: "node", format: "esm", - outdir: "hermes/bundle", + outdir: "harnesses/hermes/bundle", external: [ "node:*", "node-liblzma", @@ -280,12 +280,12 @@ await build({ }); for (const h of hermesAll) { - chmodSync(`hermes/bundle/${h.out}.js`, 0o755); + chmodSync(`harnesses/hermes/bundle/${h.out}.js`, 0o755); } // Pi (badlogic/pi-mono) — ships a wiki-worker bundle, a skillify-worker // bundle, and an autopull-worker bundle. The pi extension itself is raw .ts -// at pi/extension-source/hivemind.ts; we don't bundle it because pi's +// at harnesses/pi/extension-source/hivemind.ts; we don't bundle it because pi's // runtime compiles + loads the .ts file directly. Embed daemon reuses the // canonical ~/.hivemind/embed-deps/embed-daemon.js — no per-pi embed // bundle needed. Skillify worker is the same shared module used by @@ -306,7 +306,7 @@ await build({ bundle: true, platform: "node", format: "esm", - outdir: "pi/bundle", + outdir: "harnesses/pi/bundle", external: [ "node:*", "node-liblzma", @@ -321,10 +321,10 @@ await build({ }, }); for (const h of piWorker) { - chmodSync(`pi/bundle/${h.out}.js`, 0o755); + chmodSync(`harnesses/pi/bundle/${h.out}.js`, 0o755); } -writeFileSync("pi/bundle/package.json", esmPackageJson); -writeFileSync("hermes/bundle/package.json", esmPackageJson); +writeFileSync("harnesses/pi/bundle/package.json", esmPackageJson); +writeFileSync("harnesses/hermes/bundle/package.json", esmPackageJson); // OpenClaw plugin bundle. The shared CC/Codex source modules reference a // handful of HIVEMIND_* env vars for dev-only overrides. Those env paths are @@ -333,13 +333,13 @@ writeFileSync("hermes/bundle/package.json", esmPackageJson); // with `undefined` at build time to avoid shipping dead env-read code in the // plugin bundle. await build({ - entryPoints: { index: "openclaw/src/index.ts" }, + entryPoints: { index: "harnesses/openclaw/src/index.ts" }, bundle: true, splitting: true, chunkNames: "chunks/[name]-[hash]", platform: "node", format: "esm", - outdir: "openclaw/dist", + outdir: "harnesses/openclaw/dist", external: ["node:*"], // Guarantee `globalThis.__hivemind_tuning__` exists as an object before any // bundled module's lazy env reads execute. esbuild's `define` rewrites @@ -403,7 +403,7 @@ await build({ }, plugins: [{ // Dead-code elimination for transitively bundled CC/Codex-only features. - // openclaw/src/index.ts imports shared modules from ../../src/ (DeeplakeApi, + // harnesses/openclaw/src/index.ts imports shared modules from ../../../src/ (DeeplakeApi, // grep-core, virtual-table-query, auth device-flow). Several of those // modules also host CC-specific helpers that shell out with execSync — // opening the browser for SSO, nudging claude-plugin-update, spawning the @@ -425,7 +425,7 @@ await build({ }, }], }); -writeFileSync("openclaw/dist/package.json", esmPackageJson); +writeFileSync("harnesses/openclaw/dist/package.json", esmPackageJson); // OpenClaw skillify-worker bundle. Same shared module CC/Codex/Cursor/Hermes/Pi // use; openclaw spawns it from its agent_end hook to mine reusable skills out @@ -436,7 +436,7 @@ writeFileSync("openclaw/dist/package.json", esmPackageJson); // with no stubs. // 2. The main bundle uses code splitting (chunks/), and we don't want the // worker's modules entangled with the gateway's chunk graph. -// Lands at openclaw/dist/skillify-worker.js — install-openclaw.ts already +// Lands at harnesses/openclaw/dist/skillify-worker.js — install-openclaw.ts already // copies the entire dist/ recursively, so it ships to // ~/.openclaw/extensions/hivemind/dist/skillify-worker.js with no other change. await build({ @@ -444,7 +444,7 @@ await build({ bundle: true, platform: "node", format: "esm", - outdir: "openclaw/dist", + outdir: "harnesses/openclaw/dist", external: ["node:*"], // Same banner as the main openclaw bundle — see the comment there for // the rationale. The worker entry itself overwrites this with the @@ -500,7 +500,7 @@ await build({ "process.env.HIVEMIND_STATE_DIR": "globalThis.__hivemind_tuning__.HIVEMIND_STATE_DIR", }, }); -chmodSync("openclaw/dist/skillify-worker.js", 0o755); +chmodSync("harnesses/openclaw/dist/skillify-worker.js", 0o755); // Hivemind MCP server (stdio). Reused by Cline / Roo / Kilo / any MCP-aware // agent. Lives at ~/.hivemind/mcp/server.js after install. diff --git a/claude-code/.claude-plugin/plugin.json b/harnesses/claude-code/.claude-plugin/plugin.json similarity index 100% rename from claude-code/.claude-plugin/plugin.json rename to harnesses/claude-code/.claude-plugin/plugin.json diff --git a/claude-code/commands/login.md b/harnesses/claude-code/commands/login.md similarity index 100% rename from claude-code/commands/login.md rename to harnesses/claude-code/commands/login.md diff --git a/claude-code/commands/update.md b/harnesses/claude-code/commands/update.md similarity index 100% rename from claude-code/commands/update.md rename to harnesses/claude-code/commands/update.md diff --git a/claude-code/hooks/hooks.json b/harnesses/claude-code/hooks/hooks.json similarity index 100% rename from claude-code/hooks/hooks.json rename to harnesses/claude-code/hooks/hooks.json diff --git a/claude-code/skills/hivemind-goals/SKILL.md b/harnesses/claude-code/skills/hivemind-goals/SKILL.md similarity index 100% rename from claude-code/skills/hivemind-goals/SKILL.md rename to harnesses/claude-code/skills/hivemind-goals/SKILL.md diff --git a/claude-code/skills/hivemind-graph/SKILL.md b/harnesses/claude-code/skills/hivemind-graph/SKILL.md similarity index 100% rename from claude-code/skills/hivemind-graph/SKILL.md rename to harnesses/claude-code/skills/hivemind-graph/SKILL.md diff --git a/claude-code/skills/hivemind-memory/SKILL.md b/harnesses/claude-code/skills/hivemind-memory/SKILL.md similarity index 100% rename from claude-code/skills/hivemind-memory/SKILL.md rename to harnesses/claude-code/skills/hivemind-memory/SKILL.md diff --git a/claude-code/tsconfig.json b/harnesses/claude-code/tsconfig.json similarity index 100% rename from claude-code/tsconfig.json rename to harnesses/claude-code/tsconfig.json diff --git a/codex/.codex-plugin/plugin.json b/harnesses/codex/.codex-plugin/plugin.json similarity index 100% rename from codex/.codex-plugin/plugin.json rename to harnesses/codex/.codex-plugin/plugin.json diff --git a/codex/INSTALL.md b/harnesses/codex/INSTALL.md similarity index 100% rename from codex/INSTALL.md rename to harnesses/codex/INSTALL.md diff --git a/codex/commands/login.md b/harnesses/codex/commands/login.md similarity index 100% rename from codex/commands/login.md rename to harnesses/codex/commands/login.md diff --git a/codex/commands/update.md b/harnesses/codex/commands/update.md similarity index 100% rename from codex/commands/update.md rename to harnesses/codex/commands/update.md diff --git a/codex/hooks/hooks.json b/harnesses/codex/hooks/hooks.json similarity index 100% rename from codex/hooks/hooks.json rename to harnesses/codex/hooks/hooks.json diff --git a/codex/install.sh b/harnesses/codex/install.sh similarity index 100% rename from codex/install.sh rename to harnesses/codex/install.sh diff --git a/codex/package.json b/harnesses/codex/package.json similarity index 100% rename from codex/package.json rename to harnesses/codex/package.json diff --git a/codex/skills/deeplake-memory/SKILL.md b/harnesses/codex/skills/deeplake-memory/SKILL.md similarity index 100% rename from codex/skills/deeplake-memory/SKILL.md rename to harnesses/codex/skills/deeplake-memory/SKILL.md diff --git a/codex/skills/hivemind-goals/SKILL.md b/harnesses/codex/skills/hivemind-goals/SKILL.md similarity index 100% rename from codex/skills/hivemind-goals/SKILL.md rename to harnesses/codex/skills/hivemind-goals/SKILL.md diff --git a/codex/skills/hivemind-graph/SKILL.md b/harnesses/codex/skills/hivemind-graph/SKILL.md similarity index 100% rename from codex/skills/hivemind-graph/SKILL.md rename to harnesses/codex/skills/hivemind-graph/SKILL.md diff --git a/harnesses/cursor/.gitkeep b/harnesses/cursor/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/harnesses/hermes/.gitkeep b/harnesses/hermes/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/openclaw/README.md b/harnesses/openclaw/README.md similarity index 100% rename from openclaw/README.md rename to harnesses/openclaw/README.md diff --git a/openclaw/openclaw.plugin.json b/harnesses/openclaw/openclaw.plugin.json similarity index 100% rename from openclaw/openclaw.plugin.json rename to harnesses/openclaw/openclaw.plugin.json diff --git a/openclaw/package.json b/harnesses/openclaw/package.json similarity index 100% rename from openclaw/package.json rename to harnesses/openclaw/package.json diff --git a/openclaw/skills/SKILL.md b/harnesses/openclaw/skills/SKILL.md similarity index 100% rename from openclaw/skills/SKILL.md rename to harnesses/openclaw/skills/SKILL.md diff --git a/openclaw/skills/hivemind-goals/SKILL.md b/harnesses/openclaw/skills/hivemind-goals/SKILL.md similarity index 100% rename from openclaw/skills/hivemind-goals/SKILL.md rename to harnesses/openclaw/skills/hivemind-goals/SKILL.md diff --git a/openclaw/src/index.ts b/harnesses/openclaw/src/index.ts similarity index 98% rename from openclaw/src/index.ts rename to harnesses/openclaw/src/index.ts index 9fe32fb7..84c551be 100644 --- a/openclaw/src/index.ts +++ b/harnesses/openclaw/src/index.ts @@ -18,23 +18,23 @@ function loadSetupConfig(): Promise { return import("./setup-config.js"); } // Network-only helpers stay as static imports — auth.js no longer touches fs -// (its credential IO moved to ../../src/commands/auth-creds.js, which we load +// (its credential IO moved to ../../../src/commands/auth-creds.js, which we load // lazily below so esbuild emits it as a separate chunk). -import { requestDeviceCode, pollForToken, listOrgs, switchOrg, listWorkspaces, switchWorkspace, healDriftedOrgToken } from "../../src/commands/auth.js"; -import { DeeplakeApi } from "../../src/deeplake-api.js"; +import { requestDeviceCode, pollForToken, listOrgs, switchOrg, listWorkspaces, switchWorkspace, healDriftedOrgToken } from "../../../src/commands/auth.js"; +import { DeeplakeApi } from "../../../src/deeplake-api.js"; // Lazy-loaders for the fs-touching shared modules. Each becomes its own // esbuild chunk; the main openclaw bundle stays free of fs imports. -type CredsModule = typeof import("../../src/commands/auth-creds.js"); -type ConfigModule = typeof import("../../src/config.js"); +type CredsModule = typeof import("../../../src/commands/auth-creds.js"); +type ConfigModule = typeof import("../../../src/config.js"); let credsModulePromise: Promise | null = null; let configModulePromise: Promise | null = null; function loadCredsModule(): Promise { - if (!credsModulePromise) credsModulePromise = import("../../src/commands/auth-creds.js"); + if (!credsModulePromise) credsModulePromise = import("../../../src/commands/auth-creds.js"); return credsModulePromise; } function loadConfigModule(): Promise { - if (!configModulePromise) configModulePromise = import("../../src/config.js"); + if (!configModulePromise) configModulePromise = import("../../../src/config.js"); return configModulePromise; } async function loadCredentials() { @@ -50,23 +50,23 @@ async function loadConfig() { const m = await loadConfigModule(); return m.loadConfig(); } -import { sqlStr } from "../../src/utils/sql.js"; -import { deeplakeClientHeader } from "../../src/utils/client-header.js"; +import { sqlStr } from "../../../src/utils/sql.js"; +import { deeplakeClientHeader } from "../../../src/utils/client-header.js"; // Memory-access primitives reused directly from the CC/Codex hooks so the // openclaw agent gets the same search + read semantics (multi-word across // memory ∪ sessions, path filters, JSONB normalization, virtual /index.md). -import { searchDeeplakeTables, buildGrepSearchOptions, compileGrepRegex, normalizeContent, type GrepMatchParams } from "../../src/shell/grep-core.js"; -import { readVirtualPathContent } from "../../src/hooks/virtual-table-query.js"; +import { searchDeeplakeTables, buildGrepSearchOptions, compileGrepRegex, normalizeContent, type GrepMatchParams } from "../../../src/shell/grep-core.js"; +import { readVirtualPathContent } from "../../../src/hooks/virtual-table-query.js"; // Standalone embed client. Produces real document embeddings ONLY when the // canonical shared daemon at ~/.hivemind/embed-deps/embed-daemon.js is // present (deposited out-of-band by `hivemind embeddings install`). The // helper never installs transformers itself — that's explicit user opt-in // per src/user-config.ts. Returns null → caller writes NULL into // message_embedding (today's behavior, preserved on every failure mode). -import { tryEmbedStandalone, _setSpawnImpl } from "../../src/embeddings/standalone-embed-client.js"; -import { embeddingSqlLiteral } from "../../src/embeddings/sql.js"; +import { tryEmbedStandalone, _setSpawnImpl } from "../../../src/embeddings/standalone-embed-client.js"; +import { embeddingSqlLiteral } from "../../../src/embeddings/sql.js"; // Resolve sibling skillify-worker.js path at runtime via import.meta.url. The -// openclaw plugin is bundled to openclaw/dist/index.js, then installed to +// openclaw plugin is bundled to harnesses/openclaw/dist/index.js, then installed to // ~/.openclaw/extensions/hivemind/dist/index.js by install-openclaw.ts. The // worker bundle is its sibling at the same level. import { fileURLToPath } from "node:url"; diff --git a/openclaw/src/setup-config.ts b/harnesses/openclaw/src/setup-config.ts similarity index 100% rename from openclaw/src/setup-config.ts rename to harnesses/openclaw/src/setup-config.ts diff --git a/pi/extension-source/hivemind.ts b/harnesses/pi/extension-source/hivemind.ts similarity index 99% rename from pi/extension-source/hivemind.ts rename to harnesses/pi/extension-source/hivemind.ts index b8026a4e..ae3d47a0 100644 --- a/pi/extension-source/hivemind.ts +++ b/harnesses/pi/extension-source/hivemind.ts @@ -413,7 +413,7 @@ async function tryEmbedOverSocket(text: string, kind: "document" | "query"): Pro // Mirror of src/hooks/summary-state.ts (same dir, same JSON shape, shared // across CC/codex/cursor/hermes — session ids are UUIDs so collisions are // impossible). The pi extension increments totalCount on every captured -// event and spawns the bundled wiki-worker (see pi/bundle/wiki-worker.js) +// event and spawns the bundled wiki-worker (see harnesses/pi/bundle/wiki-worker.js) // when the threshold is hit. The worker, after generating the summary, // calls finalizeSummary() / releaseLock() against this same dir. So the // extension and the worker share state. @@ -754,7 +754,7 @@ function spawnWikiWorker( // ---------- skillify worker spawn --------------------------------------------- // // Mirror of src/skillify/spawn-skillify-worker.ts and src/skillify/triggers.ts — -// inlined here because pi/extension-source/hivemind.ts is shipped as raw .ts +// inlined here because harnesses/pi/extension-source/hivemind.ts is shipped as raw .ts // with zero non-builtin runtime dependencies (pi compiles + loads it at // extension-load time). The shared TypeScript modules under src/skillify/ // can't be imported from this file. diff --git a/library/knowledge/private/architecture/system-overview.md b/library/knowledge/private/architecture/system-overview.md index bd6f2e78..0173e214 100644 --- a/library/knowledge/private/architecture/system-overview.md +++ b/library/knowledge/private/architecture/system-overview.md @@ -33,22 +33,22 @@ hivemind/ │ ├── hooks/codex/ ← Codex hooks │ ├── hooks/cursor/ ← Cursor hooks │ ├── hooks/hermes/ ← Hermes shell hooks -│ ├── hooks/pi/ ← pi wiki-worker (extension in pi/extension-source/) +│ ├── hooks/pi/ ← pi wiki-worker (extension in harnesses/pi/extension-source/) │ ├── embeddings/ ← nomic embed-daemon + protocol + SQL helpers │ ├── mcp/ ← MCP server (Hermes today; any MCP-aware client later) │ ├── commands/ ← auth, auth-creds, auth-login, session-prune │ └── cli/ ← unified `hivemind install` CLI + per-agent installers -├── claude-code/ ← Claude Code plugin source (marketplace-distributed) -├── codex/ ← Codex plugin build output (npm-distributed) +├── harnesses/claude-code/ ← Claude Code plugin source (marketplace-distributed) +├── harnesses/codex/ ← Codex plugin build output (npm-distributed) ├── cursor/ ← Cursor plugin build output (npm-distributed) -├── hermes/ ← Hermes plugin build output (npm-distributed) +├── harnesses/hermes/ ← Hermes plugin build output (npm-distributed) ├── mcp/ ← MCP server build output -├── openclaw/ ← OpenClaw plugin source + build output (ClawHub) -├── pi/ ← pi extension source (ships raw .ts; pi compiles at load) +├── harnesses/openclaw/ ← OpenClaw plugin source + build output (ClawHub) +├── harnesses/pi/ ← pi extension source (ships raw .ts; pi compiles at load) └── bundle/ ← unified `hivemind` CLI build output ``` -The Claude Code hooks under `src/hooks/` are the reference implementation. The per-agent subdirectories (`src/hooks/codex/`, `cursor/`, `hermes/`, `pi/`) re-express the same handlers against each assistant's event names and payload shapes, reusing the shared core for the actual work. The build step (`npm run build`) runs `tsc` plus `esbuild` and emits the per-agent bundles into `claude-code/bundle/`, `codex/bundle/`, `cursor/bundle/`, `openclaw/dist/`, `mcp/bundle/`, and `bundle/cli.js`. +The Claude Code hooks under `src/hooks/` are the reference implementation. The per-agent subdirectories (`src/hooks/codex/`, `cursor/`, `hermes/`, `pi/`) re-express the same handlers against each assistant's event names and payload shapes, reusing the shared core for the actual work. The build step (`npm run build`) runs `tsc` plus `esbuild` and emits the per-agent bundles into `harnesses/claude-code/bundle/`, `harnesses/codex/bundle/`, `cursor/bundle/`, `harnesses/openclaw/dist/`, `mcp/bundle/`, and `bundle/cli.js`. --- diff --git a/library/knowledge/private/infrastructure/monorepo-build-release.md b/library/knowledge/private/infrastructure/monorepo-build-release.md index 972a47e1..3939930e 100644 --- a/library/knowledge/private/infrastructure/monorepo-build-release.md +++ b/library/knowledge/private/infrastructure/monorepo-build-release.md @@ -61,10 +61,10 @@ const SOURCE = "package.json"; // Scalar targets: each has a single top-level `version` field tracking package.json. export const SCALAR_TARGETS = [ ".claude-plugin/plugin.json", - "claude-code/.claude-plugin/plugin.json", - "openclaw/openclaw.plugin.json", - "openclaw/package.json", - "codex/package.json", + "harnesses/claude-code/.claude-plugin/plugin.json", + "harnesses/openclaw/openclaw.plugin.json", + "harnesses/openclaw/package.json", + "harnesses/codex/package.json", ]; ``` @@ -109,12 +109,12 @@ The core bundling engine is `esbuild.config.mjs`. It orchestrates compilation co Esbuild generates separate distribution bundles under the following directories: -* **Claude Code:** Generated under `claude-code/bundle/`. It packs hooks like `session-start`, `session-end`, `pre-tool-use`, `capture`, and several specialized background workers. -* **Codex:** Generated under `codex/bundle/`. It includes the Codex-specific lifecycle shims and background tasks. +* **Claude Code:** Generated under `harnesses/claude-code/bundle/`. It packs hooks like `session-start`, `session-end`, `pre-tool-use`, `capture`, and several specialized background workers. +* **Codex:** Generated under `harnesses/codex/bundle/`. It includes the Codex-specific lifecycle shims and background tasks. * **Cursor:** Generated under `cursor/bundle/`. It packages the `session-start`, `capture`, `pre-tool-use`, `session-end`, and `graph-on-stop` hooks. -* **Hermes Agent:** Generated under `hermes/bundle/`. It bundles hooks following the NousResearch/hermes-agent shell hook protocol. -* **pi:** Generated under `pi/bundle/`. It bundles background workers like the `wiki-worker` and `skillify-worker`. (The main pi extension runs raw TypeScript compiled by pi's runtime.) -* **OpenClaw:** Generated under `openclaw/dist/`. It outputs the compiled HTTP/WebSocket plugin gateway, along with its async `skillify-worker`. +* **Hermes Agent:** Generated under `harnesses/hermes/bundle/`. It bundles hooks following the NousResearch/hermes-agent shell hook protocol. +* **pi:** Generated under `harnesses/pi/bundle/`. It bundles background workers like the `wiki-worker` and `skillify-worker`. (The main pi extension runs raw TypeScript compiled by pi's runtime.) +* **OpenClaw:** Generated under `harnesses/openclaw/dist/`. It outputs the compiled HTTP/WebSocket plugin gateway, along with its async `skillify-worker`. * **MCP Server:** Generated under `mcp/bundle/`. It builds the standalone Model Context Protocol server that can be hooked into Cline, Roo, or Kilo. * **Unified CLI:** Generated under `bundle/`. It produces the primary `hivemind` executable binary. * **Embed Daemon:** Generated under `embeddings/`. It compiles the standalone semantic search background daemon. @@ -131,7 +131,7 @@ await build({ bundle: true, platform: "node", format: "esm", - outdir: "claude-code/bundle", + outdir: "harnesses/claude-code/bundle", external: [ "node:*", "node-liblzma", @@ -174,7 +174,7 @@ To eliminate unreachable code that would trigger security warnings on the ClawHu ```404:426:esbuild.config.mjs plugins: [{ // Dead-code elimination for transitively bundled CC/Codex-only features. - // openclaw/src/index.ts imports shared modules from ../../src/ (DeeplakeApi, + // harnesses/openclaw/src/index.ts imports shared modules from ../../src/ (DeeplakeApi, // grep-core, virtual-table-query, auth device-flow). Several of those // modules also host CC-specific helpers that shell out with execSync — // opening the browser for SSO, nudging claude-plugin-update, spawning the diff --git a/library/knowledge/private/overview.md b/library/knowledge/private/overview.md index b3baefc6..0e12e774 100644 --- a/library/knowledge/private/overview.md +++ b/library/knowledge/private/overview.md @@ -53,7 +53,7 @@ External dependencies are intentionally few: Deeplake for storage, the host agen | MCP server | `src/mcp/` | Recall surface for Hermes and future MCP clients | | Skillify | `src/skillify/` | Trace-to-skill mining pipeline | | Graph | `src/graph/` | Live codebase graph extraction and recall | -| Plugin build outputs | `claude-code/`, `codex/`, `cursor/`, `hermes/`, `openclaw/`, `pi/` | Distributable artifacts per assistant | +| Plugin build outputs | `harnesses/claude-code/`, `harnesses/codex/`, `cursor/`, `harnesses/hermes/`, `harnesses/openclaw/`, `harnesses/pi/` | Distributable artifacts per assistant | --- diff --git a/library/knowledge/private/plugins/hook-lifecycle.md b/library/knowledge/private/plugins/hook-lifecycle.md index a5903011..80f32e59 100644 --- a/library/knowledge/private/plugins/hook-lifecycle.md +++ b/library/knowledge/private/plugins/hook-lifecycle.md @@ -65,7 +65,7 @@ src/hooks/ ├── session-end.ts ← reference ├── wiki-worker.ts ← reference (shells claude -p) ├── spawn-wiki-worker.ts ← reference -├── codex/ +├── harnesses/codex/ │ ├── session-start.ts # minimal inject; async setup in session-start-setup.ts │ ├── session-start-setup.ts # table DDL, placeholder, version check (detached) │ ├── capture.ts # same logic, Codex payload shape @@ -79,18 +79,18 @@ src/hooks/ │ ├── session-end.ts # sessionEnd event name │ ├── wiki-worker.ts # shells `cursor-agent` or falls back to claude │ └── spawn-wiki-worker.ts # Cursor config keys -├── hermes/ +├── harnesses/hermes/ │ ├── session-start.ts # { context: "..." } output, MCP tools mention in inject │ ├── capture.ts # same logic, Hermes payload shape │ ├── pre-tool-use.ts # terminal tool intercept only │ ├── session-end.ts # on_session_end event │ ├── wiki-worker.ts # shells `hermes` in non-interactive mode │ └── spawn-wiki-worker.ts # Hermes config keys -└── pi/ +└── harnesses/pi/ └── wiki-worker.ts # shells `pi --print --provider

--model ` ``` -The pi extension itself lives at `pi/extension-source/hivemind.ts` (installed as `~/.pi/agent/hivemind/hivemind.ts`). It contains the pi-native session hooks and spawns the wiki worker. Only the wiki worker is in `src/hooks/pi/` because the extension entry point is pi-specific TypeScript that pi compiles directly. +The pi extension itself lives at `harnesses/pi/extension-source/hivemind.ts` (installed as `~/.pi/agent/hivemind/hivemind.ts`). It contains the pi-native session hooks and spawns the wiki worker. Only the wiki worker is in `src/hooks/pi/` because the extension entry point is pi-specific TypeScript that pi compiles directly. --- diff --git a/library/knowledge/private/plugins/integration-model.md b/library/knowledge/private/plugins/integration-model.md index 5ef9e04e..056f0d5e 100644 --- a/library/knowledge/private/plugins/integration-model.md +++ b/library/knowledge/private/plugins/integration-model.md @@ -70,7 +70,7 @@ Second, Cursor's PreToolUse only intercepts Shell tool calls. It cannot intercep ## OpenClaw: native extension with contracted tools -OpenClaw loads plugins from `~/.openclaw/extensions/hivemind/` as a native extension using a synchronous `register(pluginApi)` entry point defined in `openclaw/src/index.ts`. The `register` function must return synchronously; all async work executes inside a fire-and-forget IIFE. +OpenClaw loads plugins from `~/.openclaw/extensions/hivemind/` as a native extension using a synchronous `register(pluginApi)` entry point defined in `harnesses/openclaw/src/index.ts`. The `register` function must return synchronously; all async work executes inside a fire-and-forget IIFE. The extension wires two hook events via `pluginApi.on(event, handler)`: @@ -96,7 +96,7 @@ Hermes's PreToolUse intercepts only the `terminal` tool, not Write or Edit. Goal ## pi: TypeScript extension and AGENTS.md -pi loads the Hivemind extension from `~/.pi/agent/hivemind/` as raw TypeScript files that pi compiles at load time. There is no pre-compiled bundle; the extension source ships in `pi/` under version control. A static `AGENTS.md` block at `~/.pi/agent/AGENTS.md` provides recall instructions that pi injects into every session's system context. +pi loads the Hivemind extension from `~/.pi/agent/hivemind/` as raw TypeScript files that pi compiles at load time. There is no pre-compiled bundle; the extension source ships in `harnesses/pi/` under version control. A static `AGENTS.md` block at `~/.pi/agent/AGENTS.md` provides recall instructions that pi injects into every session's system context. pi has no PreToolUse hook. Recall happens via grep on `~/.deeplake/memory/`, as documented in the `AGENTS.md` block. The wiki worker (`src/hooks/pi/wiki-worker.ts`) is invoked by the extension and shells out to `pi --print --provider

--model ` for summary generation, using pi's non-interactive mode so it does not recurse back into the extension. diff --git a/library/knowledge/private/plugins/mcp-and-extension-surfaces.md b/library/knowledge/private/plugins/mcp-and-extension-surfaces.md index 3cd179c5..7a09e976 100644 --- a/library/knowledge/private/plugins/mcp-and-extension-surfaces.md +++ b/library/knowledge/private/plugins/mcp-and-extension-surfaces.md @@ -20,8 +20,8 @@ The three non-hook integration surfaces in Hivemind: the MCP server that exposes Hook-based capture and VFS recall cover Claude Code, Codex, Cursor, Hermes, and pi through their respective `hooks.json` or extension entry points. Three additional surfaces exist for agents or runtimes that cannot or should not use hooks for recall: 1. The **MCP server** (`src/mcp/server.ts`) provides a standard Model Context Protocol interface to Hivemind memory, currently used by Hermes and available to any future MCP-capable client. -2. The **OpenClaw extension** (`openclaw/src/index.ts`) is a native plugin that integrates through OpenClaw's gateway contract, registering tools, commands, and a memory-corpus supplement instead of hook events. -3. The **distributable bundles** (`claude-code/`, `cursor/`) are the packaged artifacts that ship to their respective marketplaces and hook registries, built from the shared core sources. +2. The **OpenClaw extension** (`harnesses/openclaw/src/index.ts`) is a native plugin that integrates through OpenClaw's gateway contract, registering tools, commands, and a memory-corpus supplement instead of hook events. +3. The **distributable bundles** (`harnesses/claude-code/`, `cursor/`) are the packaged artifacts that ship to their respective marketplaces and hook registries, built from the shared core sources. --- @@ -69,11 +69,11 @@ The server calls `loadCredentials` at the start of every tool invocation via `ge ## The OpenClaw extension -OpenClaw loads plugins from `~/.openclaw/extensions/hivemind/dist/index.js`. The extension is built from `openclaw/src/index.ts` and installed by `src/cli/install-openclaw.ts`. It follows OpenClaw's `definePluginEntry` contract: a synchronous `register(pluginApi)` function that must complete registration before returning, with all async work in a fire-and-forget IIFE. +OpenClaw loads plugins from `~/.openclaw/extensions/hivemind/dist/index.js`. The extension is built from `harnesses/openclaw/src/index.ts` and installed by `src/cli/install-openclaw.ts`. It follows OpenClaw's `definePluginEntry` contract: a synchronous `register(pluginApi)` function that must complete registration before returning, with all async work in a fire-and-forget IIFE. ### Plugin manifest -The OpenClaw plugin manifest at `openclaw/openclaw.plugin.json` declares the extension's contract: +The OpenClaw plugin manifest at `harnesses/openclaw/openclaw.plugin.json` declares the extension's contract: ```json { @@ -141,7 +141,7 @@ The extension registers a `MemoryCorpusSupplement` object with `search` and `get ### Claude Code marketplace plugin -The Claude Code integration ships as a marketplace plugin from the `claude-code/` directory. The build step (`npm run build`) runs `tsc` then `esbuild` and emits the bundle to `claude-code/bundle/`. The plugin is submitted to the Claude Code Marketplace and installed by users via `hivemind install` or by running the marketplace installer directly. +The Claude Code integration ships as a marketplace plugin from the `harnesses/claude-code/` directory. The build step (`npm run build`) runs `tsc` then `esbuild` and emits the bundle to `harnesses/claude-code/bundle/`. The plugin is submitted to the Claude Code Marketplace and installed by users via `hivemind install` or by running the marketplace installer directly. The plugin manifest specifies the hook entry points and declares the Claude Code-specific capabilities (tool permissions, capabilities flags). The actual hook logic lives in `src/hooks/` and is bundled into the plugin output. @@ -155,16 +155,16 @@ Cursor 1.7+ introduced a hooks mechanism that is semantically similar to Claude ```mermaid flowchart LR - src["src/hooks/\nsrc/mcp/\nopenclaw/src/"] -- tsc + esbuild --> bundles + src["src/hooks/\nsrc/mcp/\nharnesses/openclaw/src/"] -- tsc + esbuild --> bundles subgraph bundles["Build outputs"] - ccBundle["claude-code/bundle/\n→ Claude Code Marketplace"] - codexBundle["codex/bundle/\n→ npm @deeplake/hivemind"] + ccBundle["harnesses/claude-code/bundle/\n→ Claude Code Marketplace"] + codexBundle["harnesses/codex/bundle/\n→ npm @deeplake/hivemind"] cursorBundle["cursor/bundle/\n→ npm + hooks.json"] - hermesBundle["hermes/bundle/\n→ npm"] + hermesBundle["harnesses/hermes/bundle/\n→ npm"] mcpBundle["mcp/bundle/\n→ npm (Hermes uses this)"] - openclawDist["openclaw/dist/\n→ ClawHub"] - piSource["pi/extension-source/\n→ npm (raw .ts, pi compiles)"] + openclawDist["harnesses/openclaw/dist/\n→ ClawHub"] + piSource["harnesses/pi/extension-source/\n→ npm (raw .ts, pi compiles)"] cliBundle["bundle/cli.js\n→ npm @deeplake/hivemind"] end ``` diff --git a/package.json b/package.json index cf225667..0680ccc2 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,16 @@ }, "files": [ "bundle", - "codex/bundle", - "codex/skills", + "harnesses/codex/bundle", + "harnesses/codex/skills", "cursor/bundle", - "hermes/bundle", + "harnesses/hermes/bundle", "mcp/bundle", - "pi/extension-source", - "openclaw/dist", - "openclaw/skills", - "openclaw/openclaw.plugin.json", - "openclaw/package.json", + "harnesses/pi/extension-source", + "harnesses/openclaw/dist", + "harnesses/openclaw/skills", + "harnesses/openclaw/openclaw.plugin.json", + "harnesses/openclaw/package.json", ".claude-plugin", "scripts", "README.md", diff --git a/scripts/audit-openclaw-bundle.mjs b/scripts/audit-openclaw-bundle.mjs index 9a2a793b..f3a0a3f0 100644 --- a/scripts/audit-openclaw-bundle.mjs +++ b/scripts/audit-openclaw-bundle.mjs @@ -11,7 +11,7 @@ * Reference: openclaw repo, src/security/skill-scanner.ts:147-206 * * Usage: - * node scripts/audit-openclaw-bundle.mjs # scan openclaw/dist + * node scripts/audit-openclaw-bundle.mjs # scan harnesses/openclaw/dist * node scripts/audit-openclaw-bundle.mjs # scan a specific dir * * Exits non-zero if any "critical" or "warn" finding is reported. @@ -32,7 +32,7 @@ import { join, extname } from "node:path"; // surface every drift before it ships. const rawArgs = process.argv.slice(2); const STRICT_MODE = !rawArgs.includes("--criticals-only"); -const SCAN_DIR = rawArgs.find(a => !a.startsWith("--")) ?? "openclaw/dist"; +const SCAN_DIR = rawArgs.find(a => !a.startsWith("--")) ?? "harnesses/openclaw/dist"; const SCANNABLE_EXT = new Set([".js", ".mjs", ".cjs"]); const MAX_FILE_BYTES = 1024 * 1024; // 1MB; matches upstream default diff --git a/scripts/sync-versions.mjs b/scripts/sync-versions.mjs index 4e243cc8..e2d7f194 100644 --- a/scripts/sync-versions.mjs +++ b/scripts/sync-versions.mjs @@ -15,10 +15,10 @@ const SOURCE = "package.json"; // Scalar targets: each has a single top-level `version` field tracking package.json. export const SCALAR_TARGETS = [ ".claude-plugin/plugin.json", - "claude-code/.claude-plugin/plugin.json", - "openclaw/openclaw.plugin.json", - "openclaw/package.json", - "codex/package.json", + "harnesses/claude-code/.claude-plugin/plugin.json", + "harnesses/openclaw/openclaw.plugin.json", + "harnesses/openclaw/package.json", + "harnesses/codex/package.json", ]; // Marketplace target: has BOTH metadata.version AND every plugins[].version. diff --git a/src/cli/embeddings.ts b/src/cli/embeddings.ts index 9f1ed3f6..e1e78dd7 100644 --- a/src/cli/embeddings.ts +++ b/src/cli/embeddings.ts @@ -54,8 +54,8 @@ export function findHivemindInstalls(home: string = HOME): AgentInstall[] { const dir = join(ccCache, ver); try { if (!statSync(dir).isDirectory()) continue; } catch { continue; } // Bundle layout differs slightly: marketplace installs put it directly - // under /bundle, while local-clone-style layouts use /claude-code/bundle. - const candidates = [join(dir, "bundle"), join(dir, "claude-code", "bundle")]; + // under /bundle, while local-clone-style layouts use /harnesses/claude-code/bundle. + const candidates = [join(dir, "bundle"), join(dir, "harnesses", "claude-code", "bundle")]; if (candidates.some(p => existsSync(p))) { out.push({ id: `claude (${ver})`, pluginDir: dir }); } diff --git a/src/cli/index.ts b/src/cli/index.ts index 546e3137..31b6430c 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -138,7 +138,7 @@ Team-wide rules: Cross-agent helpers: hivemind context Print the rules + open-goals block on demand. - Fallback for pi/openclaw agents (no SessionStart hook) + Fallback for harnesses/pi/openclaw agents (no SessionStart hook) and read-only diagnostic for any agent. Account / org / workspace: diff --git a/src/cli/install-codex.ts b/src/cli/install-codex.ts index 8b93d073..99b571fe 100644 --- a/src/cli/install-codex.ts +++ b/src/cli/install-codex.ts @@ -31,7 +31,7 @@ function buildHooksJson(): Record { PostToolUse: [hookCmd("capture.js", 15)], // One Stop matcher-block with TWO commands — stop.js (capture) + // graph-on-stop.js (code-graph auto-build, G3). Single block (not two) - // mirrors the static codex/hooks/hooks.json and keeps one entry per + // mirrors the static harnesses/codex/hooks/hooks.json and keeps one entry per // event for the merge/dedupe logic. Stop: [stopBlockWithGraph(30)], }, @@ -228,8 +228,8 @@ function stripLegacyCodexHooksKey(): void { } export function installCodex(): void { - const srcBundle = join(pkgRoot(), "codex", "bundle"); - const srcSkills = join(pkgRoot(), "codex", "skills"); + const srcBundle = join(pkgRoot(), "harnesses", "codex", "bundle"); + const srcSkills = join(pkgRoot(), "harnesses", "codex", "skills"); if (!existsSync(srcBundle)) { throw new Error(`Codex bundle missing at ${srcBundle}. Run 'npm run build' first.`); diff --git a/src/cli/install-hermes.ts b/src/cli/install-hermes.ts index 15a624f9..3ca5545e 100644 --- a/src/cli/install-hermes.ts +++ b/src/cli/install-hermes.ts @@ -183,7 +183,7 @@ export function installHermes(): void { log(` Hermes skill installed -> ${SKILLS_DIR}`); // 2. Hook bundle — auto-capture via Hermes shell-hooks. - const srcBundle = join(pkgRoot(), "hermes", "bundle"); + const srcBundle = join(pkgRoot(), "harnesses", "hermes", "bundle"); if (!existsSync(srcBundle)) { throw new Error(`Hermes bundle missing at ${srcBundle}. Run 'npm run build' first.`); } diff --git a/src/cli/install-openclaw.ts b/src/cli/install-openclaw.ts index 7b5f8058..559936c4 100644 --- a/src/cli/install-openclaw.ts +++ b/src/cli/install-openclaw.ts @@ -2,15 +2,15 @@ import { existsSync, copyFileSync, rmSync } from "node:fs"; import { join } from "node:path"; import { HOME, pkgRoot, ensureDir, copyDir, writeVersionStamp, log, warn } from "./util.js"; import { getVersion } from "./version.js"; -import { ensureHivemindAllowlisted } from "../../openclaw/src/setup-config.js"; +import { ensureHivemindAllowlisted } from "../../harnesses/openclaw/src/setup-config.js"; const PLUGIN_DIR = join(HOME, ".openclaw", "extensions", "hivemind"); export function installOpenclaw(): void { - const srcDist = join(pkgRoot(), "openclaw", "dist"); - const srcManifest = join(pkgRoot(), "openclaw", "openclaw.plugin.json"); - const srcPkg = join(pkgRoot(), "openclaw", "package.json"); - const srcSkills = join(pkgRoot(), "openclaw", "skills"); + const srcDist = join(pkgRoot(), "harnesses", "openclaw", "dist"); + const srcManifest = join(pkgRoot(), "harnesses", "openclaw", "openclaw.plugin.json"); + const srcPkg = join(pkgRoot(), "harnesses", "openclaw", "package.json"); + const srcSkills = join(pkgRoot(), "harnesses", "openclaw", "skills"); if (!existsSync(srcDist)) { throw new Error(`OpenClaw bundle missing at ${srcDist}. Run 'npm run build' first.`); diff --git a/src/cli/install-pi.ts b/src/cli/install-pi.ts index b344ea7f..2092cd04 100644 --- a/src/cli/install-pi.ts +++ b/src/cli/install-pi.ts @@ -120,7 +120,7 @@ export function installPi(): void { writeFileSync(AGENTS_MD, next); // 2. Extension — autocapture + first-class hivemind tools. - const srcExtension = join(pkgRoot(), "pi", "extension-source", "hivemind.ts"); + const srcExtension = join(pkgRoot(), "harnesses", "pi", "extension-source", "hivemind.ts"); if (!existsSync(srcExtension)) { throw new Error(`pi extension source missing at ${srcExtension}. Reinstall the @deeplake/hivemind package.`); } @@ -129,7 +129,7 @@ export function installPi(): void { // 3. Wiki-worker bundle (spawned by extension at periodic + session_shutdown // triggers to generate AI summary via `pi --print`). - const srcWorker = join(pkgRoot(), "pi", "bundle", "wiki-worker.js"); + const srcWorker = join(pkgRoot(), "harnesses", "pi", "bundle", "wiki-worker.js"); if (existsSync(srcWorker)) { ensureDir(WIKI_WORKER_DIR); copyFileSync(srcWorker, WIKI_WORKER_PATH); @@ -138,7 +138,7 @@ export function installPi(): void { // 4. Skillify-worker bundle (spawned by extension on session_shutdown to // mine reusable skills from the finished session). Same dir as // wiki-worker, same shared ensureDir. - const srcSkillifyWorker = join(pkgRoot(), "pi", "bundle", "skillify-worker.js"); + const srcSkillifyWorker = join(pkgRoot(), "harnesses", "pi", "bundle", "skillify-worker.js"); if (existsSync(srcSkillifyWorker)) { ensureDir(WIKI_WORKER_DIR); copyFileSync(srcSkillifyWorker, SKILLIFY_WORKER_PATH); @@ -146,7 +146,7 @@ export function installPi(): void { // 5. Autopull-worker bundle (spawned synchronously by extension on // session_start to pull all-author skills from the org). Same dir. - const srcAutopullWorker = join(pkgRoot(), "pi", "bundle", "autopull-worker.js"); + const srcAutopullWorker = join(pkgRoot(), "harnesses", "pi", "bundle", "autopull-worker.js"); if (existsSync(srcAutopullWorker)) { ensureDir(WIKI_WORKER_DIR); copyFileSync(srcAutopullWorker, AUTOPULL_WORKER_PATH); @@ -154,7 +154,7 @@ export function installPi(): void { // 6. SkillOpt-worker bundle (spawned by extension on a user reaction to judge + // improve a recently-used org skill). Same dir, same cleanup. - const srcSkilloptWorker = join(pkgRoot(), "pi", "bundle", "skillopt-worker.js"); + const srcSkilloptWorker = join(pkgRoot(), "harnesses", "pi", "bundle", "skillopt-worker.js"); if (existsSync(srcSkilloptWorker)) { ensureDir(WIKI_WORKER_DIR); copyFileSync(srcSkilloptWorker, SKILLOPT_WORKER_PATH); diff --git a/src/cli/skillify-spec.ts b/src/cli/skillify-spec.ts index 7acd676f..41538f32 100644 --- a/src/cli/skillify-spec.ts +++ b/src/cli/skillify-spec.ts @@ -5,7 +5,7 @@ * * 1. `SKILLIFY_COMMANDS` — flat one-line-per-entry. Consumed by the four * per-agent SessionStart inject blocks (claude-code/codex/cursor/hermes), - * the pi mirror in `pi/extension-source/hivemind.ts`, and the bundle-scan + * the pi mirror in `harnesses/pi/extension-source/hivemind.ts`, and the bundle-scan * tests that assert specific subcommand strings appear verbatim in the * shipped JS. Kept as a literal array (not derived) so esbuild preserves * every entry as a string literal in the bundle. diff --git a/src/cli/util.ts b/src/cli/util.ts index aca6168c..edc71fc8 100644 --- a/src/cli/util.ts +++ b/src/cli/util.ts @@ -14,7 +14,7 @@ export const HOME = homedir(); // → install dir // Without the walk-up, the source path resolved to `src/` (one dir up // from src/cli/util.ts), so unit tests importing the installers couldn't -// find the per-agent bundles at project_root//bundle/. +// find the per-agent bundles at project_root/harnesses//bundle/. export function pkgRoot(): string { let dir = fileURLToPath(new URL(".", import.meta.url)); for (let i = 0; i < 8; i++) { diff --git a/src/graph/build-lock.ts b/src/graph/build-lock.ts index 3cba38c9..87eb8bbd 100644 --- a/src/graph/build-lock.ts +++ b/src/graph/build-lock.ts @@ -119,7 +119,7 @@ export function acquireBuildLock(baseDir: string): LockResult { * will fall through to the stale-recovery path after STALE_LOCK_MS. */ export function releaseBuildLock(baseDir: string): void { - // Owner-gated release (codex/CodeRabbit P1): if stale-recovery happened + // Owner-gated release (harnesses/codex/CodeRabbit P1): if stale-recovery happened // while an older build was still running, the older process must NOT // unlink the NEWER process's lock when it eventually exits. Read the // lock's `pid` field and only unlink when it matches ours. diff --git a/src/hooks/cursor/session-end.ts b/src/hooks/cursor/session-end.ts index 7b8a4048..924a5999 100644 --- a/src/hooks/cursor/session-end.ts +++ b/src/hooks/cursor/session-end.ts @@ -7,7 +7,7 @@ * final_status, error_message? } + common payload. * * Spawns a final wiki-worker run via cursor-agent --print so the session - * gets an AI summary in the memory table. Mirrors the codex/CC flow. + * gets an AI summary in the memory table. Mirrors the harnesses/codex/CC flow. */ import { readStdin } from "../../utils/stdin.js"; diff --git a/src/hooks/graph-on-stop.ts b/src/hooks/graph-on-stop.ts index 97f26a85..a689eaa1 100644 --- a/src/hooks/graph-on-stop.ts +++ b/src/hooks/graph-on-stop.ts @@ -1,7 +1,7 @@ /** * Auto-build hook for the codebase-graph feature (Phase 1.5). * - * Registered in claude-code/hooks/hooks.json under BOTH "Stop" AND + * Registered in harnesses/claude-code/hooks/hooks.json under BOTH "Stop" AND * "SessionEnd" with async: true. Why both: * - "Stop" fires after every model turn in INTERACTIVE Claude sessions. * Rate-limit gate (10 min default) keeps the per-turn cost ~5ms in diff --git a/src/hooks/hermes/session-end.ts b/src/hooks/hermes/session-end.ts index 295d44d8..a0b31454 100644 --- a/src/hooks/hermes/session-end.ts +++ b/src/hooks/hermes/session-end.ts @@ -2,7 +2,7 @@ * Hermes on_session_end hook (fire-and-forget). * * Spawns a final wiki-worker run via `hermes -z` so the session gets - * an AI summary in the memory table. Mirrors the codex/CC flow. + * an AI summary in the memory table. Mirrors the harnesses/codex/CC flow. */ import { readStdin } from "../../utils/stdin.js"; diff --git a/src/hooks/hermes/session-start.ts b/src/hooks/hermes/session-start.ts index 3d1038e2..ae25810e 100644 --- a/src/hooks/hermes/session-start.ts +++ b/src/hooks/hermes/session-start.ts @@ -206,7 +206,7 @@ async function main(): Promise { // `hivemind goal add/list/...` via terminal. End state in tables // is identical to the VFS-routed path. const baseWithGoals = creds?.token ? `${baseContext}\n\n${GOALS_INSTRUCTIONS_CLI}` : baseContext; - // Code-graph inject. Unlike claude-code/cursor this is user-visible in the + // Code-graph inject. Unlike harnesses/claude-code/cursor this is user-visible in the // Hermes TUI (Hermes has no model-only SessionStart channel), but an // always-present structural index is worth the extra lines. graphContextLine // returns null — and appends nothing — when no graph exists for this repo yet. diff --git a/src/hooks/pi/wiki-worker.ts b/src/hooks/pi/wiki-worker.ts index 5ce4b7a2..51af6da3 100644 --- a/src/hooks/pi/wiki-worker.ts +++ b/src/hooks/pi/wiki-worker.ts @@ -5,7 +5,7 @@ * runs `pi --print` to generate a wiki summary, and uploads it to the * memory table. * - * Invoked by pi/extension-source/hivemind.ts (periodic + session_shutdown + * Invoked by harnesses/pi/extension-source/hivemind.ts (periodic + session_shutdown * triggers) as: node wiki-worker.js . The extension itself * is shipped as raw .ts and can't import this file directly — that's why * this lives as a standalone bundle deposited next to the extension at diff --git a/src/hooks/session-notifications.ts b/src/hooks/session-notifications.ts index 6062c807..f11746e7 100644 --- a/src/hooks/session-notifications.ts +++ b/src/hooks/session-notifications.ts @@ -3,7 +3,7 @@ /** * Claude Code SessionStart hook entry point — notifications channel. * - * Wired as a SECOND SessionStart hook command in claude-code/hooks/hooks.json, + * Wired as a SECOND SessionStart hook command in harnesses/claude-code/hooks/hooks.json, * alongside the existing memory/hivemind hook (session-start.js). * * Bundle target: bundle/session-notifications.js. See esbuild.config.mjs. diff --git a/src/hooks/shared/context-renderer.ts b/src/hooks/shared/context-renderer.ts index 77f32aaa..13dafdd7 100644 --- a/src/hooks/shared/context-renderer.ts +++ b/src/hooks/shared/context-renderer.ts @@ -16,7 +16,7 @@ * - Per-agent forks differ only in how they wrap the surrounding * context (stdin shape, output envelope, agent-specific log lines). * The rules / goals rendering is invariant. - * - `hivemind context` CLI for pi/openclaw calls the same renderer + * - `hivemind context` CLI for harnesses/pi/openclaw calls the same renderer * to print the block on demand — same output as SessionStart, * deterministically. * diff --git a/src/hooks/shared/skillopt-hook.ts b/src/hooks/shared/skillopt-hook.ts index 4ef46ee3..e083517b 100644 --- a/src/hooks/shared/skillopt-hook.ts +++ b/src/hooks/shared/skillopt-hook.ts @@ -11,7 +11,7 @@ import { SKILLOPT_ENV } from "../../skillify/skillopt-env.js"; /** * Recover an org-skill ref from a tool call that LOADS a skill's SKILL.md — how agents without * a first-class `Skill` tool use skills: pi `read`s `.../skills/

/SKILL.md` (structured - * `path`), codex/hermes SHELL a read of it (path inside `command`). The `` segment is the + * `path`), harnesses/codex/hermes SHELL a read of it (path inside `command`). The `` segment is the * ref. Returns null when it isn't a SKILL.md load. markSkillPending still gates the ref * (org-shape + manifest), so a bare/non-org dir is rejected there. */ diff --git a/src/skillify/agent-model.ts b/src/skillify/agent-model.ts index 3b3102d5..ac6209d1 100644 --- a/src/skillify/agent-model.ts +++ b/src/skillify/agent-model.ts @@ -105,7 +105,7 @@ function envModel(agent: Agent, role: ScorerRole, env: NodeJS.ProcessEnv): strin return env[specific] ?? env[fallback]; } -/** Per-agent provider override (hermes/pi): HIVEMIND_SKILLOPT__PROVIDER. The +/** Per-agent provider override (harnesses/hermes/pi): HIVEMIND_SKILLOPT__PROVIDER. The * openrouter default is wrong for a user on a different provider (e.g. AWS Bedrock), * so the provider must be overridable per install. */ function envProvider(agent: Agent, env: NodeJS.ProcessEnv): string | undefined { @@ -132,7 +132,7 @@ export function agentModel(opts: { const spawnFn = opts.spawnImpl ?? (nodeSpawn as unknown as SpawnFn); const bin = opts.bin ?? findAgentBin(opts.agent); return (system, user) => new Promise((resolve, reject) => { - // Fail fast on a provider override without a matching model (hermes/pi): the default + // Fail fast on a provider override without a matching model (harnesses/hermes/pi): the default // model is provider-specific (openrouter-style ids), so a bare ..._PROVIDER=bedrock // with no ..._MODEL would silently send a wrong model id. Surface it loudly. if (providerOverride && !modelOverride && (opts.agent === "hermes" || opts.agent === "pi")) { diff --git a/src/skillify/autopull-worker.ts b/src/skillify/autopull-worker.ts index 7f464a93..e2d47159 100644 --- a/src/skillify/autopull-worker.ts +++ b/src/skillify/autopull-worker.ts @@ -2,7 +2,7 @@ * Standalone entrypoint that runs `autoPullSkills()` once and exits. * Bundled by esbuild for agents that can't import the shared module * (currently just pi, which ships its extension as raw .ts with zero - * non-builtin runtime dependencies — see pi/extension-source/hivemind.ts). + * non-builtin runtime dependencies — see harnesses/pi/extension-source/hivemind.ts). * * Pi spawns this synchronously from session_start and waits for it to * exit before assembling the additionalContext payload. That mirrors diff --git a/src/skillify/gate-runner.ts b/src/skillify/gate-runner.ts index f4029609..1dba99eb 100644 --- a/src/skillify/gate-runner.ts +++ b/src/skillify/gate-runner.ts @@ -23,8 +23,8 @@ import { createRequire } from "node:module"; // the gate prompt, but the literal symbol name `execFileSync` paired with // a `child_process` import would trip ClawHub's per-bundle static scanner // (`dangerous-exec`) when this module is bundled into -// `openclaw/dist/skillify-worker.js`. Mirrors the same `createRequire`- -// based bypass used by `openclaw/src/index.ts:78-80` for `spawn`. The +// `harnesses/openclaw/dist/skillify-worker.js`. Mirrors the same `createRequire`- +// based bypass used by `harnesses/openclaw/src/index.ts:78-80` for `spawn`. The // scanner's regex `\bexecFileSync\s*\(` doesn't match the renamed // identifier, and esbuild can't statically intercept `require()` returned // from `createRequire`. @@ -76,7 +76,7 @@ export interface GateRunResult { * install locations, in priority order, until one exists on disk. * * Why no `which` / no PATH walk: this module is bundled into the openclaw - * skillify-worker (`openclaw/dist/skillify-worker.js`), which ClawHub + * skillify-worker (`harnesses/openclaw/dist/skillify-worker.js`), which ClawHub * scans per-file at publish time. Both `child_process.execFileSync` * (`dangerous-exec`) and `process.env.PATH` reads (`env-harvesting`) * trip critical rules because the worker also `fetch()`-es Deeplake. So diff --git a/src/skillify/skill-invocations.ts b/src/skillify/skill-invocations.ts index e1430951..289a332b 100644 --- a/src/skillify/skill-invocations.ts +++ b/src/skillify/skill-invocations.ts @@ -46,7 +46,7 @@ export function parseMessage(m: unknown): ParsedMsg | null { /** Match a path that loads a skill's SKILL.md anywhere in `s` → the `` ref (name--author), * else null. Works on a bare path (pi `read` tool_input.path) or inside a shell command string - * (codex/hermes `cat …/SKILL.md`). The dir class excludes whitespace/quotes so a command's + * (harnesses/codex/hermes `cat …/SKILL.md`). The dir class excludes whitespace/quotes so a command's * trailing args don't get swallowed into the ref. */ export function pathToSkillRef(s: unknown): string | null { if (typeof s !== "string") return null; diff --git a/src/skillify/skillopt-worker.ts b/src/skillify/skillopt-worker.ts index 7f84f515..9959178e 100644 --- a/src/skillify/skillopt-worker.ts +++ b/src/skillify/skillopt-worker.ts @@ -62,7 +62,7 @@ async function main(): Promise { const now = new Date().toISOString(); // Score on the USER's own agent (cost lands on them), not hardcoded claude — a - // codex/hermes/cursor/pi user with no local `claude` still gets SkillOpt. The + // harnesses/codex/hermes/cursor/pi user with no local `claude` still gets SkillOpt. The // judge/proposer run no-tools (untrusted reaction/transcript text in the prompt). const agent = detectScorerAgent(); const agentBin = resolveAgentBin(agent); diff --git a/tests/claude-code/embeddings-bundle-scan.test.ts b/tests/claude-code/embeddings-bundle-scan.test.ts index 22020977..27ea58ab 100644 --- a/tests/claude-code/embeddings-bundle-scan.test.ts +++ b/tests/claude-code/embeddings-bundle-scan.test.ts @@ -24,13 +24,13 @@ interface AgentBundle { const AGENTS: AgentBundle[] = [ { agent: "claude-code", - embedDaemon: join(repoRoot, "claude-code", "bundle", "embeddings", "embed-daemon.js"), - captureHook: join(repoRoot, "claude-code", "bundle", "capture.js"), + embedDaemon: join(repoRoot, "harnesses", "claude-code", "bundle", "embeddings", "embed-daemon.js"), + captureHook: join(repoRoot, "harnesses", "claude-code", "bundle", "capture.js"), }, { agent: "codex", - embedDaemon: join(repoRoot, "codex", "bundle", "embeddings", "embed-daemon.js"), - captureHook: join(repoRoot, "codex", "bundle", "capture.js"), + embedDaemon: join(repoRoot, "harnesses", "codex", "bundle", "embeddings", "embed-daemon.js"), + captureHook: join(repoRoot, "harnesses", "codex", "bundle", "capture.js"), }, { agent: "cursor", @@ -39,8 +39,8 @@ const AGENTS: AgentBundle[] = [ }, { agent: "hermes", - embedDaemon: join(repoRoot, "hermes", "bundle", "embeddings", "embed-daemon.js"), - captureHook: join(repoRoot, "hermes", "bundle", "capture.js"), + embedDaemon: join(repoRoot, "harnesses", "hermes", "bundle", "embeddings", "embed-daemon.js"), + captureHook: join(repoRoot, "harnesses", "hermes", "bundle", "capture.js"), }, ]; @@ -105,11 +105,11 @@ describe("shipped shell/deeplake-shell.js — embed daemon path resolves to an e // bundled shell would look for it). const SHELL_BUNDLES: Array<[string, string, string]> = [ ["claude-code", - join(repoRoot, "claude-code", "bundle", "shell", "deeplake-shell.js"), - join(repoRoot, "claude-code", "bundle", "embeddings", "embed-daemon.js")], + join(repoRoot, "harnesses", "claude-code", "bundle", "shell", "deeplake-shell.js"), + join(repoRoot, "harnesses", "claude-code", "bundle", "embeddings", "embed-daemon.js")], ["codex", - join(repoRoot, "codex", "bundle", "shell", "deeplake-shell.js"), - join(repoRoot, "codex", "bundle", "embeddings", "embed-daemon.js")], + join(repoRoot, "harnesses", "codex", "bundle", "shell", "deeplake-shell.js"), + join(repoRoot, "harnesses", "codex", "bundle", "embeddings", "embed-daemon.js")], ["cursor", join(repoRoot, "cursor", "bundle", "shell", "deeplake-shell.js"), join(repoRoot, "cursor", "bundle", "embeddings", "embed-daemon.js")], diff --git a/tests/claude-code/notifications.test.ts b/tests/claude-code/notifications.test.ts index 3ad39bb9..8879d507 100644 --- a/tests/claude-code/notifications.test.ts +++ b/tests/claude-code/notifications.test.ts @@ -730,7 +730,7 @@ describe("drainSessionStart resilience", () => { // --------------------------------------------------------------------------- describe("bundle/session-notifications.js (built artifact)", () => { - const bundlePath = join(process.cwd(), "claude-code", "bundle", "session-notifications.js"); + const bundlePath = join(process.cwd(), "harnesses", "claude-code", "bundle", "session-notifications.js"); // spawnSync (vs execFileSync) so we can capture stdout + stderr separately. // The banner is userVisibleOnly: it must appear in the top-level diff --git a/tests/claude-code/periodic-summary-bundles.test.ts b/tests/claude-code/periodic-summary-bundles.test.ts index 95f49112..897d0b01 100644 --- a/tests/claude-code/periodic-summary-bundles.test.ts +++ b/tests/claude-code/periodic-summary-bundles.test.ts @@ -28,22 +28,22 @@ import { resolve } from "node:path"; const BUNDLE_ROOT = resolve(process.cwd()); const SESSION_END_HOOKS: Array<[string, string]> = [ - ["claude-code session-end", resolve(BUNDLE_ROOT, "claude-code", "bundle", "session-end.js")], - ["codex stop", resolve(BUNDLE_ROOT, "codex", "bundle", "stop.js")], + ["claude-code session-end", resolve(BUNDLE_ROOT, "harnesses", "claude-code", "bundle", "session-end.js")], + ["codex stop", resolve(BUNDLE_ROOT, "harnesses", "codex", "bundle", "stop.js")], ]; const CAPTURE_HOOKS: Array<[string, string]> = [ - ["claude-code capture", resolve(BUNDLE_ROOT, "claude-code", "bundle", "capture.js")], - ["codex capture", resolve(BUNDLE_ROOT, "codex", "bundle", "capture.js")], + ["claude-code capture", resolve(BUNDLE_ROOT, "harnesses", "claude-code", "bundle", "capture.js")], + ["codex capture", resolve(BUNDLE_ROOT, "harnesses", "codex", "bundle", "capture.js")], ]; const ALL_BUNDLES: Array<[string, string]> = [ ...SESSION_END_HOOKS, ...CAPTURE_HOOKS, - ["claude-code session-start", resolve(BUNDLE_ROOT, "claude-code", "bundle", "session-start.js")], - ["claude-code session-start-setup", resolve(BUNDLE_ROOT, "claude-code", "bundle", "session-start-setup.js")], - ["codex session-start", resolve(BUNDLE_ROOT, "codex", "bundle", "session-start.js")], - ["codex session-start-setup", resolve(BUNDLE_ROOT, "codex", "bundle", "session-start-setup.js")], + ["claude-code session-start", resolve(BUNDLE_ROOT, "harnesses", "claude-code", "bundle", "session-start.js")], + ["claude-code session-start-setup", resolve(BUNDLE_ROOT, "harnesses", "claude-code", "bundle", "session-start-setup.js")], + ["codex session-start", resolve(BUNDLE_ROOT, "harnesses", "codex", "bundle", "session-start.js")], + ["codex session-start-setup", resolve(BUNDLE_ROOT, "harnesses", "codex", "bundle", "session-start-setup.js")], ]; describe("bundles exist", () => { diff --git a/tests/claude-code/plugin-cache-bundles.test.ts b/tests/claude-code/plugin-cache-bundles.test.ts index c5eca6a2..f4b4a899 100644 --- a/tests/claude-code/plugin-cache-bundles.test.ts +++ b/tests/claude-code/plugin-cache-bundles.test.ts @@ -18,7 +18,7 @@ import { join } from "node:path"; * versioned dirs at SessionEnd) and that's still asserted below. */ -const claudeCodeRoot = join(process.cwd(), "claude-code"); +const claudeCodeRoot = join(process.cwd(), "harnesses", "claude-code"); const claudeCodeBundleDir = join(claudeCodeRoot, "bundle"); describe("shipped bundles contain plugin-cache safety", () => { diff --git a/tests/claude-code/plugin-cache-gc-bundle.integration.test.ts b/tests/claude-code/plugin-cache-gc-bundle.integration.test.ts index 84744de4..e4f1184e 100644 --- a/tests/claude-code/plugin-cache-gc-bundle.integration.test.ts +++ b/tests/claude-code/plugin-cache-gc-bundle.integration.test.ts @@ -13,7 +13,7 @@ import { tmpdir } from "node:os"; * Requires the bundle to be built (`npm run build`). Skipped if missing. */ -const bundlePath = resolve(process.cwd(), "claude-code", "bundle", "plugin-cache-gc.js"); +const bundlePath = resolve(process.cwd(), "harnesses", "claude-code", "bundle", "plugin-cache-gc.js"); const bundleExists = existsSync(bundlePath); function makeFakeHome(): string { @@ -132,14 +132,14 @@ describe.skipIf(!bundleExists)("plugin-cache-gc shipped bundle", () => { it("skips silently when bundle is in a local --plugin-dir layout (not under ~/.claude/plugins/cache)", () => { const sandbox = join(tmpdir(), `hivemind-dev-it-${Date.now()}-${Math.random().toString(36).slice(2)}`); - mkdirSync(join(sandbox, "claude-code", "bundle"), { recursive: true }); + mkdirSync(join(sandbox, "harnesses", "claude-code", "bundle"), { recursive: true }); try { - cpSync(bundlePath, join(sandbox, "claude-code", "bundle", "plugin-cache-gc.js")); - const { stdout, stderr } = runGcBundle(sandbox, join(sandbox, "claude-code", "bundle", "plugin-cache-gc.js")); + cpSync(bundlePath, join(sandbox, "harnesses", "claude-code", "bundle", "plugin-cache-gc.js")); + const { stdout, stderr } = runGcBundle(sandbox, join(sandbox, "harnesses", "claude-code", "bundle", "plugin-cache-gc.js")); // Must not crash; output is fine to be empty. expect(stderr).not.toMatch(/TypeError|ReferenceError|Cannot find module/); // The sandbox dir should be unchanged. - expect(statSync(join(sandbox, "claude-code")).isDirectory()).toBe(true); + expect(statSync(join(sandbox, "harnesses", "claude-code")).isDirectory()).toBe(true); expect(stdout).toBe(""); } finally { rmSync(sandbox, { recursive: true, force: true }); diff --git a/tests/claude-code/plugin-cache-gc.test.ts b/tests/claude-code/plugin-cache-gc.test.ts index e8a6a191..6fa13ca0 100644 --- a/tests/claude-code/plugin-cache-gc.test.ts +++ b/tests/claude-code/plugin-cache-gc.test.ts @@ -72,7 +72,7 @@ describe("runGc", () => { }); it("logs and returns when bundleDir is not a versioned install", () => { - runGc(join(root, "claude-code", "bundle"), { log }); + runGc(join(root, "harnesses", "claude-code", "bundle"), { log }); expect(logs).toContain("not a versioned install, skipping"); }); diff --git a/tests/claude-code/plugin-cache.test.ts b/tests/claude-code/plugin-cache.test.ts index f91db797..a794e8df 100644 --- a/tests/claude-code/plugin-cache.test.ts +++ b/tests/claude-code/plugin-cache.test.ts @@ -51,7 +51,7 @@ describe("resolveVersionedPluginDir", () => { it("rejects a local --plugin-dir layout", () => { const root = mkRoot(); try { - const bundle = join(root, "claude-code", "bundle"); + const bundle = join(root, "harnesses", "claude-code", "bundle"); mkdirSync(bundle, { recursive: true }); expect(resolveVersionedPluginDir(bundle)).toBeNull(); } finally { diff --git a/tests/claude-code/plugin-version-resolution.test.ts b/tests/claude-code/plugin-version-resolution.test.ts index 4f31b0a1..e3acba08 100644 --- a/tests/claude-code/plugin-version-resolution.test.ts +++ b/tests/claude-code/plugin-version-resolution.test.ts @@ -36,11 +36,11 @@ interface AgentLayout { } const AGENTS: AgentLayout[] = [ - { agent: "claude-code", bundleDir: resolve(REPO_ROOT, "claude-code", "bundle"), manifestDir: ".claude-plugin" }, - { agent: "codex", bundleDir: resolve(REPO_ROOT, "codex", "bundle"), manifestDir: ".codex-plugin" }, + { agent: "claude-code", bundleDir: resolve(REPO_ROOT, "harnesses", "claude-code", "bundle"), manifestDir: ".claude-plugin" }, + { agent: "codex", bundleDir: resolve(REPO_ROOT, "harnesses", "codex", "bundle"), manifestDir: ".codex-plugin" }, { agent: "cursor", bundleDir: resolve(REPO_ROOT, "cursor", "bundle"), manifestDir: ".claude-plugin" }, - { agent: "hermes", bundleDir: resolve(REPO_ROOT, "hermes", "bundle"), manifestDir: ".claude-plugin" }, - { agent: "pi", bundleDir: resolve(REPO_ROOT, "pi", "bundle"), manifestDir: ".claude-plugin" }, + { agent: "hermes", bundleDir: resolve(REPO_ROOT, "harnesses", "hermes", "bundle"), manifestDir: ".claude-plugin" }, + { agent: "pi", bundleDir: resolve(REPO_ROOT, "harnesses", "pi", "bundle"), manifestDir: ".claude-plugin" }, ]; describe("plugin_version stamps a non-empty value for every shipped agent", () => { @@ -64,12 +64,12 @@ describe("plugin_version is wired into every agent's capture INSERT", () => { // SQL for every agent's session-event writer. Mirrors the spirit of // wiki-worker-upload-sql.test.ts. const CAPTURE_BUNDLES: Array<[string, string]> = [ - ["claude-code capture", resolve(REPO_ROOT, "claude-code", "bundle", "capture.js")], - ["codex capture", resolve(REPO_ROOT, "codex", "bundle", "capture.js")], + ["claude-code capture", resolve(REPO_ROOT, "harnesses", "claude-code", "bundle", "capture.js")], + ["codex capture", resolve(REPO_ROOT, "harnesses", "codex", "bundle", "capture.js")], ["cursor capture", resolve(REPO_ROOT, "cursor", "bundle", "capture.js")], - ["hermes capture", resolve(REPO_ROOT, "hermes", "bundle", "capture.js")], - ["codex stop", resolve(REPO_ROOT, "codex", "bundle", "stop.js")], - ["openclaw index", resolve(REPO_ROOT, "openclaw", "dist", "index.js")], + ["hermes capture", resolve(REPO_ROOT, "harnesses", "hermes", "bundle", "capture.js")], + ["codex stop", resolve(REPO_ROOT, "harnesses", "codex", "bundle", "stop.js")], + ["openclaw index", resolve(REPO_ROOT, "harnesses", "openclaw", "dist", "index.js")], ]; it.each(CAPTURE_BUNDLES)("%s INSERT lists plugin_version column", (_label, path) => { @@ -84,10 +84,10 @@ describe("plugin_version is wired into every agent's capture INSERT", () => { describe("plugin_version is wired into every agent's session-start placeholder INSERT", () => { const PLACEHOLDER_BUNDLES: Array<[string, string]> = [ - ["claude-code session-start", resolve(REPO_ROOT, "claude-code", "bundle", "session-start.js")], - ["codex session-start-setup", resolve(REPO_ROOT, "codex", "bundle", "session-start-setup.js")], + ["claude-code session-start", resolve(REPO_ROOT, "harnesses", "claude-code", "bundle", "session-start.js")], + ["codex session-start-setup", resolve(REPO_ROOT, "harnesses", "codex", "bundle", "session-start-setup.js")], ["cursor session-start", resolve(REPO_ROOT, "cursor", "bundle", "session-start.js")], - ["hermes session-start", resolve(REPO_ROOT, "hermes", "bundle", "session-start.js")], + ["hermes session-start", resolve(REPO_ROOT, "harnesses", "hermes", "bundle", "session-start.js")], ]; it.each(PLACEHOLDER_BUNDLES)("%s placeholder INSERT lists plugin_version column", (_label, path) => { diff --git a/tests/claude-code/pre-tool-use.test.ts b/tests/claude-code/pre-tool-use.test.ts index be3a7724..6313c39d 100644 --- a/tests/claude-code/pre-tool-use.test.ts +++ b/tests/claude-code/pre-tool-use.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { execFileSync } from "node:child_process"; import { join } from "node:path"; -const bundleDir = join(process.cwd(), "claude-code", "bundle"); +const bundleDir = join(process.cwd(), "harnesses", "claude-code", "bundle"); /** * Pipe JSON into the CC pre-tool-use hook and return parsed output. diff --git a/tests/claude-code/session-start.test.ts b/tests/claude-code/session-start.test.ts index 72eb0604..3ae27ce0 100644 --- a/tests/claude-code/session-start.test.ts +++ b/tests/claude-code/session-start.test.ts @@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process"; import { readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; -const ccRoot = join(process.cwd(), "claude-code"); +const ccRoot = join(process.cwd(), "harnesses", "claude-code"); const bundleDir = join(ccRoot, "bundle"); // ── hooks.json structure tests ────────────────────────────────────────────── diff --git a/tests/claude-code/shell-bundle-sql-trace-silence.test.ts b/tests/claude-code/shell-bundle-sql-trace-silence.test.ts index f238674e..6b9b21cd 100644 --- a/tests/claude-code/shell-bundle-sql-trace-silence.test.ts +++ b/tests/claude-code/shell-bundle-sql-trace-silence.test.ts @@ -29,7 +29,7 @@ import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const BUNDLE_PATH = join(process.cwd(), "claude-code", "bundle", "shell", "deeplake-shell.js"); +const BUNDLE_PATH = join(process.cwd(), "harnesses", "claude-code", "bundle", "shell", "deeplake-shell.js"); describe("shell bundle one-shot: SQL trace silence (fix #3)", () => { it("does not write [deeplake-sql] to stderr even when trace env vars are set", () => { diff --git a/tests/claude-code/skillify-bundle-scan.test.ts b/tests/claude-code/skillify-bundle-scan.test.ts index 0f878f70..be30785d 100644 --- a/tests/claude-code/skillify-bundle-scan.test.ts +++ b/tests/claude-code/skillify-bundle-scan.test.ts @@ -19,7 +19,8 @@ const AGENTS = ["claude-code", "codex", "cursor", "hermes", "openclaw"] as const function bundlePath(agent: string, file: string): string { const dir = agent === "openclaw" ? "dist" : "bundle"; - return join(ROOT, agent, dir, file); + const base = agent === "cursor" ? join(ROOT, agent) : join(ROOT, "harnesses", agent); + return join(base, dir, file); } describe("skillify-worker bundle is shipped per agent", () => { @@ -135,8 +136,8 @@ describe("legacy state-dir migration is shipped in every agent's bundle", () => }); } - it("openclaw/dist/index.js: inlined migration present and called before fsMkdir", () => { - const text = readFileSync(join(ROOT, "openclaw", "dist", "index.js"), "utf-8"); + it("harnesses/openclaw/dist/index.js: inlined migration present and called before fsMkdir", () => { + const text = readFileSync(join(ROOT, "harnesses", "openclaw", "dist", "index.js"), "utf-8"); expect(text).toContain("function migrateOpenclawSkillifyLegacyStateDir"); // Must be called inside tryAcquireOpenclawSkillifyLock before the fsMkdir. // The order matters: once fsMkdir creates the new dir, the migration diff --git a/tests/claude-code/skillify-session-start-injection.test.ts b/tests/claude-code/skillify-session-start-injection.test.ts index 1a532bd6..518695a0 100644 --- a/tests/claude-code/skillify-session-start-injection.test.ts +++ b/tests/claude-code/skillify-session-start-injection.test.ts @@ -22,28 +22,28 @@ const BUNDLE_ROOT = resolve(process.cwd()); // AGENT_CHANNELS.md → Codex), so the skillify command list lives in the // auto-loaded `hivemind-memory` skill instead of the hook context. const SESSION_START_BUNDLES: Array<[string, string]> = [ - ["claude-code", resolve(BUNDLE_ROOT, "claude-code", "bundle", "session-start.js")], + ["claude-code", resolve(BUNDLE_ROOT, "harnesses", "claude-code", "bundle", "session-start.js")], ["cursor", resolve(BUNDLE_ROOT, "cursor", "bundle", "session-start.js")], - ["hermes", resolve(BUNDLE_ROOT, "hermes", "bundle", "session-start.js")], + ["hermes", resolve(BUNDLE_ROOT, "harnesses", "hermes", "bundle", "session-start.js")], ]; // Pi and OpenClaw don't go through the same esbuild bundle pipeline: -// - Pi ships pi/extension-source/hivemind.ts as raw .ts (pi compiles it) -// - OpenClaw exposes its surface via openclaw/skills/SKILL.md (loaded by +// - Pi ships harnesses/pi/extension-source/hivemind.ts as raw .ts (pi compiles it) +// - OpenClaw exposes its surface via harnesses/openclaw/skills/SKILL.md (loaded by // the openclaw runtime's skill index, not bundled JS) // Codex sits here too: its skillify discoverability lives in -// codex/skills/deeplake-memory/SKILL.md (auto-loaded), not in the hook bundle. +// harnesses/codex/skills/deeplake-memory/SKILL.md (auto-loaded), not in the hook bundle. const NON_BUNDLE_SURFACES: Array<[string, string]> = [ - ["pi-extension-source", resolve(BUNDLE_ROOT, "pi", "extension-source", "hivemind.ts")], - ["openclaw-skill", resolve(BUNDLE_ROOT, "openclaw", "skills", "SKILL.md")], - ["codex-skill", resolve(BUNDLE_ROOT, "codex", "skills", "deeplake-memory", "SKILL.md")], + ["pi-extension-source", resolve(BUNDLE_ROOT, "harnesses", "pi", "extension-source", "hivemind.ts")], + ["openclaw-skill", resolve(BUNDLE_ROOT, "harnesses", "openclaw", "skills", "SKILL.md")], + ["codex-skill", resolve(BUNDLE_ROOT, "harnesses", "codex", "skills", "deeplake-memory", "SKILL.md")], ]; // Codex bundle — separate matrix because it asserts a NEGATIVE: the slim // invariant says the bundle MUST NOT inline the verbose skillify command list // (every byte there is shown to the user as `hook context: ...`). const CODEX_BUNDLE: [string, string] = [ - "codex", resolve(BUNDLE_ROOT, "codex", "bundle", "session-start.js"), + "codex", resolve(BUNDLE_ROOT, "harnesses", "codex", "bundle", "session-start.js"), ]; describe("skillify SessionStart injection (per-agent bundles)", () => { @@ -119,7 +119,7 @@ describe("Codex bundle slim invariant + skill-as-source-of-truth", () => { it("Codex bundle does NOT inline the skillify command list (it lives in the skill)", () => { const text = readFileSync(CODEX_BUNDLE[1], "utf-8"); - // The skillify list belongs in codex/skills/deeplake-memory/SKILL.md + // The skillify list belongs in harnesses/codex/skills/deeplake-memory/SKILL.md // (auto-loaded by codex's skill loader) — emitting it via stdout would // dump ~50 lines into the user's `hook context: ...` history cell. expect(text).not.toMatch(/skillify pull --user/); @@ -183,7 +183,7 @@ describe("skillify discoverability on non-bundle agent surfaces (Pi + OpenClaw + // npm-bin unification) and now propagated. OpenClaw uses /hivemind_* // plugin-native commands which are a different surface — covered by // openclaw.plugin.json contracts.commands, not by this assertion. - const text = readFileSync(resolve(BUNDLE_ROOT, "pi", "extension-source", "hivemind.ts"), "utf-8"); + const text = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "pi", "extension-source", "hivemind.ts"), "utf-8"); expect(text).toMatch(/Organization management/); expect(text).toMatch(/hivemind whoami\b/); expect(text).toMatch(/hivemind org list\b/); @@ -197,8 +197,8 @@ describe("Pi skillify worker (mining) wiring", () => { // assertions catch any regression that drops the bundle entry, removes // the install copy, or unwires the spawn call. - it("ships pi/bundle/skillify-worker.js after build", () => { - const p = resolve(BUNDLE_ROOT, "pi", "bundle", "skillify-worker.js"); + it("ships harnesses/pi/bundle/skillify-worker.js after build", () => { + const p = resolve(BUNDLE_ROOT, "harnesses", "pi", "bundle", "skillify-worker.js"); expect(existsSync(p)).toBe(true); }); @@ -208,17 +208,17 @@ describe("Pi skillify worker (mining) wiring", () => { expect(cfg).toMatch(/dist\/src\/skillify\/skillify-worker\.js[^"]*"\s*,\s*out:\s*"skillify-worker"/); }); - it("install-pi.ts copies pi/bundle/skillify-worker.js to ~/.pi/agent/hivemind/", () => { + it("install-pi.ts copies harnesses/pi/bundle/skillify-worker.js to ~/.pi/agent/hivemind/", () => { const src = readFileSync(resolve(BUNDLE_ROOT, "src", "cli", "install-pi.ts"), "utf-8"); expect(src).toMatch(/SKILLIFY_WORKER_PATH\s*=/); - // join(pkgRoot(), "pi", "bundle", "skillify-worker.js") — the source path + // join(pkgRoot(), "harnesses", "pi", "bundle", "skillify-worker.js") — the source path expect(src).toMatch(/"pi",\s*"bundle",\s*"skillify-worker\.js"/); // copyFileSync(srcSkillifyWorker, SKILLIFY_WORKER_PATH) — the install step expect(src).toMatch(/copyFileSync\(srcSkillifyWorker,\s*SKILLIFY_WORKER_PATH\)/); }); it("pi extension defines spawnPiSkillifyWorker and wires it into session_shutdown", () => { - const ext = readFileSync(resolve(BUNDLE_ROOT, "pi", "extension-source", "hivemind.ts"), "utf-8"); + const ext = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "pi", "extension-source", "hivemind.ts"), "utf-8"); // Function exists expect(ext).toMatch(/function spawnPiSkillifyWorker\b/); // Path const points at the right install location @@ -232,7 +232,7 @@ describe("Pi skillify worker (mining) wiring", () => { it("pi skillify worker bundle embeds the same worker code as the other agents", () => { // Same shared module — guard against an accidental empty bundle by // checking the canonical entry-point + module markers are present. - const text = readFileSync(resolve(BUNDLE_ROOT, "pi", "bundle", "skillify-worker.js"), "utf-8"); + const text = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "pi", "bundle", "skillify-worker.js"), "utf-8"); // The worker reads its config from process.argv[2] expect(text).toMatch(/process\.argv\[2\]/); // The worker writes to the skills table via INSERT (append-only design) @@ -247,12 +247,12 @@ describe("Pi skillify worker (mining) wiring", () => { describe("OpenClaw skillify worker (mining) wiring", () => { // OpenClaw mines via a separate bundled worker spawned from the agent_end // hook. The worker bundle is built as a second openclaw esbuild entry - // landing at openclaw/dist/skillify-worker.js (sibling of index.js). + // landing at harnesses/openclaw/dist/skillify-worker.js (sibling of index.js). // install-openclaw.ts already copies the entire dist/ recursively, so // no install step change is required. - it("ships openclaw/dist/skillify-worker.js after build", () => { - const p = resolve(BUNDLE_ROOT, "openclaw", "dist", "skillify-worker.js"); + it("ships harnesses/openclaw/dist/skillify-worker.js after build", () => { + const p = resolve(BUNDLE_ROOT, "harnesses", "openclaw", "dist", "skillify-worker.js"); expect(existsSync(p)).toBe(true); }); @@ -267,8 +267,8 @@ describe("OpenClaw skillify worker (mining) wiring", () => { expect(cfg).toMatch(/outdir:\s*"openclaw\/dist"[\s\S]{0,2000}skillify-worker/); }); - it("openclaw/src/index.ts bypasses the child_process stub via createRequire", () => { - const src = readFileSync(resolve(BUNDLE_ROOT, "openclaw", "src", "index.ts"), "utf-8"); + it("harnesses/openclaw/src/index.ts bypasses the child_process stub via createRequire", () => { + const src = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "openclaw", "src", "index.ts"), "utf-8"); // The main openclaw bundle stubs out node:child_process to drop CC dead // code. createRequire(import.meta.url) is the runtime escape hatch — it // is NOT intercepted by esbuild's static analysis. @@ -276,8 +276,8 @@ describe("OpenClaw skillify worker (mining) wiring", () => { expect(src).toMatch(/requireFromOpenclaw\("node:child_process"\)/); }); - it("openclaw/src/index.ts defines spawnOpenclawSkillifyWorker and wires it into agent_end", () => { - const src = readFileSync(resolve(BUNDLE_ROOT, "openclaw", "src", "index.ts"), "utf-8"); + it("harnesses/openclaw/src/index.ts defines spawnOpenclawSkillifyWorker and wires it into agent_end", () => { + const src = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "openclaw", "src", "index.ts"), "utf-8"); expect(src).toMatch(/function spawnOpenclawSkillifyWorker\b/); // OPENCLAW_SKILLIFY_WORKER_PATH must be a sibling of import.meta.url expect(src).toMatch(/OPENCLAW_SKILLIFY_WORKER_PATH\s*=\s*joinPath\(__openclaw_dirname,\s*"skillify-worker\.js"\)/); @@ -300,7 +300,7 @@ describe("OpenClaw skillify worker (mining) wiring", () => { }); it("openclaw bundle preserves the createRequire spawn (not stubbed by esbuild)", () => { - const text = readFileSync(resolve(BUNDLE_ROOT, "openclaw", "dist", "index.js"), "utf-8"); + const text = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "openclaw", "dist", "index.js"), "utf-8"); // After bundling, the createRequire + dynamic require call must still be there expect(text).toMatch(/createRequire\(import\.meta\.url\)/); expect(text).toMatch(/requireFromOpenclaw\("node:child_process"\)/); @@ -321,7 +321,7 @@ describe("OpenClaw skillify worker (mining) wiring", () => { // and pass it as `gateAgent`; the worker dispatches `runGate` against // that delegate while keeping `agent: "openclaw"` for source_agent // provenance in the skills table. Regression guard. - const src = readFileSync(resolve(BUNDLE_ROOT, "openclaw", "src", "index.ts"), "utf-8"); + const src = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "openclaw", "src", "index.ts"), "utf-8"); expect(src).toMatch(/function detectOpenclawGateAgent\b/); // The candidate list: the five CLIs the worker's gate-runner knows about. expect(src).toMatch(/"claude_code",\s*"claude"/); @@ -334,14 +334,14 @@ describe("OpenClaw skillify worker (mining) wiring", () => { // gateAgent threaded into the worker config — same key the worker reads. expect(src).toMatch(/gateAgent,/); // Bundled output must preserve the detection + threading. - const text = readFileSync(resolve(BUNDLE_ROOT, "openclaw", "dist", "index.js"), "utf-8"); + const text = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "openclaw", "dist", "index.js"), "utf-8"); expect(text).toMatch(/detectOpenclawGateAgent/); expect(text).toMatch(/gateAgent/); expect(text).toMatch(/no delegate gate CLI found/); }); it("openclaw worker bundle embeds the same shared worker code as other agents", () => { - const text = readFileSync(resolve(BUNDLE_ROOT, "openclaw", "dist", "skillify-worker.js"), "utf-8"); + const text = readFileSync(resolve(BUNDLE_ROOT, "harnesses", "openclaw", "dist", "skillify-worker.js"), "utf-8"); expect(text).toMatch(/process\.argv\[2\]/); expect(text).toMatch(/INSERT INTO/); expect(text).toMatch(/gate-runner|runGate/); diff --git a/tests/claude-code/utils-version-check.test.ts b/tests/claude-code/utils-version-check.test.ts index 4d94f683..b5b86787 100644 --- a/tests/claude-code/utils-version-check.test.ts +++ b/tests/claude-code/utils-version-check.test.ts @@ -49,10 +49,10 @@ describe("getInstalledVersion — plugin-manifest branch", () => { }); it("prefers /..//plugin.json when present", () => { - const bundle = join(root, "claude-code", "bundle"); - mkdirSync(join(root, "claude-code", ".claude-plugin"), { recursive: true }); + const bundle = join(root, "harnesses", "claude-code", "bundle"); + mkdirSync(join(root, "harnesses", "claude-code", ".claude-plugin"), { recursive: true }); mkdirSync(bundle, { recursive: true }); - writeFileSync(join(root, "claude-code", ".claude-plugin", "plugin.json"), + writeFileSync(join(root, "harnesses", "claude-code", ".claude-plugin", "plugin.json"), JSON.stringify({ version: "1.2.3" })); writeFileSync(join(root, "package.json"), JSON.stringify({ name: "@deeplake/hivemind", version: "9.9.9" })); diff --git a/tests/claude-code/version-check.test.ts b/tests/claude-code/version-check.test.ts index 4d01aad7..7aba297b 100644 --- a/tests/claude-code/version-check.test.ts +++ b/tests/claude-code/version-check.test.ts @@ -32,27 +32,27 @@ describe("getInstalledVersion", () => { }); it("prefers plugin manifest when present", () => { - const bundleDir = join(root, "claude-code", "bundle"); - mkdirSync(join(root, "claude-code", ".claude-plugin"), { recursive: true }); + const bundleDir = join(root, "harnesses", "claude-code", "bundle"); + mkdirSync(join(root, "harnesses", "claude-code", ".claude-plugin"), { recursive: true }); mkdirSync(bundleDir, { recursive: true }); - writeFileSync(join(root, "claude-code", ".claude-plugin", "plugin.json"), JSON.stringify({ version: "0.6.37" })); + writeFileSync(join(root, "harnesses", "claude-code", ".claude-plugin", "plugin.json"), JSON.stringify({ version: "0.6.37" })); writeFileSync(join(root, "package.json"), JSON.stringify({ name: "hivemind", version: "0.1.0" })); expect(getInstalledVersion(bundleDir, ".claude-plugin")).toBe("0.6.37"); }); it("falls back to package.json when plugin manifest has no version", () => { - const bundleDir = join(root, "claude-code", "bundle"); - mkdirSync(join(root, "claude-code", ".claude-plugin"), { recursive: true }); + const bundleDir = join(root, "harnesses", "claude-code", "bundle"); + mkdirSync(join(root, "harnesses", "claude-code", ".claude-plugin"), { recursive: true }); mkdirSync(bundleDir, { recursive: true }); - writeFileSync(join(root, "claude-code", ".claude-plugin", "plugin.json"), JSON.stringify({ name: "hivemind" })); + writeFileSync(join(root, "harnesses", "claude-code", ".claude-plugin", "plugin.json"), JSON.stringify({ name: "hivemind" })); writeFileSync(join(root, "package.json"), JSON.stringify({ name: "hivemind", version: "0.6.41" })); expect(getInstalledVersion(bundleDir, ".claude-plugin")).toBe("0.6.41"); }); it("walks up to package.json when plugin manifest is absent", () => { - const bundleDir = join(root, "codex", "bundle"); + const bundleDir = join(root, "harnesses", "codex", "bundle"); mkdirSync(bundleDir, { recursive: true }); writeFileSync(join(root, "package.json"), JSON.stringify({ name: "hivemind-codex", version: "0.6.40" })); @@ -68,10 +68,10 @@ describe("getInstalledVersion", () => { }); it("returns null when the plugin manifest is invalid json and no package matches", () => { - const bundleDir = join(root, "claude-code", "bundle"); - mkdirSync(join(root, "claude-code", ".claude-plugin"), { recursive: true }); + const bundleDir = join(root, "harnesses", "claude-code", "bundle"); + mkdirSync(join(root, "harnesses", "claude-code", ".claude-plugin"), { recursive: true }); mkdirSync(bundleDir, { recursive: true }); - writeFileSync(join(root, "claude-code", ".claude-plugin", "plugin.json"), "{bad-json"); + writeFileSync(join(root, "harnesses", "claude-code", ".claude-plugin", "plugin.json"), "{bad-json"); expect(getInstalledVersion(bundleDir, ".claude-plugin")).toBeNull(); }); diff --git a/tests/claude-code/version-define-bundles.test.ts b/tests/claude-code/version-define-bundles.test.ts index 9813810c..f94aa71a 100644 --- a/tests/claude-code/version-define-bundles.test.ts +++ b/tests/claude-code/version-define-bundles.test.ts @@ -31,10 +31,10 @@ function listBundleFiles(dir: string): string[] { } const BUNDLE_DIRS = [ - ["claude-code", resolve(ROOT, "claude-code", "bundle")], - ["codex", resolve(ROOT, "codex", "bundle")], + ["claude-code", resolve(ROOT, "harnesses", "claude-code", "bundle")], + ["codex", resolve(ROOT, "harnesses", "codex", "bundle")], ["cursor", resolve(ROOT, "cursor", "bundle")], - ["hermes", resolve(ROOT, "hermes", "bundle")], + ["hermes", resolve(ROOT, "harnesses", "hermes", "bundle")], ]; for (const [label, dir] of BUNDLE_DIRS) { diff --git a/tests/claude-code/wiki-next-steps-contract.test.ts b/tests/claude-code/wiki-next-steps-contract.test.ts index eef85def..3d662912 100644 --- a/tests/claude-code/wiki-next-steps-contract.test.ts +++ b/tests/claude-code/wiki-next-steps-contract.test.ts @@ -26,7 +26,7 @@ import { WIKI_PROMPT_TEMPLATE as HERMES_TEMPLATE } from "../../src/hooks/hermes/ // executed here, so lift the template literal from source — same approach as // tests/pi/pi-extension-source.test.ts. const PI_TEMPLATE = readFileSync( - join(process.cwd(), "pi", "extension-source", "hivemind.ts"), + join(process.cwd(), "harnesses", "pi", "extension-source", "hivemind.ts"), "utf-8", ); diff --git a/tests/claude-code/wiki-worker-plugin-version.test.ts b/tests/claude-code/wiki-worker-plugin-version.test.ts index 256dec0b..da93915e 100644 --- a/tests/claude-code/wiki-worker-plugin-version.test.ts +++ b/tests/claude-code/wiki-worker-plugin-version.test.ts @@ -7,7 +7,7 @@ import { join } from "node:path"; * Functional pluginVersion threading test for the cursor / hermes / pi * wiki-worker variants. * - * `claude-code/tests/wiki-worker.test.ts` already exhaustively tests the + * `harnesses/claude-code/tests/wiki-worker.test.ts` already exhaustively tests the * shared worker structure for the claude-code variant. This file is a * narrower guard: for each of the three sibling agents, the worker MUST * read `cfg.pluginVersion` from its spawn-config JSON and forward it to diff --git a/tests/claude-code/wiki-worker-upload-sql.test.ts b/tests/claude-code/wiki-worker-upload-sql.test.ts index 7ccec8be..849c5dfd 100644 --- a/tests/claude-code/wiki-worker-upload-sql.test.ts +++ b/tests/claude-code/wiki-worker-upload-sql.test.ts @@ -15,8 +15,8 @@ import { resolve } from "node:path"; const ROOT = process.cwd(); const BUNDLES: Array<[string, string]> = [ - ["claude-code", resolve(ROOT, "claude-code", "bundle", "wiki-worker.js")], - ["codex", resolve(ROOT, "codex", "bundle", "wiki-worker.js")], + ["claude-code", resolve(ROOT, "harnesses", "claude-code", "bundle", "wiki-worker.js")], + ["codex", resolve(ROOT, "harnesses", "codex", "bundle", "wiki-worker.js")], ]; for (const [label, path] of BUNDLES) { diff --git a/tests/cli/cli-embeddings.test.ts b/tests/cli/cli-embeddings.test.ts index afa66c06..50520f91 100644 --- a/tests/cli/cli-embeddings.test.ts +++ b/tests/cli/cli-embeddings.test.ts @@ -76,9 +76,9 @@ describe("findHivemindInstalls", () => { expect(installs.map(i => i.id).sort()).toEqual(["claude (0.7.0)", "claude (0.7.1)"]); }); - it("supports the alternate /claude-code/bundle layout", () => { + it("supports the alternate /harnesses/claude-code/bundle layout", () => { const cache = join(tmpHome, ".claude", "plugins", "cache", "hivemind", "hivemind"); - fakeBundleAt(join(cache, "0.7.0", "claude-code")); + fakeBundleAt(join(cache, "0.7.0", "harnesses", "claude-code")); const installs = findHivemindInstalls(tmpHome); expect(installs).toHaveLength(1); expect(installs[0].id).toBe("claude (0.7.0)"); diff --git a/tests/cli/cli-install-codex-fs.test.ts b/tests/cli/cli-install-codex-fs.test.ts index 9694b24f..8c2990c0 100644 --- a/tests/cli/cli-install-codex-fs.test.ts +++ b/tests/cli/cli-install-codex-fs.test.ts @@ -37,11 +37,11 @@ beforeEach(() => { mkdirSync(join(tmpHome, ".codex"), { recursive: true }); // Mock package layout: pkgRoot/codex/{bundle,skills}/ - mkdirSync(join(tmpPkg, "codex", "bundle"), { recursive: true }); - writeFileSync(join(tmpPkg, "codex", "bundle", "session-start.js"), "// fake bundle file"); - writeFileSync(join(tmpPkg, "codex", "bundle", "capture.js"), "// fake bundle file"); - mkdirSync(join(tmpPkg, "codex", "skills", "deeplake-memory"), { recursive: true }); - writeFileSync(join(tmpPkg, "codex", "skills", "deeplake-memory", "SKILL.md"), "fake skill body"); + mkdirSync(join(tmpPkg, "harnesses", "codex", "bundle"), { recursive: true }); + writeFileSync(join(tmpPkg, "harnesses", "codex", "bundle", "session-start.js"), "// fake bundle file"); + writeFileSync(join(tmpPkg, "harnesses", "codex", "bundle", "capture.js"), "// fake bundle file"); + mkdirSync(join(tmpPkg, "harnesses", "codex", "skills", "deeplake-memory"), { recursive: true }); + writeFileSync(join(tmpPkg, "harnesses", "codex", "skills", "deeplake-memory", "SKILL.md"), "fake skill body"); // Mock package.json so getVersion() resolves to a known value. writeFileSync(join(tmpPkg, "package.json"), JSON.stringify({ version: "1.2.3" })); @@ -271,14 +271,14 @@ describe("installCodex — happy path", () => { }); it("warns and skips the symlink (without throwing) when the skill source is missing", async () => { - rmSync(join(tmpPkg, "codex", "skills", "deeplake-memory"), { recursive: true, force: true }); + rmSync(join(tmpPkg, "harnesses", "codex", "skills", "deeplake-memory"), { recursive: true, force: true }); const { installCodex } = await importInstaller(); expect(() => installCodex()).not.toThrow(); expect(existsSync(join(tmpHome, ".agents", "skills", "hivemind-memory"))).toBe(false); }); it("throws when the bundle source is missing (build hasn't run)", async () => { - rmSync(join(tmpPkg, "codex", "bundle"), { recursive: true, force: true }); + rmSync(join(tmpPkg, "harnesses", "codex", "bundle"), { recursive: true, force: true }); const { installCodex } = await importInstaller(); expect(() => installCodex()).toThrow(/Codex bundle missing/); }); diff --git a/tests/cli/cli-install-hermes.test.ts b/tests/cli/cli-install-hermes.test.ts index e48a770f..1e11a503 100644 --- a/tests/cli/cli-install-hermes.test.ts +++ b/tests/cli/cli-install-hermes.test.ts @@ -24,11 +24,11 @@ beforeEach(() => { tmpPkg = join(tmpRoot, "pkg"); mkdirSync(tmpHome, { recursive: true }); - mkdirSync(join(tmpPkg, "hermes", "bundle"), { recursive: true }); - writeFileSync(join(tmpPkg, "hermes", "bundle", "session-start.js"), "// fake bundle"); - writeFileSync(join(tmpPkg, "hermes", "bundle", "capture.js"), "// fake bundle"); - writeFileSync(join(tmpPkg, "hermes", "bundle", "pre-tool-use.js"), "// fake bundle"); - writeFileSync(join(tmpPkg, "hermes", "bundle", "session-end.js"), "// fake bundle"); + mkdirSync(join(tmpPkg, "harnesses", "hermes", "bundle"), { recursive: true }); + writeFileSync(join(tmpPkg, "harnesses", "hermes", "bundle", "session-start.js"), "// fake bundle"); + writeFileSync(join(tmpPkg, "harnesses", "hermes", "bundle", "capture.js"), "// fake bundle"); + writeFileSync(join(tmpPkg, "harnesses", "hermes", "bundle", "pre-tool-use.js"), "// fake bundle"); + writeFileSync(join(tmpPkg, "harnesses", "hermes", "bundle", "session-end.js"), "// fake bundle"); mkdirSync(join(tmpPkg, "mcp", "bundle"), { recursive: true }); writeFileSync(join(tmpPkg, "mcp", "bundle", "server.js"), "// fake mcp server"); @@ -136,7 +136,7 @@ describe("installHermes — cold install", () => { }); it("throws when the hermes hook bundle source is missing (build hasn't run)", async () => { - rmSync(join(tmpPkg, "hermes", "bundle"), { recursive: true, force: true }); + rmSync(join(tmpPkg, "harnesses", "hermes", "bundle"), { recursive: true, force: true }); const { installHermes } = await importInstaller(); expect(() => installHermes()).toThrow(/Hermes bundle missing/); }); diff --git a/tests/cli/cli-install-openclaw.test.ts b/tests/cli/cli-install-openclaw.test.ts index 59204199..0538f461 100644 --- a/tests/cli/cli-install-openclaw.test.ts +++ b/tests/cli/cli-install-openclaw.test.ts @@ -19,12 +19,12 @@ beforeEach(() => { tmpPkg = join(tmpRoot, "pkg"); mkdirSync(tmpHome, { recursive: true }); - mkdirSync(join(tmpPkg, "openclaw", "dist"), { recursive: true }); - writeFileSync(join(tmpPkg, "openclaw", "dist", "index.js"), "// fake dist"); - writeFileSync(join(tmpPkg, "openclaw", "openclaw.plugin.json"), JSON.stringify({ name: "hivemind", version: "1.2.3" })); - writeFileSync(join(tmpPkg, "openclaw", "package.json"), JSON.stringify({ name: "hivemind", version: "1.2.3" })); - mkdirSync(join(tmpPkg, "openclaw", "skills"), { recursive: true }); - writeFileSync(join(tmpPkg, "openclaw", "skills", "hivemind.md"), "skill body"); + mkdirSync(join(tmpPkg, "harnesses", "openclaw", "dist"), { recursive: true }); + writeFileSync(join(tmpPkg, "harnesses", "openclaw", "dist", "index.js"), "// fake dist"); + writeFileSync(join(tmpPkg, "harnesses", "openclaw", "openclaw.plugin.json"), JSON.stringify({ name: "hivemind", version: "1.2.3" })); + writeFileSync(join(tmpPkg, "harnesses", "openclaw", "package.json"), JSON.stringify({ name: "hivemind", version: "1.2.3" })); + mkdirSync(join(tmpPkg, "harnesses", "openclaw", "skills"), { recursive: true }); + writeFileSync(join(tmpPkg, "harnesses", "openclaw", "skills", "hivemind.md"), "skill body"); writeFileSync(join(tmpPkg, "package.json"), JSON.stringify({ version: "1.2.3" })); vi.stubEnv("HOME", tmpHome); @@ -83,8 +83,8 @@ describe("installOpenclaw", () => { }); it("skips optional sources that don't exist (skills, manifest) without throwing", async () => { - rmSync(join(tmpPkg, "openclaw", "skills"), { recursive: true, force: true }); - rmSync(join(tmpPkg, "openclaw", "openclaw.plugin.json")); + rmSync(join(tmpPkg, "harnesses", "openclaw", "skills"), { recursive: true, force: true }); + rmSync(join(tmpPkg, "harnesses", "openclaw", "openclaw.plugin.json")); const { installOpenclaw } = await importInstaller(); expect(() => installOpenclaw()).not.toThrow(); const root = join(tmpHome, ".openclaw", "extensions", "hivemind"); @@ -94,7 +94,7 @@ describe("installOpenclaw", () => { }); it("throws when the dist source is missing (build hasn't run)", async () => { - rmSync(join(tmpPkg, "openclaw", "dist"), { recursive: true, force: true }); + rmSync(join(tmpPkg, "harnesses", "openclaw", "dist"), { recursive: true, force: true }); const { installOpenclaw } = await importInstaller(); expect(() => installOpenclaw()).toThrow(/OpenClaw bundle missing/); }); diff --git a/tests/cli/cli-install-pi-fs.test.ts b/tests/cli/cli-install-pi-fs.test.ts index f97c38f9..f8bcf305 100644 --- a/tests/cli/cli-install-pi-fs.test.ts +++ b/tests/cli/cli-install-pi-fs.test.ts @@ -24,8 +24,8 @@ beforeEach(() => { tmpPkg = join(tmpRoot, "pkg"); mkdirSync(tmpHome, { recursive: true }); - mkdirSync(join(tmpPkg, "pi", "extension-source"), { recursive: true }); - writeFileSync(join(tmpPkg, "pi", "extension-source", "hivemind.ts"), "// fake pi extension"); + mkdirSync(join(tmpPkg, "harnesses", "pi", "extension-source"), { recursive: true }); + writeFileSync(join(tmpPkg, "harnesses", "pi", "extension-source", "hivemind.ts"), "// fake pi extension"); writeFileSync(join(tmpPkg, "package.json"), JSON.stringify({ version: "7.7.7" })); vi.stubEnv("HOME", tmpHome); @@ -77,7 +77,7 @@ describe("installPi — cold install", () => { }); it("throws with a 'reinstall the package' hint when the extension source is absent", async () => { - rmSync(join(tmpPkg, "pi", "extension-source"), { recursive: true, force: true }); + rmSync(join(tmpPkg, "harnesses", "pi", "extension-source"), { recursive: true, force: true }); const { installPi } = await importInstaller(); expect(() => installPi()).toThrow(/pi extension source missing/); expect(() => installPi()).toThrow(/Reinstall the @deeplake\/hivemind package/); diff --git a/tests/cli/install-end-to-end.test.ts b/tests/cli/install-end-to-end.test.ts index 6a805c2d..4578e7f5 100644 --- a/tests/cli/install-end-to-end.test.ts +++ b/tests/cli/install-end-to-end.test.ts @@ -313,7 +313,7 @@ describe("installHermes / uninstallHermes", () => { }); // OpenClaw tests intentionally omitted from this end-to-end file: -// `openclaw/dist/` is gitignored (esbuild output, see .gitignore: dist/), +// `harnesses/openclaw/dist/` is gitignored (esbuild output, see .gitignore: dist/), // so it doesn't exist on a fresh CI checkout the way the committed // codex/cursor/hermes bundles do. The dedicated test file // `cli-install-openclaw.test.ts` covers OpenClaw via mock-pkgRoot → diff --git a/tests/codex/codex-capture.test.ts b/tests/codex/codex-capture.test.ts index 9ebc2dff..d0ef4388 100644 --- a/tests/codex/codex-capture.test.ts +++ b/tests/codex/codex-capture.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from "vitest"; import { sqlStr } from "../../src/utils/sql.js"; -// ── buildSessionPath (mirrors codex/capture.ts and codex/stop.ts) ──────────── +// ── buildSessionPath (mirrors harnesses/codex/capture.ts and harnesses/codex/stop.ts) ──────────── function buildSessionPath(config: { userName: string; orgName: string; workspaceId: string }, sessionId: string): string { return `/sessions/${config.userName}/${config.userName}_${config.orgName}_${config.workspaceId}_${sessionId}.jsonl`; diff --git a/tests/codex/codex-hooks.test.ts b/tests/codex/codex-hooks.test.ts index a42716dc..94b6a4b2 100644 --- a/tests/codex/codex-hooks.test.ts +++ b/tests/codex/codex-hooks.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -const codexRoot = join(process.cwd(), "codex"); +const codexRoot = join(process.cwd(), "harnesses", "codex"); describe("codex hooks.json", () => { const hooks = JSON.parse(readFileSync(join(codexRoot, "hooks", "hooks.json"), "utf-8")); diff --git a/tests/codex/codex-integration.test.ts b/tests/codex/codex-integration.test.ts index 6858e895..59edfabb 100644 --- a/tests/codex/codex-integration.test.ts +++ b/tests/codex/codex-integration.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { execFileSync } from "node:child_process"; import { join } from "node:path"; -const bundleDir = join(process.cwd(), "codex", "bundle"); +const bundleDir = join(process.cwd(), "harnesses", "codex", "bundle"); /** Pipe JSON into a bundle and return parsed stdout. */ function runHook(bundle: string, input: Record, extraEnv: Record = {}): string { @@ -68,7 +68,7 @@ function parseOutput(raw: string): Record | null { // pushes to both the user-visible entries vec AND the model context vec). // Because of this we deliberately keep `additionalContext` MINIMAL — only a // 1-line status. The full memory tier doc + CLI command list moved into the -// `hivemind-memory` skill (codex/skills/deeplake-memory/SKILL.md), which the +// `hivemind-memory` skill (harnesses/codex/skills/deeplake-memory/SKILL.md), which the // model loads on demand without spamming the terminal every session start. describe("codex integration: session-start", () => { diff --git a/tests/openclaw/auto-recall.test.ts b/tests/openclaw/auto-recall.test.ts index 13ef133d..fcfb1c19 100644 --- a/tests/openclaw/auto-recall.test.ts +++ b/tests/openclaw/auto-recall.test.ts @@ -54,7 +54,7 @@ async function loadPluginWithHooks(): Promise<{ mockApi: ReturnType; }> { vi.resetModules(); - const mod = await import("../../openclaw/src/index.js"); + const mod = await import("../../harnesses/openclaw/src/index.js"); const plugin = mod.default as { register: (api: ReturnType) => void }; const hooks = new Map(); const mockApi = buildMockApi(hooks); diff --git a/tests/openclaw/hivemind-tools.test.ts b/tests/openclaw/hivemind-tools.test.ts index b7115f57..af61701e 100644 --- a/tests/openclaw/hivemind-tools.test.ts +++ b/tests/openclaw/hivemind-tools.test.ts @@ -69,7 +69,7 @@ type MockTool = { async function loadPluginWithTools() { vi.resetModules(); - const mod = await import("../../openclaw/src/index.js"); + const mod = await import("../../harnesses/openclaw/src/index.js"); const plugin = mod.default as { register: (api: any) => void }; const tools: MockTool[] = []; const mockApi = { @@ -123,7 +123,7 @@ describe("openclaw hivemind tools — registration", () => { it("skips tool registration when host does not expose registerTool", async () => { vi.resetModules(); - const mod = await import("../../openclaw/src/index.js"); + const mod = await import("../../harnesses/openclaw/src/index.js"); const plugin = mod.default as { register: (api: any) => void }; let threw: unknown = null; try { @@ -160,7 +160,7 @@ describe("openclaw hivemind tools — registration", () => { (globalThis as any).__HIVEMIND_SKILL__ = "TEST_SKILL_BODY_CONTENT"; try { vi.resetModules(); - const mod = await import("../../openclaw/src/index.js"); + const mod = await import("../../harnesses/openclaw/src/index.js"); const plugin = mod.default as { register: (api: any) => void }; const onMock = vi.fn(); plugin.register({ @@ -183,7 +183,7 @@ describe("openclaw hivemind tools — registration", () => { it("registers memoryCorpusSupplement when host exposes it", async () => { const supplementMock = vi.fn(); vi.resetModules(); - const mod = await import("../../openclaw/src/index.js"); + const mod = await import("../../harnesses/openclaw/src/index.js"); const plugin = mod.default as { register: (api: any) => void }; plugin.register({ logger: { info: vi.fn(), error: vi.fn() }, diff --git a/tests/openclaw/openclaw-embed-bundle.test.ts b/tests/openclaw/openclaw-embed-bundle.test.ts index 8ddff196..fa83417c 100644 --- a/tests/openclaw/openclaw-embed-bundle.test.ts +++ b/tests/openclaw/openclaw-embed-bundle.test.ts @@ -7,8 +7,8 @@ import { resolve } from "node:path"; * * The shipped openclaw bundle MUST produce real embeddings (not silent * NULL) when the canonical shared daemon is available. The source-level - * wiring is verified at openclaw/src/index.ts, but the actually-shipped - * surface is openclaw/dist/index.js — a slip in the esbuild config + * wiring is verified at harnesses/openclaw/src/index.ts, but the actually-shipped + * surface is harnesses/openclaw/dist/index.js — a slip in the esbuild config * (e.g. an over-aggressive stub-unused-child-process matcher, a * tree-shake of the spawn-impl injection, or a re-introduced INSERT * that omits the embedding column) would silently regress capture @@ -16,7 +16,7 @@ import { resolve } from "node:path"; * the load-bearing strings so the regression is caught at build time. */ -const BUNDLE_PATH = resolve(process.cwd(), "openclaw", "dist", "index.js"); +const BUNDLE_PATH = resolve(process.cwd(), "harnesses", "openclaw", "dist", "index.js"); const SRC = readFileSync(BUNDLE_PATH, "utf-8"); describe("openclaw dist bundle — embeddings wiring", () => { diff --git a/tests/openclaw/setup-command.test.ts b/tests/openclaw/setup-command.test.ts index 8e171340..143ffb95 100644 --- a/tests/openclaw/setup-command.test.ts +++ b/tests/openclaw/setup-command.test.ts @@ -50,7 +50,7 @@ type CommandRegistration = { async function loadSetupCommand(): Promise { vi.resetModules(); - const mod = await import("../../openclaw/src/index.js"); + const mod = await import("../../harnesses/openclaw/src/index.js"); const plugin = mod.default as { register: (api: any) => void }; const commands: CommandRegistration[] = []; plugin.register({ @@ -294,7 +294,7 @@ describe("/hivemind_setup", () => { tools: { profile: "coding", alsoAllow: ["hivemind"] }, }); vi.resetModules(); - const { detectAllowlistMissing } = await import("../../openclaw/src/setup-config.js"); + const { detectAllowlistMissing } = await import("../../harnesses/openclaw/src/setup-config.js"); expect(detectAllowlistMissing()).toBe(true); }); @@ -304,7 +304,7 @@ describe("/hivemind_setup", () => { tools: { profile: "coding", alsoAllow: ["hivemind"] }, }); vi.resetModules(); - const { detectAllowlistMissing } = await import("../../openclaw/src/setup-config.js"); + const { detectAllowlistMissing } = await import("../../harnesses/openclaw/src/setup-config.js"); expect(detectAllowlistMissing()).toBe(false); }); @@ -313,7 +313,7 @@ describe("/hivemind_setup", () => { tools: { profile: "coding", alsoAllow: ["hivemind"] }, }); vi.resetModules(); - const { detectAllowlistMissing } = await import("../../openclaw/src/setup-config.js"); + const { detectAllowlistMissing } = await import("../../harnesses/openclaw/src/setup-config.js"); expect(detectAllowlistMissing()).toBe(false); }); }); diff --git a/tests/pi/pi-extension-source.test.ts b/tests/pi/pi-extension-source.test.ts index 9994f87b..79d19d0f 100644 --- a/tests/pi/pi-extension-source.test.ts +++ b/tests/pi/pi-extension-source.test.ts @@ -19,7 +19,7 @@ import { join } from "node:path"; */ const PI_SRC = readFileSync( - join(process.cwd(), "pi", "extension-source", "hivemind.ts"), + join(process.cwd(), "harnesses", "pi", "extension-source", "hivemind.ts"), "utf-8", ); diff --git a/tests/pi/skillify-spec-drift.test.ts b/tests/pi/skillify-spec-drift.test.ts index b5733ac5..6ab0bb4b 100644 --- a/tests/pi/skillify-spec-drift.test.ts +++ b/tests/pi/skillify-spec-drift.test.ts @@ -15,7 +15,7 @@ import { join } from "node:path"; import { SKILLIFY_COMMANDS } from "../../src/cli/skillify-spec.js"; const PI_SOURCE = readFileSync( - join(process.cwd(), "pi", "extension-source", "hivemind.ts"), + join(process.cwd(), "harnesses", "pi", "extension-source", "hivemind.ts"), "utf-8", ); @@ -27,7 +27,7 @@ const piArrayMatch = PI_SOURCE.match( describe("pi skillify spec drift", () => { it("pi mirror block is present", () => { - expect(piArrayMatch, "PI_SKILLIFY_COMMANDS array literal not found in pi/extension-source/hivemind.ts").toBeTruthy(); + expect(piArrayMatch, "PI_SKILLIFY_COMMANDS array literal not found in harnesses/pi/extension-source/hivemind.ts").toBeTruthy(); }); it("pi mirror has the same number of entries as the canonical spec", () => { diff --git a/tests/scripts/sync-versions.test.ts b/tests/scripts/sync-versions.test.ts index 57dfa803..9e0f8551 100644 --- a/tests/scripts/sync-versions.test.ts +++ b/tests/scripts/sync-versions.test.ts @@ -61,7 +61,7 @@ describe("syncVersions", () => { it("partial-sync state: writes only the targets that drifted", () => { seedFixture("1.2.3", { ".claude-plugin/plugin.json": "1.2.3", // already synced - "codex/package.json": "0.5.0", // drifted + "harnesses/codex/package.json": "0.5.0", // drifted }); const result = syncVersions({ root, log: () => {} }); expect(result.writes).toBeGreaterThan(0); @@ -84,7 +84,7 @@ describe("syncVersions", () => { it("throws when a target file is missing", () => { seedFixture("1.2.3"); - rmSync(resolve(root, "codex/package.json")); + rmSync(resolve(root, "harnesses/codex/package.json")); expect(() => syncVersions({ root, log: () => {} })).toThrow(/codex\/package\.json/); }); diff --git a/tests/shared/agent-model.test.ts b/tests/shared/agent-model.test.ts index 79edb012..7995d2ea 100644 --- a/tests/shared/agent-model.test.ts +++ b/tests/shared/agent-model.test.ts @@ -99,7 +99,7 @@ describe("agentModel — per-agent no-tools dispatch", () => { expect(argVal(calls[0].args, "-m")).toBe("o3"); // the `model ? ["-m", model] : []` present-branch }); - it("rejects a hermes/pi provider override with NO model (the default id wouldn't match)", async () => { + it("rejects a harnesses/hermes/pi provider override with NO model (the default id wouldn't match)", async () => { const { spawnImpl } = fakeSpawn("x"); const env = { HIVEMIND_SKILLOPT_HERMES_PROVIDER: "bedrock" } as unknown as NodeJS.ProcessEnv; await expect(agentModel({ agent: "hermes", role: "judge", bin: "/x/hermes", spawnImpl, env })("S", "U")) diff --git a/tests/shared/embeddings-schema.test.ts b/tests/shared/embeddings-schema.test.ts index e2f673b7..39c22392 100644 --- a/tests/shared/embeddings-schema.test.ts +++ b/tests/shared/embeddings-schema.test.ts @@ -8,8 +8,8 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; const BUNDLE_DIRS = [ - "claude-code/bundle", - "codex/bundle", + "harnesses/claude-code/bundle", + "harnesses/codex/bundle", ]; function read(path: string): string { diff --git a/tests/shared/skillopt-hook.test.ts b/tests/shared/skillopt-hook.test.ts index 92b34ac7..77a92511 100644 --- a/tests/shared/skillopt-hook.test.ts +++ b/tests/shared/skillopt-hook.test.ts @@ -32,7 +32,7 @@ describe("armSkillOptOnSkillUse", () => { expect(markSkillPending).toHaveBeenCalledWith("s1", "posthog--kamo", "tu2"); }); - it("arms on a SHELL command that reads SKILL.md (codex/hermes style — path in the command)", () => { + it("arms on a SHELL command that reads SKILL.md (harnesses/codex/hermes style — path in the command)", () => { armSkillOptOnSkillUse("s1", "Bash", { command: 'cat "/home/u/.agents/skills/posthog--kamo/SKILL.md"' }, "tu3"); expect(markSkillPending).toHaveBeenCalledWith("s1", "posthog--kamo", "tu3"); }); diff --git a/vitest.config.ts b/vitest.config.ts index 2db42b1e..f1536847 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "vitest/config"; // Root vitest config. `npm test` runs `vitest run` from the repo root, so -// this is the file that actually gets picked up. The one in claude-code/ +// this is the file that actually gets picked up. The one in harnesses/claude-code/ // is a historical leftover and is not used by the root test script. // // Coverage thresholds are enforced per-file on the files touched by each From c0147cf421550fb2677a96619c68bd24f665d97c Mon Sep 17 00:00:00 2001 From: Mario Aldayuz <36048374+thenotoriousllama@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:10:53 -0400 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- tests/claude-code/utils-version-check.test.ts | 6 +++--- tests/claude-code/version-check.test.ts | 5 ++--- tests/cli/cli-install-codex-fs.test.ts | 4 ++-- tests/cli/cli-install-hermes.test.ts | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/claude-code/utils-version-check.test.ts b/tests/claude-code/utils-version-check.test.ts index b5b86787..7d9745a1 100644 --- a/tests/claude-code/utils-version-check.test.ts +++ b/tests/claude-code/utils-version-check.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; @@ -42,7 +42,7 @@ describe("getInstalledVersion — plugin-manifest branch", () => { let root: string; beforeEach(() => { - root = join(tmpdir(), `hm-uvc-${Date.now()}-${Math.random().toString(36).slice(2)}`); + root = mkdtempSync(join(tmpdir(), "hm-uvc-")); }); afterEach(() => { rmSync(root, { recursive: true, force: true }); @@ -72,7 +72,7 @@ describe("getInstalledVersion — plugin-manifest branch", () => { describe("getInstalledVersion — .hivemind_version stamp branch", () => { let root: string; beforeEach(() => { - root = join(tmpdir(), `hm-uvc-${Date.now()}-${Math.random().toString(36).slice(2)}`); + root = mkdtempSync(join(tmpdir(), "hm-uvc-")); }); afterEach(() => { rmSync(root, { recursive: true, force: true }); }); diff --git a/tests/claude-code/version-check.test.ts b/tests/claude-code/version-check.test.ts index 7aba297b..9a107626 100644 --- a/tests/claude-code/version-check.test.ts +++ b/tests/claude-code/version-check.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { tmpdir } from "node:os"; import { @@ -23,8 +23,7 @@ describe("getInstalledVersion", () => { let root: string; beforeEach(() => { - root = join(tmpdir(), `hivemind-version-${Date.now()}-${Math.random().toString(36).slice(2)}`); - mkdirSync(root, { recursive: true }); + root = mkdtempSync(join(tmpdir(), "hivemind-version-")); }); afterEach(() => { diff --git a/tests/cli/cli-install-codex-fs.test.ts b/tests/cli/cli-install-codex-fs.test.ts index 8c2990c0..8bb2e66d 100644 --- a/tests/cli/cli-install-codex-fs.test.ts +++ b/tests/cli/cli-install-codex-fs.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync, statSync, utimesSync } from "node:fs"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync, statSync, utimesSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; @@ -29,7 +29,7 @@ vi.mock("node:child_process", () => ({ })); beforeEach(() => { - tmpRoot = join(tmpdir(), `hm-codex-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tmpRoot = mkdtempSync(join(tmpdir(), "hm-codex-")); tmpHome = join(tmpRoot, "home"); tmpPkg = join(tmpRoot, "pkg"); diff --git a/tests/cli/cli-install-hermes.test.ts b/tests/cli/cli-install-hermes.test.ts index 1e11a503..198d8aba 100644 --- a/tests/cli/cli-install-hermes.test.ts +++ b/tests/cli/cli-install-hermes.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs"; +import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync, mkdtempSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import * as yaml from "js-yaml"; @@ -19,7 +19,7 @@ let tmpHome: string; let tmpPkg: string; beforeEach(() => { - tmpRoot = join(tmpdir(), `hm-hermes-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tmpRoot = mkdtempSync(join(tmpdir(), "hm-hermes-")); tmpHome = join(tmpRoot, "home"); tmpPkg = join(tmpRoot, "pkg"); mkdirSync(tmpHome, { recursive: true }); From 2ffc08f20641373dddbf4fd13eb3953eb381a44b Mon Sep 17 00:00:00 2001 From: Mario Aldayuz <36048374+thenotoriousllama@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:11:27 -0400 Subject: [PATCH 3/5] Potential fix for pull request finding 'CodeQL / Insecure temporary file' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- tests/cli/cli-install-openclaw.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cli/cli-install-openclaw.test.ts b/tests/cli/cli-install-openclaw.test.ts index 0538f461..ab13c315 100644 --- a/tests/cli/cli-install-openclaw.test.ts +++ b/tests/cli/cli-install-openclaw.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; @@ -14,7 +14,7 @@ let tmpHome: string; let tmpPkg: string; beforeEach(() => { - tmpRoot = join(tmpdir(), `hm-claw-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tmpRoot = mkdtempSync(join(tmpdir(), "hm-claw-")); tmpHome = join(tmpRoot, "home"); tmpPkg = join(tmpRoot, "pkg"); mkdirSync(tmpHome, { recursive: true }); From 780b46453ecb89e81270e92da8f41b1b3faaf19e Mon Sep 17 00:00:00 2001 From: Mario Aldayuz <36048374+thenotoriousllama@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:11:35 -0400 Subject: [PATCH 4/5] Potential fix for pull request finding 'CodeQL / Insecure temporary file' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- tests/cli/cli-install-pi-fs.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cli/cli-install-pi-fs.test.ts b/tests/cli/cli-install-pi-fs.test.ts index f8bcf305..f1c0aaf0 100644 --- a/tests/cli/cli-install-pi-fs.test.ts +++ b/tests/cli/cli-install-pi-fs.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs"; +import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync, mkdtempSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; @@ -19,7 +19,7 @@ let tmpHome: string; let tmpPkg: string; beforeEach(() => { - tmpRoot = join(tmpdir(), `hm-pi-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tmpRoot = mkdtempSync(join(tmpdir(), "hm-pi-")); tmpHome = join(tmpRoot, "home"); tmpPkg = join(tmpRoot, "pkg"); mkdirSync(tmpHome, { recursive: true }); From 7ac3fdcfceb9205f8b81d0c268e014487af41953 Mon Sep 17 00:00:00 2001 From: Mario Aldayuz Date: Fri, 12 Jun 2026 20:15:12 -0400 Subject: [PATCH 5/5] fix: update esbuild path assertions for harnesses/ layout The openclaw skillify-worker esbuild outdir moved to harnesses/openclaw/dist during the harness reorganization; align the config regex assertion. --- tests/claude-code/skillify-session-start-injection.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/claude-code/skillify-session-start-injection.test.ts b/tests/claude-code/skillify-session-start-injection.test.ts index 518695a0..3230252b 100644 --- a/tests/claude-code/skillify-session-start-injection.test.ts +++ b/tests/claude-code/skillify-session-start-injection.test.ts @@ -264,7 +264,7 @@ describe("OpenClaw skillify worker (mining) wiring", () => { expect(cfg).toMatch(/"skillify-worker":\s*"dist\/src\/skillify\/skillify-worker\.js"/); // Window is generous to leave room for the bundle's comments + the // env-var → globalThis.__hivemind_tuning__ define dispatch table. - expect(cfg).toMatch(/outdir:\s*"openclaw\/dist"[\s\S]{0,2000}skillify-worker/); + expect(cfg).toMatch(/outdir:\s*"harnesses\/openclaw\/dist"[\s\S]{0,2000}skillify-worker/); }); it("harnesses/openclaw/src/index.ts bypasses the child_process stub via createRequire", () => {