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
22 changes: 17 additions & 5 deletions .claude/skills/open-edit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,17 @@ any of that means.

## PREFLIGHT — ALWAYS run at session start
An installed skill contains this file plus `scripts/preflight.sh`; the full runtime may not exist yet.
Resolve **SKILL_ROOT** as the directory containing this `SKILL.md`, and **WORKSPACE** as the user's current
project root (or current directory outside a project). Resolve the supplied video to an absolute path
from WORKSPACE before changing working directories.
Resolve **SKILL_ROOT** as the directory containing this `SKILL.md`. Then resolve **WORKSPACE** by the first
rule that applies:

1. **If SKILL_ROOT sits inside an Open Edit checkout, WORKSPACE is that checkout** — preflight reuses it,
and the run exercises that code.
2. **Otherwise** WORKSPACE is the user's current project root, or the current directory outside a project —
preflight creates its own runtime at `<WORKSPACE>/.open-edit/runtime` and every step below runs there.

Preflight names which of the two it resolved (`reusing the local checkout at …` or `will use a managed clone
at …`); read that line before trusting a run to be testing your changes. Resolve the supplied video to an
absolute path from WORKSPACE before changing working directories.

At the start of EVERY session, before doing Open Edit work, run:
```
Expand All @@ -73,8 +81,12 @@ bash "$SKILL_ROOT/scripts/preflight.sh" --auto-approve --workspace "$WORKSPACE"
approval from the original render request. If the user approves only selected actions, perform only those exact
commands yourself, then rerun `--dry`. If nothing needs approval, do not mention preflight.

Exit **0** means stdout is **OPEN_EDIT_ROOT**; use it for every repo-relative command below. Exit **10** means
approval is required or an approved prerequisite is still missing. Exit **1** is a hard invariant/install error.
Exit **0** means stdout is **OPEN_EDIT_ROOT**; use it for every repo-relative command below. It does **not**
mean setup is finished — a `--dry` run exits 0 while listing the `WOULD APPLY LOCALLY` work that bare
preflight performs itself, and then ends on `not ready yet — run bare preflight …`. Read the final
`preflight:` line, not the exit code: `ready — OPEN_EDIT_ROOT=…` means go. Exit **10** means **only** that
`APPROVAL REQUIRED` was printed and the user must approve every listed action first. Exit **1** is a hard
invariant/install error.
For development, `--repository <URL-or-local-path> --ref <branch>` overrides the initial clone source. A managed
clone records its origin, branch, and commit and rejects conflicting later overrides. A clean checkout is offered
a fast-forward update; any local or untracked changes are reported and left untouched.
Expand Down
24 changes: 20 additions & 4 deletions .claude/skills/open-edit/hooks/install-project-hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,30 @@ async function readJson(path) {
}
}

// Identity is the hook PATH, not the exact command string. Comparing strings meant a config holding
// `bash <path> codex` never matched the generated `bash "<path>" codex`, so every run appended another
// copy — for anyone whose config was written by anything but this exact generator.
const isOurs = (group, hookPath) =>
Array.isArray(group?.hooks) && group.hooks.some(hook => typeof hook?.command === 'string'
&& hook.command.includes(hookPath));

async function merge(path, event) {
const document = await readJson(path);
document.hooks ??= {};
document.hooks.SessionStart ??= [];
const groups = document.hooks.SessionStart;
const alreadyPresent = groups.some(group =>
Array.isArray(group?.hooks) && group.hooks.some(hook => hook?.command === event.command));
if (alreadyPresent) return;
let groups = document.hooks.SessionStart;
const mine = groups.filter(group => isOurs(group, relativeHook));
if (mine.length > 0) {
// Collapse any duplicates a previous version accumulated; never touch anyone else's hooks.
if (mine.length === 1) return;
const first = mine[0];
document.hooks.SessionStart = groups.filter(group => !isOurs(group, relativeHook) || group === first);
groups = document.hooks.SessionStart;
await mkdir(dirname(path), { recursive: true });
await writeFile(path, `${JSON.stringify(document, null, 2)}\n`);
console.error(`preflight: collapsed ${mine.length - 1} duplicate SessionStart hook(s) in ${relative(workspace, path)}`);
return;
}
groups.push(event.group);
await mkdir(dirname(path), { recursive: true });
await writeFile(path, `${JSON.stringify(document, null, 2)}\n`);
Expand Down
22 changes: 21 additions & 1 deletion .claude/skills/open-edit/scripts/preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ discover_runtime() {
}

discover_runtime

# Say which code is about to run, and warn when a local checkout is being bypassed. WORKSPACE is only
# reused when it IS an Open Edit checkout; otherwise everything below runs from a clone of
# $DEFAULT_REF, so pointing --workspace at the wrong directory silently runs different code.
BUNDLED_CHECKOUT="$(resolve_dir "$SCRIPT_ROOT/../../.." 2>/dev/null || true)"
if [ "$ROOT_KIND" = "reused" ]; then
say "reusing the local checkout at $ROOT"
else
say "workspace $WORKSPACE will use a managed clone at $MANAGED_ROOT"
if [ -n "$BUNDLED_CHECKOUT" ] && is_open_edit_checkout "$BUNDLED_CHECKOUT" \
&& [ "$BUNDLED_CHECKOUT" != "$WORKSPACE" ]; then
say "NOTE: this skill lives in the checkout $BUNDLED_CHECKOUT, which will NOT be used."
say " To run that code instead, pass --workspace $BUNDLED_CHECKOUT"
fi
fi

if [ "$ROOT_KIND" = "managed" ]; then
REPOSITORY="$RECORDED_REPOSITORY"; REF="$RECORDED_REF"
else
Expand Down Expand Up @@ -309,8 +325,12 @@ if [ "$ROOT_KIND" = "missing" ]; then
say "runtime is not ready"
elif repo_deps_ready && [ -x "$(engine_path)" ]; then
say "ready — OPEN_EDIT_ROOT=$ROOT"
else
elif [ "$NEEDS_APPROVAL" -ne 0 ]; then
say "local setup is incomplete because an approved prerequisite is missing"
else
# Nothing is awaiting approval: the outstanding work is the WOULD APPLY LOCALLY list above, which
# bare preflight performs itself. Saying "approval" here sent agents looking for a user to ask.
say "not ready yet — run bare preflight (no --dry) to apply the local setup listed above"
fi

if [ "$NEEDS_APPROVAL" -ne 0 ]; then
Expand Down
4 changes: 4 additions & 0 deletions pipeline/scripts/preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
# Compatibility entrypoint. The installable skill owns the canonical preflight implementation.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Only default the workspace when the caller did not give one, otherwise it is passed twice.
for arg in "$@"; do
[ "$arg" = "--workspace" ] && exec bash "$REPO_ROOT/.claude/skills/open-edit/scripts/preflight.sh" "$@"
done
exec bash "$REPO_ROOT/.claude/skills/open-edit/scripts/preflight.sh" --workspace "$REPO_ROOT" "$@"