From 187fa2392f3df7ccf21ece0219892d6d17b27c6d Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 20:15:07 +0200 Subject: [PATCH 01/10] flocky exec 01 core rename package identity to scoped flocky 1.0.0 --- .github/workflows/ci.yml | 4 ++-- bin/orchestration.js | 2 +- hooks/pre-push | 12 ++++++------ package.json | 13 +++++++------ src/doctor.js | 2 +- src/installer.js | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88e17b1..98cddab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,14 +28,14 @@ jobs: - run: npm test - run: npm pack --dry-run - name: Verify publish manifest - run: node -e "const p=require('./package.json'); if (p.bin['opencode-herdr-orchestration'] !== 'bin/orchestration.js') process.exit(1)" + run: node -e "const p=require('./package.json'); if (p.bin['flocky'] !== 'bin/orchestration.js') process.exit(1)" - name: Verify packed CLI shell: bash run: | set -euo pipefail package="$(npm pack --silent)" npm install --global "$package" - opencode-herdr-orchestration status + flocky status gate: name: CI diff --git a/bin/orchestration.js b/bin/orchestration.js index 486e9ca..70840ce 100755 --- a/bin/orchestration.js +++ b/bin/orchestration.js @@ -206,7 +206,7 @@ try { else if (command === "status") fullStatus(); else if (command === "doctor") runDoctor(); else { - process.stderr.write("Usage: opencode-herdr-orchestration [--with-hooks] [--force]\n"); + process.stderr.write("Usage: flocky [--with-hooks] [--force]\n"); process.exitCode = 2; } } catch (error) { diff --git a/hooks/pre-push b/hooks/pre-push index f57d6f7..3fe0c44 100644 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -5,7 +5,7 @@ set -eu mode=${SHEPHERD_MODE:-none} case "$mode" in shepherd|sheepdog|grazer|sheep|shearer) - echo "opencode-herdr-orchestration: $mode may not push; final delivery belongs to the shepherd-governor." >&2 + echo "flocky: $mode may not push; final delivery belongs to the shepherd-governor." >&2 exit 1 ;; governor) @@ -17,7 +17,7 @@ esac current_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true) if [ -z "$current_branch" ]; then - echo "opencode-herdr-orchestration: shepherd-governor may not push from detached HEAD." >&2 + echo "flocky: shepherd-governor may not push from detached HEAD." >&2 exit 1 fi @@ -32,7 +32,7 @@ is_protected() { } if is_protected "$current_branch"; then - echo "opencode-herdr-orchestration: shepherd-governor may not push protected branch $current_branch." >&2 + echo "flocky: shepherd-governor may not push protected branch $current_branch." >&2 exit 1 fi @@ -41,20 +41,20 @@ while read -r local_ref local_sha remote_ref remote_sha; do [ -n "${local_ref:-}" ] || continue if [ "$local_sha" = "$zero" ]; then - echo "opencode-herdr-orchestration: shepherd-governor may not delete remote refs." >&2 + echo "flocky: shepherd-governor may not delete remote refs." >&2 exit 1 fi expected_local="refs/heads/$current_branch" expected_remote="refs/heads/$current_branch" if [ "$local_ref" != "$expected_local" ] || [ "$remote_ref" != "$expected_remote" ]; then - echo "opencode-herdr-orchestration: shepherd-governor may push only $expected_local to the same remote branch." >&2 + echo "flocky: shepherd-governor may push only $expected_local to the same remote branch." >&2 exit 1 fi remote_branch=${remote_ref#refs/heads/} if is_protected "$remote_branch"; then - echo "opencode-herdr-orchestration: shepherd-governor may not update protected branch $remote_branch." >&2 + echo "flocky: shepherd-governor may not update protected branch $remote_branch." >&2 exit 1 fi done diff --git a/package.json b/package.json index 89832bf..692023a 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "opencode-herdr-orchestration", - "version": "0.3.4", + "name": "@ia-forge/flocky", + "version": "1.0.0", "description": "Capability-separated Herdr orchestration agents for OpenCode", "author": "CodingJinxx", "repository": { "type": "git", - "url": "git+https://github.com/CodingJinxx/opencode-herdr-orchestration.git" + "url": "git+https://github.com/CodingJinxx/flocky.git" }, - "homepage": "https://github.com/CodingJinxx/opencode-herdr-orchestration#readme", + "homepage": "https://github.com/CodingJinxx/flocky#readme", "bugs": { - "url": "https://github.com/CodingJinxx/opencode-herdr-orchestration/issues" + "url": "https://github.com/CodingJinxx/flocky/issues" }, "type": "module", "exports": { @@ -17,7 +17,7 @@ "./core": "./src/index.js" }, "bin": { - "opencode-herdr-orchestration": "bin/orchestration.js" + "flocky": "bin/orchestration.js" }, "files": [ "bin", @@ -34,6 +34,7 @@ "node": ">=20" }, "keywords": [ + "flocky", "opencode", "herdr", "orchestration", diff --git a/src/doctor.js b/src/doctor.js index b65066e..3653799 100644 --- a/src/doctor.js +++ b/src/doctor.js @@ -207,7 +207,7 @@ export function formatHumanSummary(report) { ? `Flapping signals: ${flapping.detail || flapping.signals?.join("; ")}. A global opencode upgrade recreates the npm shims, so a previously durable order can flap back to shim-first with a bumped opencode --version versus npm view opencode-ai version. Re-inspect (Get-Command opencode -All).Source for a reappearing shim.` : "Flapping signals: none. No reappearing shim and no bumped version versus npm view opencode-ai version.", ); - lines.push(`PATH inspected read-only via $env:PATH plus (Get-Command herdr).Source plus (Get-Command herdr -All).Source; Herdr resolves to a single bin exe with no shim confusion per herdr --version ${HEALTHY_HERDR_VERSION}. Confirm the server split with herdr status showing status: running for server separate from client. Autoupdate context is npm view opencode-herdr-orchestration version plus herdr channel show reporting ${STABLE_CHANNEL} plus herdr update --help plus opencode upgrade --help.`); + lines.push(`PATH inspected read-only via $env:PATH plus (Get-Command herdr).Source plus (Get-Command herdr -All).Source; Herdr resolves to a single bin exe with no shim confusion per herdr --version ${HEALTHY_HERDR_VERSION}. Confirm the server split with herdr status showing status: running for server separate from client. Autoupdate context is npm view @ia-forge/flocky version plus herdr channel show reporting ${STABLE_CHANNEL} plus herdr update --help plus opencode upgrade --help.`); lines.push("Operator-executed remedies (this package never overwrites the global environment and never sets global environment values automatically):"); lines.push("- For current-session relief use the winning full exe path directly."); lines.push("- For a durable fix apply a persistent user-chosen PATH reorder that places the direct exe directory before the npm shim directory, then restart Herdr plus the terminal plus OpenCode intentionally when ready."); diff --git a/src/installer.js b/src/installer.js index fd1af31..3d6509f 100644 --- a/src/installer.js +++ b/src/installer.js @@ -13,7 +13,7 @@ import { steerCommandEntry as steerEntry, } from "./steer.js"; -export const PACKAGE_NAME = "opencode-herdr-orchestration"; +export const PACKAGE_NAME = "@ia-forge/flocky"; const NPM_COMMAND = "npm"; const OPENCODE_COMMAND = "opencode"; export const AGENT_NAMES = [ From c104e830f8c54f4bfc1c49054f38a9446e9378d9 Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 20:16:34 +0200 Subject: [PATCH 02/10] docs: rename site to flocky with scoped package --- README.md | 12 ++++++------ docs/_config.yml | 4 ++-- docs/cli-reference.md | 12 ++++++------ docs/configuration.md | 16 ++++++++-------- docs/getting-started.md | 22 +++++++++++----------- docs/index.md | 4 ++-- docs/troubleshooting.md | 4 ++-- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 65e8195..72ff280 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# opencode-herdr-orchestration +# flocky -[![CI](https://github.com/CodingJinxx/opencode-herdr-orchestration/actions/workflows/ci.yml/badge.svg)](https://github.com/CodingJinxx/opencode-herdr-orchestration/actions/workflows/ci.yml) -[![npm version](https://img.shields.io/npm/v/opencode-herdr-orchestration.svg)](https://www.npmjs.com/package/opencode-herdr-orchestration) +[![CI](https://github.com/CodingJinxx/flocky/actions/workflows/ci.yml/badge.svg)](https://github.com/CodingJinxx/flocky/actions/workflows/ci.yml) +[![npm version](https://img.shields.io/npm/v/@codingjinxx/flocky.svg)](https://www.npmjs.com/package/@codingjinxx/flocky) [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org/) Capability-separated OpenCode agents for planning, implementation, independent review, and delivery through Herdr, organized as **Developer → Shepherd → Flock**. This package registers the agents, provides structured worker-response retrieval, injects session-specific orchestration mode into shell environments, and ships a reproducible Git `pre-push` policy for new and existing repositories. @@ -17,8 +17,8 @@ You need Node.js 20 or newer, a working OpenCode install, and Herdr on the stabl Install or update with the cross-platform npm CLI: ```bash -npx -y opencode-herdr-orchestration@latest install -npx -y opencode-herdr-orchestration@latest install --with-hooks +npx -y @codingjinxx/flocky@latest install +npx -y @codingjinxx/flocky@latest install --with-hooks ``` The first form installs the global OpenCode plugin entry and agent models. The second form also installs the shared Git push policy. For update, manual setup, stale file migration, and full options, see [Getting Started](docs/getting-started.md) and [CLI Reference](docs/cli-reference.md). @@ -106,7 +106,7 @@ For workarounds, launcher resolution, integration checks, interrupted-operation ## Docs -Start with [Getting Started](docs/getting-started.md) for install plus update plus migration, then use the guides below for daily work. Detail lives in docs so this landing page stays short. Published site: https://codingjinxx.github.io/opencode-herdr-orchestration/ +Start with [Getting Started](docs/getting-started.md) for install plus update plus migration, then use the guides below for daily work. Detail lives in docs so this landing page stays short. Published site: https://codingjinxx.github.io/flocky/ - [Home](docs/index.md) — docs landing with navigation to every guide. - [Getting Started](docs/getting-started.md) — install, update, manual setup, stale file migration, and verify steps. diff --git a/docs/_config.yml b/docs/_config.yml index efa8a77..cb21dbc 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,3 +1,3 @@ -title: opencode-herdr-orchestration -baseurl: "/opencode-herdr-orchestration" +title: flocky +baseurl: "/flocky" theme: minima diff --git a/docs/cli-reference.md b/docs/cli-reference.md index dc45839..750ffef 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -5,20 +5,20 @@ layout: default # CLI Reference -This page documents every `opencode-herdr-orchestration` package command exactly as implemented in `bin/orchestration.js`. For the install plus upgrade walkthrough, see [Getting Started](./getting-started.html). +This page documents every `@codingjinxx/flocky` package command exactly as implemented in `bin/orchestration.js`. For the install plus upgrade walkthrough, see [Getting Started](./getting-started.html). Live versions at verification time were package `0.3.2`, OpenCode `1.18.29`, Herdr `0.8.2`, channel `stable`, and integration `opencode: current (v10)`. When command output disagrees with this page, trust the live output from `status` and `doctor`. ## Usage ```text -Usage: opencode-herdr-orchestration [--with-hooks] [--force] +Usage: flocky [--with-hooks] [--force] ``` Invoke through the published package or from a checkout: ```bash -npx -y opencode-herdr-orchestration@latest +npx -y @codingjinxx/flocky@latest node ./bin/orchestration.js ``` @@ -70,19 +70,19 @@ The `uninstall` command removes the native steer command entry, removes the orch The Git policy procedure uses two focused commands. To enable the policy, run: ```bash -npx -y opencode-herdr-orchestration@latest install-hooks +npx -y @codingjinxx/flocky@latest install-hooks ``` This path copies the packaged `pre-push` hook into the stable user location below and points global `core.hooksPath` there: ```text -~/.config/opencode-herdr-orchestration/hooks +~/.config/flocky/hooks ``` From that point every new clone, initialized repository, and linked worktree receives the same policy automatically. When global `core.hooksPath` already holds a different value, the command reports the conflict and requires an explicit `--force` rerun only after the existing hook setup has been reviewed. To inspect hook state, use `status`. To disable the policy, run: ```bash -npx -y opencode-herdr-orchestration@latest uninstall-hooks +npx -y @codingjinxx/flocky@latest uninstall-hooks ``` Removal clears the global setting only when it points at this package and deletes the installed hook directory; any unrelated hooks path is left unchanged with an explanatory message. In governance mode the policy always protects `main` plus `master`; repository-specific branches are added with: diff --git a/docs/configuration.md b/docs/configuration.md index 5b3fc85..a24c8ba 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -11,12 +11,12 @@ Live versions at verification time were package `0.3.2`, OpenCode `1.18.29`, Her ## Global plugin entry -The global OpenCode file is `~/.config/opencode/opencode.json` or `~/.config/opencode/opencode.jsonc` unless `OPENCODE_CONFIG_DIR` points elsewhere. The published package entry is the bare name: +The global OpenCode file is `~/.config/opencode/opencode.json` or `~/.config/opencode/opencode.jsonc` unless `OPENCODE_CONFIG_DIR` points elsewhere. The published package entry is the scoped name: ```jsonc { "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-herdr-orchestration"] + "plugin": ["@codingjinxx/flocky"] } ``` @@ -27,7 +27,7 @@ Current OpenCode releases may record but not execute an npm plugin referenced on ```jsonc { "plugin": [ - "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/opencode-herdr-orchestration/src/plugin.js" + "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/@codingjinxx/flocky/src/plugin.js" ] } ``` @@ -40,12 +40,12 @@ During local development, reference a local checkout directly with a stable file { "$schema": "https://opencode.ai/config.json", "plugin": [ - "file:///C:/Dev/opencode-herdr-orchestration/src/plugin.js" + "file:///C:/Dev/flocky/src/plugin.js" ] } ``` -Replace the example checkout path with the real absolute path to the local source file. This development entry is for a local checkout only; machines that track the published package keep the bare name or the installed file path above. +Replace the example checkout path with the real absolute path to the local source file. This development entry is for a local checkout only; machines that track the published package keep the scoped name or the installed file path above. ## Model defaults in a plugin tuple @@ -55,7 +55,7 @@ Model choices live as options in a plugin tuple beside the plugin name. The tupl { "plugin": [ [ - "opencode-herdr-orchestration", + "@codingjinxx/flocky", { "shepherdModel": "anthropic/claude-sonnet-4-6", "sheepdogModel": "litellm/glm-5.3-flash", @@ -87,7 +87,7 @@ Machine-specific planning permissions and instructions use two scoped options th { "plugin": [ [ - "opencode-herdr-orchestration", + "@codingjinxx/flocky", { "shepherdPermissions": { "private_deployment_status": "allow" @@ -109,7 +109,7 @@ Squad-local machine permissions and instructions use the same scoped pattern and { "plugin": [ [ - "opencode-herdr-orchestration", + "@codingjinxx/flocky", { "sheepdogPermissions": { "private_squad_status": "allow" diff --git a/docs/getting-started.md b/docs/getting-started.md index 3154cc1..e46dc0b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -5,7 +5,7 @@ layout: default # Getting Started -This guide installs the `opencode-herdr-orchestration` package, updates it safely, and removes leftover agent files from the previous architecture. For the full command list, see [CLI Reference](./cli-reference.html). +This guide installs the `@codingjinxx/flocky` package, updates it safely, and removes leftover agent files from the previous architecture. For the full command list, see [CLI Reference](./cli-reference.html). Live-verified versions used below were checked before printing: package `0.3.2`, OpenCode `1.18.29`, Herdr `0.8.2`, Herdr channel `stable`, integration `opencode: current (v10)`, Node.js `>=20` (host reported `v24.19.0`). When any version below disagrees with `opencode --version`, `node bin/orchestration.js status`, or `node bin/orchestration.js doctor`, trust the live command output. @@ -20,13 +20,13 @@ Default model choices presented during setup are the shepherd phases inheriting For most machines the cross-platform installer is the supported path: ```bash -npx -y opencode-herdr-orchestration@latest install +npx -y @codingjinxx/flocky@latest install ``` To include the shared Git push policy in the same step, add the hooks flag: ```bash -npx -y opencode-herdr-orchestration@latest install --with-hooks +npx -y @codingjinxx/flocky@latest install --with-hooks ``` During `install` the tool locates the global OpenCode config directory, installs that exact package version there, prompts for shepherd plus sheepdog plus worker plus reviewer model choices, preserves JSONC comments plus trailing commas plus existing plugins plus tuple options, records the stable file entry current OpenCode plugin loading requires, writes timestamped backups of touched config plus npm manifest files, and validates every agent role through a short-lived OpenCode debug run. The run never stops or signals a running OpenCode process. @@ -40,9 +40,9 @@ OpenCode reads plugins plus agents only at startup. After every install, update, Never hand-edit the global config while an OpenCode process that depends on it is running. Complete or stop dependent processes first, then run: ```bash -npx -y opencode-herdr-orchestration@latest update -npx -y opencode-herdr-orchestration@latest configure-agents -npx -y opencode-herdr-orchestration@latest status +npx -y @codingjinxx/flocky@latest update +npx -y @codingjinxx/flocky@latest configure-agents +npx -y @codingjinxx/flocky@latest status ``` The `update` step resolves the newest published version, installs it exactly, preserves comments plus tuples, creates fresh backups, revalidates all roles, and restores the previous config when validation fails. The `configure-agents` step revisits the same model prompts later: press Enter to keep the displayed value or enter `-` to return that role to its package default. The `status` step reports the CLI version, installed version, latest version, configured file, detected agents, and obsolete files so the operator can confirm whether another restart is needed. @@ -56,7 +56,7 @@ When the automated installer is unavailable, add the published package to `~/.co ```jsonc { "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-herdr-orchestration"] + "plugin": ["@codingjinxx/flocky"] } ``` @@ -64,7 +64,7 @@ When a `plugin` list already exists, append the package name rather than replaci ```bash cd ~/.config/opencode -npm install --save-exact opencode-herdr-orchestration@latest +npm install --save-exact @codingjinxx/flocky@latest ``` Current OpenCode releases may record but not execute an npm plugin referenced only by bare package name. When that happens, keep the installed package in place and use its installed entry path in a plugin tuple instead: @@ -72,7 +72,7 @@ Current OpenCode releases may record but not execute an npm plugin referenced on ```jsonc { "plugin": [ - "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/opencode-herdr-orchestration/src/plugin.js" + "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/@codingjinxx/flocky/src/plugin.js" ] } ``` @@ -107,8 +107,8 @@ From the global config directory, the following checks confirm a healthy install ```bash opencode agent list -npx -y opencode-herdr-orchestration@latest status -npx -y opencode-herdr-orchestration@latest doctor +npx -y @codingjinxx/flocky@latest status +npx -y @codingjinxx/flocky@latest doctor ``` The agent list shows all seven packaged roles. The status output shows matching installed plus latest package versions with agents ready. The doctor output reports launcher candidates plus agreement plus spawn probe plus `opencode: current (v10)` integration presence plus flapping signals as JSON with a human summary. Full option details plus hook management live in [CLI Reference](./cli-reference.html). diff --git a/docs/index.md b/docs/index.md index b64187a..1e1ca64 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,9 +1,9 @@ --- -title: opencode-herdr-orchestration +title: flocky layout: default --- -# opencode-herdr-orchestration +# flocky Capability-separated Herdr orchestration agents for OpenCode, organized as **Developer → Shepherd → Flock** for planning, implementation, independent review, and delivery. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b8375da..964c40c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -25,7 +25,7 @@ Live-verified on `herdr 0.8.2` plus `opencode 1.18.29` plus package `0.3.2` plus **Cause:** On Windows PowerShell `opencode` may resolve to the direct exe (`node_modules/opencode-ai/bin/opencode.exe`) or to a shim (`opencode.cmd`, `opencode.ps1`, `opencode.exe` in the npm prefix) depending on `PATH` order. `PATH` here means the session `PATH` inspected read-only with `$env:PATH`. File-version text is not authoritative next to CLI `opencode --version` output `1.18.29`. Herdr panes inherit the Herdr server environment, not the current session `PATH`. A global `opencode upgrade` recreates the npm shims, so a previously durable order can flap back to shim-first with a bumped `opencode --version` versus `npm view opencode-ai version`. -**Check:** Inspect the resolver without changing it. `(Get-Command opencode).Source` shows the winning launcher. `(Get-Command opencode -All).Source` lists every candidate in order and reveals a reappearing shim after a global update. `(Get-Command herdr).Source` plus `(Get-Command herdr -All).Source` confirms Herdr resolves to a single `bin` exe with no shim confusion. Compare `opencode --version` against the full-path exe and shim forms; healthy hosts report the same `1.18.29` from each, while a bumped `opencode --version` against `npm view opencode-ai version` signals a recent global update. Confirm the process-spawn path with `Start-Process` using `-FilePath` from the resolved launcher plus `-ArgumentList --version -NoNewWindow -Wait`; live the extensionless shim fails with `%1 is not a valid Win32 application` while the direct exe reports `1.18.29`. Inspect order read-only with `$env:PATH`. Confirm the server split with `herdr status` showing `status: running` for `server` separate from client. For the read-only launcher report run `node bin/orchestration.js doctor`, which prints the winning launcher plus ordered candidates plus per-candidate versions plus agreement plus spawn probe plus integration presence plus flapping signals as JSON plus a human summary with operator-executed remedies. This package never overwrites the global environment. Config resolution only reads the environment in `src/installer.js:48` (`configDirectory`) and validation runs isolated with `OPENCODE_DISABLE_PROJECT_CONFIG` in `src/installer.js:594` (`validateOpenCode`), with status reporting in `src/installer.js:617` (`status`). Autoupdate context is `npm view opencode-herdr-orchestration version` reporting `0.3.2` matching `node bin/orchestration.js status` plus `herdr channel show` reporting `stable` plus `herdr update --help` describing the handoff option plus `opencode upgrade --help` listing upgrade targets and methods. +**Check:** Inspect the resolver without changing it. `(Get-Command opencode).Source` shows the winning launcher. `(Get-Command opencode -All).Source` lists every candidate in order and reveals a reappearing shim after a global update. `(Get-Command herdr).Source` plus `(Get-Command herdr -All).Source` confirms Herdr resolves to a single `bin` exe with no shim confusion. Compare `opencode --version` against the full-path exe and shim forms; healthy hosts report the same `1.18.29` from each, while a bumped `opencode --version` against `npm view opencode-ai version` signals a recent global update. Confirm the process-spawn path with `Start-Process` using `-FilePath` from the resolved launcher plus `-ArgumentList --version -NoNewWindow -Wait`; live the extensionless shim fails with `%1 is not a valid Win32 application` while the direct exe reports `1.18.29`. Inspect order read-only with `$env:PATH`. Confirm the server split with `herdr status` showing `status: running` for `server` separate from client. For the read-only launcher report run `node bin/orchestration.js doctor`, which prints the winning launcher plus ordered candidates plus per-candidate versions plus agreement plus spawn probe plus integration presence plus flapping signals as JSON plus a human summary with operator-executed remedies. This package never overwrites the global environment. Config resolution only reads the environment in `src/installer.js:48` (`configDirectory`) and validation runs isolated with `OPENCODE_DISABLE_PROJECT_CONFIG` in `src/installer.js:594` (`validateOpenCode`), with status reporting in `src/installer.js:617` (`status`). Autoupdate context is `npm view @codingjinxx/flocky version` reporting `0.3.2` matching `node bin/orchestration.js status` plus `herdr channel show` reporting `stable` plus `herdr update --help` describing the handoff option plus `opencode upgrade --help` listing upgrade targets and methods. **Fix:** For current-session relief use the winning full exe path directly. For a durable fix apply a persistent user-chosen `PATH` reorder that places the direct exe directory before the npm shim directory, then restart Herdr plus the terminal plus OpenCode intentionally when ready, then follow Upgrade for the package update path. Session-local changes alone never fix Herdr spawns because panes inherit the Herdr server environment. This package never reorders global state automatically and never sets global environment values automatically; the persistent reorder is an operator-chosen Windows user environment change, not a package write. This package never overwrites the global environment and never sets global environment values automatically. @@ -37,7 +37,7 @@ Live-verified on `herdr 0.8.2` plus `opencode 1.18.29` plus package `0.3.2` plus **Cause:** Launcher resolution and permission plus configuration faults share surface symptoms such as missing agents in `opencode agent list` or `"agentsReady": false` in `node bin/orchestration.js status`. Routing by the wrong cause wastes effort: reordering a healthy launcher never repairs a permission fault, and editing config never repairs a shim-first resolver. -**Check:** Route on the resolver evidence first. A launcher problem shows a `Source` mismatch in `(Get-Command opencode).Source` versus the direct exe, `%1 is not a valid Win32 application` from the `Start-Process` probe, candidate disagreement in `(Get-Command opencode -All).Source`, or a bumped `opencode --version` versus `npm view opencode-ai version`. A permission plus configuration problem shows the correct `Source` plus `opencode --version` `1.18.29` but `node bin/orchestration.js status` still reports missing agents or `"agentsReady": false`, or `opencode debug agent shepherd` fails while the resolver is healthy. Confirm Herdr itself is healthy with `(Get-Command herdr).Source` plus `herdr status` showing `status: running`, and confirm the package context with `npm view opencode-herdr-orchestration version` reporting `0.3.2`. +**Check:** Route on the resolver evidence first. A launcher problem shows a `Source` mismatch in `(Get-Command opencode).Source` versus the direct exe, `%1 is not a valid Win32 application` from the `Start-Process` probe, candidate disagreement in `(Get-Command opencode -All).Source`, or a bumped `opencode --version` versus `npm view opencode-ai version`. A permission plus configuration problem shows the correct `Source` plus `opencode --version` `1.18.29` but `node bin/orchestration.js status` still reports missing agents or `"agentsReady": false`, or `opencode debug agent shepherd` fails while the resolver is healthy. Confirm Herdr itself is healthy with `(Get-Command herdr).Source` plus `herdr status` showing `status: running`, and confirm the package context with `npm view @codingjinxx/flocky version` reporting `0.3.2`. **Fix:** For launcher evidence follow the Windows exe versus shim launcher resolution fix above instead of editing permissions. For permission plus configuration evidence follow Installation plus Upgrade plus Recovery instead of launcher reordering, then quit and restart OpenCode intentionally when ready because existing processes keep old config. From e8c72777f604e831f84c41226ac73164f0cf0a2d Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 20:19:45 +0200 Subject: [PATCH 03/10] docs: use locked scoped name plus keep legacy hooks path --- README.md | 6 +++--- docs/cli-reference.md | 12 +++++++----- docs/configuration.md | 10 +++++----- docs/getting-started.md | 22 +++++++++++----------- docs/troubleshooting.md | 4 ++-- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 72ff280..12f22e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # flocky [![CI](https://github.com/CodingJinxx/flocky/actions/workflows/ci.yml/badge.svg)](https://github.com/CodingJinxx/flocky/actions/workflows/ci.yml) -[![npm version](https://img.shields.io/npm/v/@codingjinxx/flocky.svg)](https://www.npmjs.com/package/@codingjinxx/flocky) +[![npm version](https://img.shields.io/npm/v/@ia-forge/flocky.svg)](https://www.npmjs.com/package/@ia-forge/flocky) [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org/) Capability-separated OpenCode agents for planning, implementation, independent review, and delivery through Herdr, organized as **Developer → Shepherd → Flock**. This package registers the agents, provides structured worker-response retrieval, injects session-specific orchestration mode into shell environments, and ships a reproducible Git `pre-push` policy for new and existing repositories. @@ -17,8 +17,8 @@ You need Node.js 20 or newer, a working OpenCode install, and Herdr on the stabl Install or update with the cross-platform npm CLI: ```bash -npx -y @codingjinxx/flocky@latest install -npx -y @codingjinxx/flocky@latest install --with-hooks +npx -y @ia-forge/flocky@latest install +npx -y @ia-forge/flocky@latest install --with-hooks ``` The first form installs the global OpenCode plugin entry and agent models. The second form also installs the shared Git push policy. For update, manual setup, stale file migration, and full options, see [Getting Started](docs/getting-started.md) and [CLI Reference](docs/cli-reference.md). diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 750ffef..6aa8001 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -5,7 +5,7 @@ layout: default # CLI Reference -This page documents every `@codingjinxx/flocky` package command exactly as implemented in `bin/orchestration.js`. For the install plus upgrade walkthrough, see [Getting Started](./getting-started.html). +This page documents every `@ia-forge/flocky` package command exactly as implemented in `bin/orchestration.js`. For the install plus upgrade walkthrough, see [Getting Started](./getting-started.html). Live versions at verification time were package `0.3.2`, OpenCode `1.18.29`, Herdr `0.8.2`, channel `stable`, and integration `opencode: current (v10)`. When command output disagrees with this page, trust the live output from `status` and `doctor`. @@ -18,7 +18,7 @@ Usage: flocky +npx -y @ia-forge/flocky@latest node ./bin/orchestration.js ``` @@ -70,19 +70,21 @@ The `uninstall` command removes the native steer command entry, removes the orch The Git policy procedure uses two focused commands. To enable the policy, run: ```bash -npx -y @codingjinxx/flocky@latest install-hooks +npx -y @ia-forge/flocky@latest install-hooks ``` This path copies the packaged `pre-push` hook into the stable user location below and points global `core.hooksPath` there: ```text -~/.config/flocky/hooks +~/.config/opencode-herdr-orchestration/hooks ``` +KEEP: the hooks location stays at the legacy `~/.config/opencode-herdr-orchestration/hooks` path; warn only on mismatch, do not migrate automatically. + From that point every new clone, initialized repository, and linked worktree receives the same policy automatically. When global `core.hooksPath` already holds a different value, the command reports the conflict and requires an explicit `--force` rerun only after the existing hook setup has been reviewed. To inspect hook state, use `status`. To disable the policy, run: ```bash -npx -y @codingjinxx/flocky@latest uninstall-hooks +npx -y @ia-forge/flocky@latest uninstall-hooks ``` Removal clears the global setting only when it points at this package and deletes the installed hook directory; any unrelated hooks path is left unchanged with an explanatory message. In governance mode the policy always protects `main` plus `master`; repository-specific branches are added with: diff --git a/docs/configuration.md b/docs/configuration.md index a24c8ba..e6278ee 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,7 @@ The global OpenCode file is `~/.config/opencode/opencode.json` or `~/.config/ope ```jsonc { "$schema": "https://opencode.ai/config.json", - "plugin": ["@codingjinxx/flocky"] + "plugin": ["@ia-forge/flocky"] } ``` @@ -27,7 +27,7 @@ Current OpenCode releases may record but not execute an npm plugin referenced on ```jsonc { "plugin": [ - "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/@codingjinxx/flocky/src/plugin.js" + "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/@ia-forge/flocky/src/plugin.js" ] } ``` @@ -55,7 +55,7 @@ Model choices live as options in a plugin tuple beside the plugin name. The tupl { "plugin": [ [ - "@codingjinxx/flocky", + "@ia-forge/flocky", { "shepherdModel": "anthropic/claude-sonnet-4-6", "sheepdogModel": "litellm/glm-5.3-flash", @@ -87,7 +87,7 @@ Machine-specific planning permissions and instructions use two scoped options th { "plugin": [ [ - "@codingjinxx/flocky", + "@ia-forge/flocky", { "shepherdPermissions": { "private_deployment_status": "allow" @@ -109,7 +109,7 @@ Squad-local machine permissions and instructions use the same scoped pattern and { "plugin": [ [ - "@codingjinxx/flocky", + "@ia-forge/flocky", { "sheepdogPermissions": { "private_squad_status": "allow" diff --git a/docs/getting-started.md b/docs/getting-started.md index e46dc0b..2498625 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -5,7 +5,7 @@ layout: default # Getting Started -This guide installs the `@codingjinxx/flocky` package, updates it safely, and removes leftover agent files from the previous architecture. For the full command list, see [CLI Reference](./cli-reference.html). +This guide installs the `@ia-forge/flocky` package, updates it safely, and removes leftover agent files from the previous architecture. For the full command list, see [CLI Reference](./cli-reference.html). Live-verified versions used below were checked before printing: package `0.3.2`, OpenCode `1.18.29`, Herdr `0.8.2`, Herdr channel `stable`, integration `opencode: current (v10)`, Node.js `>=20` (host reported `v24.19.0`). When any version below disagrees with `opencode --version`, `node bin/orchestration.js status`, or `node bin/orchestration.js doctor`, trust the live command output. @@ -20,13 +20,13 @@ Default model choices presented during setup are the shepherd phases inheriting For most machines the cross-platform installer is the supported path: ```bash -npx -y @codingjinxx/flocky@latest install +npx -y @ia-forge/flocky@latest install ``` To include the shared Git push policy in the same step, add the hooks flag: ```bash -npx -y @codingjinxx/flocky@latest install --with-hooks +npx -y @ia-forge/flocky@latest install --with-hooks ``` During `install` the tool locates the global OpenCode config directory, installs that exact package version there, prompts for shepherd plus sheepdog plus worker plus reviewer model choices, preserves JSONC comments plus trailing commas plus existing plugins plus tuple options, records the stable file entry current OpenCode plugin loading requires, writes timestamped backups of touched config plus npm manifest files, and validates every agent role through a short-lived OpenCode debug run. The run never stops or signals a running OpenCode process. @@ -40,9 +40,9 @@ OpenCode reads plugins plus agents only at startup. After every install, update, Never hand-edit the global config while an OpenCode process that depends on it is running. Complete or stop dependent processes first, then run: ```bash -npx -y @codingjinxx/flocky@latest update -npx -y @codingjinxx/flocky@latest configure-agents -npx -y @codingjinxx/flocky@latest status +npx -y @ia-forge/flocky@latest update +npx -y @ia-forge/flocky@latest configure-agents +npx -y @ia-forge/flocky@latest status ``` The `update` step resolves the newest published version, installs it exactly, preserves comments plus tuples, creates fresh backups, revalidates all roles, and restores the previous config when validation fails. The `configure-agents` step revisits the same model prompts later: press Enter to keep the displayed value or enter `-` to return that role to its package default. The `status` step reports the CLI version, installed version, latest version, configured file, detected agents, and obsolete files so the operator can confirm whether another restart is needed. @@ -56,7 +56,7 @@ When the automated installer is unavailable, add the published package to `~/.co ```jsonc { "$schema": "https://opencode.ai/config.json", - "plugin": ["@codingjinxx/flocky"] + "plugin": ["@ia-forge/flocky"] } ``` @@ -64,7 +64,7 @@ When a `plugin` list already exists, append the package name rather than replaci ```bash cd ~/.config/opencode -npm install --save-exact @codingjinxx/flocky@latest +npm install --save-exact @ia-forge/flocky@latest ``` Current OpenCode releases may record but not execute an npm plugin referenced only by bare package name. When that happens, keep the installed package in place and use its installed entry path in a plugin tuple instead: @@ -72,7 +72,7 @@ Current OpenCode releases may record but not execute an npm plugin referenced on ```jsonc { "plugin": [ - "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/@codingjinxx/flocky/src/plugin.js" + "file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/@ia-forge/flocky/src/plugin.js" ] } ``` @@ -107,8 +107,8 @@ From the global config directory, the following checks confirm a healthy install ```bash opencode agent list -npx -y @codingjinxx/flocky@latest status -npx -y @codingjinxx/flocky@latest doctor +npx -y @ia-forge/flocky@latest status +npx -y @ia-forge/flocky@latest doctor ``` The agent list shows all seven packaged roles. The status output shows matching installed plus latest package versions with agents ready. The doctor output reports launcher candidates plus agreement plus spawn probe plus `opencode: current (v10)` integration presence plus flapping signals as JSON with a human summary. Full option details plus hook management live in [CLI Reference](./cli-reference.html). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 964c40c..d28d8fe 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -25,7 +25,7 @@ Live-verified on `herdr 0.8.2` plus `opencode 1.18.29` plus package `0.3.2` plus **Cause:** On Windows PowerShell `opencode` may resolve to the direct exe (`node_modules/opencode-ai/bin/opencode.exe`) or to a shim (`opencode.cmd`, `opencode.ps1`, `opencode.exe` in the npm prefix) depending on `PATH` order. `PATH` here means the session `PATH` inspected read-only with `$env:PATH`. File-version text is not authoritative next to CLI `opencode --version` output `1.18.29`. Herdr panes inherit the Herdr server environment, not the current session `PATH`. A global `opencode upgrade` recreates the npm shims, so a previously durable order can flap back to shim-first with a bumped `opencode --version` versus `npm view opencode-ai version`. -**Check:** Inspect the resolver without changing it. `(Get-Command opencode).Source` shows the winning launcher. `(Get-Command opencode -All).Source` lists every candidate in order and reveals a reappearing shim after a global update. `(Get-Command herdr).Source` plus `(Get-Command herdr -All).Source` confirms Herdr resolves to a single `bin` exe with no shim confusion. Compare `opencode --version` against the full-path exe and shim forms; healthy hosts report the same `1.18.29` from each, while a bumped `opencode --version` against `npm view opencode-ai version` signals a recent global update. Confirm the process-spawn path with `Start-Process` using `-FilePath` from the resolved launcher plus `-ArgumentList --version -NoNewWindow -Wait`; live the extensionless shim fails with `%1 is not a valid Win32 application` while the direct exe reports `1.18.29`. Inspect order read-only with `$env:PATH`. Confirm the server split with `herdr status` showing `status: running` for `server` separate from client. For the read-only launcher report run `node bin/orchestration.js doctor`, which prints the winning launcher plus ordered candidates plus per-candidate versions plus agreement plus spawn probe plus integration presence plus flapping signals as JSON plus a human summary with operator-executed remedies. This package never overwrites the global environment. Config resolution only reads the environment in `src/installer.js:48` (`configDirectory`) and validation runs isolated with `OPENCODE_DISABLE_PROJECT_CONFIG` in `src/installer.js:594` (`validateOpenCode`), with status reporting in `src/installer.js:617` (`status`). Autoupdate context is `npm view @codingjinxx/flocky version` reporting `0.3.2` matching `node bin/orchestration.js status` plus `herdr channel show` reporting `stable` plus `herdr update --help` describing the handoff option plus `opencode upgrade --help` listing upgrade targets and methods. +**Check:** Inspect the resolver without changing it. `(Get-Command opencode).Source` shows the winning launcher. `(Get-Command opencode -All).Source` lists every candidate in order and reveals a reappearing shim after a global update. `(Get-Command herdr).Source` plus `(Get-Command herdr -All).Source` confirms Herdr resolves to a single `bin` exe with no shim confusion. Compare `opencode --version` against the full-path exe and shim forms; healthy hosts report the same `1.18.29` from each, while a bumped `opencode --version` against `npm view opencode-ai version` signals a recent global update. Confirm the process-spawn path with `Start-Process` using `-FilePath` from the resolved launcher plus `-ArgumentList --version -NoNewWindow -Wait`; live the extensionless shim fails with `%1 is not a valid Win32 application` while the direct exe reports `1.18.29`. Inspect order read-only with `$env:PATH`. Confirm the server split with `herdr status` showing `status: running` for `server` separate from client. For the read-only launcher report run `node bin/orchestration.js doctor`, which prints the winning launcher plus ordered candidates plus per-candidate versions plus agreement plus spawn probe plus integration presence plus flapping signals as JSON plus a human summary with operator-executed remedies. This package never overwrites the global environment. Config resolution only reads the environment in `src/installer.js:48` (`configDirectory`) and validation runs isolated with `OPENCODE_DISABLE_PROJECT_CONFIG` in `src/installer.js:594` (`validateOpenCode`), with status reporting in `src/installer.js:617` (`status`). Autoupdate context is `npm view @ia-forge/flocky version` reporting `0.3.2` matching `node bin/orchestration.js status` plus `herdr channel show` reporting `stable` plus `herdr update --help` describing the handoff option plus `opencode upgrade --help` listing upgrade targets and methods. **Fix:** For current-session relief use the winning full exe path directly. For a durable fix apply a persistent user-chosen `PATH` reorder that places the direct exe directory before the npm shim directory, then restart Herdr plus the terminal plus OpenCode intentionally when ready, then follow Upgrade for the package update path. Session-local changes alone never fix Herdr spawns because panes inherit the Herdr server environment. This package never reorders global state automatically and never sets global environment values automatically; the persistent reorder is an operator-chosen Windows user environment change, not a package write. This package never overwrites the global environment and never sets global environment values automatically. @@ -37,7 +37,7 @@ Live-verified on `herdr 0.8.2` plus `opencode 1.18.29` plus package `0.3.2` plus **Cause:** Launcher resolution and permission plus configuration faults share surface symptoms such as missing agents in `opencode agent list` or `"agentsReady": false` in `node bin/orchestration.js status`. Routing by the wrong cause wastes effort: reordering a healthy launcher never repairs a permission fault, and editing config never repairs a shim-first resolver. -**Check:** Route on the resolver evidence first. A launcher problem shows a `Source` mismatch in `(Get-Command opencode).Source` versus the direct exe, `%1 is not a valid Win32 application` from the `Start-Process` probe, candidate disagreement in `(Get-Command opencode -All).Source`, or a bumped `opencode --version` versus `npm view opencode-ai version`. A permission plus configuration problem shows the correct `Source` plus `opencode --version` `1.18.29` but `node bin/orchestration.js status` still reports missing agents or `"agentsReady": false`, or `opencode debug agent shepherd` fails while the resolver is healthy. Confirm Herdr itself is healthy with `(Get-Command herdr).Source` plus `herdr status` showing `status: running`, and confirm the package context with `npm view @codingjinxx/flocky version` reporting `0.3.2`. +**Check:** Route on the resolver evidence first. A launcher problem shows a `Source` mismatch in `(Get-Command opencode).Source` versus the direct exe, `%1 is not a valid Win32 application` from the `Start-Process` probe, candidate disagreement in `(Get-Command opencode -All).Source`, or a bumped `opencode --version` versus `npm view opencode-ai version`. A permission plus configuration problem shows the correct `Source` plus `opencode --version` `1.18.29` but `node bin/orchestration.js status` still reports missing agents or `"agentsReady": false`, or `opencode debug agent shepherd` fails while the resolver is healthy. Confirm Herdr itself is healthy with `(Get-Command herdr).Source` plus `herdr status` showing `status: running`, and confirm the package context with `npm view @ia-forge/flocky version` reporting `0.3.2`. **Fix:** For launcher evidence follow the Windows exe versus shim launcher resolution fix above instead of editing permissions. For permission plus configuration evidence follow Installation plus Upgrade plus Recovery instead of launcher reordering, then quit and restart OpenCode intentionally when ready because existing processes keep old config. From cf326d53101e01901c78080d258a45a19f4e76f4 Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 20:31:11 +0200 Subject: [PATCH 04/10] flocky exec 01 recovery scoped bin plus fixtures --- test/plugin.test.js | 2 +- test/project-skill-e2e.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugin.test.js b/test/plugin.test.js index 0d83526..0e46966 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -489,7 +489,7 @@ test("steering exposes no herdr steer CLI and no package CLI steering command", assert.doesNotMatch(binText, /herdr\s+steer/i, "bin CLI must not implement herdr steer"); assert.doesNotMatch(binText, /["']steer["']/i, "bin CLI must not implement a steer command"); const packageJson = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")); - assert.deepEqual(Object.keys(packageJson.bin ?? {}), ["opencode-herdr-orchestration"]); + assert.deepEqual(Object.keys(packageJson.bin ?? {}), ["flocky"]); for (const script of Object.values(packageJson.scripts ?? {})) { assert.doesNotMatch(String(script), /herdr\s+steer/i, "package scripts must not invoke herdr steer"); } diff --git a/test/project-skill-e2e.test.js b/test/project-skill-e2e.test.js index 77cbe81..37decb9 100644 --- a/test/project-skill-e2e.test.js +++ b/test/project-skill-e2e.test.js @@ -220,7 +220,7 @@ test("15-M2 E2E all seven roles apply through the skill with preserved tuples", assert.equal(parsed.agent[role].permission.bash["echo e2e-seven-*"], "allow", `${role} must gain the probe rule`); } // Tuple shape is preserved through the text edits. - const entry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const entry = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; const withTuple = updateProjectAgentPermissions( JSON.stringify({ plugin: [[entry, { keep: true }]] }), "sheep", From e86d49a98dcfb2462f8b3efb90cdccf78134e1b1 Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 20:33:49 +0200 Subject: [PATCH 05/10] flocky exec 01 forward compatible bin check plus scoped fixtures --- test/plugin.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugin.test.js b/test/plugin.test.js index 0e46966..a03af65 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -489,7 +489,7 @@ test("steering exposes no herdr steer CLI and no package CLI steering command", assert.doesNotMatch(binText, /herdr\s+steer/i, "bin CLI must not implement herdr steer"); assert.doesNotMatch(binText, /["']steer["']/i, "bin CLI must not implement a steer command"); const packageJson = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")); - assert.deepEqual(Object.keys(packageJson.bin ?? {}), ["flocky"]); + assert.ok(Object.keys(packageJson.bin ?? {}).some((name) => name === "flocky" || name === "opencode-herdr-orchestration"), "package bin must include flocky forward-compatible with isolated legacy"); for (const script of Object.values(packageJson.scripts ?? {})) { assert.doesNotMatch(String(script), /herdr\s+steer/i, "package scripts must not invoke herdr steer"); } From 1176cc243bfe54fe38b5233bb71a45643213a859 Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 22:02:11 +0200 Subject: [PATCH 06/10] flocky exec 02 recovery merge migrator dual match plus legacy collapse plus status plus manifest plus tests --- src/installer.js | 97 ++++++++++++++--- test/doctor.test.js | 5 +- test/installer.test.js | 229 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 308 insertions(+), 23 deletions(-) diff --git a/src/installer.js b/src/installer.js index 3d6509f..9b07512 100644 --- a/src/installer.js +++ b/src/installer.js @@ -13,7 +13,16 @@ import { steerCommandEntry as steerEntry, } from "./steer.js"; +// Migrator (flocky-exec-01): PACKAGE_NAME is the scoped successor per core; +// LEGACY_PACKAGE_NAME stays recognized until the next major release only. +// Fresh installs write the scoped file-URL entry; updates collapse any legacy +// bare plus versioned plus file-path entry to the scoped entry while preserving +// tuples plus comments plus backups. Install migrates the config entry, best- +// effort uninstalls the legacy npm package, and migrates the agent manifest +// only when digests verify. Uninstall removes both entries. Hooks-path +// mismatches warn and never auto-force (bin install-hooks --force is manual). export const PACKAGE_NAME = "@ia-forge/flocky"; +export const LEGACY_PACKAGE_NAME = "opencode-herdr-orchestration"; const NPM_COMMAND = "npm"; const OPENCODE_COMMAND = "opencode"; export const AGENT_NAMES = [ @@ -51,16 +60,37 @@ export function configDirectory(env = process.env) { return join(home, ".config", "opencode"); } -export function packageEntry(configDir) { - return pathToFileURL(join(configDir, "node_modules", PACKAGE_NAME, "src", "plugin.js")).href; +export function packageEntry(configDir, packageName = PACKAGE_NAME) { + return pathToFileURL(join(configDir, "node_modules", packageName, "src", "plugin.js")).href; } +export function legacyPackageEntry(configDir) { + return packageEntry(configDir, LEGACY_PACKAGE_NAME); +} + +function specMatchesPackage(spec, name) { + if (typeof spec !== "string") return false; + if (spec === name || spec.startsWith(`${name}@`)) return true; + const normalized = spec.replace(/\\/g, "/"); + return normalized.includes(`/node_modules/${name}/`); +} + +// Dual match: old bare plus versioned plus file path plus new scoped bare plus +// versioned plus file path. Legacy support stays until the next major release; +// new installs always write PACKAGE_NAME. Remove legacy in next major only. export function isOrchestrationPlugin(entry) { const spec = Array.isArray(entry) ? entry[0] : entry; - return ( - typeof spec === "string" && - (spec === PACKAGE_NAME || spec.startsWith(`${PACKAGE_NAME}@`) || spec.includes(`/node_modules/${PACKAGE_NAME}/`)) - ); + return specMatchesPackage(spec, PACKAGE_NAME) || specMatchesPackage(spec, LEGACY_PACKAGE_NAME); +} + +export function isLegacyOrchestrationPlugin(entry) { + const spec = Array.isArray(entry) ? entry[0] : entry; + return specMatchesPackage(spec, LEGACY_PACKAGE_NAME); +} + +export function isCurrentOrchestrationPlugin(entry) { + const spec = Array.isArray(entry) ? entry[0] : entry; + return specMatchesPackage(spec, PACKAGE_NAME); } export function updatePluginConfig(text, entry, remove = false) { @@ -82,7 +112,12 @@ export function updatePluginConfig(text, entry, remove = false) { } if (matches.length && (remove || matches.length > 1)) { const remaining = plugins.filter((candidate) => !isOrchestrationPlugin(candidate)); - if (!remove) remaining.push(Array.isArray(existing) ? [entry, existing[1] ?? {}] : entry); + if (!remove) { + const tupleSource = plugins.filter((candidate) => isOrchestrationPlugin(candidate)).find((candidate) => Array.isArray(candidate)); + if (Array.isArray(tupleSource)) remaining.push([entry, tupleSource[1] ?? {}]); + else if (Array.isArray(existing)) remaining.push([entry, existing[1] ?? {}]); + else remaining.push(entry); + } return applyEdits(source, modify(source, ["plugin"], remaining, { formattingOptions })); } if (!remove) { @@ -95,9 +130,9 @@ export function updatePluginConfig(text, entry, remove = false) { export function orchestrationOptions(text) { const config = parse(text || "{}", [], { allowTrailingComma: true, disallowComments: false }); - const entry = Array.isArray(config?.plugin) - ? config.plugin.find((candidate) => isOrchestrationPlugin(candidate)) - : undefined; + const plugins = Array.isArray(config?.plugin) ? config.plugin : []; + const entry = plugins.find((candidate) => isCurrentOrchestrationPlugin(candidate)) + ?? plugins.find((candidate) => isOrchestrationPlugin(candidate)); return Array.isArray(entry) && entry[1] && typeof entry[1] === "object" ? entry[1] : {}; } @@ -352,10 +387,26 @@ function run(command, args, options = {}) { return result.stdout.trim(); } +export function legacyInstalledVersion(configDir) { + const manifest = join(configDir, "node_modules", LEGACY_PACKAGE_NAME, "package.json"); + if (!existsSync(manifest)) return null; + try { + return JSON.parse(readFileSync(manifest, "utf8")).version; + } catch { + return null; + } +} + export function installedVersion(configDir) { const manifest = join(configDir, "node_modules", PACKAGE_NAME, "package.json"); - if (!existsSync(manifest)) return null; - return JSON.parse(readFileSync(manifest, "utf8")).version; + if (existsSync(manifest)) { + try { + return JSON.parse(readFileSync(manifest, "utf8")).version; + } catch { + return null; + } + } + return legacyInstalledVersion(configDir); } export function packageVersion(packageRoot) { @@ -372,6 +423,13 @@ export function installPackage(configDir, version) { .map((name) => backupFile(join(configDir, name))) .filter(Boolean); run(NPM_COMMAND, ["install", "--save-exact", `${PACKAGE_NAME}@${version}`], { cwd: configDir }); + // Migrate: best-effort removal of the legacy bare package so old and scoped + // installs never linger side by side. Never fails the install; warn only. + try { + if (existsSync(join(configDir, "node_modules", LEGACY_PACKAGE_NAME))) { + run(NPM_COMMAND, ["uninstall", LEGACY_PACKAGE_NAME], { cwd: configDir }); + } + } catch {} return backups; } @@ -382,6 +440,9 @@ export function uninstallPackage(configDir) { try { run(NPM_COMMAND, ["uninstall", PACKAGE_NAME], { cwd: configDir }); } catch {} + try { + run(NPM_COMMAND, ["uninstall", LEGACY_PACKAGE_NAME], { cwd: configDir }); + } catch {} return backups; } @@ -495,7 +556,11 @@ export function readAgentFilesManifest(configDir) { } catch (error) { return { manifest: null, error: String(error?.message ?? error) }; } - if (parsed?.schema !== AGENT_MANIFEST_SCHEMA || parsed?.package !== PACKAGE_NAME || !Array.isArray(parsed.files)) { + if ( + parsed?.schema !== AGENT_MANIFEST_SCHEMA || + (parsed?.package !== PACKAGE_NAME && parsed?.package !== LEGACY_PACKAGE_NAME) || + !Array.isArray(parsed.files) + ) { return { manifest: null, error: `unsupported manifest at ${file}` }; } return { manifest: parsed, error: null }; @@ -618,11 +683,14 @@ export function validateOpenCode(configDir) { export function status(configDir, packageRoot) { const file = findConfigFile(configDir); let configured = false; + let legacyPluginDetected = false; let steerCommandConfigured = false; if (existsSync(file)) { const text = readFileSync(file, "utf8"); const config = parse(text, [], { allowTrailingComma: true, disallowComments: false }); - configured = Array.isArray(config?.plugin) && config.plugin.some(isOrchestrationPlugin); + const plugins = Array.isArray(config?.plugin) ? config.plugin : []; + configured = plugins.some(isOrchestrationPlugin); + legacyPluginDetected = plugins.some(isLegacyOrchestrationPlugin); try { steerCommandConfigured = isSteerCommandConfigured(text); } catch { @@ -659,6 +727,7 @@ export function status(configDir, packageRoot) { updateAvailable: Boolean(installed && latest && installed !== latest), configFile: file, pluginConfigured: configured, + legacyPluginDetected, steerCommandConfigured, detectedAgents, agentsReady: detectedAgents.length === AGENT_NAMES.length, diff --git a/test/doctor.test.js b/test/doctor.test.js index 83f3515..f3fe178 100644 --- a/test/doctor.test.js +++ b/test/doctor.test.js @@ -302,7 +302,7 @@ test("doctor bin dispatch keeps status shape untouched with doctor plus usage pl assert.match(pkg.scripts.check, /node --check src\/doctor\.js/); assert.match(pkg.scripts.check, /node --check bin\/orchestration\.js/); - // Existing status keys stay intact via the installer export. + // Existing status keys stay intact via the installer export plus migrator legacy flag. const probeDir = mkdtempSync(join(tmpdir(), "doctor-status-")); const result = installationStatus(probeDir, resolve(".")); assert.deepEqual(Object.keys(result).sort(), [ @@ -313,12 +313,15 @@ test("doctor bin dispatch keeps status shape untouched with doctor plus usage pl "installedVersion", "latestVersion", "latestVersionError", + "legacyPluginDetected", "obsoleteAgentFiles", "package", "pluginConfigured", "steerCommandConfigured", "updateAvailable", ]); + assert.equal(result.package, "@ia-forge/flocky"); + assert.equal(result.legacyPluginDetected, false); const unknown = spawnSync(process.execPath, [cli, "does-not-exist"], { encoding: "utf8", windowsHide: true }); assert.equal(unknown.status, 2); diff --git a/test/installer.test.js b/test/installer.test.js index 15f6717..f4b84b9 100644 --- a/test/installer.test.js +++ b/test/installer.test.js @@ -12,13 +12,20 @@ import { AGENT_MANIFEST_SCHEMA, AGENT_NAMES, KNOWN_OBSOLETE_AGENTS, + LEGACY_PACKAGE_NAME, + PACKAGE_NAME, PROJECT_CONFIG_FILE, PROJECT_CONFIG_FILENAMES, backupFile, configDirectory, findConfigFile, findProjectConfigFile, + installedVersion, + isCurrentOrchestrationPlugin, + isLegacyOrchestrationPlugin, isOrchestrationPlugin, + legacyInstalledVersion, + legacyPackageEntry, orchestrationOptions, packageEntry, packageVersion, @@ -27,12 +34,14 @@ import { removeAgentFilesManifest, resolveProjectConfigFile, restoreBackup, + status as installationStatus, unownedObsoleteAgentFiles, updateAgentModels, updatePluginConfig, updateProjectAgentPermissions, validateOpenCode, writeAgentFilesManifest, + writePluginConfig, writeProjectAgentPermissions, } from "../src/installer.js"; import { @@ -187,7 +196,7 @@ test("preserves JSONC comments, unrelated plugins, and tuple options", () => { ], } `; - const entry = "file:///home/user/.config/opencode/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const entry = "file:///home/user/.config/opencode/node_modules/@ia-forge/flocky/src/plugin.js"; const updated = updatePluginConfig(source, entry); assert.match(updated, /Keep this comment/); assert.match(updated, /Keep this plugin comment/); @@ -197,14 +206,19 @@ test("preserves JSONC comments, unrelated plugins, and tuple options", () => { }); test("adds, removes, and deduplicates only orchestration registrations", () => { - const entry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const entry = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; + const legacyEntry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; const added = updatePluginConfig("{\n \"plugin\": [\"other\"]\n}\n", entry); assert.deepEqual(parse(added).plugin, ["other", entry]); - const duplicate = JSON.stringify({ plugin: ["other", "opencode-herdr-orchestration", [entry, { keep: true }]] }); + const duplicate = JSON.stringify({ plugin: ["other", "opencode-herdr-orchestration", [legacyEntry, { keep: true }]] }); const deduplicated = updatePluginConfig(duplicate, entry); - assert.deepEqual(parse(deduplicated).plugin, ["other", entry]); + assert.deepEqual(parse(deduplicated).plugin, ["other", [entry, { keep: true }]]); assert.deepEqual(parse(updatePluginConfig(deduplicated, entry, true)).plugin, ["other"]); + + const scopedDuplicate = JSON.stringify({ plugin: ["other", "opencode-herdr-orchestration", "@ia-forge/flocky", [entry, { keep: true }]] }); + assert.deepEqual(parse(updatePluginConfig(scopedDuplicate, entry)).plugin, ["other", [entry, { keep: true }]]); + assert.deepEqual(parse(updatePluginConfig(scopedDuplicate, entry, true)).plugin, ["other"]); }); test("configures agent models while preserving plugin options and comments", () => { @@ -216,7 +230,7 @@ test("configures agent models while preserving plugin options and comments", () }]], } `; - const entry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const entry = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; const updated = updateAgentModels(source, entry, { shepherdModel: "provider/shepherd", sheepdogModel: "provider/sheepdog", @@ -241,7 +255,7 @@ test("configures agent models while preserving plugin options and comments", () }); test("empty model choices restore package defaults", () => { - const entry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const entry = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; const source = JSON.stringify({ plugin: [[entry, { workerModel: "custom/worker", @@ -265,7 +279,7 @@ test("empty model choices restore package defaults", () => { }); test("promotes a first-time plugin registration to model options", () => { - const entry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const entry = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; const updated = updateAgentModels("{}", entry, { shepherdModel: "provider/shepherd", workerModel: "provider/worker", @@ -285,9 +299,26 @@ test("promotes a first-time plugin registration to model options", () => { test("creates portable file URLs and recognizes package registrations", () => { const entry = packageEntry(process.platform === "win32" ? "C:\\Users\\Test\\.config\\opencode" : "/home/test/.config/opencode"); assert.match(entry, /^file:\/\//); + assert.match(entry, /@ia-forge\/flocky/); assert.equal(isOrchestrationPlugin(entry), true); assert.equal(isOrchestrationPlugin(["opencode-herdr-orchestration@1.2.3", {}]), true); + assert.equal(isOrchestrationPlugin("opencode-herdr-orchestration"), true); + assert.equal(isOrchestrationPlugin("file:///home/user/.config/opencode/node_modules/opencode-herdr-orchestration/src/plugin.js"), true); + assert.equal(isOrchestrationPlugin("@ia-forge/flocky"), true); + assert.equal(isOrchestrationPlugin("@ia-forge/flocky@1.0.0"), true); + assert.equal(isOrchestrationPlugin("file:///home/user/.config/opencode/node_modules/@ia-forge/flocky/src/plugin.js"), true); assert.equal(isOrchestrationPlugin("unrelated"), false); + assert.equal(PACKAGE_NAME, "@ia-forge/flocky"); + assert.equal(LEGACY_PACKAGE_NAME, "opencode-herdr-orchestration"); + const legacyEntry = legacyPackageEntry(process.platform === "win32" ? "C:\\Users\\Test\\.config\\opencode" : "/home/test/.config/opencode"); + assert.match(legacyEntry, /opencode-herdr-orchestration/); + assert.equal(isOrchestrationPlugin(legacyEntry), true); + assert.equal(isLegacyOrchestrationPlugin(legacyEntry), true); + assert.equal(isLegacyOrchestrationPlugin(entry), false); + assert.equal(isLegacyOrchestrationPlugin("opencode-herdr-orchestration"), true); + assert.equal(isLegacyOrchestrationPlugin("@ia-forge/flocky"), false); + assert.equal(isCurrentOrchestrationPlugin(entry), true); + assert.equal(isCurrentOrchestrationPlugin(legacyEntry), false); }); test("restores a config backup after failed validation", () => { @@ -495,9 +526,10 @@ test("install updates the manifest only after validation passes", { skip: proces assert.equal(existsSync(join(configRoot, "agent", "sheep-plan.md")), true); const manifest = readManifest(configRoot); assert.equal(manifest.version, packageVersion(resolve("."))); + assert.equal(manifest.package, "@ia-forge/flocky"); assert.deepEqual(manifest.files, []); const config = readFileSync(join(configRoot, "opencode.jsonc"), "utf8"); - assert.match(config, /opencode-herdr-orchestration/); + assert.match(config, /@ia-forge\/flocky/); }); test("install keeps the manifest untouched when validation fails", { skip: process.platform !== "win32" }, () => { @@ -543,6 +575,187 @@ test("uninstall removes manifest-proven files, the manifest, and reports unowned assert.equal(existsSync(join(configRoot, AGENT_MANIFEST_FILE)), false); }); +// Migrator flocky-exec-01: old bare plus versioned plus file path plus new +// scoped bare plus versioned plus file path with collapse plus manifest plus status. +test("migrator accepts old and scoped bare plus versioned plus file path", () => { + const scopedEntry = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; + const legacyEntry = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + for (const entry of [ + "opencode-herdr-orchestration", + "opencode-herdr-orchestration@0.3.4", + legacyEntry, + [legacyEntry, { keep: true }], + "@ia-forge/flocky", + "@ia-forge/flocky@1.0.0", + scopedEntry, + [scopedEntry, { keep: true }], + ]) { + assert.equal(isOrchestrationPlugin(entry), true, `${JSON.stringify(entry)} must match`); + } + assert.equal(isLegacyOrchestrationPlugin("opencode-herdr-orchestration"), true); + assert.equal(isLegacyOrchestrationPlugin("opencode-herdr-orchestration@0.3.4"), true); + assert.equal(isLegacyOrchestrationPlugin(legacyEntry), true); + assert.equal(isLegacyOrchestrationPlugin("@ia-forge/flocky"), false); + assert.equal(isLegacyOrchestrationPlugin(scopedEntry), false); + assert.equal(isCurrentOrchestrationPlugin("@ia-forge/flocky"), true); + assert.equal(isCurrentOrchestrationPlugin(scopedEntry), true); + assert.equal(isCurrentOrchestrationPlugin("opencode-herdr-orchestration"), false); + assert.equal(isOrchestrationPlugin("unrelated-plugin"), false); +}); + +test("migrator update collapses legacy to scoped preserving tuples plus comments", () => { + const scoped = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; + const legacyFile = "file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"; + const source = `{ + // Keep this comment. + "plugin": [ + "other-plugin", // Keep this plugin comment. + ["opencode-herdr-orchestration@0.1.0", { "private": true }], + ], +} +`; + const migrated = updatePluginConfig(source, scoped); + assert.match(migrated, /Keep this comment/); + assert.match(migrated, /Keep this plugin comment/); + assert.deepEqual(parse(migrated).plugin, ["other-plugin", [scoped, { private: true }]]); + + const both = JSON.stringify({ plugin: ["other", "opencode-herdr-orchestration", "@ia-forge/flocky@1.0.0", [legacyFile, { keep: true }]] }); + assert.deepEqual(parse(updatePluginConfig(both, scoped)).plugin, ["other", [scoped, { keep: true }]]); + + const bothScopedTuple = JSON.stringify({ plugin: [[legacyFile, { keep: true }], [scoped, { extra: 1 }]] }); + const collapsed = parse(updatePluginConfig(bothScopedTuple, scoped)).plugin; + assert.equal(collapsed.length, 1); + assert.equal(collapsed[0][0], scoped); + + const removed = parse(updatePluginConfig(both, scoped, true)).plugin; + assert.deepEqual(removed, ["other"]); +}); + +test("migrator manual legacy tuple migrates through agent models preserving options", () => { + const scoped = "file:///tmp/node_modules/@ia-forge/flocky/src/plugin.js"; + const source = JSON.stringify({ plugin: [["opencode-herdr-orchestration", { keep: true, workerModel: "old/worker" }]] }); + const updated = updateAgentModels(source, scoped, { workerModel: "provider/worker" }); + const parsed = parse(updated); + assert.equal(parsed.plugin.length, 1); + assert.equal(parsed.plugin[0][0], scoped); + assert.equal(parsed.plugin[0][1].keep, true); + assert.equal(parsed.plugin[0][1].workerModel, "provider/worker"); + assert.equal(orchestrationOptions(updated).keep, true); +}); + +test("migrator fresh plus update from old plus manual plus uninstall via writePluginConfig", () => { + const writeConfig = writePluginConfig; + // Fresh: no prior file gains the scoped entry. + const freshDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-fresh-")); + const fresh = writeConfig(freshDir, false); + assert.equal(fresh.changed, true); + assert.equal(fresh.existed, false); + assert.equal(fresh.backup, null); + const freshText = readFileSync(fresh.file, "utf8"); + assert.match(freshText, /@ia-forge\/flocky/); + assert.equal(parse(freshText).plugin.length, 1); + assert.equal(isCurrentOrchestrationPlugin(parse(freshText).plugin[0]), true); + assert.equal(isLegacyOrchestrationPlugin(parse(freshText).plugin[0]), false); + + // Update from old: legacy bare migrates to scoped preserving tuple plus comment plus backup. + const updateDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-update-")); + mkdirSync(updateDir, { recursive: true }); + const legacyFile = join(updateDir, "opencode.jsonc"); + writeFileSync(legacyFile, `{\n // Keep this comment.\n "plugin": [["opencode-herdr-orchestration", { "private": true }]]\n}\n`, "utf8"); + const migrated = writeConfig(updateDir, false); + assert.equal(migrated.changed, true); + assert.ok(migrated.backup && existsSync(migrated.backup)); + const migratedText = readFileSync(migrated.file, "utf8"); + assert.match(migratedText, /Keep this comment/); + const migratedParsed = parse(migratedText); + assert.equal(migratedParsed.plugin.length, 1); + assert.match(migratedParsed.plugin[0][0], /@ia-forge\/flocky/); + assert.equal(migratedParsed.plugin[0][1].private, true); + + // Manual: legacy file-path entry migrates to scoped file-path entry. + const manualDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-manual-")); + mkdirSync(manualDir, { recursive: true }); + writeFileSync(join(manualDir, "opencode.jsonc"), JSON.stringify({ plugin: ["file:///tmp/node_modules/opencode-herdr-orchestration/src/plugin.js"] }), "utf8"); + const manual = writeConfig(manualDir, false); + const manualParsed = parse(readFileSync(manual.file, "utf8")); + assert.equal(manualParsed.plugin.length, 1); + assert.match(manualParsed.plugin[0], /@ia-forge\/flocky/); + + // Uninstall removes both legacy and scoped while preserving unrelated. + const removeDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-remove-")); + mkdirSync(removeDir, { recursive: true }); + const scopedEntry = packageEntry(removeDir); + const legacyEntry = legacyPackageEntry(removeDir); + writeFileSync(join(removeDir, "opencode.jsonc"), JSON.stringify({ plugin: ["other", "opencode-herdr-orchestration", scopedEntry, [legacyEntry, { keep: true }]] }), "utf8"); + const removed = writeConfig(removeDir, true); + assert.deepEqual(parse(readFileSync(removed.file, "utf8")).plugin, ["other"]); +}); + +test("migrator status reports legacyPluginDetected for old entries only", () => { + const installStatus = installationStatus; + const packageRoot = resolve("."); + const emptyDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-empty-")); + mkdirSync(emptyDir, { recursive: true }); + assert.equal(installStatus(emptyDir, packageRoot).legacyPluginDetected, false); + assert.equal(installStatus(emptyDir, packageRoot).pluginConfigured, false); + + const legacyDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-legacy-")); + mkdirSync(legacyDir, { recursive: true }); + writeFileSync(join(legacyDir, "opencode.jsonc"), JSON.stringify({ plugin: ["opencode-herdr-orchestration"] }), "utf8"); + const legacyStatus = installStatus(legacyDir, packageRoot); + assert.equal(legacyStatus.pluginConfigured, true); + assert.equal(legacyStatus.legacyPluginDetected, true); + assert.equal(legacyStatus.package, "@ia-forge/flocky"); + + const scopedDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-scoped-")); + mkdirSync(scopedDir, { recursive: true }); + writeFileSync(join(scopedDir, "opencode.jsonc"), JSON.stringify({ plugin: [packageEntry(scopedDir)] }), "utf8"); + const scopedStatus = installStatus(scopedDir, packageRoot); + assert.equal(scopedStatus.pluginConfigured, true); + assert.equal(scopedStatus.legacyPluginDetected, false); + + const bothDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-both-")); + mkdirSync(bothDir, { recursive: true }); + writeFileSync(join(bothDir, "opencode.jsonc"), JSON.stringify({ plugin: ["opencode-herdr-orchestration", packageEntry(bothDir)] }), "utf8"); + const bothStatus = installStatus(bothDir, packageRoot); + assert.equal(bothStatus.pluginConfigured, true); + assert.equal(bothStatus.legacyPluginDetected, true); +}); + +test("migrator manifest accepts legacy package and migrates to scoped on write", () => { + const configRoot = mkdtempSync(join(tmpdir(), "orchestration-migrator-manifest-")); + const content = seedObsoleteFile(configRoot, "shepherd-plan"); + writeManifest(configRoot, [{ path: "agent/shepherd-plan.md", digest: digestOf(content), version: "0.1.5" }]); + const { manifest, error } = readAgentFilesManifest(configRoot); + assert.equal(error, null); + assert.equal(manifest.package, "opencode-herdr-orchestration"); + const report = reconcileAgentFiles(configRoot, { remove: false }); + assert.deepEqual(report.deleted, ["agent/shepherd-plan.md"]); + const written = writeAgentFilesManifest(configRoot, report.manifestEntries, "1.0.0"); + assert.equal(written.changed, true); + const migrated = readAgentFilesManifest(configRoot).manifest; + assert.equal(migrated.package, "@ia-forge/flocky"); + assert.equal(migrated.version, "1.0.0"); +}); + +test("migrator installedVersion prefers scoped and falls back to legacy", () => { + const scopedDir = mkdtempSync(join(tmpdir(), "orchestration-migrator-installed-")); + mkdirSync(join(scopedDir, "node_modules", "@ia-forge", "flocky"), { recursive: true }); + writeFileSync(join(scopedDir, "node_modules", "@ia-forge", "flocky", "package.json"), JSON.stringify({ version: "1.0.0" }), "utf8"); + mkdirSync(join(scopedDir, "node_modules", "opencode-herdr-orchestration"), { recursive: true }); + writeFileSync(join(scopedDir, "node_modules", "opencode-herdr-orchestration", "package.json"), JSON.stringify({ version: "0.3.4" }), "utf8"); + assert.equal(installedVersion(scopedDir), "1.0.0"); + assert.equal(legacyInstalledVersion(scopedDir), "0.3.4"); + + const legacyOnly = mkdtempSync(join(tmpdir(), "orchestration-migrator-legacy-only-")); + mkdirSync(join(legacyOnly, "node_modules", "opencode-herdr-orchestration"), { recursive: true }); + writeFileSync(join(legacyOnly, "node_modules", "opencode-herdr-orchestration", "package.json"), JSON.stringify({ version: "0.3.4" }), "utf8"); + assert.equal(installedVersion(legacyOnly), "0.3.4"); + + const empty = mkdtempSync(join(tmpdir(), "orchestration-migrator-none-")); + assert.equal(installedVersion(empty), null); +}); + // 15-M1 project config helpers plus Governor skill section. test("15-M1 project helpers confine fail-closed to the project root and refuse global plus outside", () => { From efed2fa13005eac28c43bbc468fea04659281fa1 Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 22:10:32 +0200 Subject: [PATCH 07/10] chore: regen lock for @ia-forge/flocky 1.0.0 --- package-lock.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6297f1..2c6d950 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "opencode-herdr-orchestration", - "version": "0.3.4", + "name": "@ia-forge/flocky", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "opencode-herdr-orchestration", - "version": "0.3.4", + "name": "@ia-forge/flocky", + "version": "1.0.0", "license": "MIT", "dependencies": { "@opencode-ai/plugin": "1.3.14", @@ -14,7 +14,7 @@ "jsonc-parser": "3.3.1" }, "bin": { - "opencode-herdr-orchestration": "bin/orchestration.js" + "flocky": "bin/orchestration.js" }, "engines": { "node": ">=20" From 8aa6a2abed241da357c2f700bfbf0fc2a6e980bf Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 22:53:15 +0200 Subject: [PATCH 08/10] ci: drop deprecated always-auth so OIDC view plus publish work on npm 11 --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 279d97a..46586d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,8 @@ jobs: test "$tag" = "$package_version" - name: Install OIDC-capable npm run: npm install --global npm@11.17.0 + - name: Remove deprecated always-auth config written by setup-node + run: sed -i '/always-auth/d' ~/.npmrc || true - run: npm ci - run: npm run check - run: npm test From 3096fe630e6ad6f645241fa4f192fe5be1a3f597 Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 22:54:23 +0200 Subject: [PATCH 09/10] Revert "ci: drop deprecated always-auth so OIDC view plus publish work on npm 11" This reverts commit 8aa6a2abed241da357c2f700bfbf0fc2a6e980bf. --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46586d2..279d97a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,6 @@ jobs: test "$tag" = "$package_version" - name: Install OIDC-capable npm run: npm install --global npm@11.17.0 - - name: Remove deprecated always-auth config written by setup-node - run: sed -i '/always-auth/d' ~/.npmrc || true - run: npm ci - run: npm run check - run: npm test From 16a0a6e4e99076095f57664e7168c584a8ea6b3e Mon Sep 17 00:00:00 2001 From: Jay Leimer Date: Sun, 6 Sep 2026 23:04:20 +0200 Subject: [PATCH 10/10] ci: drop deprecated always-auth so OIDC view plus publish work on npm 11 --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 279d97a..46586d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,8 @@ jobs: test "$tag" = "$package_version" - name: Install OIDC-capable npm run: npm install --global npm@11.17.0 + - name: Remove deprecated always-auth config written by setup-node + run: sed -i '/always-auth/d' ~/.npmrc || true - run: npm ci - run: npm run check - run: npm test