Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/orca-plugin-ref.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Orca Plugin Ref

# Republish `plugins/genie` as a standalone git ref that Orca can install.
#
# Orca installs a plugin from a git URL + ref (or a local folder) whose ROOT
# holds `orca-plugin.json`. This repository root can never be that tree:
#
# * the loader rejects any tree containing a symlink ("unsafe file path or
# symlink"), and `docs -> .docs-vendor/genie` is one;
# * it caps an install at 2000 files / 50 MB, and a dev checkout is ~14k files;
# * the manifest is nested at `plugins/genie/orca-plugin.json`, invisible to a
# git source, which only looks at the root.
#
# `plugins/genie` on its own is symlink-free, ~132 files / ~1.3 MB, and holds the
# manifest at its root — so this workflow publishes exactly that subtree:
#
# main -> refs/heads/orca-plugin (what orca-marketplace.json points at)
# dev -> refs/heads/orca-plugin-dev (pre-release channel)
#
# SECURITY: `contents: write` is scoped to this one job, which runs no package
# script, dependency installer, hook, or any executable from the checkout. Every
# command below is trusted workflow control; the only inputs are a git tree hash
# derived from the pushed commit and the repository's own ref state.
#
# The published ref is TREE-ONLY BY DESIGN: each publish is a fresh parentless
# root commit carrying just the subtree, force-pushed over the previous one. It
# has no history, shares no ancestry with main/dev, and must never be merged
# back. Orca pins the commit it fetched, so a rewritten ref cannot retroactively
# change an existing install.

on:
push:
branches: [main, dev]
paths:
- 'plugins/genie/**'
- '.github/workflows/orca-plugin-ref.yml'
# Manual repair path: republish the current branch tip's subtree.
workflow_dispatch:
Comment on lines +31 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Republish the auto-version child

On every normal dev release, this workflow publishes the developer-pushed commit, but .github/workflows/version.yml subsequently rewrites the plugins/genie version metadata and pushes a version-only child using github.token; that workflow itself notes that GITHUB_TOKEN-authored pushes do not trigger workflows. Consequently, orca-plugin-dev remains on the pre-bump tree rather than mirroring the resulting dev tip, so pre-release installations receive stale version metadata until an unrelated qualifying push or manual repair occurs. Explicitly dispatch this publisher after the atomic version push or otherwise add a trigger for the version child.

Useful? React with 👍 / 👎.


permissions:
contents: read

# Serialize per source branch. Never cancel: a cancelled force-push could leave
# the plugin ref pointing at an older subtree than the branch it mirrors.
concurrency:
group: orca-plugin-ref-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
name: Publish plugins/genie subtree
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write # force-push the tree-only orca-plugin[-dev] ref
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
# Only the pushed commit's tree is needed; the publish never walks
# history. Credentials are bound below with `gh auth setup-git` once
# the target ref has been resolved.
fetch-depth: 1
persist-credentials: false

- name: Publish the subtree ref
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.ref_name }}
GIT_AUTHOR_NAME: 'github-actions[bot]'
GIT_AUTHOR_EMAIL: 'github-actions[bot]@users.noreply.github.com'
GIT_COMMITTER_NAME: 'github-actions[bot]'
GIT_COMMITTER_EMAIL: 'github-actions[bot]@users.noreply.github.com'
shell: bash
run: |
set -euo pipefail

case "${BRANCH}" in
main) TARGET_REF=orca-plugin ;;
dev) TARGET_REF=orca-plugin-dev ;;
*)
echo "::error ::orca-plugin-ref.unexpected-branch ${BRANCH} has no plugin ref mapping"
exit 1
;;
esac

SOURCE_SHA="$(git rev-parse HEAD)"
SUBTREE="$(git rev-parse "HEAD:plugins/genie")"
echo "source ${BRANCH}@${SOURCE_SHA} subtree ${SUBTREE} -> refs/heads/${TARGET_REF}"

# Idempotent: skip when the published ref already carries this exact
# tree. A missing ref (first publish) is the only tolerated lookup
# failure — anything else must abort rather than force-push blind.
PUBLISHED_TREE=""
REF_STATUS=0
REF_SHA="$(gh api "repos/${REPO}/git/ref/heads/${TARGET_REF}" --jq '.object.sha' 2>/dev/null)" || REF_STATUS=$?
if [ "${REF_STATUS}" -eq 0 ] && [ -n "${REF_SHA}" ]; then
PUBLISHED_TREE="$(gh api "repos/${REPO}/git/commits/${REF_SHA}" --jq '.tree.sha')"
else
echo "refs/heads/${TARGET_REF} not published yet"
fi

if [ "${PUBLISHED_TREE}" = "${SUBTREE}" ]; then
echo "::notice ::orca-plugin-ref.unchanged refs/heads/${TARGET_REF} already carries subtree ${SUBTREE}"
exit 0
fi

# Parentless root commit whose ROOT IS `plugins/genie`. `commit-tree`
# writes no working tree and copies no history.
COMMIT="$(git commit-tree "${SUBTREE}" \
-m "chore(orca-plugin): plugins/genie subtree of ${BRANCH} ${SOURCE_SHA}")"

gh auth setup-git
# Force is structural, not a race override: every publish is a fresh
# root commit, so the ref can only ever be replaced, never advanced.
git push --force origin "${COMMIT}:refs/heads/${TARGET_REF}"
Comment on lines +113 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject publication from a stale source commit

When a historical successful workflow run is rerun, checkout remains pinned to that run's old push commit, and this unconditional force-push replaces the current orca-plugin or orca-plugin-dev ref with the old subtree. The concurrency group prevents simultaneous publishers but never verifies that SOURCE_SHA is still the source branch tip, so new Orca installations can regress after a rerun; check the current remote main/dev head before publishing and skip stale runs.

Useful? React with 👍 / 👎.

echo "::notice ::orca-plugin-ref.published refs/heads/${TARGET_REF} = ${COMMIT} (subtree ${SUBTREE})"
5 changes: 2 additions & 3 deletions .github/workflows/version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,14 @@ jobs:
# SECURITY: every command in this step is trusted workflow control loaded
# from main. Do not run package scripts, dependency installers, hooks, or
# any executable from the dev checkout in this write-capable workflow.
- name: Synchronize the exact ten version fields
- name: Synchronize the exact nine version fields
if: steps.context.outputs.should_bump == 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
JSON_FILES=(
package.json
orca-plugin.json
plugins/genie/.claude-plugin/plugin.json
plugins/genie/.codex-plugin/plugin.json
plugins/genie/.kimi-plugin/plugin.json
Expand Down Expand Up @@ -270,7 +269,7 @@ jobs:
EXPECTED="$(printf '%s\n' "${VERSION_PATHS[@]}" | LC_ALL=C sort)"
ACTUAL="$(git diff --cached --name-only -- | LC_ALL=C sort)"
if [[ "$ACTUAL" != "$EXPECTED" ]]; then
echo "::error ::release-version.delta_untrusted expected exactly ten version files"
echo "::error ::release-version.delta_untrusted expected exactly nine version files"
printf 'expected:\n%s\nactual:\n%s\n' "$EXPECTED" "$ACTUAL" >&2
exit 1
fi
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ Biome's `noExcessiveCognitiveComplexity` is set to `maxAllowedComplexity: 25` (w
- **Hook dispatch is provider-aware and fail-closed** — the plugin-local Codex launcher selects only the canonical `$GENIE_HOME/bin/genie`, preserves stdin/stdout/signals, bounds child time/output, validates event-specific JSON, and converts launch/timeout/schema failures into a reasoned deny. Claude dispatch remains a short-lived in-process `genie hook dispatch` fork. Neither path is a daemon.
- **`AskUserQuestion` is the one PreToolUse carve-out** — it is in `NON_INTERCEPTABLE_PRE_TOOL_USE_TOOLS` and MUST get an EMPTY response, not the neutral `{ decision: 'block' }` block form. Empirically CC consumes any additionalContext as the synthesized answer, so a fail-closed block would corrupt the inline picker. The fail-closed envelope special-cases this tool.
- **The product MCP server and its launchers are retired** — `genie mcp` is a stub that writes a stable diagnostic to stderr and exits 1; no plugin ships an MCP declaration or launcher, and `genie init` only retires proven Genie-owned historical routes (in `.mcp.json` it retires only the dead `genie mcp` entry — a genie binary with args exactly `["mcp"]` — backing the file up first and preserving every other server byte-for-byte; `genie doctor` warns while that entry is still present). The UI-owned `genie ui-bridge` is retired too — there is no Genie UI any more, the Orca integration is the only UI surface — so it is the same shape of stub (stable stderr diagnostic, exit 1), and its private transport (`mcp-server.ts`), tool registry (`mcp-tools.ts`), and change watcher (`bridge-watcher.ts`) are deleted.
- **The two repo-root Orca files are source-only, and never ship in a tarball** — Orca accepts exactly a marketplace source (git repo whose ROOT holds `orca-marketplace.json`) or a plugin source (git repo whose ROOT holds `orca-plugin.json`, or a local folder containing one), so the nested `plugins/genie/orca-plugin.json` alone makes the repo un-installable from git. Root `/orca-plugin.json` is the payload manifest with `main` re-rooted to `plugins/genie/orca-entrypoint.min.js` and nothing else changed; root `/orca-marketplace.json` publishes the single `automagik.genie` entry at ref `main`. `scripts/build-binary.sh` copies neither, so `release-payload-version.ts` must NOT stamp or gate them — their version currency comes from `scripts/version.ts` and the `version.yml` JSON_FILES list (now ten version files), and `release-guard.sh` treats the root manifest as an OPTIONAL version-only child member exactly like the payload manifest. `scripts/orca-manifest-parity.test.ts` is the drift guard. `genie setup --orchestration-mode orca` selects authority only; it never registers the plugin with Orca.
- **The Orca plugin ships as a tree-only subtree ref, never from the repo root** — Orca installs a plugin from a git URL+ref (or a local folder) whose ROOT holds `orca-plugin.json`, and its loader rejects any tree containing a symlink ("unsafe file path or symlink") and caps an install at 2000 files / 50 MB. The genie root can therefore never be the install tree (`docs -> .docs-vendor/genie`; ~14k files in a dev checkout), and a re-rooted root `orca-plugin.json` does NOT fix that — do not reintroduce one, and do not add it to `scripts/version.ts`, the `version.yml` JSON_FILES list (still nine version files), or `release-guard.sh`. `plugins/genie` alone is symlink-free, ~132 files, ~1.3 MB, and holds the manifest at its root, so `.github/workflows/orca-plugin-ref.yml` force-pushes `git commit-tree HEAD:plugins/genie` (a parentless, history-free commit) to `refs/heads/orca-plugin` from main and `refs/heads/orca-plugin-dev` from dev, skipping when the tree hash already matches. Those refs are never merged back. The only repo-root Orca file is `orca-marketplace.json` — a source-only, versionless index pointing `automagik.genie` at ref `orca-plugin`, copied into no tarball. `scripts/orca-manifest-parity.test.ts` is the drift guard and also asserts `plugins/genie` stays symlink-free and inside the file cap. `genie setup --orchestration-mode orca` selects authority only; it never registers the plugin with Orca.
- **Lifecycle authority is an explicit mode, never inferred** — `standalone` is the default (including when `orchestration.mode` is absent); installing or opening Orca changes nothing. `genie setup --orchestration-mode orca` probes the shipped plugin payload and a compatible Orca runtime before atomically switching, after which Genie refuses local `genie.db` lifecycle reads/writes and roadmap writes/syncs/exports. Switching back with `--orchestration-mode standalone` imports no Orca state. There is no fallback database in either direction.
- **Two `genie.db` files, never cross-import** — per-repo `.genie/genie.db` (`genie-db.ts`, task/board/wish) and global `~/.genie/genie.db` (`global-db.ts`, omni queue + inbox) are independent databases with their own schemas and `user_version`. `global-db.ts` shares only `sqlite-open.ts` with the per-repo one — do not reach across for path constants.
- **Codex integration health is native state, not OTel** — the old Genie exporter at `127.0.0.1:14318` has no relay and is removed by an exact-match, backup-first migration. Preserve unrelated OTel settings and `disable_paste_burst`.
Expand Down
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,26 @@ plugin with Orca — that is a separate, Orca-side install. Orca accepts exactly
- a **marketplace source**: a git repo whose *root* holds `orca-marketplace.json`;
- a **plugin source**: a git repo whose *root* holds `orca-plugin.json`, or a local folder containing `orca-plugin.json`.

This repository root carries both, so all three routes work:
**The genie repository root can never be the plugin tree.** Orca's loader rejects any install tree containing a symlink
("unsafe file path or symlink") and caps an install at 2000 files / 50 MB. This repo has `docs -> .docs-vendor/genie`,
runs to roughly 14,000 files in a dev checkout, and keeps its manifest nested at `plugins/genie/orca-plugin.json`, which
a git plugin source never looks at. So the plugin is published as a **tree-only git ref whose root *is*
`plugins/genie`** — symlink-free, ~132 files, ~1.3 MB:

| Route | What to give Orca |
|-------|-------------------|
| Marketplace source | `https://github.com/automagik-dev/genie.git`, ref `main` — lists `automagik.genie` |
| Plugin git source | `https://github.com/automagik-dev/genie.git`, ref `main` |
| Local folder | `~/.genie/plugins/genie` (what `genie update` ships), or this repo's checkout root for contributors |

The root `orca-plugin.json` is the same manifest as the shipped `plugins/genie/orca-plugin.json`, differing only in
`main`, which is re-rooted to `plugins/genie/orca-entrypoint.min.js`. It is source-only: no release tarball contains it,
and `scripts/orca-manifest-parity.test.ts` fails the build if the two manifests, or the marketplace entry, ever drift.
| Marketplace source | `https://github.com/automagik-dev/genie.git`, ref `main` — the index; the plugin itself resolves to ref `orca-plugin` |
| Plugin git source | `https://github.com/automagik-dev/genie.git`, ref `orca-plugin` (stable) or `orca-plugin-dev` (pre-release) |
| Local folder | `~/.genie/plugins/genie` (what `genie install`/`genie update` ships) |

`.github/workflows/orca-plugin-ref.yml` republishes those refs: every push to `main` that touches `plugins/genie`
force-pushes a parentless commit carrying that subtree to `refs/heads/orca-plugin`, and every such push to `dev` does the
same to `refs/heads/orca-plugin-dev`. They are tree-only by design — no history, no shared ancestry with `main`, never
merged back. Orca pins the commit it fetched, so a republish cannot retroactively change an existing install.

The repo root carries only `orca-marketplace.json`, a source-only index no release tarball contains.
`scripts/orca-manifest-parity.test.ts` fails the build if the index drifts from the plugin's identity, or if
`plugins/genie` ever grows a symlink or crosses Orca's file cap.

### Install, update, rollback, and uninstall

Expand Down
2 changes: 1 addition & 1 deletion orca-marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"source": {
"kind": "git",
"url": "https://github.com/automagik-dev/genie.git",
"ref": "main"
"ref": "orca-plugin"
},
"description": "Genie workflows backed by Orca as the sole lifecycle authority.",
"categories": ["workflows"]
Expand Down
27 changes: 0 additions & 27 deletions orca-plugin.json

This file was deleted.

Loading
Loading