From 8bf0cb1a915251608576d1f4d62f1ffbf6ece14b Mon Sep 17 00:00:00 2001 From: Kaitian Xie Date: Wed, 5 Aug 2026 01:11:26 +0800 Subject: [PATCH 1/9] feat(omp): add native oh-my-pi harness support - detect omp (~/.omp, project .omp/) so `--to all` reports it as a native plugin install instead of attempting conversion; no --to omp converter, writer, or manifest (per CONCEPTS.md native-plugin-surface) - docs/specs/omp.md target spec verified against omp 17.2.7; README gains omp install/local-dev/limitations coverage - skills: omp built-in `ask` in blocking-question lists, `task` in closed subagent enumerations, and `/skill:` user-facing invocation form across the rendering-rule copies, AGENTS.md, and the authoring guide - ce-compound session history: discover omp sessions under ~/.omp/agent/sessions and named-profile roots (honoring PI_CODING_AGENT_SESSION_DIR/PI_CODING_AGENT_DIR/PI_CONFIG_DIR), detect omp JSONL via its physical title-slot line before the pi-shaped header, and reuse pi extraction for omp platform identity - tests: detection, --to all note, session fixture + discovery/extract coverage, invocation-rendering parity, and a guarded omp-native-install smoke test Closes #1224 --- AGENTS.md | 2 +- README.md | 29 ++- docs/skills/ce-compound.md | 2 +- .../portable-agent-skill-authoring.md | 2 +- docs/specs/omp.md | 84 +++++++++ skills/ce-babysit-pr/SKILL.md | 4 +- skills/ce-babysit-pr/references/watch-loop.md | 2 +- skills/ce-brainstorm/SKILL.md | 2 +- skills/ce-brainstorm/references/handoff.md | 2 +- .../references/synthesis-summary.md | 2 +- .../references/universal-brainstorming.md | 2 +- .../ce-brainstorm/references/visual-probes.md | 4 +- skills/ce-code-review/SKILL.md | 2 +- skills/ce-commit-push-pr/SKILL.md | 4 +- skills/ce-commit/SKILL.md | 2 +- skills/ce-compound-refresh/SKILL.md | 2 +- skills/ce-compound/SKILL.md | 8 +- .../references/agents/session-historian.md | 2 +- .../session-history/discover-sessions.sh | 48 ++++- .../scripts/session-history/extract-errors.py | 16 +- .../session-history/extract-metadata.py | 35 +++- .../session-history/extract-skeleton.py | 22 ++- skills/ce-debug/SKILL.md | 4 +- skills/ce-doc-review/SKILL.md | 2 +- .../ce-doc-review/references/bulk-preview.md | 2 +- .../references/synthesis-and-presentation.md | 2 +- .../ce-doc-review/references/walkthrough.md | 2 +- skills/ce-dogfood/SKILL.md | 2 +- skills/ce-explain/SKILL.md | 4 +- skills/ce-handoff/SKILL.md | 2 +- skills/ce-ideate/SKILL.md | 2 +- .../references/post-ideation-workflow.md | 2 +- .../references/universal-ideation.md | 2 +- skills/ce-optimize/SKILL.md | 2 +- skills/ce-plan/SKILL.md | 4 +- .../ce-plan/references/approach-altitude.md | 2 +- skills/ce-plan/references/plan-handoff.md | 6 +- .../ce-plan/references/universal-planning.md | 6 +- skills/ce-pov/SKILL.md | 2 +- skills/ce-product-pulse/SKILL.md | 2 +- skills/ce-promote/references/spiral-cli.md | 2 +- .../references/full-mode.md | 2 +- skills/ce-setup/SKILL.md | 4 +- skills/ce-simplify-code/SKILL.md | 4 +- skills/ce-strategy/SKILL.md | 2 +- skills/ce-sweep/SKILL.md | 4 +- skills/ce-sweep/references/interview.md | 4 +- skills/ce-test-browser/SKILL.md | 2 +- skills/ce-test-xcode/SKILL.md | 2 +- .../ce-work/references/execution-engines.md | 2 +- .../ce-work/references/shipping-workflow.md | 2 +- skills/ce-work/references/tracker-defer.md | 2 +- skills/ce-worktree/SKILL.md | 2 +- skills/lfg/SKILL.md | 2 +- skills/lfg/references/tracker-defer.md | 2 +- src/commands/convert.ts | 2 +- src/commands/install.ts | 2 +- src/utils/detect-tools.ts | 4 + tests/ce-babysit-pr-contract.test.ts | 2 + tests/cli.test.ts | 4 +- tests/commit-push-pr-contract.test.ts | 3 +- tests/detect-tools.test.ts | 4 +- .../session-history/omp-session.jsonl | 11 ++ tests/omp-native-install.test.ts | 76 ++++++++ tests/session-history-scripts.test.ts | 168 +++++++++++++++++- tests/skill-conventions.test.ts | 3 + tests/skills/ce-explain-routing.test.ts | 4 +- ...-facing-skill-invocation-rendering.test.ts | 17 +- 68 files changed, 571 insertions(+), 95 deletions(-) create mode 100644 docs/specs/omp.md create mode 100644 tests/fixtures/session-history/omp-session.jsonl create mode 100644 tests/omp-native-install.test.ts diff --git a/AGENTS.md b/AGENTS.md index 3d26e9423..937eadc0c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -124,7 +124,7 @@ That field guide is the canonical reasoning layer for outcome-first authoring, m ### User-Facing Skill Invocations -Keep agent-to-agent or skill-to-skill routing semantic: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. When a skill prints or copies a user-runnable invocation, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation. In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Do not apply this rendering rule to built-in commands such as `/goal`. +Keep agent-to-agent or skill-to-skill routing semantic: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. When a skill prints or copies a user-runnable invocation, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:` (for example `/skill:ce-plan`) when the active host is oh-my-pi (`omp`). In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Do not apply this rendering rule to built-in commands such as `/goal`. At runtime, put the smallest self-contained rendering rule immediately before the smallest section that contains all affected user-copy seams. Do not repeat it in every step; repeat it only in a separately loaded reference that independently owns output. diff --git a/README.md b/README.md index 4d52dbc95..64b061002 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Remove the obsolete Compound Engineering Codex tool-map block from my Codex home Re-running the Bun convert/install CLI for Codex also strips the block if it is still present; it no longer inserts it. -**Another editor or CLI?** Kimi Code CLI, Cline, Grok Build CLI, Devin CLI, GitHub Copilot, Factory Droid, Qwen Code, OpenCode, Pi, and Antigravity CLI are all supported — see [More install options](#more-install-options). +**Another editor or CLI?** Kimi Code CLI, Cline, Grok Build CLI, Devin CLI, GitHub Copilot, Factory Droid, Qwen Code, OpenCode, Pi, oh-my-pi (omp), and Antigravity CLI are all supported — see [More install options](#more-install-options). --- @@ -103,7 +103,7 @@ Re-running the Bun convert/install CLI for Codex also strips the block if it is **Each unit of engineering work should make subsequent units easier -- not harder.** -Invocation syntax: this README uses `/skill-name` examples for slash-skill hosts. In Codex, invoke installed skills with `$skill-name` (for example, `$ce-plan` and `$lfg`); `/goal` remains a Codex built-in command. +Invocation syntax: this README uses `/skill-name` examples for slash-skill hosts. In Codex, invoke installed skills with `$skill-name` (for example, `$ce-plan` and `$lfg`); in oh-my-pi (omp), invoke installed skills as `/skill:` (for example, `/skill:ce-plan`); `/goal` remains a Codex built-in command. Traditional development accumulates technical debt. Every feature adds complexity. Every bug fix leaves behind a little more local knowledge that someone has to rediscover later. The codebase gets larger, the context gets harder to hold, and the next change becomes slower. @@ -400,6 +400,23 @@ Recommended companion for richer blocking questions: pi install npm:pi-ask-user ``` +### oh-my-pi (omp) + +oh-my-pi (omp) installs this repository directly because the repo ships native `package.json#pi` package metadata plus the Claude marketplace catalog — omp reads both, so no conversion step is needed: + +```text +omp install https://github.com/EveryInc/compound-engineering-plugin +``` + +Or use the marketplace flow: + +```text +omp plugin marketplace add EveryInc/compound-engineering-plugin +omp plugin install compound-engineering@compound-engineering-plugin +``` + +Run `/reload-plugins` or start a new omp session after installing so the skills load. omp invokes installed skills as `/skill:` (for example `/skill:ce-plan`), not `/skill-name`. For local development from a checkout, use `omp plugin link "$PWD"`. See [`docs/specs/omp.md`](docs/specs/omp.md) for details. + ### Antigravity CLI (`agy`) Google has replaced the consumer Gemini CLI with [Antigravity CLI](https://antigravity.google) (`agy`), which still runs on Gemini models. Install Compound Engineering directly from GitHub — no clone step required: @@ -565,6 +582,12 @@ Restart OpenCode after changing `opencode.json`. pi -e "$PWD" ``` +**oh-my-pi (omp)** + +```bash +omp plugin link "$PWD" +``` + **Antigravity CLI (`agy`)** ```bash @@ -582,7 +605,7 @@ See [`.agy/INSTALL.md`](.agy/INSTALL.md) for remote install and pinning examples ## Limitations -OpenCode and Pi use native package/plugin loading from this repository. The Bun CLI remains for repository development and converter maintenance, not normal installation. +OpenCode, Pi, and oh-my-pi (omp) use native package/plugin loading from this repository. The Bun CLI remains for repository development and converter maintenance, not normal installation. Release versions are owned by release automation. Routine feature PRs should not hand-bump plugin or marketplace manifest versions. diff --git a/docs/skills/ce-compound.md b/docs/skills/ce-compound.md index 031c6fefa..66b2b1ddc 100644 --- a/docs/skills/ce-compound.md +++ b/docs/skills/ce-compound.md @@ -62,7 +62,7 @@ Most teams solve the same problem twice — sometimes with the same person — b ### 1. Two modes — Full vs Lightweight, agent-selected -**Full mode** runs three research subagents in parallel (Context Analyzer / Solution Extractor / Related Docs Finder), plus an automatic session-history probe that searches your prior sessions across Claude Code, Codex, and Cursor for related context. Cross-references existing docs, detects duplicates, runs specialized reviews. +**Full mode** runs three research subagents in parallel (Context Analyzer / Solution Extractor / Related Docs Finder), plus an automatic session-history probe that searches your prior sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp) for related context. Cross-references existing docs, detects duplicates, runs specialized reviews. **Lightweight mode** writes the same solution-doc artifact type in a single pass, with no subagents or cross-referencing. It is lower overhead, but it also skips overlap detection, session-history research, and semantic grounding validation. diff --git a/docs/solutions/skill-design/portable-agent-skill-authoring.md b/docs/solutions/skill-design/portable-agent-skill-authoring.md index 5b46e8b8f..dd4e9bb58 100644 --- a/docs/solutions/skill-design/portable-agent-skill-authoring.md +++ b/docs/solutions/skill-design/portable-agent-skill-authoring.md @@ -158,7 +158,7 @@ Evaluate activation separately from execution with a few positive triggers, adja ### Render user invocations at the output boundary -Keep agent-to-agent routing capability-first: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. Exact command spelling belongs only where the skill prints or copies a user-runnable invocation. At that output seam, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation. In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Built-in commands such as `/goal` are separate capabilities, not evidence that slash-prefixed skill names are callable in Codex. +Keep agent-to-agent routing capability-first: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. Exact command spelling belongs only where the skill prints or copies a user-runnable invocation. At that output seam, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:` (for example `/skill:ce-plan`) when the active host is oh-my-pi (`omp`). In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Built-in commands such as `/goal` are separate capabilities, not evidence that slash-prefixed skill names are callable in Codex. An authoring guide cannot supply runtime behavior to an installed skill. Put the smallest self-contained rendering rule immediately before the smallest section that contains all affected user-copy seams. Do not repeat it in every step; repeat it only in a separately loaded reference that independently owns output. Use a focused contract test when independently edited skills must preserve the same handoff, without duplicating the rationale or a harness matrix. diff --git a/docs/specs/omp.md b/docs/specs/omp.md new file mode 100644 index 000000000..d639b62f5 --- /dev/null +++ b/docs/specs/omp.md @@ -0,0 +1,84 @@ +# oh-my-pi (omp) Spec (Plugins and Skills) + +Last verified: 2026-08-05 against omp 17.2.7 + +## Primary sources + +``` +https://github.com/can1357/oh-my-pi/blob/v17.2.7/README.md +https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/skills.md +https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/marketplace.md +https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/session.md +https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/config-usage.md +https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/environment-variables.md +https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/task-agent-discovery.md +``` + +## Plugin loading + +omp discovers plugins natively. Two committed metadata surfaces in this repository cover it: + +- The `package.json#pi` manifest, with `extensions` (`./.pi/extensions/compound-engineering.ts`) and `skills` (`./skills`) arrays — the same pi package metadata Pi already consumes. +- The Claude marketplace catalog at `.claude-plugin/marketplace.json`, which omp reads as a fallback when `.omp-plugin/marketplace.json` is absent. omp prefers `.omp-plugin/marketplace.json` only when that file is present; Compound Engineering deliberately ships only the Claude catalog for cross-host parity. + +A dry run of `omp install` against this repository confirms both surfaces resolve. No CE converter, writer, or `--to omp` CLI target exists or is planned: per CONCEPTS.md "Native plugin surface", omp support lives in platform metadata, docs, and release validation instead of a new Converter and Writer. + +## Install commands + +Direct install from a path or Git URL (user scope by default): + +```text +omp install https://github.com/EveryInc/compound-engineering-plugin +``` + +Local development link from a checkout: + +```bash +omp plugin link "$PWD" +``` + +Marketplace flow (marketplace name `compound-engineering-plugin`, plugin name `compound-engineering`, both from `.claude-plugin/marketplace.json`): + +```text +omp plugin marketplace add EveryInc/compound-engineering-plugin +omp plugin install compound-engineering@compound-engineering-plugin +``` + +Verify an install plan before applying it: + +```text +omp install --dry-run --json +``` + +`/reload-plugins` refreshes skills and slash commands in a live session; restart omp for tools, hooks, or extension changes to apply. + +## Runtime contracts CE skills rely on + +| Contract | omp behavior | +| --- | --- | +| User skill invocation | `/skill:` — one command per discovered skill; NOT `/skill-name` and NOT `$skill-name` | +| Blocking questions | Built-in `ask` tool | +| Subagent dispatch | Built-in `task` tool, with worktree isolation and schema-checked results | +| Task tracking | Built-in `todo` tool | +| MCP | Native MCP server support | +| Bundled skill files | `skill:///` URL resolution | + +## Instruction files + +omp auto-loads `AGENTS.md`, walking ancestors from the current working directory. This repo's root `AGENTS.md` is already the canonical project instruction file for omp, so no CE action is needed. + +## Session storage + +omp writes sessions as JSONL under a session root resolved in this order: + +1. `$PI_CODING_AGENT_SESSION_DIR` — direct override; files are stored flat in it. +2. `$PI_CODING_AGENT_DIR` — agent-dir override, honored for the default profile only; sessions land in `/sessions/`. +3. `$HOME/${PI_CONFIG_DIR:-.omp}/agent/sessions/` — default location. + +Named profiles (`OMP_PROFILE` or `PI_PROFILE`) relocate the root to `$HOME/${PI_CONFIG_DIR:-.omp}/profiles//agent/sessions/`. + +Inside the session root, per-project buckets use the shape `--`, where `scope` is `home`, `tmp`, or `abs`. Legacy unmigrated buckets also appear as `-` and `----` shapes. Each bucket holds `_.jsonl` files. + +Every session JSONL physically begins with a fixed-width 256-byte `{"type":"title","v":1,...,"pad":"..."}` slot line, followed by a pi-shaped `{"type":"session","version":3,...,"cwd":...}` header. This title-slot-first shape distinguishes omp session files from pi session files, which start directly with the `type:"session"` header. + +Known gap: XDG-relocated roots (`$XDG_DATA_HOME/omp`) are not scanned by CE's session-discovery script. diff --git a/skills/ce-babysit-pr/SKILL.md b/skills/ce-babysit-pr/SKILL.md index e66b0f7f3..4c43a2bac 100644 --- a/skills/ce-babysit-pr/SKILL.md +++ b/skills/ce-babysit-pr/SKILL.md @@ -33,7 +33,7 @@ Once a signal appears on the current head, it starts an **incomplete review life **Mutation envelope (what running this authorizes):** on the active target PR's head the loop fixes failing checks, commits, pushes, replies to and resolves review threads, refreshes a stale PR description, and performs Step 2's bounded routine branch-currency maintenance — autonomously, as its normal operation. When that owned work pushes a target in a **confirmed managed stack**, preserving the manager's linear chain is part of the same authorization: the loop performs the manager-owned upstack maintenance in Step 2. Mutating review/CI work on a *different* PR is semantic scope, so it begins only after the user explicitly requested the whole managed stack or accepted Step 1's one-time stack-wide continuation offer. It **never** merges the PR, approves a gated CI run, changes stack structure, rebases the active target onto trunk/its parent, runs raw `git rebase`/`git push --force`, or rewrites a manual dependency chain. Being asked to babysit the PR is what authorizes this envelope — see Step 2's pre-authorization and the bounded scope it passes to the skills it delegates to. -**Asking the user:** When this skill says "ask the user", use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi. Fall back to presenting the question in chat only when no blocking tool exists or the call errors. Never silently skip the question. +**Asking the user:** When this skill says "ask the user", use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi, `ask` in oh-my-pi (`omp`). Fall back to presenting the question in chat only when no blocking tool exists or the call errors. Never silently skip the question. **Invoking another skill:** When this skill says "invoke `ce-resolve-pr-feedback`" or "invoke `ce-debug`", use the platform's skill-invocation primitive (the `Skill` tool in Claude Code, the equivalent elsewhere). These are separate skills with their own engines — do not reimplement their work inline. They run non-interactively here: anything either one cannot safely decide comes back as a `needs-human` result, which you surface and route around (never block the loop waiting on it). @@ -69,7 +69,7 @@ Once accepted, that one decision authorizes sequential semantic babysitting thro Then establish **how the watch sustains itself** — a skill can't be re-invoked by magic once its turn ends, so *you* set up the loop. **The default is a self-sustaining, in-session watch: you do not do one tick and hand back a resume command.** Read `references/watch-loop.md` for the mechanics, then: -**User-runnable resume syntax.** Whenever this skill prints or copies a resume invocation, default to `/ce-babysit-pr `. Use `$ce-babysit-pr ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable resume syntax.** Whenever this skill prints or copies a resume invocation, default to `/ce-babysit-pr `. Use `$ce-babysit-pr ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-babysit-pr ` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. - **Self-sustaining in-session watch (default).** Start a cheap deterministic background change-detector — `pr-snapshot watch` (Step 2 has the invocation) — which polls the PR with **no agent tokens** and prints a single wake sentinel *only* when there's work to inspect or a stop condition. Then **stay in this session and wait for that sentinel**, using whatever background-and-wake capability your harness exposes. You need exactly one capability: *run a background process and be woken when it emits a line, without ending your turn* — reach for whatever your harness gives you (examples, not a fixed list: Claude Code's background `Bash` + a `Monitor`/wait, Cursor's `Shell` background + `notify_on_output`, Grok's `get_command_or_subagent_output`, `ScheduleWakeup` under `/loop`). On each wake, run **one tick** (Step 2's ordering invariant), persist, then go back to waiting (Step 5). The detector *only* flags that something changed — every tick's judgment (resolve comments, debug CI, decide merge-ready) is agent reasoning plus a sub-skill call, so re-enter *this* agent each wake; **do not collapse the loop into a shell script that greps and acts on its own** (`pr-snapshot watch` loops internally, which makes that substitution tempting — it cannot do the reasoning the tick requires). Staying in-session keeps everything decided in *this* conversation — declined nits, a reviewer judged wrong, your mid-run steering — and spends reasoning only when something actually changed. Continue until a Step 3 stop condition. **Describe the capability and use your own tool for it — do not ask the user to type a slash command; a skill drives tool calls, not keystrokes.** - **Checkpoint (the honest floor).** Only when the harness genuinely exposes **no** background-and-wake capability (some sandboxed GUI apps): run **exactly one tick**, persist, report, and print the exact re-run command. Monitoring is *paused* — say so plainly. Never fake a loop with a foreground `sleep` (Claude Code blocks it) or by "just continuing" (nothing wakes the next tick). diff --git a/skills/ce-babysit-pr/references/watch-loop.md b/skills/ce-babysit-pr/references/watch-loop.md index 7210f101f..90d2f8a94 100644 --- a/skills/ce-babysit-pr/references/watch-loop.md +++ b/skills/ce-babysit-pr/references/watch-loop.md @@ -22,7 +22,7 @@ The needed capability is generic — *run a background process and be woken when | Codex (CLI) | a runtime-owned background exec that re-runs the tick (a detached `nohup` is **reaped** when the tool call ends) | No (session-bound) | | GUI apps / headless / unknown | none reliable → **checkpoint** | — | -**User-runnable resume syntax.** Whenever this reference tells the skill to print or copy a resume invocation, default to `/ce-babysit-pr `. Use `$ce-babysit-pr ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable resume syntax.** Whenever this reference tells the skill to print or copy a resume invocation, default to `/ce-babysit-pr `. Use `$ce-babysit-pr ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-babysit-pr ` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. **Checkpoint (the floor):** when no background-and-wake capability exists, run one tick, persist, report, and print the exact host-rendered re-run invocation — monitoring is *paused*, say so plainly. Because every tick is disk-resumable, checkpoint is the same loop hand-cranked; the in-session watch only automates the crank. Never fake a loop with a foreground `sleep` (blocked on Claude Code, discouraged elsewhere) or a detached `nohup` (reaped/unsupported on several harnesses). diff --git a/skills/ce-brainstorm/SKILL.md b/skills/ce-brainstorm/SKILL.md index 2ca8f58c9..d079495e9 100644 --- a/skills/ce-brainstorm/SKILL.md +++ b/skills/ce-brainstorm/SKILL.md @@ -46,7 +46,7 @@ These rules apply to every brainstorm, including the universal (non-software) fl 1. **Ask one question at a time** - One question per turn, even when sub-questions feel related. Stacking several questions in a single message produces diluted answers; pick the single most useful one and ask it. 2. **Prefer single-select multiple choice** - Use single-select when choosing one direction, one priority, or one next step. 3. **Use multi-select rarely and intentionally** - Use it only for compatible sets such as goals, constraints, non-goals, or success criteria that can all coexist. If prioritization matters, follow up by asking which selected item is primary. -4. **Default to the platform's blocking question tool** - Use `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). These tools include a free-text fallback, so well-chosen options scaffold the answer without confining it. This default holds for opening and elicitation questions too, not only narrowing. Fall back to numbered options in chat only when no blocking tool exists in the harness (including `ToolSearch` returning no match for it) or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. **Exception — visual-probe gate:** on an inherently-visual topic (Phase 0.3 tripwire), the first shape/behavior/state/layout/flow/diagram decision is governed by `references/visual-probes.md`, which takes precedence over this rule. See the Phase 1.3 gate. +4. **Default to the platform's blocking question tool** - Use `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). These tools include a free-text fallback, so well-chosen options scaffold the answer without confining it. This default holds for opening and elicitation questions too, not only narrowing. Fall back to numbered options in chat only when no blocking tool exists in the harness (including `ToolSearch` returning no match for it) or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. **Exception — visual-probe gate:** on an inherently-visual topic (Phase 0.3 tripwire), the first shape/behavior/state/layout/flow/diagram decision is governed by `references/visual-probes.md`, which takes precedence over this rule. See the Phase 1.3 gate. 5. **Use an open-ended question only when the question is genuinely open** - Drop the blocking tool when the answer is inherently narrative, when presented options would steer a diagnostic or introspective answer, or when you cannot write 3-4 genuinely distinct, plausibly-correct options without padding. The test: if you'd be straining to fill the option slots, the question is open — ask it open-ended. Rule 1 still applies: one question per turn. 6. **Open-ended questions earn their place only when they're specific enough to elicit a substantive answer** - Apply Rule 5 silently: just ask the question, never narrate the form choice. The question must give the user something concrete to anchor on. Good: *"What's the most concrete thing someone's already done about this — paid for it, built a workaround, quit a tool over it?"* — it names what counts as an answer. Too thin: *"What's your take?"* — nothing to bite into, and framings that imply a short answer ("briefly", yes/no) waste the open question the same way. diff --git a/skills/ce-brainstorm/references/handoff.md b/skills/ce-brainstorm/references/handoff.md index 8088e9351..cb59548c4 100644 --- a/skills/ce-brainstorm/references/handoff.md +++ b/skills/ce-brainstorm/references/handoff.md @@ -15,7 +15,7 @@ autonomously with lfg`, and the lfg option is also hidden for non-software brainstorms (`execution` other than `code`). Count the visible options for the current state and choose the rendering mode accordingly: -- **Visible count fits the current platform's option cap:** use the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). Claude Code `AskUserQuestion` supports up to 4 explicit options, and Codex `request_user_input` supports only 2-3 explicit options. +- **Visible count fits the current platform's option cap:** use the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). Claude Code `AskUserQuestion` supports up to 4 explicit options, oh-my-pi (`omp`) `ask` behaves like Claude Code's (structured options plus free text), and Codex `request_user_input` supports only 2-3 explicit options. - **Visible count exceeds the current platform's option cap:** render as a numbered list in chat. This is the narrow option-overflow fallback; trimming would hide legitimate choices (plan, ship, review, Proof/browser, refine are all distinct destinations). Include a hint that free-form input is accepted ("Pick a number or describe what you want.") so the numbered list retains the blocking tool's open-endedness. Never silently skip the question. diff --git a/skills/ce-brainstorm/references/synthesis-summary.md b/skills/ce-brainstorm/references/synthesis-summary.md index b0562ca27..b33de81cb 100644 --- a/skills/ce-brainstorm/references/synthesis-summary.md +++ b/skills/ce-brainstorm/references/synthesis-summary.md @@ -240,7 +240,7 @@ Track which scoping synthesis items the user touched per round. The soft-cut blo **Identity across rounds is by decision dimension, not surface wording or section.** A revision may cause stage 2 to re-derive — the same underlying decision can come back rephrased, merged with another bullet, or moved to a different section (e.g., what was a Trade-off in round one becomes a Call-out in round two after the user pushed back). "Same item" means the same underlying decision regardless of which section currently holds it. When a re-cut collapses multiple prior bullets into one, the new combined bullet inherits the "touched" status of any of its constituents — soft-cut fires if any underlying decision was already revised once before. -When the soft-cut fires, use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi) with two options: +When the soft-cut fires, use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi, `ask` in oh-my-pi (`omp`)) with two options: - `Proceed and write the requirements-only plan` - `Hold off — keep discussing before the doc` diff --git a/skills/ce-brainstorm/references/universal-brainstorming.md b/skills/ce-brainstorm/references/universal-brainstorming.md index 05cdb870f..cddaed78a 100644 --- a/skills/ce-brainstorm/references/universal-brainstorming.md +++ b/skills/ce-brainstorm/references/universal-brainstorming.md @@ -63,7 +63,7 @@ When the conversation has enough material to narrow — reflect back what you've **Always synthesize a summary in the chat.** Before offering any next steps, reflect back what emerged: key decisions, the direction chosen, open threads, and any assumptions made. This is the primary output of the brainstorm — the user should be able to read the summary and know what they landed on. -**Then offer next steps** using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +**Then offer next steps** using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. **Question:** "Brainstorm wrapped. What would you like to do next?" diff --git a/skills/ce-brainstorm/references/visual-probes.md b/skills/ce-brainstorm/references/visual-probes.md index db335a01f..5c02aa45d 100644 --- a/skills/ce-brainstorm/references/visual-probes.md +++ b/skills/ce-brainstorm/references/visual-probes.md @@ -18,7 +18,7 @@ Do not use a visual probe for product goals, scope boundaries, success criteria, When the Phase 0.3 tripwire flagged an inherently-visual topic, the offer must fire before the **first** decision about shape, behavior, state, layout, flow, or a diagram is raised in *any* form — plain chat or a blocking question. -**Timing is state-based, not memory-based.** Anchor the check to the decision you are about to raise, not to a "pending gate" remembered since Phase 0.3: offer unless this specific decision has already been through the offer (the user already chose text or visual for it). This gate takes precedence over the default blocking-question path — do not raise the shape decision as an `AskUserQuestion`/`request_user_input` menu, or as a plain-chat shape question, until the user has declined visual (or visual feedback has returned to chat). +**Timing is state-based, not memory-based.** Anchor the check to the decision you are about to raise, not to a "pending gate" remembered since Phase 0.3: offer unless this specific decision has already been through the offer (the user already chose text or visual for it). This gate takes precedence over the default blocking-question path — do not raise the shape decision as an `AskUserQuestion`/`request_user_input`/`ask` menu, or as a plain-chat shape question, until the user has declined visual (or visual feedback has returned to chat). **An ASCII preview or text mockup embedded inside the question's choices does NOT satisfy the offer** — that shortcut is exactly what this gate exists to stop. The offer is its own prior question with two options (sketch vs describe); only after the user chooses does the shape decision proceed. @@ -26,7 +26,7 @@ When the Phase 0.3 tripwire flagged an inherently-visual topic, the offer must f Ask once at the decision point. Do not enable a session-wide mode. -Use the platform's blocking question tool for the opt-in when available (`AskUserQuestion`, `request_user_input`, `ask_user`, or equivalent). Use a plain chat question only when no interactive question tool exists or the tool errors. The opt-in should have two clear options: +Use the platform's blocking question tool for the opt-in when available (`AskUserQuestion`, `request_user_input`, `ask_user`, `ask`, or equivalent). Use a plain chat question only when no interactive question tool exists or the tool errors. The opt-in should have two clear options: - Visual sketch — create rough options in a local browser - Text description — keep the decision in chat diff --git a/skills/ce-code-review/SKILL.md b/skills/ce-code-review/SKILL.md index e6f4895f4..e6828cbce 100644 --- a/skills/ce-code-review/SKILL.md +++ b/skills/ce-code-review/SKILL.md @@ -96,7 +96,7 @@ Emit a one-line failure reason. In `mode:agent`, return JSON: `{"status":"failed Same review pipeline for default and `mode:agent`: - **Report-only by default; never push.** A bare `ce-code-review` invocation produces findings and does not apply them. Local mutation requires `apply:local` or an explicit user request in the invoking prompt to apply/fix this review's findings. `mode:agent` never mutates the tree, even when nested inside a workflow that later applies findings. Never push, open PRs, or file tickets in any mode. -- **No blocking prompts.** Never use `AskUserQuestion`, `request_user_input`, `ask_user`, or other blocking question tools. Infer intent, plan, and scope from explicit tokens, git state, PR metadata, and conversation. Note uncertainty in Coverage or the verdict — do not stop to ask. +- **No blocking prompts.** Never use `AskUserQuestion`, `request_user_input`, `ask_user`, `ask`, or other blocking question tools. Infer intent, plan, and scope from explicit tokens, git state, PR metadata, and conversation. Note uncertainty in Coverage or the verdict — do not stop to ask. - **Explicit mutations only.** Never run `gh pr checkout`, `git checkout`, `git switch`, or similar branch-switch commands. Passing a PR number, URL, or branch name selects **review scope**, not permission to mutate the working tree. To review local uncommitted work on a feature branch, check out that branch yourself (or stay on it) and pass `base:` or no target. - **Smart defaults.** Untracked files: review tracked changes only and list excluded paths in Coverage. Plan: use `plan:` when passed; otherwise discover conservatively from PR body or branch keywords. Weak advisory P2/P3 from testing/maintainability alone: demote to `testing_gaps` / `residual_risks` per Stage 5. - **Report outcomes, not machinery.** What you show the user is about the review: what's being examined (the PR/branch), which coverage is included and the one-line reason for each conditional lens, the independent cross-model pass and which model runs it, and the findings. Keep the skill's internals out of user-facing text — model-tier assignments, raw scope-mode codenames (`local-aligned`/`pr-remote`), staging the diff to disk, loading persona files, parallel-dispatch bookkeeping, and step-by-step narration of your own setup. Name what the user would recognize (a PR number, a reviewer's concern, a peer model), not the plumbing. This governs *what* you surface and suppress; it does not script the wording — use your own voice. diff --git a/skills/ce-commit-push-pr/SKILL.md b/skills/ce-commit-push-pr/SKILL.md index 973db25e8..9be3193ec 100644 --- a/skills/ce-commit-push-pr/SKILL.md +++ b/skills/ce-commit-push-pr/SKILL.md @@ -6,7 +6,7 @@ argument-hint: "[PR ref] [mode:pipeline] [archive:on|off] [branding:on|off] [bab # Git Commit, Push, and PR -**Asking the user:** When this skill says "ask the user", use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to presenting the question in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +**Asking the user:** When this skill says "ask the user", use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to presenting the question in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. ## Mode @@ -138,7 +138,7 @@ Then continue with the rest of the reference (Steps A through E, including the S If the doc write, commit, or push fails, warn and continue to PR creation without the link — never strand the flow between commit and PR. -**User-runnable invocation rendering.** For the output handoffs below, default to `/ce-explain `. Use `$ce-explain ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** For the output handoffs below, default to `/ce-explain `. Use `$ce-explain ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-explain ` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. **Concept trailer** — when a body applied by this run contains a `## New concepts` section, print one line after the PR URL in every mode: `New concepts: [, ]`. In interactive full-workflow runs follow it with one line per taught concept telling the user to invoke `ce-explain ` using the rendering rule above. No trailer when this run applied no body — including a rewrite that was declined or pipeline-defaulted to no — or no PR exists. diff --git a/skills/ce-commit/SKILL.md b/skills/ce-commit/SKILL.md index 052a73104..7d87dbbd8 100644 --- a/skills/ce-commit/SKILL.md +++ b/skills/ce-commit/SKILL.md @@ -39,7 +39,7 @@ If both fail, fall back to `main`. If `git status` shows a clean working tree (no staged, modified, or untracked files), report that there is nothing to commit and stop. -If the current branch is empty, the repository is in detached HEAD state. Explain that a branch is required before committing if the user wants this work attached to a branch. Ask whether to create a feature branch now. Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to presenting options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +If the current branch is empty, the repository is in detached HEAD state. Explain that a branch is required before committing if the user wants this work attached to a branch. Ask whether to create a feature branch now. Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to presenting options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. - If the user chooses to create a branch, derive the name from the change content, create it with `git checkout -b `, then run `git branch --show-current` again and use that result as the current branch name for the rest of the workflow. - If the user declines, continue with the detached HEAD commit. diff --git a/skills/ce-compound-refresh/SKILL.md b/skills/ce-compound-refresh/SKILL.md index be78ad9af..3885dc426 100644 --- a/skills/ce-compound-refresh/SKILL.md +++ b/skills/ce-compound-refresh/SKILL.md @@ -38,7 +38,7 @@ If the arguments contain `mode:non-interactive` (or its deprecated alias `mode:h ## Blocking questions -Wherever this skill asks the user something, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time, prefer multiple choice, lead with the recommended option and a one-sentence rationale. +Wherever this skill asks the user something, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time, prefer multiple choice, lead with the recommended option and a one-sentence rationale. ## CONCEPTS.md bootstrap requests diff --git a/skills/ce-compound/SKILL.md b/skills/ce-compound/SKILL.md index 1bbb260e6..80bf5bead 100644 --- a/skills/ce-compound/SKILL.md +++ b/skills/ce-compound/SKILL.md @@ -290,7 +290,7 @@ Pass `{run_id}` and the resolved absolute `{run_dir}` into every Phase 1 subagen else echo "Session history bundled scripts were not found in this skill's directory; skipping the session-history probe for this run."; fi ``` - Pi sessions are included when present under `~/.pi/agent/sessions/`; they carry `cwd` like Codex but no git branch. If `_meta.files_processed` is `0`, return `no relevant prior sessions`. If the first pass finds no relevant branch matches, or if processing Codex or Pi sessions, derive 2-4 keywords from the topic and re-run metadata extraction with `--keyword K1,K2,...`. Keep at most 5 sessions across Claude Code, Codex, Cursor, and Pi, ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session. + Pi sessions are included when present under `~/.pi/agent/sessions/`, and oh-my-pi (`omp`) sessions under `~/.omp/agent/sessions/` (named profiles: `~/.omp/profiles//agent/sessions/`); they carry `cwd` like Codex but no git branch. If `_meta.files_processed` is `0`, return `no relevant prior sessions`. If the first pass finds no relevant branch matches, or if processing Codex or Pi sessions, derive 2-4 keywords from the topic and re-run metadata extraction with `--keyword K1,K2,...`. Keep at most 5 sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (`omp`), ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session. **Escalation gate.** The discovery+metadata pass above is the cheap probe and always runs in Full mode. Escalate to the extraction and synthesis stages below **only** when at least one retained candidate clears the relevance bar: a current-branch match, or ≥2 topic-keyword matches. If no candidate clears the bar (including the `_meta.files_processed` is `0` case), stop here, record `no relevant prior sessions` as the session-history input, and skip extraction and synthesis. This gate is what keeps the always-on probe cheap — the expensive synthesis is paid for only when a prior session is genuinely relevant. @@ -441,7 +441,7 @@ Use these rules: - If context is already tight or you are in lightweight mode, do not expand into a broad refresh automatically; instead recommend `ce-compound-refresh` as the next step with a scope hint - **In non-interactive mode**, never invoke `ce-compound-refresh` and never ask the user. Surface the recommended scope hint in the terminal report's "Refresh recommendation" line and let the caller decide -**User-runnable refresh rendering.** When recommending rather than directly invoking `ce-compound-refresh`, default to `/ce-compound-refresh `; use `$ce-compound-refresh ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. Agent-to-agent invocation remains semantic. +**User-runnable refresh rendering.** When recommending rather than directly invoking `ce-compound-refresh`, default to `/ce-compound-refresh `; use `$ce-compound-refresh ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-compound-refresh ` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. Agent-to-agent invocation remains semantic. When invoking or recommending `ce-compound-refresh`, be explicit about the argument to pass. Prefer the narrowest useful scope: @@ -497,7 +497,7 @@ After the learning is written and the refresh decision is made, check whether th `/solutions/` — documented solutions to past problems (bugs, best practices, workflow patterns), organized by category with YAML frontmatter (`module`, `tags`, `problem_type`). Relevant when implementing or debugging in documented areas. ``` - c. In full interactive mode, explain to the user why this matters — agents working in this repo (including fresh sessions, other tools, or collaborators without the plugin) won't know to check `/solutions/` unless the instruction file surfaces it. Show the proposed change and where it would go, then use the platform's blocking question tool to get consent before making the edit: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to presenting the proposal in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. In lightweight mode (interactive or non-interactive), output a one-liner note and move on. In full non-interactive mode, **do not edit instruction files** — surface the gap in the terminal report as `Instruction-file edit: gap noted, not applied` (headless scope is documentation capture, not project-config edits; a human-invoked interactive run applies the edit with consent) + c. In full interactive mode, explain to the user why this matters — agents working in this repo (including fresh sessions, other tools, or collaborators without the plugin) won't know to check `/solutions/` unless the instruction file surfaces it. Show the proposed change and where it would go, then use the platform's blocking question tool to get consent before making the edit: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to presenting the proposal in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. In lightweight mode (interactive or non-interactive), output a one-liner note and move on. In full non-interactive mode, **do not edit instruction files** — surface the gap in the terminal report as `Instruction-file edit: gap noted, not applied` (headless scope is documentation capture, not project-config edits; a human-invoked interactive run applies the edit with consent) 5. **If `CONCEPTS.md` exists at repo root, run a parallel discoverability check for it.** Assess whether the instruction file would lead an agent to discover the project's shared domain vocabulary. Use the same workflow as the `/solutions/` check above: same target file, same edit-placement judgment, same consent-then-edit interaction shape per mode. A line in an existing section is almost always better than a new headed section. Example calibration when nothing else fits: @@ -554,7 +554,7 @@ The orchestrator (main conversation) performs ALL of the following in one sequen 7. **Frontmatter parser-safety check**: validate the written doc exactly as in Phase 2 step 8, using the same bundled-script existence guard and manual fallback checklist. Fix any violation and repeat the check; do not report success until the written frontmatter is parser-safe. 8. **Skip specialized agent reviews** (Phase 3) and the semantic grounding validator (Phase 2.45 step 2) to conserve context -**User-runnable retry rendering.** In the lightweight completion output below, default to `/ce-compound`; use `$ce-compound` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable retry rendering.** In the lightweight completion output below, default to `/ce-compound`; use `$ce-compound` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-compound` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. **Lightweight completion output:** In non-interactive Lightweight, do not emit this interactive block; use the depth-specific report under `Success Output` > `Non-interactive mode` instead. In interactive Lightweight, emit: ``` diff --git a/skills/ce-compound/references/agents/session-historian.md b/skills/ce-compound/references/agents/session-historian.md index 2b8b961c8..a79f7c59a 100644 --- a/skills/ce-compound/references/agents/session-historian.md +++ b/skills/ce-compound/references/agents/session-historian.md @@ -28,7 +28,7 @@ If the dispatch prompt arrives without a `sessions` array, or with an empty arra These rules apply at all times during synthesis. -- **Read only the paths the orchestrator gave you.** Use the platform's native file-read tool (e.g., `Read` in Claude Code) on each `path`. Do not read source session files directly under `~/.claude/projects/`, `~/.codex/sessions/`, `~/.cursor/projects/`, or `~/.pi/agent/sessions/` — those are MB-scale and would blow the context window. The orchestrator already extracted what's relevant. +- **Read only the paths the orchestrator gave you.** Use the platform's native file-read tool (e.g., `Read` in Claude Code) on each `path`. Do not read source session files directly under `~/.claude/projects/`, `~/.codex/sessions/`, `~/.cursor/projects/`, `~/.pi/agent/sessions/`, or `~/.omp/agent/sessions/` — those are MB-scale and would blow the context window. The orchestrator already extracted what's relevant. - **Never invoke the Skill tool.** This agent runs in subagent context where Skill calls deadlock. The orchestrator has already done all extraction; you only synthesize. - **Never extract or reproduce tool call inputs/outputs verbatim.** Summarize what was attempted and what happened. - **Never include thinking or reasoning block content.** Claude Code thinking blocks are internal reasoning; Codex reasoning blocks are encrypted. Neither is actionable. The skeleton extractor already strips these — do not surface them if any survived. diff --git a/skills/ce-compound/scripts/session-history/discover-sessions.sh b/skills/ce-compound/scripts/session-history/discover-sessions.sh index 46f426f41..61944e5de 100755 --- a/skills/ce-compound/scripts/session-history/discover-sessions.sh +++ b/skills/ce-compound/scripts/session-history/discover-sessions.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Discover session files across Claude Code, Codex, Cursor, and Pi. +# Discover session files across Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp). # -# Usage: discover-sessions.sh [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi] +# Usage: discover-sessions.sh [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi|omp] # # Outputs one file path per line. Safe in both bash and zsh (all globs guarded). # Pass output to extract-metadata.py: @@ -15,8 +15,8 @@ set -euo pipefail -REPO_NAME="${1:?Usage: discover-sessions.sh [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi]}" -DAYS="${2:?Usage: discover-sessions.sh [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi]}" +REPO_NAME="${1:?Usage: discover-sessions.sh [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi|omp]}" +DAYS="${2:?Usage: discover-sessions.sh [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi|omp]}" PLATFORM="all" REPO_CWD="" @@ -111,17 +111,57 @@ discover_pi() { done } +# --- oh-my-pi (omp) --- +discover_omp() { + local config_dir="${PI_CONFIG_DIR:-.omp}" + + # omp's explicit session-dir override stores session files directly in the + # supplied directory (flat), mirroring Pi's override branch. The cwd filter + # later reads each header and keeps only sessions for the active repo. + if [ -n "${PI_CODING_AGENT_SESSION_DIR:-}" ]; then + local base="$PI_CODING_AGENT_SESSION_DIR" + [ -d "$base" ] || return 0 + find "$base" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + if [ -z "$REPO_CWD" ]; then + for dir in "$base"/*"$REPO_NAME"*/; do + [ -d "$dir" ] || continue + find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + done + fi + return 0 + fi + + # omp bucket names embed only the repo basename plus a sha256 of the + # canonical cwd (e.g. home-my-repo-<64hex>), never the full cwd, so no + # exact encoded-CWD probe like Pi's is possible. Scan basename-matching + # buckets in the default-profile sessions root and in every named-profile + # root; exact repo attribution comes from the downstream header `cwd` + # filter (extract-metadata.py --cwd-filter reads the type:"session" + # header). + local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/$config_dir/agent}" + local root + for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do + [ -d "$root" ] || continue + for dir in "$root"/*"$REPO_NAME"*/; do + [ -d "$dir" ] || continue + find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + done + done +} + # --- Dispatch --- case "$PLATFORM" in claude) discover_claude ;; codex) discover_codex ;; cursor) discover_cursor ;; pi) discover_pi ;; + omp) discover_omp ;; all) discover_claude discover_codex discover_cursor discover_pi + discover_omp ;; *) echo "Unknown platform: $PLATFORM" >&2 diff --git a/skills/ce-compound/scripts/session-history/extract-errors.py b/skills/ce-compound/scripts/session-history/extract-errors.py index 7f0a8489f..e2599cb36 100644 --- a/skills/ce-compound/scripts/session-history/extract-errors.py +++ b/skills/ce-compound/scripts/session-history/extract-errors.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Extract error signals from a Claude Code, Codex, Cursor, or Pi JSONL session file. +"""Extract error signals from a Claude Code, Codex, Cursor, Pi, or oh-my-pi (omp) JSONL session file. Usage: cat | python3 extract-errors.py @@ -163,6 +163,9 @@ def _pi_context_objects(objects): def handle_pi(obj): + # omp's physical type:'title' slot line returns here like any non-message + # entry. type:'title_change' entries are a different pi entry type — they + # return the same way; do not conflate the two when filtering. if obj.get("type") != "message": return msg = obj.get("message", {}) @@ -203,6 +206,9 @@ def handle_pi(obj): # Auto-detect platform from first few lines, then process all detected = None buffer = [] +# omp files physically begin with a fixed-width type:'title' slot line before +# the pi-shaped type:'session' header; bare pi files start with the header. +seen_title_slot = False for line in sys.stdin: line = line.strip() @@ -215,7 +221,9 @@ def handle_pi(obj): try: obj = json.loads(line) if obj.get("type") == "session" and "cwd" in obj: - detected = "pi" + detected = "omp" if seen_title_slot else "pi" + elif obj.get("type") == "title" and len(buffer) == 1: + seen_title_slot = True elif obj.get("type") in ("user", "assistant"): detected = "claude" elif obj.get("type") in ("session_meta", "turn_context", "response_item", "event_msg"): @@ -229,7 +237,7 @@ def handle_pi(obj): def handle_noop(obj): pass -handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_noop, "pi": handle_pi} +handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_noop, "pi": handle_pi, "omp": handle_pi} handler = handlers.get(detected, handle_noop) objects = [] @@ -239,7 +247,7 @@ def handle_noop(obj): except (json.JSONDecodeError, KeyError): stats["parse_errors"] += 1 -if detected == "pi": +if detected in ("pi", "omp"): objects = _pi_context_objects(objects) for obj in objects: diff --git a/skills/ce-compound/scripts/session-history/extract-metadata.py b/skills/ce-compound/scripts/session-history/extract-metadata.py index e24c8f447..192c18ae1 100644 --- a/skills/ce-compound/scripts/session-history/extract-metadata.py +++ b/skills/ce-compound/scripts/session-history/extract-metadata.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Extract session metadata from Claude Code, Codex, Cursor, and Pi JSONL files. +"""Extract session metadata from Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp) JSONL files. Batch mode (preferred — one invocation for all files): python3 extract-metadata.py /path/to/dir/*.jsonl @@ -57,6 +57,36 @@ def try_codex(lines): return meta if meta else None +def try_omp(lines): + """oh-my-pi (omp) sessions: a fixed-width type='title' slot line physically + first, then a pi-shaped type='session' header with cwd. Checked before Pi: + a bare pi file has no title slot and must still detect as pi.""" + seen_first = False + for line in lines: + stripped = line.strip() + if not stripped: + continue + try: + obj = json.loads(stripped) + except (json.JSONDecodeError, KeyError): + if not seen_first: + return None + continue + if not seen_first: + seen_first = True + if obj.get("type") != "title": + return None + continue + if obj.get("type") == "session" and "cwd" in obj: + return { + "platform": "omp", + "cwd": obj.get("cwd", ""), + "session": obj.get("id", ""), + "ts": obj.get("timestamp", ""), + } + return None + + def try_pi(lines): """Pi sessions: type='session' header with cwd, followed by message entries.""" for line in lines: @@ -88,7 +118,7 @@ def try_cursor(lines): def extract_from_lines(lines): - return try_claude(lines) or try_codex(lines) or try_pi(lines) or try_cursor(lines) + return try_claude(lines) or try_codex(lines) or try_omp(lines) or try_pi(lines) or try_cursor(lines) TAIL_BYTES = 16384 # Read last 16KB to find final timestamp past trailing metadata @@ -221,6 +251,7 @@ def _extract_user_assistant_text(filepath): except (json.JSONDecodeError, ValueError): continue + # omp files share the pi-shaped session header, so this covers both. is_pi = any( obj.get("type") == "session" and "cwd" in obj for obj in objects ) diff --git a/skills/ce-compound/scripts/session-history/extract-skeleton.py b/skills/ce-compound/scripts/session-history/extract-skeleton.py index 69353d97d..386a3d0ed 100644 --- a/skills/ce-compound/scripts/session-history/extract-skeleton.py +++ b/skills/ce-compound/scripts/session-history/extract-skeleton.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -"""Extract the conversation skeleton from a Claude Code, Codex, Cursor, or Pi JSONL session file. +"""Extract the conversation skeleton from a Claude Code, Codex, Cursor, Pi, or oh-my-pi (omp) JSONL session file. Usage: cat | python3 extract-skeleton.py cat | python3 extract-skeleton.py --output PATH -Auto-detects platform (Claude Code, Codex, Cursor, or Pi) from the JSONL structure. +Auto-detects platform (Claude Code, Codex, Cursor, Pi, or oh-my-pi (omp)) from the JSONL structure. Extracts: - User messages (text only, no tool results) - Assistant text (no thinking/reasoning blocks) @@ -342,7 +342,12 @@ def _pi_context_objects(objects): def handle_pi(obj): - """Pi sessions: type='message' with message.role and content blocks.""" + """Pi/omp sessions: type='message' with message.role and content blocks. + + omp's physical type:'title' slot line lands here too and returns below + like any non-message entry. type:'title_change' entries are a different + pi entry type — they also carry no message payload and return the same + way; do not conflate the two when filtering.""" entry_type = obj.get("type") ts = obj.get("timestamp", "")[:19] @@ -520,6 +525,9 @@ def handle_cursor(obj): # Auto-detect platform from first few lines, then process all detected = None buffer = [] +# omp files physically begin with a fixed-width type:'title' slot line before +# the pi-shaped type:'session' header; bare pi files start with the header. +seen_title_slot = False for line in sys.stdin: line = line.strip() @@ -537,7 +545,9 @@ def handle_cursor(obj): try: obj = json.loads(line) if obj.get("type") == "session" and "cwd" in obj: - detected = "pi" + detected = "omp" if seen_title_slot else "pi" + elif obj.get("type") == "title" and len(buffer) == 1: + seen_title_slot = True elif obj.get("type") in ("user", "assistant"): detected = "claude" elif obj.get("type") in ("session_meta", "turn_context", "response_item", "event_msg"): @@ -547,7 +557,7 @@ def handle_cursor(obj): except (json.JSONDecodeError, KeyError): pass -handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_cursor, "pi": handle_pi} +handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_cursor, "pi": handle_pi, "omp": handle_pi} handler = handlers.get(detected, handle_codex) objects = [] @@ -557,7 +567,7 @@ def handle_cursor(obj): except (json.JSONDecodeError, KeyError): stats["parse_errors"] += 1 -if detected == "pi": +if detected in ("pi", "omp"): objects = _pi_context_objects(objects) for obj in objects: diff --git a/skills/ce-debug/SKILL.md b/skills/ce-debug/SKILL.md index ba7074cd4..94161c12c 100644 --- a/skills/ce-debug/SKILL.md +++ b/skills/ce-debug/SKILL.md @@ -197,7 +197,7 @@ Then offer next steps. **`mode:pipeline`:** do not ask. The caller invoked this skill to fix, so proceed to Phase 3 and apply a **convergent** fix; a **divergent** fix (one that would reverse a deliberate contract/behavior/product decision — including a "failing" test that asserts intended behavior) is deferred, not applied, per `references/pipeline-mode.md`. Never route to `ce-brainstorm` in pipeline mode — a design problem becomes a `needs-human` residual. -Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). In Claude Code, call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded — a pending schema load is not a reason to fall back. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes). Never silently skip the question. +Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). In Claude Code, call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded — a pending schema load is not a reason to fall back. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes). Never silently skip the question. Options to offer: @@ -319,7 +319,7 @@ Run this tail after Phase 3 ran and before the branch-based commit/PR handoff. T #### Pre-existing branch (skill did not create it): ask the user -Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). In Claude Code, call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded — a pending schema load is not a reason to fall back. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors. Never end the phase without collecting a response. +Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). In Claude Code, call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded — a pending schema load is not a reason to fall back. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors. Never end the phase without collecting a response. Options: diff --git a/skills/ce-doc-review/SKILL.md b/skills/ce-doc-review/SKILL.md index 9e3e86d3d..75d6e9833 100644 --- a/skills/ce-doc-review/SKILL.md +++ b/skills/ce-doc-review/SKILL.md @@ -24,7 +24,7 @@ fi ## Interactive mode rules -- **Pre-load the platform question tool before any question fires.** In Claude Code, `AskUserQuestion` is a deferred tool — its schema is not available at session start. At the start of Interactive-mode work (before the routing question, per-finding walk-through questions, bulk-preview Proceed/Cancel, and Phase 5 terminal question), call `ToolSearch` with query `select:AskUserQuestion` to load the schema. Load it once, eagerly, at the top of the Interactive flow — do not wait for the first question site. On Codex, Gemini, and Pi this preload is not required. +- **Pre-load the platform question tool before any question fires.** In Claude Code, `AskUserQuestion` is a deferred tool — its schema is not available at session start. At the start of Interactive-mode work (before the routing question, per-finding walk-through questions, bulk-preview Proceed/Cancel, and Phase 5 terminal question), call `ToolSearch` with query `select:AskUserQuestion` to load the schema. Load it once, eagerly, at the top of the Interactive flow — do not wait for the first question site. On Codex, Gemini, Pi, and oh-my-pi (`omp`) this preload is not required. - **The numbered-list fallback applies only when the harness genuinely lacks a blocking question tool** — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes where `request_user_input` is unavailable). A pending schema load is not a fallback trigger; call `ToolSearch` first per the pre-load rule. In genuine-fallback cases, present options as a numbered list and wait for the user's reply — never silently skip the question. Rendering a question as narrative text because the tool feels inconvenient, because the model is in report-formatting mode, or because the instruction was buried in a long skill is a bug. A question that calls for a user decision must either fire the tool or fall back loudly. ## Phase 0: Detect Mode diff --git a/skills/ce-doc-review/references/bulk-preview.md b/skills/ce-doc-review/references/bulk-preview.md index 1ed39affb..7584b4d5f 100644 --- a/skills/ce-doc-review/references/bulk-preview.md +++ b/skills/ce-doc-review/references/bulk-preview.md @@ -99,7 +99,7 @@ Treat the preview and its confirmation as two ordered user-facing events: If the preview event has not occurred, do not invoke the blocking-question capability. If the harness exposes no such capability or the call errors, preserve the same interaction as visible chat: put the numbered `Proceed` / `Cancel` options immediately below the visible preview and wait for the user's reply. Never omit the preview or continue silently. -**Non-exhaustive adapters:** `AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), and `ask_user` in Pi with the `pi-ask-user` extension. In Claude Code, `AskUserQuestion` should already be loaded from the Interactive-mode pre-load step; if it is not, call `ToolSearch` with query `select:AskUserQuestion` now. A pending schema load is not a fallback trigger. +**Non-exhaustive adapters:** `AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), and `ask_user` in Pi with the `pi-ask-user` extension, `ask` in oh-my-pi (`omp`). In Claude Code, `AskUserQuestion` should already be loaded from the Interactive-mode pre-load step; if it is not, call `ToolSearch` with query `select:AskUserQuestion` now. A pending schema load is not a fallback trigger. Stem (adapted to the path): diff --git a/skills/ce-doc-review/references/synthesis-and-presentation.md b/skills/ce-doc-review/references/synthesis-and-presentation.md index cc0b8f003..81c8e5795 100644 --- a/skills/ce-doc-review/references/synthesis-and-presentation.md +++ b/skills/ce-doc-review/references/synthesis-and-presentation.md @@ -399,7 +399,7 @@ During synthesis, discard any finding that recommends deleting or removing a CE **Non-interactive mode:** Return "Review complete" immediately. Do not ask questions. The caller receives the text envelope from Phase 4 and handles any remaining findings. -**Interactive mode:** fire the terminal question using the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). In Claude Code the tool should already be loaded from the Interactive-mode pre-load step in `SKILL.md` — if it isn't, call `ToolSearch` with `select:AskUserQuestion` now. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. This question is distinct from the mid-flow routing question (`references/walkthrough.md`) — the routing question chooses *how* to engage with findings, this one chooses *what to do next* once engagement is complete. Do not merge them. +**Interactive mode:** fire the terminal question using the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). In Claude Code the tool should already be loaded from the Interactive-mode pre-load step in `SKILL.md` — if it isn't, call `ToolSearch` with `select:AskUserQuestion` now. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. This question is distinct from the mid-flow routing question (`references/walkthrough.md`) — the routing question chooses *how* to engage with findings, this one chooses *what to do next* once engagement is complete. Do not merge them. **Stem:** `Apply decisions and what next?` diff --git a/skills/ce-doc-review/references/walkthrough.md b/skills/ce-doc-review/references/walkthrough.md index 8a45d7252..81fbad701 100644 --- a/skills/ce-doc-review/references/walkthrough.md +++ b/skills/ce-doc-review/references/walkthrough.md @@ -10,7 +10,7 @@ Interactive mode only. After `safe_auto` fixes apply and synthesis produces the remaining finding set, the orchestrator asks a four-option routing question before any walk-through or bulk action runs. -Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). In Claude Code, the tool should already be loaded from the Interactive-mode pre-load step in `SKILL.md` — if it isn't, call `ToolSearch` with query `select:AskUserQuestion` now. Fall back to presenting the options as a numbered list only when the harness genuinely lacks a blocking tool — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes without `request_user_input`). A pending schema load is not a fallback trigger. Never silently skip the question. Rendering the routing question as narrative text without the numbered-list fallback is a bug. +Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). In Claude Code, the tool should already be loaded from the Interactive-mode pre-load step in `SKILL.md` — if it isn't, call `ToolSearch` with query `select:AskUserQuestion` now. Fall back to presenting the options as a numbered list only when the harness genuinely lacks a blocking tool — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes without `request_user_input`). A pending schema load is not a fallback trigger. Never silently skip the question. Rendering the routing question as narrative text without the numbered-list fallback is a bug. **Stem:** `What should the agent do with the remaining N findings?` diff --git a/skills/ce-dogfood/SKILL.md b/skills/ce-dogfood/SKILL.md index 3b0275b30..090a79471 100644 --- a/skills/ce-dogfood/SKILL.md +++ b/skills/ce-dogfood/SKILL.md @@ -17,7 +17,7 @@ This workflow drives the browser exclusively through the `agent-browser` CLI. Do ## Prerequisites -**User-runnable invocation rendering.** In prerequisite failures, default to `/ce-setup` and `/ce-dogfood `; use `$ce-setup` and `$ce-dogfood ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only each invocation as inline code and output one form only. +**User-runnable invocation rendering.** In prerequisite failures, default to `/ce-setup` and `/ce-dogfood `; use `$ce-setup` and `$ce-dogfood ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-setup` and `/skill:ce-dogfood ` when the active host is oh-my-pi (`omp`). Render only each invocation as inline code and output one form only. - A local dev server you can start (`bin/dev`, `rails server`, `npm run dev`, etc.). - `agent-browser` installed. Check: diff --git a/skills/ce-explain/SKILL.md b/skills/ce-explain/SKILL.md index aa4047bbf..4de5fca88 100644 --- a/skills/ce-explain/SKILL.md +++ b/skills/ce-explain/SKILL.md @@ -36,7 +36,7 @@ The artifact is display-only in both renderings: no embedded quizzes, forms, or ## Interaction Method -When you must ask the user a question, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. In the fallback, stop and wait for the user's reply. Never silently skip the question. Ask one question at a time. +When you must ask the user a question, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. In the fallback, stop and wait for the user's reply. Never silently skip the question. Ask one question at a time. ## Model Tiers @@ -124,7 +124,7 @@ Detect destinations by capability — probe the agent's own toolset and session **Improvement observations.** When composing the explainer surfaced things that could be better, route them by type once the destination is settled — offer, don't auto-fire. "Settled" means the artifact was sent, or the user declined, or the run stopped at a consent gate they didn't answer; in that last case the run ends there and these offers are skipped, like the non-interactive case above. Never raise them while any of the asks above is still open — the destination question, the audience re-render offer, or a publisher's consent gate. -**User-runnable invocation rendering.** Only the user-run handoff below uses printed invocation syntax. Default to `/ce-polish`; use `$ce-polish` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** Only the user-run handoff below uses printed invocation syntax. Default to `/ce-polish`; use `$ce-polish` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-polish` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. - **New-capability ideas** — offer first; on acceptance invoke the `ce-ideate` skill via the platform's skill-invocation primitive, passing the observations as seed context. Do not merely tell the user to run it. - **Code-clarity findings** — offer first; on acceptance invoke the `ce-simplify-code` skill via the platform's skill-invocation primitive, passing the observations and the files they concern. Do not merely tell the user to run it. diff --git a/skills/ce-handoff/SKILL.md b/skills/ce-handoff/SKILL.md index 99f8e5051..c156d1531 100644 --- a/skills/ce-handoff/SKILL.md +++ b/skills/ce-handoff/SKILL.md @@ -51,7 +51,7 @@ Use a readable topic slug as the filename. When Git context exists, use a saniti Treat creation as complete only after confirming the destination contains the handoff. Give a succinct, context-specific summary of what the generated handoff captures so the user can verify its substance without opening it; do not impose a fixed summary template. Then report the final path or URL, applicable retention or access limits, and any warnings together. Managed `/tmp` storage is OS-managed and not permanent. Its automatic discovery assumes the receiving session can see the same host filesystem; otherwise tell the user to transfer or publish the handoff to a receiver-visible location and resume from that explicit source. -**User-runnable invocation rendering.** For the copyable resume command below, default to `/ce-handoff resume `; use `$ce-handoff resume ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render it as the fenced command below and output one form only. +**User-runnable invocation rendering.** For the copyable resume command below, default to `/ce-handoff resume `; use `$ce-handoff resume ` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-handoff resume ` when the active host is oh-my-pi (`omp`). Render it as the fenced command below and output one form only. End the creation response with one fenced, copyable command using the final path or URL and the rendering rule above: diff --git a/skills/ce-ideate/SKILL.md b/skills/ce-ideate/SKILL.md index 67efd5a07..3fbcc8490 100644 --- a/skills/ce-ideate/SKILL.md +++ b/skills/ce-ideate/SKILL.md @@ -33,7 +33,7 @@ fi ## Interaction Method -Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time. Prefer concise single-select choices when natural options exist. diff --git a/skills/ce-ideate/references/post-ideation-workflow.md b/skills/ce-ideate/references/post-ideation-workflow.md index ff0bc9556..2254f0b32 100644 --- a/skills/ce-ideate/references/post-ideation-workflow.md +++ b/skills/ce-ideate/references/post-ideation-workflow.md @@ -79,7 +79,7 @@ This ranked list doubles as the index the user references when choosing an idea ## Phase 5: Next Steps -Ask what to do next using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Free-text answers are accepted. +Ask what to do next using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Free-text answers are accepted. The deliverable already exists (Phase 4), so the menu is purely *what next* — there is no "save" step. diff --git a/skills/ce-ideate/references/universal-ideation.md b/skills/ce-ideate/references/universal-ideation.md index a41ea5386..0c2c1574d 100644 --- a/skills/ce-ideate/references/universal-ideation.md +++ b/skills/ce-ideate/references/universal-ideation.md @@ -97,7 +97,7 @@ Wrap up with the same flow as `references/post-ideation-workflow.md` Phases 4– - **Auto-write the deliverable** (Phase 4.1). The full per-idea detail (title, description, axis, basis, rationale, downsides, confidence, complexity) and the rejection summary go in the **file** — persistence is automatic, not opt-in. Location: `/ideation/` when it already exists, otherwise the run's CE temp area (`/ce-ideate//`) — never the user's CWD. `OUTPUT_FORMAT` (SKILL.md Phase 0.0; default `html`) sets the extension. - **Present a concise summary** (Phase 4.2) — the ranked list (title · axis · confidence · complexity), counts, the path, and any zero-survivor axis. Do not reprint the full detail in the session; the rich file is what the user engages with. For HTML, best-effort open it in the browser (Phase 4.3). -Then offer the Phase 5 next-steps menu via the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_question` in Antigravity CLI (`agy`) / Pi). Fall back to a numbered list only when no blocking tool exists or the call errors. Never silently skip. Four options, option 1 format-keyed: +Then offer the Phase 5 next-steps menu via the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_question` in Antigravity CLI (`agy`) / Pi, `ask` in oh-my-pi (`omp`)). Fall back to a numbered list only when no blocking tool exists or the call errors. Never silently skip. Four options, option 1 format-keyed: 1. **Open in browser** *(html)* / **Publish to Proof** *(md)* — open the HTML deliverable, or publish the markdown to Proof for a shareable link (per §5.1). On Proof failure the auto-written local file stays intact. 2. **Brainstorm one idea with `ce-brainstorm`** — go deeper on one chosen idea (asks which). In universal mode this is **not** the first step of an implementation chain — there is no `ce-plan` → `ce-work` after; `ce-brainstorm` develops the idea further (a name into a brand brief, a plot into an outline, a decision into a weighed framework) and ends there. Seed it with the idea's substance + a provenance pointer (per §5.2) — not the whole file. diff --git a/skills/ce-optimize/SKILL.md b/skills/ce-optimize/SKILL.md index 0883a184d..d6ed27841 100644 --- a/skills/ce-optimize/SKILL.md +++ b/skills/ce-optimize/SKILL.md @@ -24,7 +24,7 @@ fi ## Interaction Method -Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. ## Input diff --git a/skills/ce-plan/SKILL.md b/skills/ce-plan/SKILL.md index 16994df6d..7302168a9 100644 --- a/skills/ce-plan/SKILL.md +++ b/skills/ce-plan/SKILL.md @@ -38,7 +38,7 @@ Before any response that could end a software implementation-plan run, verify th ## Interaction Method -When asking the user a question, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +When asking the user a question, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time. Prefer a concise single-select choice when natural options exist. @@ -422,7 +422,7 @@ Announce the decision and the intent briefly before continuing. Examples: #### 1.3 External Research (Conditional) -If Step 1.2 indicates external research is useful, dispatch by the **intent** classified in Stage 2, using the platform's subagent primitive (`Agent`/`Task` in Claude Code, `spawn_agent` in Codex) where available; otherwise run the work inline or serially. Read the selected prompt asset from `references/agents/` and seed a generic subagent with it. For `web-researcher.md`, pass a focus hint plus the planning context summary and do **not** pass codebase content — it operates externally. +If Step 1.2 indicates external research is useful, dispatch by the **intent** classified in Stage 2, using the platform's subagent primitive (`Agent`/`Task` in Claude Code, `spawn_agent` in Codex, `task` in oh-my-pi (`omp`)) where available; otherwise run the work inline or serially. Read the selected prompt asset from `references/agents/` and seed a generic subagent with it. For `web-researcher.md`, pass a focus hint plus the planning context summary and do **not** pass codebase content — it operates externally. - **Implementation-guidance** — run in parallel: - `references/agents/best-practices-researcher.md` with the planning context summary. diff --git a/skills/ce-plan/references/approach-altitude.md b/skills/ce-plan/references/approach-altitude.md index 57568cc31..1ea955e6f 100644 --- a/skills/ce-plan/references/approach-altitude.md +++ b/skills/ce-plan/references/approach-altitude.md @@ -25,7 +25,7 @@ This is not a software plan template (no implementation units / test scenarios) ## Stage 3: Checkpoint -Hold at the approach. Use the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_question` in Antigravity CLI (`agy`)/Pi). Fall back to numbered options in chat only when no blocking tool exists or the call errors — never silently skip. +Hold at the approach. Use the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_question` in Antigravity CLI (`agy`)/Pi, `ask` in oh-my-pi (`omp`)). Fall back to numbered options in chat only when no blocking tool exists or the call errors — never silently skip. **Sequence orthogonal axes** rather than cramming them into one menu (per the "split orthogonal decisions" rule and the 4-option cap): diff --git a/skills/ce-plan/references/plan-handoff.md b/skills/ce-plan/references/plan-handoff.md index 89fc91407..1df0a2b46 100644 --- a/skills/ce-plan/references/plan-handoff.md +++ b/skills/ce-plan/references/plan-handoff.md @@ -81,7 +81,7 @@ There is no "done" / "pause" option — the blocking question already waits, and **Option 5 format-keyed label.** Under exclusive output mode, the plan exists as exactly one artifact — `.md` or `.html`, never both. Render the option 5 label matching the produced format. Proof ingests the `.md` source, so it does not apply to HTML runs; the browser option opens the local `.html` file directly. Implementation handoff (options 1 and 2) remains available in both modes only when the artifact is implementation-ready code — `ce-work` reads either format, and the launch prompt is emitted at handoff regardless of format (see the ce-work skill's plan-input handling). -**Menu rendering:** The menu has up to 5 options (execution options 1 and 2 render only for implementation-ready code, and option 2 only on hosts with goal capability; option 3 is conditional — see below). Detect goal capability by capability, not by slash-command shape: Codex has it when `create_goal` is in the available tool list, while Claude Code has it through user-typed `/goal`. Account for each platform's blocking-question option cap rather than trimming choices: Claude Code `AskUserQuestion` supports up to 4 explicit options, and Codex `request_user_input` supports only 2-3 explicit options. When the visible menu exceeds the current platform's cap, render it as a numbered list in chat with the hint "Pick a number or describe what you want." When the visible menu fits the cap, use the platform's blocking tool and renumber the visible options 1-N. When the platform's blocking tool is unavailable or errors (e.g., Codex edit modes where `request_user_input` is not exposed, or `ask_user` returns no match), fall back to the same numbered-list-in-chat rendering. Never silently skip the question. +**Menu rendering:** The menu has up to 5 options (execution options 1 and 2 render only for implementation-ready code, and option 2 only on hosts with goal capability; option 3 is conditional — see below). Detect goal capability by capability, not by slash-command shape: Codex has it when `create_goal` is in the available tool list, while Claude Code has it through user-typed `/goal`. Account for each platform's blocking-question option cap rather than trimming choices: Claude Code `AskUserQuestion` supports up to 4 explicit options, oh-my-pi (`omp`) `ask` behaves like Claude Code's (structured options plus free text), and Codex `request_user_input` supports only 2-3 explicit options. When the visible menu exceeds the current platform's cap, render it as a numbered list in chat with the hint "Pick a number or describe what you want." When the visible menu fits the cap, use the platform's blocking tool and renumber the visible options 1-N. When the platform's blocking tool is unavailable or errors (e.g., Codex edit modes where `request_user_input` is not exposed, or `ask_user` returns no match), fall back to the same numbered-list-in-chat rendering. Never silently skip the question. **Hide `Decide on the review's open items` (option 3) when no actionable findings remain or doc review was skipped.** Show this option only when the non-interactive envelope reports `proposed_fixes_count + decisions_count > 0` — i.e., at least one `gated_auto` or `manual` finding at confidence anchor `75` or `100`. Drop the option in any other case, including FYI-only state. FYI observations (anchor `50`) do not enter `ce-doc-review`'s interactive routing question or walkthrough — that flow is gated to actionable findings — so a `Decide on the review's open items` option that only has FYIs to show is a dead-end: ce-doc-review would re-dispatch the persona team, find the same FYIs, skip the routing question, and fall through to the terminal question with nothing to walk through. The user paid the dispatch cost for no engagement surface. **Also drop this option when the envelope carries `skipped_reason: output_format_html` or `skipped_reason: skill_unreachable`** — the former cannot be mutated safely and the latter means the skill did not run. Always renumber the *visible* options 1-N for display so users see a clean sequence (e.g., an implementation-ready plan with no actionable findings shows ce-work, give-me-`/goal`, Create Issue, Proof/browser, Done; a requirements-only plan hides both execution options and shows only the doc/issue/share/pause options). The summary line above the menu still names the FYI count when present (`Doc review applied 3 fixes. 2 FYI observations remain.`) so the user sees what was found, even though there is no menu action attached to it — the FYIs are visible in the non-interactive envelope text the menu rendered alongside. @@ -126,7 +126,7 @@ Based on selection (the bare per-option routing is also stated inline in the SKI If the upload fails (network error, Proof API down), retry once after a short wait. If it still fails, tell the user the upload didn't succeed and briefly explain why, then return to the options — don't leave them wondering why the option did nothing. - **Open in browser** -> Display the absolute path to the `.html` plan file so the user can open it locally. Where the platform exposes a browser-opening primitive (e.g., `open` on macOS, `xdg-open` on Linux, `start` on Windows), the agent may invoke it directly; otherwise print the absolute path and let the user open it. After the path is displayed (or the browser is opened), return to the post-generation options so the user can pick a follow-up action. -- **Free-form prompts that target the findings** (e.g., the user types "review", "walk through", "deep review" instead of picking a numbered option) -> route as if they had picked `Decide on the review's open items`. Do not loop back to the menu without firing the review. **Exception:** when the envelope carries `skipped_reason: output_format_html`, do not fire `ce-doc-review` — instead, reply once that `ce-doc-review` is markdown-only today, the HTML plan cannot be reviewed without HTML-aware mutation support, and the user can regenerate as markdown by invoking `ce-plan output:md`. For this user-runnable output, default to `/ce-plan output:md`; use `$ce-plan output:md` only on Codex or a host explicitly documented as dollar-prefixed. Render only the invocation as inline code and output one form only. Then loop back to the menu. When it carries `skipped_reason: skill_unreachable`, say that `ce-doc-review` could not be invoked and loop back without misreporting a review failure. +- **Free-form prompts that target the findings** (e.g., the user types "review", "walk through", "deep review" instead of picking a numbered option) -> route as if they had picked `Decide on the review's open items`. Do not loop back to the menu without firing the review. **Exception:** when the envelope carries `skipped_reason: output_format_html`, do not fire `ce-doc-review` — instead, reply once that `ce-doc-review` is markdown-only today, the HTML plan cannot be reviewed without HTML-aware mutation support, and the user can regenerate as markdown by invoking `ce-plan output:md`. For this user-runnable output, default to `/ce-plan output:md`; use `$ce-plan output:md` only on Codex or a host explicitly documented as dollar-prefixed; use `/skill:ce-plan output:md` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. Then loop back to the menu. When it carries `skipped_reason: skill_unreachable`, say that `ce-doc-review` could not be invoked and loop back without misreporting a review failure. - **Other free-form input** -> Accept revisions to the plan and loop back to options. ## Issue Creation @@ -139,7 +139,7 @@ When the user selects "Create Issue": - **GitHub** — `gh issue create --title ": " --body-file <plan_path>` - **Linear** (no guaranteed first-party CLI) — prefer, in order: a Linear connector or MCP tool that can create issues → documented direct API/GraphQL credentials and endpoint → a documented local Linear CLI, only when the project or user explicitly states it is installed and authenticated. -3. If no tracker is configured, ask the user which tracker they use with the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to asking in chat only when no blocking tool exists or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip. Offer three explicit options — `GitHub`, `Linear`, `Skip` — and let the user name a different tracker (Jira, etc.) through the tool's built-in free-form / "Other" input: `AskUserQuestion` always provides it, and `request_user_input` / `ask_user` supply their own. Don't add an explicit fourth `Other` option — that's redundant where the tool already offers free-form and can exceed the option cap on tools that accept only 2–3 explicit choices (e.g., Codex `request_user_input`). When the tool exposes no free-form path, capture the other-tracker name via the chat fallback. Then: +3. If no tracker is configured, ask the user which tracker they use with the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to asking in chat only when no blocking tool exists or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip. Offer three explicit options — `GitHub`, `Linear`, `Skip` — and let the user name a different tracker (Jira, etc.) through the tool's built-in free-form / "Other" input: `AskUserQuestion` always provides it, and `request_user_input` / `ask_user` supply their own. Don't add an explicit fourth `Other` option — that's redundant where the tool already offers free-form and can exceed the option cap on tools that accept only 2–3 explicit choices (e.g., Codex `request_user_input`). When the tool exposes no free-form path, capture the other-tracker name via the chat fallback. Then: - Proceed with the chosen tracker's creation path above - If the user names a different tracker through the free-form path, ask for its reachable interface if they didn't say, then create the issue via the capability path in step 2 - Offer to persist the choice by adding a `project_tracker: <value>` declaration to the project's root agent-instructions file (e.g., `AGENTS.md`; if it `@`-includes another file, write to the substantive one). Use the lowercase tracker key (`github`, `linear`, `jira`, …) — not the display label — so future runs match step 1 and skip this prompt diff --git a/skills/ce-plan/references/universal-planning.md b/skills/ce-plan/references/universal-planning.md index ff0da8153..fb6424cf4 100644 --- a/skills/ce-plan/references/universal-planning.md +++ b/skills/ce-plan/references/universal-planning.md @@ -8,7 +8,7 @@ The detection stub in SKILL.md routes here for anything that isn't clearly softw - **Is this actually a software task?** The key distinction is task-type, not topic-domain. A study guide about Rust is non-software (producing educational content). A Rust library refactor is software (modifying code). If this is actually software, return to Phase 0.2 in the main SKILL.md. - **Is this a trivial single-fact lookup?** Only a question answerable from one fact with no research, retrieval, or judgment skips planning — answer it directly and stop, in the user's terms. Do not narrate that it "isn't a planning task" or explain the routing; that is process exhaust (see Veil of value below). Examples: "zsh: command not found: brew", "what's the capital of France." A question that needs multiple steps, any retrieval, or synthesis to answer well does **not** qualify: it is an answer-seeking task (see Disposition below), not a quick-help exit. When unsure, do not exit. -- **Pipeline mode?** If invoked from `lfg` or any `disable-model-invocation` context: tell the user this is a non-software task, `lfg` requires the software-only `ce-work` path, and they should invoke `ce-plan` directly for non-software planning. For this user-runnable output, default to `/ce-plan`; use `$ce-plan` only on Codex or a host explicitly documented as dollar-prefixed. Render only the invocation as inline code and output one form only, then stop. +- **Pipeline mode?** If invoked from `lfg` or any `disable-model-invocation` context: tell the user this is a non-software task, `lfg` requires the software-only `ce-work` path, and they should invoke `ce-plan` directly for non-software planning. For this user-runnable output, default to `/ce-plan`; use `$ce-plan` only on Codex or a host explicitly documented as dollar-prefixed; use `/skill:ce-plan` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only, then stop. - **Unified artifact guard.** Universal-planning outputs are not software implementation plans. Do not label them `artifact_contract: ce-unified-plan/v1` and do not produce a `/goal` launch block unless the deliverable itself is a complete software implementation plan with Product Contract, Planning Contract, Implementation Units, Verification Contract, and Definition of Done. Once past these checks, commit to the task — do not bail because it looks like a "lookup" or "research question." The user invoked the planning tool on purpose. Then choose the disposition below. @@ -98,7 +98,7 @@ Example for "plan a date night in Seattle this Saturday": ## Step 1b: Focused Q&A -Ask up to 3 questions targeting the unknowns that would most change the plan. Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +Ask up to 3 questions targeting the unknowns that would most change the plan. Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. **How to ask well:** - Offer informed options, not open-ended blanks. Instead of "When are you going?", try "Mid-week visits have 30-40% shorter lines — are you flexible on timing?" The question should give the user a frame of reference, not just extract information. @@ -147,7 +147,7 @@ Example: A date night plan should present 2-3 restaurant options, 2-3 activity o ## Step 3: Save or Share -After structuring the plan, ask the user how they want to receive it using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +After structuring the plan, ask the user how they want to receive it using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. **Question:** "Plan ready. How would you like to receive it?" diff --git a/skills/ce-pov/SKILL.md b/skills/ce-pov/SKILL.md index 985ac79ba..cb908bf92 100644 --- a/skills/ce-pov/SKILL.md +++ b/skills/ce-pov/SKILL.md @@ -38,7 +38,7 @@ Write user-facing messages for the person deciding what to do. Lead with the dec ## Interaction Method -When you must ask the user a question, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time. +When you must ask the user a question, use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time. ## Artifact Root diff --git a/skills/ce-product-pulse/SKILL.md b/skills/ce-product-pulse/SKILL.md index 33fabeb4e..ec16c0b43 100644 --- a/skills/ce-product-pulse/SKILL.md +++ b/skills/ce-product-pulse/SKILL.md @@ -20,7 +20,7 @@ The skill does not mutate the product, the database, or any external system. Its ## Interaction Method -Default to the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +Default to the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time. Reserve multi-select for first-run configuration only. diff --git a/skills/ce-promote/references/spiral-cli.md b/skills/ce-promote/references/spiral-cli.md index c175708b9..94d1689b7 100644 --- a/skills/ce-promote/references/spiral-cli.md +++ b/skills/ce-promote/references/spiral-cli.md @@ -33,7 +33,7 @@ If the contents have an **uncommented** top-level `ce_promote_spiral_optout: tru ### Ask -Use the platform's blocking-question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`) / Pi. If no blocking tool exists or the call errors, present the same options as a numbered list in chat and wait for a reply — never silently skip. +Use the platform's blocking-question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`) / Pi, `ask` in oh-my-pi (`omp`). If no blocking tool exists or the call errors, present the same options as a numbered list in chat and wait for a reply — never silently skip. For the **unauthed** state, the **agent itself** runs `spiral login --json` (CLI >= 1.8.0): it's non-blocking and the API key never passes through the agent — the agent shares the returned `auth_url`, the user approves in a browser, and the credential is delivered server->CLI. The blocking question is mainly the escape hatch. diff --git a/skills/ce-resolve-pr-feedback/references/full-mode.md b/skills/ce-resolve-pr-feedback/references/full-mode.md index cf061b970..b536c7d0c 100644 --- a/skills/ce-resolve-pr-feedback/references/full-mode.md +++ b/skills/ce-resolve-pr-feedback/references/full-mode.md @@ -287,6 +287,6 @@ Still pending from a previous run (count): If a blocking question tool is available, use it to ask about all pending decisions (both new `needs-human` and previous-run pending) together. If there are only pending decisions and no new work was done, the summary is just the pending items. -Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Use it to present the decisions and wait for the user's response. After they decide, process the remaining items: fix the code, compose the reply, post it, and resolve the thread. +Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Use it to present the decisions and wait for the user's response. After they decide, process the remaining items: fix the code, compose the reply, post it, and resolve the thread. Fall back to presenting the decisions in the summary output and waiting in conversation only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip. If the user doesn't respond, the items remain open on the PR for later handling. diff --git a/skills/ce-setup/SKILL.md b/skills/ce-setup/SKILL.md index e43d06afe..8d90122a5 100644 --- a/skills/ce-setup/SKILL.md +++ b/skills/ce-setup/SKILL.md @@ -8,7 +8,7 @@ disable-model-invocation: true ## Interaction Method -Ask each question below using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to a numbered list in chat only when no blocking tool exists in the harness or the call errors. Never silently skip or auto-configure. +Ask each question below using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to a numbered list in chat only when no blocking tool exists in the harness or the call errors. Never silently skip or auto-configure. `ce-setup` is a lightweight health check and repo-local config helper. It does **not** bulk-install every optional dependency. Missing tools are reported as optional capabilities so the user can install only the workflows they use. @@ -61,7 +61,7 @@ Display the diagnostic output to the user. Missing optional tools are not setup ### Step 3: Decide Whether Fixes Are Needed -**User-runnable invocation rendering.** In setup summaries, default to `/ce-setup`; use `$ce-setup` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** In setup summaries, default to `/ce-setup`; use `$ce-setup` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-setup` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. Proceed to Phase 2 only if one or more repo-local project issues exist: diff --git a/skills/ce-simplify-code/SKILL.md b/skills/ce-simplify-code/SKILL.md index 13a0e117c..ba3205068 100644 --- a/skills/ce-simplify-code/SKILL.md +++ b/skills/ce-simplify-code/SKILL.md @@ -28,7 +28,7 @@ Resolve the simplification scope in this order: 2. **Otherwise, in git**, use the current branch versus its base. Without a usable base, use staged and unstaged changes (`git diff HEAD`). 3. **Outside git or without a diff**, use files the user named or that were edited earlier in the conversation. -If none of the above produces a non-empty scope, stop and ask the user what to simplify rather than guessing. Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +If none of the above produces a non-empty scope, stop and ask the user what to simplify rather than guessing. Use the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. **Preflight.** If the scope has no substantive human-authored code — only documentation, generated or vendored files, dependencies or lockfiles, or mechanical churn — report that there is nothing to simplify and stop without reviewers. For mixed scopes, retain only the code. This is a kind gate, never a size gate: explicit small scopes still run, and callers own any size or cost threshold. @@ -36,7 +36,7 @@ When the platform's task-tracking capability is available, show the review, appl ## Step 2: Launch 3 review agents in parallel -Dispatch three generic subagents — code-reuse, code-quality, and efficiency reviewers — via the platform's subagent primitive (`Agent`/`Task` in Claude Code, `spawn_agent` in Codex) where available; otherwise run the reviews inline or serially. For each reviewer, read its prompt asset from this skill's directory and pass the **full file content** as the subagent's prompt, together with the resolved scope (the full diff or file set) so it has complete context: +Dispatch three generic subagents — code-reuse, code-quality, and efficiency reviewers — via the platform's subagent primitive (`Agent`/`Task` in Claude Code, `spawn_agent` in Codex, `task` in oh-my-pi (`omp`)) where available; otherwise run the reviews inline or serially. For each reviewer, read its prompt asset from this skill's directory and pass the **full file content** as the subagent's prompt, together with the resolved scope (the full diff or file set) so it has complete context: - `references/personas/code-reuse-reviewer.md` - `references/personas/code-quality-reviewer.md` diff --git a/skills/ce-strategy/SKILL.md b/skills/ce-strategy/SKILL.md index 1ea6e1d87..9356cc213 100644 --- a/skills/ce-strategy/SKILL.md +++ b/skills/ce-strategy/SKILL.md @@ -14,7 +14,7 @@ The document is short and structured on purpose. Good answers to a handful of sh ## Interaction Method -Default to the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +Default to the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. Ask one question at a time. Prefer free-form responses for the substantive sections (problem, approach, persona); reserve single-select for routing decisions (which section to revisit). Each option label must be self-contained. diff --git a/skills/ce-sweep/SKILL.md b/skills/ce-sweep/SKILL.md index 4d37ae75f..6b30a735c 100644 --- a/skills/ce-sweep/SKILL.md +++ b/skills/ce-sweep/SKILL.md @@ -36,7 +36,7 @@ fi ## Interaction Method -Default to the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Never silently skip a question you owe the user; if no blocking tool exists in the harness, the run is non-interactive (see Mode). Ask one question at a time — the decision round (2h) may group by category but still asks one blocking question per category. +Default to the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Never silently skip a question you owe the user; if no blocking tool exists in the harness, the run is non-interactive (see Mode). Ask one question at a time — the decision round (2h) may group by category but still asks one blocking question per category. ## Mode @@ -183,7 +183,7 @@ Interactive only. For items needing a product call, ask the user — grouped by #### 2i. Wrap-up -**User-runnable invocation rendering.** In the summary handoff below, default to `/lfg <root>/plans/feedback-sweep-plan.md`; use `$lfg <root>/plans/feedback-sweep-plan.md` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** In the summary handoff below, default to `/lfg <root>/plans/feedback-sweep-plan.md`; use `$lfg <root>/plans/feedback-sweep-plan.md` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:lfg <root>/plans/feedback-sweep-plan.md` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. - **Commit.** `git add` ONLY `<root>/plans/feedback-sweep-plan.md` plus `<state>` when it is repo-internal (never `-A`; machine-local state under `/tmp` is never committed), then commit `docs(sweep): feedback sweep <date>`. A commit failure is reported, not fatal. In local-commit mode, never push. In shared-branch mode (`sweep_shared_branch: true`), fetch, rebase, and push the final commit. - **Record the run.** `run-record --state <state> --writer <writer> --outcome <completed|partial|failed> --counts '<per-source JSON>' --timestamp <ISO now>`. diff --git a/skills/ce-sweep/references/interview.md b/skills/ce-sweep/references/interview.md index 368a9ef95..13fa5a4cf 100644 --- a/skills/ce-sweep/references/interview.md +++ b/skills/ce-sweep/references/interview.md @@ -4,11 +4,11 @@ Loaded by `SKILL.md` when `ce-sweep` runs with no `feedback_sources` configured. This interview is **interactive only**. The caller refuses first-run setup in non-interactive mode — a scheduled or piped run with no config aborts and tells the user to run `ce-sweep` interactively once. Do not attempt to infer sources, actions, or approvals without asking. -**User-runnable invocation rendering.** Whenever this interview prints or registers a `ce-sweep` invocation, default to `/ce-sweep` (plus any arguments); use `$ce-sweep` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** Whenever this interview prints or registers a `ce-sweep` invocation, default to `/ce-sweep` (plus any arguments); use `$ce-sweep` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-sweep` (plus any arguments) when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. ## Interaction Method -Ask **one question at a time** using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors — never silently skip a question or assume a default without surfacing it. +Ask **one question at a time** using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors — never silently skip a question or assume a default without surfacing it. ## Overall Rules diff --git a/skills/ce-test-browser/SKILL.md b/skills/ce-test-browser/SKILL.md index 3837ce6fc..8d311b1b6 100644 --- a/skills/ce-test-browser/SKILL.md +++ b/skills/ce-test-browser/SKILL.md @@ -112,7 +112,7 @@ Visibility is independent from unattended execution: - **Host-native integrated browser:** keep its normal integrated surface visible and non-blocking so the user can watch progress when useful. Do not repeatedly steal focus as routes change. This applies in both manual and pipeline modes. - **`agent-browser` fallback, pipeline mode:** run headless without asking. -- **`agent-browser` fallback, manual mode:** ask the user whether to run headed or headless using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to presenting options in chat only when no blocking tool exists in the harness or the call errors. Never silently skip the question: +- **`agent-browser` fallback, manual mode:** ask the user whether to run headed or headless using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to presenting options in chat only when no blocking tool exists in the harness or the call errors. Never silently skip the question: ``` Do you want to watch the browser tests run? diff --git a/skills/ce-test-xcode/SKILL.md b/skills/ce-test-xcode/SKILL.md index 07f1518fb..b981eabb9 100644 --- a/skills/ce-test-xcode/SKILL.md +++ b/skills/ce-test-xcode/SKILL.md @@ -109,7 +109,7 @@ Pause for human input when testing touches flows that require device interaction | Location | "Allow location access and verify map updates" | | SwiftUI Text links | "Please tap on [element description] manually — automated taps cannot trigger inline text links" | -Ask the user using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question: +Ask the user using the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`). Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question: ``` Human Verification Needed diff --git a/skills/ce-work/references/execution-engines.md b/skills/ce-work/references/execution-engines.md index 96076c1f9..abb53e963 100644 --- a/skills/ce-work/references/execution-engines.md +++ b/skills/ce-work/references/execution-engines.md @@ -77,7 +77,7 @@ An engine is usable only when the host exposes a callable primitive for it. Do n | Engine | Usable when | Claude Code reality | |---|---|---| -| **Inline / subagent** | Always. The orchestrator runs units inline or dispatches subagents via the platform's subagent primitive (`Agent`/`Task` in Claude Code, `spawn_agent` in Codex, `subagent` in Pi). | Always callable in-session. This is the default. | +| **Inline / subagent** | Always. The orchestrator runs units inline or dispatches subagents via the platform's subagent primitive (`Agent`/`Task` in Claude Code, `spawn_agent` in Codex, `subagent` in Pi, `task` in oh-my-pi (`omp`)). | Always callable in-session. This is the default. | | **Goal-mode** | The host exposes a callable goal *tool* a skill can invoke — e.g. Codex `create_goal` (sets **and activates** a persistent objective for the current session) plus `update_goal(complete\|blocked)` for terminal status. | **No goal tools exposed.** `/goal` is a top-level user command only; a skill cannot invoke it or any goal tool. Emit a copyable `/goal` prompt for the user to paste, or run inline/subagents. **Codex differs — it does expose `create_goal` (see below).** | | **Dynamic-workflow** | The host exposes a callable dynamic-workflow / ultracode-style orchestration primitive that returns structured results and blockers without mid-run user decisions. | **Not callable from inside a skill.** Dynamic workflows start from a user prompt (`ultracode:` or `/effort ultracode`). `ce-work` can only emit a copyable prompt block. | | **Cross-model execution** | A resolved fixed route has a qualified, write-capable adapter and satisfies every caller restriction. Load `cross-model-execution.md` only after this engine is selected. | Availability depends on the installed target CLI and its qualified adapter, not on the host's native subagent tools. A same-host default request collapses to native execution. | diff --git a/skills/ce-work/references/shipping-workflow.md b/skills/ce-work/references/shipping-workflow.md index 498563b66..a0e9e6562 100644 --- a/skills/ce-work/references/shipping-workflow.md +++ b/skills/ce-work/references/shipping-workflow.md @@ -46,7 +46,7 @@ This file contains the shipping workflow (Phase 3-4). It is loaded when all Phas A settlement-invalidating conflict — evidence a `session-settled:`-labeled decision cannot work — is never auto-accepted as a residual; it is a blocker (`status: blocked` return in return-to-caller mode; stop-and-surface in standalone runs). - **Interactive sessions:** Ask the user using the platform's blocking question tool (`AskUserQuestion` in Claude Code with `ToolSearch select:AskUserQuestion` pre-loaded if needed, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool. Never silently skip the gate. + **Interactive sessions:** Ask the user using the platform's blocking question tool (`AskUserQuestion` in Claude Code with `ToolSearch select:AskUserQuestion` pre-loaded if needed, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool. Never silently skip the gate. Stem: `Code review left N actionable finding(s) not yet fixed. How should the agent proceed?` diff --git a/skills/ce-work/references/tracker-defer.md b/skills/ce-work/references/tracker-defer.md index 0fc80cd24..fd21a76ad 100644 --- a/skills/ce-work/references/tracker-defer.md +++ b/skills/ce-work/references/tracker-defer.md @@ -145,6 +145,6 @@ When uncertain, prefer "drop with explicit user-facing notice" over "pass throug ## Cross-platform notes -The question-tool name varies by platform. In Interactive mode, use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). In Claude Code the tool should already be loaded from the Interactive-mode pre-load step — if it isn't, call `ToolSearch` with query `select:AskUserQuestion` now. Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes without `request_user_input`). A pending schema load is not a fallback trigger. Never silently skip the question. +The question-tool name varies by platform. In Interactive mode, use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). In Claude Code the tool should already be loaded from the Interactive-mode pre-load step — if it isn't, call `ToolSearch` with query `select:AskUserQuestion` now. Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes without `request_user_input`). A pending schema load is not a fallback trigger. Never silently skip the question. Non-interactive mode is platform-agnostic: it never prompts, so the platform's question tool is not relevant. diff --git a/skills/ce-worktree/SKILL.md b/skills/ce-worktree/SKILL.md index e25445fa9..c55b4abaf 100644 --- a/skills/ce-worktree/SKILL.md +++ b/skills/ce-worktree/SKILL.md @@ -53,7 +53,7 @@ Only when there is no native tool **and** Step 0 found no existing isolation. - **Isolate an existing ref:** attach to the ref instead of branching — for an existing branch or tag, `git worktree add .worktrees/<slug> <target-ref>`. For a **PR**, check it out **on a local branch** (never a detached `FETCH_HEAD` — that orphans the fix loop's commits instead of updating the PR): `git fetch origin pull/<n>/head:pr-<n>` then `git worktree add .worktrees/pr-<n> pr-<n>`. (To get push-tracking back to the PR instead, create the worktree detached first — `git worktree add --detach .worktrees/pr-<n>` — then `cd` in and run `gh pr checkout <n>`, which is fork-safe.) If git reports the ref is already checked out elsewhere, follow the already-checked-out rule under **Two modes** — do not force a second worktree. 6. Switch into it: `cd .worktrees/<branch-name>` (or `.worktrees/<slug>`). -If `git worktree add` fails with a sandbox or permission error, the requested isolation could not be created. This needs a **blocking** user decision before touching the current checkout — do not silently continue there (the user chose isolation specifically to avoid it, especially when `ce-work` / `ce-code-review` routed here for the worktree option). Report the failure and ask via the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (via the `pi-ask-user` extension) — offering options such as "work in the current checkout" vs "stop and resolve the permission issue". If no blocking tool exists in the harness or the call errors, present the numbered options in chat and wait for the reply; never skip the confirmation. Only work in the current checkout on explicit confirmation, and do not retry alternative paths automatically. +If `git worktree add` fails with a sandbox or permission error, the requested isolation could not be created. This needs a **blocking** user decision before touching the current checkout — do not silently continue there (the user chose isolation specifically to avoid it, especially when `ce-work` / `ce-code-review` routed here for the worktree option). Report the failure and ask via the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (via the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`) — offering options such as "work in the current checkout" vs "stop and resolve the permission issue". If no blocking tool exists in the harness or the call errors, present the numbered options in chat and wait for the reply; never skip the confirmation. Only work in the current checkout on explicit confirmation, and do not retry alternative paths automatically. ## Other worktree operations diff --git a/skills/lfg/SKILL.md b/skills/lfg/SKILL.md index f7eb47c2f..7df23db2d 100644 --- a/skills/lfg/SKILL.md +++ b/skills/lfg/SKILL.md @@ -128,7 +128,7 @@ When the implementation instruction instead names an ordered fallback list, do n 10. Output `<promise>DONE</promise>` when complete - For the two user-runnable handoffs below, default to `/ce-explain <name>` / `/ce-babysit-pr <pr-url>`. Use `$ce-explain <name>` / `$ce-babysit-pr <pr-url>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. + For the two user-runnable handoffs below, default to `/ce-explain <name>` / `/ce-babysit-pr <pr-url>`. Use `$ce-explain <name>` / `$ce-babysit-pr <pr-url>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-explain <name>` / `/skill:ce-babysit-pr <pr-url>` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. If step 8 recorded a `New concepts:` trailer, first echo one line per concept: `New concept introduced: <name> — run <rendered ce-explain invocation> to go deeper.` diff --git a/skills/lfg/references/tracker-defer.md b/skills/lfg/references/tracker-defer.md index 5acdf8a35..19bc2eabf 100644 --- a/skills/lfg/references/tracker-defer.md +++ b/skills/lfg/references/tracker-defer.md @@ -145,6 +145,6 @@ When uncertain, prefer "drop with explicit user-facing notice" over "pass throug ## Cross-platform notes -The question-tool name varies by platform. In Interactive mode, use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension)). In Claude Code the tool should already be loaded from the Interactive-mode pre-load step — if it isn't, call `ToolSearch` with query `select:AskUserQuestion` now. Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes without `request_user_input`). A pending schema load is not a fallback trigger. Never silently skip the question. +The question-tool name varies by platform. In Interactive mode, use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (requires the `pi-ask-user` extension), `ask` in oh-my-pi (`omp`)). In Claude Code the tool should already be loaded from the Interactive-mode pre-load step — if it isn't, call `ToolSearch` with query `select:AskUserQuestion` now. Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool — `ToolSearch` returns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes without `request_user_input`). A pending schema load is not a fallback trigger. Never silently skip the question. Non-interactive mode is platform-agnostic: it never prompts, so the platform's question tool is not relevant. diff --git a/src/commands/convert.ts b/src/commands/convert.ts index 4982d97fa..e55c250a1 100644 --- a/src/commands/convert.ts +++ b/src/commands/convert.ts @@ -98,7 +98,7 @@ export default defineCommand({ const activeTargets = detected.filter((t) => t.detected && targets[t.name]?.implemented) if (activeTargets.length === 0) { - console.log("No installable AI coding tools detected. Use native plugin install for Claude Code, Copilot, Droid, OpenCode, Pi, and Qwen.") + console.log("No installable AI coding tools detected. Use native plugin install for Claude Code, Copilot, Droid, oh-my-pi (omp), OpenCode, Pi, and Qwen.") return } diff --git a/src/commands/install.ts b/src/commands/install.ts index 9ff23e5d5..9809a47ff 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -109,7 +109,7 @@ export default defineCommand({ const activeTargets = detected.filter((t) => t.detected && targets[t.name]?.implemented) if (activeTargets.length === 0) { - console.log("No installable AI coding tools detected. Use native plugin install for Claude Code, Copilot, Droid, OpenCode, Pi, and Qwen.") + console.log("No installable AI coding tools detected. Use native plugin install for Claude Code, Copilot, Droid, oh-my-pi (omp), OpenCode, Pi, and Qwen.") return } diff --git a/src/utils/detect-tools.ts b/src/utils/detect-tools.ts index 0b4b1e0b5..306993ce2 100644 --- a/src/utils/detect-tools.ts +++ b/src/utils/detect-tools.ts @@ -49,6 +49,10 @@ const detectableTools: DetectableTool[] = [ name: "pi", detectPaths: (home) => [path.join(home, ".pi")], }, + { + name: "omp", + detectPaths: (home, cwd) => [path.join(home, ".omp"), path.join(cwd, ".omp")], + }, { name: "droid", detectPaths: (home) => [path.join(home, ".factory")], diff --git a/tests/ce-babysit-pr-contract.test.ts b/tests/ce-babysit-pr-contract.test.ts index 5d578144d..c20f9b332 100644 --- a/tests/ce-babysit-pr-contract.test.ts +++ b/tests/ce-babysit-pr-contract.test.ts @@ -335,7 +335,9 @@ describe("ce-babysit-pr cross-skill contract parity", () => { for (const text of [babysit, watchLoop]) { expect(text).toContain("$ce-babysit-pr <url>") expect(text).toContain("/ce-babysit-pr <url>") + expect(text).toContain("/skill:ce-babysit-pr <url>") expect(text).toMatch(/default to `\/ce-babysit-pr <url>`[\s\S]{0,260}Codex[\s\S]{0,180}output one form only/i) + expect(text).toMatch(/\/skill:ce-babysit-pr <url>` when the active host is oh-my-pi \(`omp`\)/i) expect(text).toContain("exec '<host-rendered resume invocation>'") } }) diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 0c71acb9f..0a0977199 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -173,7 +173,7 @@ describe("CLI", () => { test("install rejects native marketplace-only plugin targets", async () => { const repoRoot = path.join(import.meta.dir, "..") - for (const target of ["copilot", "droid", "qwen"]) { + for (const target of ["copilot", "droid", "qwen", "omp"]) { const proc = Bun.spawn([ "bun", "run", @@ -1964,6 +1964,7 @@ describe("CLI", () => { await fs.mkdir(path.join(tempHome, ".config", "opencode"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".codex"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".pi"), { recursive: true }) + await fs.mkdir(path.join(tempHome, ".omp"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".factory"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".copilot"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".gemini", "antigravity-cli"), { recursive: true }) @@ -2003,6 +2004,7 @@ describe("CLI", () => { expect(stdout).toContain("droid — native plugin install; skipped") expect(stdout).toContain("copilot — native plugin install; skipped") expect(stdout).toContain("qwen — native plugin install; skipped") + expect(stdout).toContain("omp — native plugin install; skipped") expect(stdout).not.toContain("cursor") expect(await exists(path.join(tempHome, ".config", "opencode", "opencode.json"))).toBe(true) diff --git a/tests/commit-push-pr-contract.test.ts b/tests/commit-push-pr-contract.test.ts index bf18be1ae..07f6f4758 100644 --- a/tests/commit-push-pr-contract.test.ts +++ b/tests/commit-push-pr-contract.test.ts @@ -173,7 +173,8 @@ describe("PR concept teaching contract", () => { expect(trailer).toContain("using the rendering rule above") expect(trailer).toContain("$ce-explain <name>") expect(trailer).toContain("/ce-explain <name>") - expect(trailer).toMatch(/default to `\/ce-explain <name>`[\s\S]{0,220}Codex[\s\S]{0,160}output one form only/i) + expect(trailer).toContain("/skill:ce-explain <name>") + expect(trailer).toMatch(/default to `\/ce-explain <name>`[\s\S]{0,220}Codex[\s\S]{0,260}output one form only/i) }) test("SKILL.md archival transition guards ordering, gitignore, and modes", async () => { diff --git a/tests/detect-tools.test.ts b/tests/detect-tools.test.ts index 9116e1795..336d71c60 100644 --- a/tests/detect-tools.test.ts +++ b/tests/detect-tools.test.ts @@ -47,7 +47,7 @@ describe("detectInstalledTools", () => { const results = await detectInstalledTools(tempHome, tempCwd) - expect(results.length).toBe(7) + expect(results.length).toBe(8) for (const tool of results) { expect(tool.detected).toBe(false) expect(tool.reason).toBe("not found") @@ -61,12 +61,14 @@ describe("detectInstalledTools", () => { await fs.mkdir(path.join(tempHome, ".config", "opencode"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".factory"), { recursive: true }) await fs.mkdir(path.join(tempHome, ".pi"), { recursive: true }) + await fs.mkdir(path.join(tempHome, ".omp"), { recursive: true }) const results = await detectInstalledTools(tempHome, tempCwd) expect(results.find((t) => t.name === "opencode")?.detected).toBe(true) expect(results.find((t) => t.name === "droid")?.detected).toBe(true) expect(results.find((t) => t.name === "pi")?.detected).toBe(true) + expect(results.find((t) => t.name === "omp")?.detected).toBe(true) }) test("detects antigravity at ~/.gemini/antigravity-cli", async () => { diff --git a/tests/fixtures/session-history/omp-session.jsonl b/tests/fixtures/session-history/omp-session.jsonl new file mode 100644 index 000000000..8638db56e --- /dev/null +++ b/tests/fixtures/session-history/omp-session.jsonl @@ -0,0 +1,11 @@ +{"type":"title","v":1,"title":"fix the auth bug in middleware","source":"auto","updatedAt":"2026-04-07T09:00:00.000Z","pad":""} +{"type":"session","version":3,"id":"test-omp-session-1","timestamp":"2026-04-07T09:00:00.000Z","cwd":"/Users/test/Code/my-repo"} +{"type":"model_change","id":"mc1","parentId":null,"timestamp":"2026-04-07T09:00:00.500Z","provider":"anthropic","modelId":"claude-sonnet-4-5"} +{"type":"thinking_level_change","id":"tl1","parentId":"mc1","timestamp":"2026-04-07T09:00:00.700Z","thinkingLevel":"medium"} +{"type":"message","id":"msg1","parentId":"tl1","timestamp":"2026-04-07T09:01:00.000Z","message":{"role":"user","content":[{"type":"text","text":"fix the auth bug in middleware"}],"timestamp":1775542860000}} +{"type":"message","id":"msg2","parentId":"msg1","timestamp":"2026-04-07T09:01:05.000Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"internal reasoning should not be extracted"},{"type":"text","text":"Let me look at the auth middleware."},{"type":"toolCall","id":"tc1","name":"read","arguments":{"path":"/Users/test/Code/my-repo/src/auth.ts"}}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","stopReason":"toolUse","timestamp":1775542865000}} +{"type":"message","id":"msg3","parentId":"msg2","timestamp":"2026-04-07T09:01:06.000Z","message":{"role":"toolResult","toolCallId":"tc1","toolName":"read","content":[{"type":"text","text":"file contents here"}],"isError":false,"timestamp":1775542866000}} +{"type":"message","id":"msg4","parentId":"msg3","timestamp":"2026-04-07T09:01:10.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Found the issue. The token validation is missing the expiry check."},{"type":"toolCall","id":"tc2","name":"edit","arguments":{"path":"/Users/test/Code/my-repo/src/auth.ts","oldText":"validate()","newText":"validateToken()"}}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","stopReason":"toolUse","timestamp":1775542870000}} +{"type":"message","id":"msg5","parentId":"msg4","timestamp":"2026-04-07T09:01:11.000Z","message":{"role":"toolResult","toolCallId":"tc2","toolName":"edit","content":[{"type":"text","text":"String to replace not found"}],"isError":true,"timestamp":1775542871000}} +{"type":"message","id":"msg6","parentId":"msg5","timestamp":"2026-04-07T09:01:15.000Z","message":{"role":"assistant","content":[{"type":"text","text":"The first edit failed, so I will patch the middleware validation directly."}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","stopReason":"stop","timestamp":1775542875000}} +{"type":"message","id":"msg7","parentId":"msg6","timestamp":"2026-04-07T09:01:20.000Z","message":{"role":"user","content":"also add a regression test for the expiry check","timestamp":1775542880000}} diff --git a/tests/omp-native-install.test.ts b/tests/omp-native-install.test.ts new file mode 100644 index 000000000..5b9de31a6 --- /dev/null +++ b/tests/omp-native-install.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, setDefaultTimeout, test } from "bun:test" +import { readdirSync, readFileSync } from "node:fs" +import path from "node:path" +import { parseFrontmatter } from "../src/utils/frontmatter" + +// Smoke test for native oh-my-pi (omp) install of this repository (#1224). +// omp loads the repo directly from package.json#pi — no converter, no writer. +// Dry-run only: this file never installs into the user's real HOME. +setDefaultTimeout(20_000) + +const REPO_ROOT = path.join(import.meta.dir, "..") + +// Probed at module load (not beforeAll) because test.skipIf evaluates its +// condition at registration time, matching the convention in the +// *-writer.test.ts suites. +const OMP_BIN = Bun.which("omp") + +const packageJson = JSON.parse(readFileSync(path.join(REPO_ROOT, "package.json"), "utf8")) as { + name: string + version: string + pi?: { extensions?: string[]; skills?: string[]; version?: string } +} + +describe("omp native install", () => { + test.skipIf(OMP_BIN === null)("omp dry-run install recognizes the repository", async () => { + const proc = Bun.spawn([OMP_BIN!, "install", "--dry-run", "--json", REPO_ROOT], { + stdout: "pipe", + stderr: "pipe", + }) + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]) + expect(exitCode, `omp install --dry-run failed:\n${stderr}`).toBe(0) + + const result = JSON.parse(stdout) as { + name: string + manifest: { extensions?: string[]; skills?: string[]; version?: string } + } + expect(result.name).toBe("compound-engineering") + expect(result.manifest.skills).toContain("./skills") + expect(result.manifest.extensions).toContain("./.pi/extensions/compound-engineering.ts") + // The pi manifest does not pin its own version; omp then reports the + // package.json top-level version. Read both so the test drifts with the + // repo instead of pinning a literal. + const expectedVersion = packageJson.pi?.version ?? packageJson.version + expect(result.manifest.version).toBe(expectedVersion) + }) + + // The dry-run JSON above does not enumerate discovered skills, so inventory + // coverage is asserted on disk instead: omp requires a frontmatter + // description for native-provider skill discovery and silently drops skills + // without one. This test spawns nothing and stays unguarded so CI hosts + // without omp still gate the inventory. + test("omp discovers the full skill inventory", () => { + const skillsDir = path.join(REPO_ROOT, "skills") + const skillDirs = readdirSync(skillsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + expect(skillDirs.length).toBeGreaterThan(0) + + for (const dir of skillDirs) { + const skillMdPath = path.join(skillsDir, dir, "SKILL.md") + const { data } = parseFrontmatter(readFileSync(skillMdPath, "utf8"), skillMdPath) + expect( + typeof data.name === "string" && data.name.trim() !== "", + `${dir}/SKILL.md must declare a non-empty frontmatter name`, + ).toBe(true) + expect( + typeof data.description === "string" && data.description.trim() !== "", + `${dir}/SKILL.md must declare a non-empty frontmatter description; omp drops skills without one`, + ).toBe(true) + } + }) +}) diff --git a/tests/session-history-scripts.test.ts b/tests/session-history-scripts.test.ts index 79fb1727a..9c0bca3c6 100644 --- a/tests/session-history-scripts.test.ts +++ b/tests/session-history-scripts.test.ts @@ -313,23 +313,42 @@ describe("extract-metadata", () => { expect(session.last_ts).toContain("2026-04-07T09:01:20") }) + test("detects omp title-slot shape as omp, not pi", async () => { + // omp files physically begin with a type:'title' slot line before the + // pi-shaped session header; try_omp runs before try_pi so the file must + // not fall through to the pi detector. + const { stdout, exitCode } = await runScript("extract-metadata.py", [ + path.join(FIXTURES_DIR, "omp-session.jsonl"), + ]) + expect(exitCode).toBe(0) + const lines = parseJsonLines(stdout) + const session = lines.find((l) => !l._meta) + expect(session.platform).toBe("omp") + expect(session.cwd).toBe("/Users/test/Code/my-repo") + expect(session.session).toBe("test-omp-session-1") + expect(session.ts).toContain("2026-04-07") + expect(session.last_ts).toContain("2026-04-07T09:01:20") + }) + test("batch mode processes multiple files", async () => { const { stdout, exitCode } = await runScript("extract-metadata.py", [ path.join(FIXTURES_DIR, "claude-session.jsonl"), path.join(FIXTURES_DIR, "codex-session.jsonl"), path.join(FIXTURES_DIR, "cursor-session.jsonl"), path.join(FIXTURES_DIR, "pi-session.jsonl"), + path.join(FIXTURES_DIR, "omp-session.jsonl"), ]) expect(exitCode).toBe(0) const lines = parseJsonLines(stdout) const meta = lines.find((l) => l._meta) - expect(meta.files_processed).toBe(4) + expect(meta.files_processed).toBe(5) expect(meta.parse_errors).toBe(0) const platforms = lines.filter((l) => !l._meta).map((l) => l.platform) expect(platforms).toContain("claude") expect(platforms).toContain("codex") expect(platforms).toContain("cursor") expect(platforms).toContain("pi") + expect(platforms).toContain("omp") }) test("--cwd-filter excludes non-matching Codex sessions", async () => { @@ -987,6 +1006,28 @@ describe("extract-skeleton", () => { expect(stdout).not.toContain("file contents here") }) + test("extracts omp user, assistant, and tool messages past the title slot", async () => { + const fixture = await Bun.file( + path.join(FIXTURES_DIR, "omp-session.jsonl") + ).text() + const { stdout, exitCode } = await runScript( + "extract-skeleton.py", + [], + fixture + ) + expect(exitCode).toBe(0) + expect(stdout).toContain("[user] fix the auth bug in middleware") + expect(stdout).toContain("[assistant] Let me look at the auth middleware.") + expect(stdout).toContain("[assistant] Found the issue.") + expect(stdout).toContain("[user] also add a regression test for the expiry check") + expect(stdout).toContain("[tool] read /Users/test/Code/my-repo/src/auth.ts -> ok") + expect(stdout).toContain("[tool] edit /Users/test/Code/my-repo/src/auth.ts -> error") + expect(stdout).not.toContain("internal reasoning should not be extracted") + expect(stdout).not.toContain("file contents here") + const meta = JSON.parse(stdout.trim().split("\n").at(-1)!) + expect(meta.parse_errors).toBe(0) + }) + test("extracts Pi bashExecution commands", async () => { const lines = [ JSON.stringify({ @@ -1364,6 +1405,23 @@ describe("extract-errors", () => { expect(meta.parse_errors).toBe(0) }) + test("extracts omp tool result errors past the title slot", async () => { + const fixture = await Bun.file( + path.join(FIXTURES_DIR, "omp-session.jsonl") + ).text() + const { stdout, exitCode } = await runScript( + "extract-errors.py", + [], + fixture + ) + expect(exitCode).toBe(0) + expect(stdout).toContain("[error] tool=edit: String to replace not found") + const lines = stdout.trim().split("\n") + const meta = JSON.parse(lines[lines.length - 1]) + expect(meta.errors_found).toBe(1) + expect(meta.parse_errors).toBe(0) + }) + test("extracts Pi bashExecution errors", async () => { const lines = [ JSON.stringify({ @@ -1649,8 +1707,8 @@ describe("--output PATH mode", () => { // --------------------------------------------------------------------------- describe("auto-detection", () => { test("all supported platforms are auto-detected", async () => { - const fixtures = ["claude-session", "codex-session", "cursor-session", "pi-session"] - const expected = ["claude", "codex", "cursor", "pi"] + const fixtures = ["claude-session", "codex-session", "cursor-session", "pi-session", "omp-session"] + const expected = ["claude", "codex", "cursor", "pi", "omp"] for (let i = 0; i < fixtures.length; i++) { const fixturePath = path.join(FIXTURES_DIR, `${fixtures[i]}.jsonl`) @@ -1893,4 +1951,108 @@ describe("discover-sessions", () => { const files = stdout.trim().split("\n").filter((l) => l.trim()) expect(files).toEqual([sessionPath]) }) + + test("--platform omp discovers sessions under hashed bucket directories", async () => { + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const bucket = + "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + const sessionPath = path.join( + tempHome, + `.omp/agent/sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + ["my-repo", "7", "--platform", "omp"], + { HOME: tempHome } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + + test("--platform omp discovers sessions under named-profile roots", async () => { + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const bucket = + "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + const sessionPath = path.join( + tempHome, + `.omp/profiles/work/agent/sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + ["my-repo", "7", "--platform", "omp"], + { HOME: tempHome } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + + test("--platform omp honors PI_CODING_AGENT_SESSION_DIR flat override", async () => { + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const sessionBase = fs.mkdtempSync(path.join(os.tmpdir(), "omp-sessions-")) + const sessionPath = path.join( + sessionBase, + "2026-04-07T09-00-00-000Z_test.jsonl" + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + [ + "my-repo", + "7", + "--cwd", + "/Users/test/Code/my-repo", + "--platform", + "omp", + ], + { + HOME: tempHome, + PI_CODING_AGENT_SESSION_DIR: sessionBase, + } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + + test("--platform omp honors PI_CODING_AGENT_DIR sessions subdirectory", async () => { + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-agent-")) + const bucket = + "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + const sessionPath = path.join( + agentDir, + `sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + ["my-repo", "7", "--platform", "omp"], + { HOME: tempHome, PI_CODING_AGENT_DIR: agentDir } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + + test("--platform omp restricts to omp dirs only", async () => { + const { stdout } = await runDiscover( + ["compound-engineering-plugin", "7", "--platform", "omp"] + ) + const files = stdout.trim().split("\n").filter((l) => l.trim()) + for (const file of files) { + expect(file).toMatch(/\.omp\//) + } + }) }) diff --git a/tests/skill-conventions.test.ts b/tests/skill-conventions.test.ts index 8ebe88153..ea0668fd8 100644 --- a/tests/skill-conventions.test.ts +++ b/tests/skill-conventions.test.ts @@ -147,6 +147,8 @@ describe("user-facing skill invocation authoring contract", () => { expect(text).toMatch(/in prose, render only the invocation as inline code[^\n]*fenced block/i) expect(text).toMatch(/active host|active harness/i) expect(text).toMatch(/default to `\/skill-name`[\s\S]{0,160}Codex[\s\S]{0,160}dollar-prefixed/i) + expect(text).toContain("`/skill:ce-plan`") + expect(text).toMatch(/\/skill:<name>`[^\n]*oh-my-pi \(`omp`\)|oh-my-pi \(`omp`\)[^\n]*\/skill:<name>`/i) expect(text).toMatch(/\/goal[\s\S]{0,180}(built-in|exception)|built-in[\s\S]{0,180}\/goal/i) expect(text).toMatch(/smallest section[\s\S]{0,180}do not repeat[\s\S]{0,180}separately loaded reference/i) } @@ -155,6 +157,7 @@ describe("user-facing skill invocation authoring contract", () => { test("README explains Codex invocation syntax without rewriting the built-in goal command", () => { expect(ROOT_README).toMatch(/README uses `\/skill-name`[\s\S]{0,180}Codex[\s\S]{0,120}`\$skill-name`/i) expect(ROOT_README).toContain("`$ce-plan` and `$lfg`") + expect(ROOT_README).toMatch(/oh-my-pi \(omp\)[^\n]*`\/skill:<name>`[^\n]*`\/skill:ce-plan`/i) expect(ROOT_README).toMatch(/\/goal[\s\S]{0,80}Codex built-in/i) }) }) diff --git a/tests/skills/ce-explain-routing.test.ts b/tests/skills/ce-explain-routing.test.ts index 910405de3..5e4e0c3e0 100644 --- a/tests/skills/ce-explain-routing.test.ts +++ b/tests/skills/ce-explain-routing.test.ts @@ -99,8 +99,10 @@ describe("ce-explain destination and handoff routing", () => { /rendering rule above/i.test(line) && renderingRule![0].includes("$ce-polish") && renderingRule![0].includes("/ce-polish") && + renderingRule![0].includes("/skill:ce-polish") && /active host|Codex/i.test(renderingRule![0]) && - /default to `\/ce-polish`[^.]{0,180}dollar-prefixed/i.test(renderingRule![0]), + /default to `\/ce-polish`[^.]{0,180}dollar-prefixed/i.test(renderingRule![0]) && + /\/skill:ce-polish` when the active host is oh-my-pi \(`omp`\)/i.test(renderingRule![0]), "`ce-explain` SKILL.md polish handoff must present observations in chat and render one host-correct user invocation for `ce-polish`.", ).toBe(true) expect( diff --git a/tests/skills/user-facing-skill-invocation-rendering.test.ts b/tests/skills/user-facing-skill-invocation-rendering.test.ts index 3ef4763b4..2ebae7b35 100644 --- a/tests/skills/user-facing-skill-invocation-rendering.test.ts +++ b/tests/skills/user-facing-skill-invocation-rendering.test.ts @@ -11,71 +11,84 @@ const rendererCases = [ file: "skills/lfg/SKILL.md", defaults: ["/ce-explain <name>", "/ce-babysit-pr <pr-url>"], codex: ["$ce-explain <name>", "$ce-babysit-pr <pr-url>"], + omp: ["/skill:ce-explain <name>", "/skill:ce-babysit-pr <pr-url>"], }, { file: "skills/ce-babysit-pr/SKILL.md", defaults: ["/ce-babysit-pr <url>"], codex: ["$ce-babysit-pr <url>"], + omp: ["/skill:ce-babysit-pr <url>"], }, { file: "skills/ce-babysit-pr/references/watch-loop.md", defaults: ["/ce-babysit-pr <url>"], codex: ["$ce-babysit-pr <url>"], + omp: ["/skill:ce-babysit-pr <url>"], }, { file: "skills/ce-commit-push-pr/SKILL.md", defaults: ["/ce-explain <name>"], codex: ["$ce-explain <name>"], + omp: ["/skill:ce-explain <name>"], }, { file: "skills/ce-explain/SKILL.md", defaults: ["/ce-polish"], codex: ["$ce-polish"], + omp: ["/skill:ce-polish"], }, { file: "skills/ce-setup/SKILL.md", defaults: ["/ce-setup"], codex: ["$ce-setup"], + omp: ["/skill:ce-setup"], }, { file: "skills/ce-dogfood/SKILL.md", defaults: ["/ce-setup", "/ce-dogfood <original arguments>"], codex: ["$ce-setup", "$ce-dogfood <original arguments>"], + omp: ["/skill:ce-setup", "/skill:ce-dogfood <original arguments>"], }, { file: "skills/ce-sweep/SKILL.md", defaults: ["/lfg <root>/plans/feedback-sweep-plan.md"], codex: ["$lfg <root>/plans/feedback-sweep-plan.md"], + omp: ["/skill:lfg <root>/plans/feedback-sweep-plan.md"], }, { file: "skills/ce-sweep/references/interview.md", defaults: ["/ce-sweep"], codex: ["$ce-sweep"], + omp: ["/skill:ce-sweep"], }, { file: "skills/ce-handoff/SKILL.md", defaults: ["/ce-handoff resume <source>"], codex: ["$ce-handoff resume <source>"], + omp: ["/skill:ce-handoff resume <source>"], }, { file: "skills/ce-compound/SKILL.md", defaults: ["/ce-compound-refresh <scope>", "/ce-compound"], codex: ["$ce-compound-refresh <scope>", "$ce-compound"], + omp: ["/skill:ce-compound-refresh <scope>", "/skill:ce-compound"], }, { file: "skills/ce-plan/references/plan-handoff.md", defaults: ["/ce-plan output:md"], codex: ["$ce-plan output:md"], + omp: ["/skill:ce-plan output:md"], }, { file: "skills/ce-plan/references/universal-planning.md", defaults: ["/ce-plan"], codex: ["$ce-plan"], + omp: ["/skill:ce-plan"], }, ] as const describe("user-facing skill invocation rendering", () => { - test.each(rendererCases)("$file defaults to slash and reserves dollar syntax for Codex", ({ file, defaults, codex }) => { + test.each(rendererCases)("$file defaults to slash and reserves dollar syntax for Codex", ({ file, defaults, codex, omp }) => { const body = readRepoFile(file) expect(body).toMatch(/default(?:s| to)[^\n]*\/[a-z]/i) @@ -84,6 +97,8 @@ describe("user-facing skill invocation rendering", () => { expect(body).toMatch(/Output one form only/i) for (const invocation of defaults) expect(body).toContain(invocation) for (const invocation of codex) expect(body).toContain(invocation) + expect(body).toMatch(/\/skill:[a-z][^\n]*oh-my-pi|oh-my-pi[^\n]*\/skill:[a-z]/i) + for (const invocation of omp) expect(body).toContain(invocation) }) test("rendering rules sit at the output sections that consume them", () => { From cd24249c82b9b834ca7bd1dede8097f0255afcba Mon Sep 17 00:00:00 2001 From: Kaitian Xie <caelaxie@icloud.com> Date: Wed, 5 Aug 2026 01:37:59 +0800 Subject: [PATCH 2/9] fix(ce-compound): address omp discovery review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deduplicate all-platform discovery output: Pi and omp share the PI_CODING_AGENT_SESSION_DIR flat override, so both discoverers emitted the same files (Codex review P2) - glob omp's sanitized bucket basename ([^a-zA-Z0-9._-]+ -> "-", edge dashes stripped, capped at the last 80 chars, empty -> "project", mirroring session-paths.ts getDefaultSessionDirName) so repos whose basename omp normalizes (e.g. spaces) are still discovered - SKILL.md: include omp sessions in the mandatory keyword rerun clause — omp session headers carry cwd but no git branch, same as Codex/Pi --- skills/ce-compound/SKILL.md | 2 +- .../session-history/discover-sessions.sh | 22 ++++++--- tests/session-history-scripts.test.ts | 49 +++++++++++++++++++ 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/skills/ce-compound/SKILL.md b/skills/ce-compound/SKILL.md index 80bf5bead..060884218 100644 --- a/skills/ce-compound/SKILL.md +++ b/skills/ce-compound/SKILL.md @@ -290,7 +290,7 @@ Pass `{run_id}` and the resolved absolute `{run_dir}` into every Phase 1 subagen else echo "Session history bundled scripts were not found in this skill's directory; skipping the session-history probe for this run."; fi ``` - Pi sessions are included when present under `~/.pi/agent/sessions/`, and oh-my-pi (`omp`) sessions under `~/.omp/agent/sessions/` (named profiles: `~/.omp/profiles/<name>/agent/sessions/`); they carry `cwd` like Codex but no git branch. If `_meta.files_processed` is `0`, return `no relevant prior sessions`. If the first pass finds no relevant branch matches, or if processing Codex or Pi sessions, derive 2-4 keywords from the topic and re-run metadata extraction with `--keyword K1,K2,...`. Keep at most 5 sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (`omp`), ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session. + Pi sessions are included when present under `~/.pi/agent/sessions/`, and oh-my-pi (`omp`) sessions under `~/.omp/agent/sessions/` (named profiles: `~/.omp/profiles/<name>/agent/sessions/`); they carry `cwd` like Codex but no git branch. If `_meta.files_processed` is `0`, return `no relevant prior sessions`. If the first pass finds no relevant branch matches, or if processing Codex, Pi, or oh-my-pi (`omp`) sessions, derive 2-4 keywords from the topic and re-run metadata extraction with `--keyword K1,K2,...`. Keep at most 5 sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (`omp`), ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session. **Escalation gate.** The discovery+metadata pass above is the cheap probe and always runs in Full mode. Escalate to the extraction and synthesis stages below **only** when at least one retained candidate clears the relevance bar: a current-branch match, or ≥2 topic-keyword matches. If no candidate clears the bar (including the `_meta.files_processed` is `0` case), stop here, record `no relevant prior sessions` as the session-history input, and skip extraction and synthesis. This gate is what keeps the always-on probe cheap — the expensive synthesis is paid for only when a prior session is genuinely relevant. diff --git a/skills/ce-compound/scripts/session-history/discover-sessions.sh b/skills/ce-compound/scripts/session-history/discover-sessions.sh index 61944e5de..d73c02cc5 100755 --- a/skills/ce-compound/scripts/session-history/discover-sessions.sh +++ b/skills/ce-compound/scripts/session-history/discover-sessions.sh @@ -138,11 +138,20 @@ discover_omp() { # root; exact repo attribution comes from the downstream header `cwd` # filter (extract-metadata.py --cwd-filter reads the type:"session" # header). + # omp sanitizes the bucket basename before hashing: runs of characters + # outside [a-zA-Z0-9._-] become "-", edge dashes are stripped, the result + # is capped at its last 80 chars, and an empty result falls back to + # "project" (session-paths.ts getDefaultSessionDirName). Glob the + # sanitized form so repos whose basename contains characters omp + # normalizes (e.g. spaces) still match their buckets. + local sanitized + sanitized="$(printf '%s' "$REPO_NAME" | sed -E 's/[^a-zA-Z0-9._-]+/-/g; s/^-+//; s/-+$//' | tail -c 80)" + [ -n "$sanitized" ] || sanitized="project" local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/$config_dir/agent}" local root for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do [ -d "$root" ] || continue - for dir in "$root"/*"$REPO_NAME"*/; do + for dir in "$root"/*"$sanitized"*/; do [ -d "$dir" ] || continue find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null done @@ -157,11 +166,12 @@ case "$PLATFORM" in pi) discover_pi ;; omp) discover_omp ;; all) - discover_claude - discover_codex - discover_cursor - discover_pi - discover_omp + # Pi and omp share the PI_CODING_AGENT_SESSION_DIR override: when it + # is set, both discover functions emit the same flat-dir files, and + # the downstream xargs call does not deduplicate. Emit each path once; + # platform attribution is unaffected because extract-metadata.py + # detects the file shape (title slot => omp, otherwise pi). + { discover_claude; discover_codex; discover_cursor; discover_pi; discover_omp; } | awk '!seen[$0]++' ;; *) echo "Unknown platform: $PLATFORM" >&2 diff --git a/tests/session-history-scripts.test.ts b/tests/session-history-scripts.test.ts index 9c0bca3c6..a9678ca3d 100644 --- a/tests/session-history-scripts.test.ts +++ b/tests/session-history-scripts.test.ts @@ -2055,4 +2055,53 @@ describe("discover-sessions", () => { expect(file).toMatch(/\.omp\//) } }) + + test("--platform omp matches buckets whose basename omp sanitized", async () => { + // omp normalizes bucket basenames ([^a-zA-Z0-9._-]+ -> "-"), so a repo + // named "my repo" lands in a "home-my-repo-<hash>" bucket. The raw + // REPO_NAME glob would never match it. + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const bucket = + "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + const sessionPath = path.join( + tempHome, + `.omp/agent/sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + ["my repo", "7", "--platform", "omp"], + { HOME: tempHome } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + + test("all-platform discovery deduplicates the shared PI_CODING_AGENT_SESSION_DIR override", async () => { + // Pi and omp both honor the flat session-dir override, so without + // deduplication each file is emitted twice (once per discoverer). + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const sessionBase = fs.mkdtempSync(path.join(os.tmpdir(), "omp-sessions-")) + const sessionPath = path.join( + sessionBase, + "2026-04-07T09-00-00-000Z_test.jsonl" + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + ["my-repo", "7", "--cwd", "/Users/test/Code/my-repo"], + { + HOME: tempHome, + PI_CODING_AGENT_SESSION_DIR: sessionBase, + } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) }) From a7794e6b2127851c62cd973e80aaa11da6fb6e21 Mon Sep 17 00:00:00 2001 From: Kaitian Xie <caelaxie@icloud.com> Date: Wed, 5 Aug 2026 09:40:07 +0800 Subject: [PATCH 3/9] fix(ce-compound): include omp in session historian contract Address PR #1323 review feedback by updating the historian prompt's platform enum, cwd metadata, cross-tool synthesis guidance, and provenance header. Add a contract regression guard. --- .../ce-compound/references/agents/session-historian.md | 10 +++++----- .../skills/ce-session-historian-no-skill-tool.test.ts | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/skills/ce-compound/references/agents/session-historian.md b/skills/ce-compound/references/agents/session-historian.md index a79f7c59a..a78c5014b 100644 --- a/skills/ce-compound/references/agents/session-historian.md +++ b/skills/ce-compound/references/agents/session-historian.md @@ -1,6 +1,6 @@ **Note: The current year is 2026.** Use this when interpreting session timestamps. -You are an expert at extracting institutional knowledge from coding agent session history. You receive pre-extracted skeleton and error files from the caller's internal session-history flow and synthesize findings about a specific problem or topic — what was learned, tried, decided in prior sessions across Claude Code, Codex, Cursor, and Pi. +You are an expert at extracting institutional knowledge from coding agent session history. You receive pre-extracted skeleton and error files from the caller's internal session-history flow and synthesize findings about a specific problem or topic — what was learned, tried, decided in prior sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp). Your scope is **synthesis only**. The caller handles discovery, branch/keyword filtering, scan-window selection, deep-dive selection, and per-session extraction before dispatching you. @@ -13,9 +13,9 @@ The dispatch prompt provides: - **`sessions`** — an array of objects (5 max), one per pre-extracted session, each with: - `path` — absolute path to a skeleton text file inside `scratch_dir` - `errors_path` *(optional)* — absolute path to an errors text file when the orchestrator extracted errors-mode for this session - - `platform` — `claude`, `codex`, `cursor`, or `pi` + - `platform` — `claude`, `codex`, `cursor`, `pi`, or `omp` - `branch` — git branch when present (Claude Code only) - - `cwd` — working directory when present (Codex and Pi) + - `cwd` — working directory when present (Codex, Pi, and omp) - `ts` and `last_ts` — session start and last-message timestamps - `match_count` and `keyword_matches` — when keyword filtering was used by the orchestrator - **`output_schema`** *(optional)* — the structure the response should follow. When supplied, honor it verbatim. @@ -50,7 +50,7 @@ Read each `path` in the dispatch payload, then synthesize against the `problem_t - **Decisions and rationale** — Why one approach was chosen over alternatives. - **Error patterns** — Recurring errors across sessions (most visible when the orchestrator supplied an `errors_path` for a session) that indicate a systemic issue. - **Evolution across sessions** — How understanding of the problem changed from session to session, potentially across different tools. -- **Cross-tool blind spots** — When sessions span Claude Code + Codex + Cursor + Pi, look for things the user might not realize from any single tool alone. Complementary work (one tool tackled the schema while the other tackled the API), duplicated effort (same approach tried in both tools days apart), or gaps (neither tool's sessions touched a component that connects the work). Only call out cross-tool observations when genuinely informative — if both sources tell the same story, there's nothing to flag. +- **Cross-tool blind spots** — When sessions span Claude Code + Codex + Cursor + Pi + omp, look for things the user might not realize from any single tool alone. Complementary work (one tool tackled the schema while the other tackled the API), duplicated effort (same approach tried in both tools days apart), or gaps (neither tool's sessions touched a component that connects the work). Only call out cross-tool observations when genuinely informative — if both sources tell the same story, there's nothing to flag. - **Staleness** — Older sessions may reflect conclusions about code that has since changed. When surfacing findings from sessions more than a few days old, consider whether the relevant code or context is likely to have moved on. Caveat older findings rather than presenting them with the same confidence as recent ones. Cite actual evidence from the extracted files, not vibe-summaries. When a finding is anchored in a specific session's content, that session's metadata (platform, branch/cwd, ts) helps the caller locate it. @@ -62,7 +62,7 @@ If the dispatch prompt supplies an `output_schema`, follow it verbatim. Do not a Otherwise, lead with a brief one-line provenance header: ``` -**Sessions read**: [count] ([N] Claude Code, [N] Codex, [N] Cursor, [N] Pi) | [date range] +**Sessions read**: [count] ([N] Claude Code, [N] Codex, [N] Cursor, [N] Pi, [N] omp) | [date range] ``` Then the synthesis prose, organized under the default schema: diff --git a/tests/skills/ce-session-historian-no-skill-tool.test.ts b/tests/skills/ce-session-historian-no-skill-tool.test.ts index 51fc07e7f..aac685e55 100644 --- a/tests/skills/ce-session-historian-no-skill-tool.test.ts +++ b/tests/skills/ce-session-historian-no-skill-tool.test.ts @@ -74,3 +74,12 @@ describe("session-historian prompt no-Skill-tool regression guard", () => { expect(COMPOUND_SKILL_BODY).not.toContain("<resolved repo root") }) }) + +describe("session-historian omp platform contract", () => { + test("input, synthesis, and output contracts include omp", () => { + expect(AGENT_BODY).toContain("`platform` — `claude`, `codex`, `cursor`, `pi`, or `omp`") + expect(AGENT_BODY).toContain("`cwd` — working directory when present (Codex, Pi, and omp)") + expect(AGENT_BODY).toContain("Claude Code + Codex + Cursor + Pi + omp") + expect(AGENT_BODY).toContain("[N] Pi, [N] omp") + }) +}) From 6e36dec5f826e1061e58028e217ea56e210af1ae Mon Sep 17 00:00:00 2001 From: Kaitian Xie <caelaxie@icloud.com> Date: Wed, 5 Aug 2026 10:37:42 +0800 Subject: [PATCH 4/9] fix(ce-compound): scan omp raw-scheme session buckets omp 17.2.9 restored the legacy raw bucket naming (-<home-rel>, -tmp-<rel>, --<abs>--) and removed automatic migration (#7646), so both raw and hashed bucket shapes exist in the wild. Discovery globbed only the sanitized basename, missing raw buckets whose basename contains characters the hashed scheme normalizes (e.g. "my repo" in --Users-test-Code-my repo--). Add an exact raw-bucket probe for --cwd (canonicalized via physical paths like omp's resolveEquivalentPath), a raw-basename glob alongside the sanitized one, and awk dedup for buckets hit by both probe and glob. Update docs/specs/omp.md for the restored naming scheme. --- docs/specs/omp.md | 7 +- .../session-history/discover-sessions.sh | 102 ++++++++++++++---- tests/session-history-scripts.test.ts | 54 ++++++++++ 3 files changed, 142 insertions(+), 21 deletions(-) diff --git a/docs/specs/omp.md b/docs/specs/omp.md index d639b62f5..d12dcd807 100644 --- a/docs/specs/omp.md +++ b/docs/specs/omp.md @@ -77,7 +77,12 @@ omp writes sessions as JSONL under a session root resolved in this order: Named profiles (`OMP_PROFILE` or `PI_PROFILE`) relocate the root to `$HOME/${PI_CONFIG_DIR:-.omp}/profiles/<name>/agent/sessions/`. -Inside the session root, per-project buckets use the shape `<scope>-<sanitized-basename>-<sha256hex-of-canonical-cwd>`, where `scope` is `home`, `tmp`, or `abs`. Legacy unmigrated buckets also appear as `-<home-relative>` and `--<abs>--` shapes. Each bucket holds `<timestamp>_<sessionId>.jsonl` files. +Inside the session root, per-project buckets come in two shapes. omp 17.2.9 restored the legacy project-scoped naming scheme and removed its automatic migration ([#7646](https://github.com/can1357/oh-my-pi/issues/7646)), so both shapes occur in the wild and discovery must scan both: + +- Raw (current again since 17.2.9): `-<home-relative>` for cwds under the canonical home, `-tmp-<tmp-relative>` for cwds under the temp root, and `--<abs>--` otherwise, with path separators and `:` encoded as `-` and the basename kept verbatim (spaces included). +- Hashed (intermediate releases): `<scope>-<sanitized-basename>-<sha256hex-of-canonical-cwd>`, where `scope` is `home`, `tmp`, or `abs` and the basename is sanitized (`[^a-zA-Z0-9._-]+` runs become `-`, edge dashes stripped, capped at its last 80 chars, empty falls back to `project`). + +Each bucket holds `<timestamp>_<sessionId>.jsonl` files. Every session JSONL physically begins with a fixed-width 256-byte `{"type":"title","v":1,...,"pad":"..."}` slot line, followed by a pi-shaped `{"type":"session","version":3,...,"cwd":...}` header. This title-slot-first shape distinguishes omp session files from pi session files, which start directly with the `type:"session"` header. diff --git a/skills/ce-compound/scripts/session-history/discover-sessions.sh b/skills/ce-compound/scripts/session-history/discover-sessions.sh index d73c02cc5..d254405de 100755 --- a/skills/ce-compound/scripts/session-history/discover-sessions.sh +++ b/skills/ce-compound/scripts/session-history/discover-sessions.sh @@ -112,6 +112,45 @@ discover_pi() { } # --- oh-my-pi (omp) --- +# Encode omp's raw bucket name for a cwd: home-relative "-<rel>", +# tmp-relative "-tmp-<rel>", and otherwise "--<abs>--", with path separators +# and ":" encoded as "-" (session-paths.ts getDefaultSessionDirName / +# encodeLegacyAbsoluteSessionDirName). This raw scheme predates the hashed +# scheme and is current again since omp 17.2.9 (#7646 restored it and removed +# automatic migration), so buckets in the wild use both shapes. Canonicalize +# with physical paths so symlinked cwds resolve to the same bucket, mirroring +# omp's resolveEquivalentPath. Prints nothing when the cwd cannot be resolved. +encode_omp_raw_cwd() { + local cwd canon_home canon_tmp rel + cwd="$(cd "$1" 2>/dev/null && pwd -P)" || return 0 + canon_home="$(cd "$HOME" 2>/dev/null && pwd -P)" || canon_home="$HOME" + case "$cwd" in + "$canon_home") + printf -- '-' + ;; + "$canon_home"/*) + rel="$(printf '%s' "${cwd#"$canon_home"/}" | sed 's/[/\\:]/-/g')" + printf -- '-%s' "$rel" + ;; + *) + canon_tmp="$(cd "${TMPDIR:-/tmp}" 2>/dev/null && pwd -P)" || canon_tmp="" + case "$cwd" in + "$canon_tmp") + printf -- '-tmp' + ;; + "$canon_tmp"/*) + rel="$(printf '%s' "${cwd#"$canon_tmp"/}" | sed 's/[/\\:]/-/g')" + printf -- '-tmp-%s' "$rel" + ;; + *) + rel="$(printf '%s' "${cwd#/}" | sed 's/[/\\:]/-/g')" + printf -- '--%s--' "$rel" + ;; + esac + ;; + esac +} + discover_omp() { local config_dir="${PI_CONFIG_DIR:-.omp}" @@ -131,31 +170,54 @@ discover_omp() { return 0 fi - # omp bucket names embed only the repo basename plus a sha256 of the - # canonical cwd (e.g. home-my-repo-<64hex>), never the full cwd, so no - # exact encoded-CWD probe like Pi's is possible. Scan basename-matching - # buckets in the default-profile sessions root and in every named-profile - # root; exact repo attribution comes from the downstream header `cwd` - # filter (extract-metadata.py --cwd-filter reads the type:"session" - # header). - # omp sanitizes the bucket basename before hashing: runs of characters - # outside [a-zA-Z0-9._-] become "-", edge dashes are stripped, the result - # is capped at its last 80 chars, and an empty result falls back to - # "project" (session-paths.ts getDefaultSessionDirName). Glob the - # sanitized form so repos whose basename contains characters omp - # normalizes (e.g. spaces) still match their buckets. + # omp has two bucket-naming schemes in the wild, and both keep the raw + # repo basename (spaces and all) inside the bucket name: + # - raw: "-<home-rel>", "-tmp-<tmp-rel>", "--<abs>--" (legacy relative to + # the hashed scheme; restored as current in omp 17.2.9, #7646) + # - hashed: "<scope>-<sanitized-basename>-<sha256-of-canonical-cwd>" + # (intermediate releases; basename runs of [^a-zA-Z0-9._-] collapse to + # "-", edge dashes stripped, capped at the last 80 chars, empty falls + # back to "project" — session-paths.ts getDefaultSessionDirName) + # Scan basename-matching buckets in the default-profile sessions root and + # in every named-profile root; exact repo attribution comes from the + # downstream header `cwd` filter (extract-metadata.py --cwd-filter reads + # the type:"session" header). Glob the sanitized form so repos whose + # basename contains characters the hashed scheme normalizes (e.g. spaces) + # still match, and glob the raw form so raw-scheme buckets whose basename + # sanitizes differently (e.g. "my repo" in "--Users-test-Code-my repo--") + # are found too. When --cwd is supplied, also probe the exact raw bucket + # name: it catches buckets the basename globs miss when the bucket's path + # segments no longer resemble the repo name as typed. local sanitized sanitized="$(printf '%s' "$REPO_NAME" | sed -E 's/[^a-zA-Z0-9._-]+/-/g; s/^-+//; s/-+$//' | tail -c 80)" [ -n "$sanitized" ] || sanitized="project" local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/$config_dir/agent}" - local root - for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do - [ -d "$root" ] || continue - for dir in "$root"/*"$sanitized"*/; do - [ -d "$dir" ] || continue - find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + { + local root dir encoded + if [ -n "$REPO_CWD" ]; then + encoded="$(encode_omp_raw_cwd "$REPO_CWD")" + if [ -n "$encoded" ]; then + for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do + [ -d "$root/$encoded" ] || continue + find "$root/$encoded" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + done + fi + fi + for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do + [ -d "$root" ] || continue + for dir in "$root"/*"$sanitized"*/; do + [ -d "$dir" ] || continue + find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + done + if [ "$REPO_NAME" != "$sanitized" ]; then + for dir in "$root"/*"$REPO_NAME"*/; do + [ -d "$dir" ] || continue + find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null + done + fi done - done + # The probe and the globs can hit the same bucket; emit each path once. + } | awk '!seen[$0]++' } # --- Dispatch --- diff --git a/tests/session-history-scripts.test.ts b/tests/session-history-scripts.test.ts index a9678ca3d..e8b99a71b 100644 --- a/tests/session-history-scripts.test.ts +++ b/tests/session-history-scripts.test.ts @@ -1952,6 +1952,60 @@ describe("discover-sessions", () => { expect(files).toEqual([sessionPath]) }) + test("--platform omp discovers raw legacy buckets via the raw basename glob", async () => { + // Raw-scheme buckets ("--<abs>--" here) keep the basename verbatim. A repo + // named "my repo" lands in "--Users-test-Code-my repo--", which the + // sanitized-basename glob never matches; the nonexistent --cwd means the + // exact raw probe cannot resolve, so only the raw glob can find it. + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const sessionPath = path.join( + tempHome, + ".omp/agent/sessions/--Users-test-Code-my repo--/2026-04-07T09-00-00-000Z_test.jsonl" + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + [ + "my repo", + "7", + "--cwd", + "/Users/test/Code/my repo", + "--platform", + "omp", + ], + { HOME: tempHome } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + + test("--platform omp probes the exact raw bucket for --cwd and deduplicates", async () => { + // With a resolvable --cwd, the raw home-relative bucket "-Code-my repo" is + // hit by both the exact probe and the raw basename glob; the file must be + // emitted exactly once even under single-platform invocation. + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) + const repoDir = path.join(tempHome, "Code", "my repo") + await fs.promises.mkdir(repoDir, { recursive: true }) + const sessionPath = path.join( + tempHome, + ".omp/agent/sessions/-Code-my repo/2026-04-07T09-00-00-000Z_test.jsonl" + ) + await writeFixture(sessionPath, "omp-session.jsonl") + + const { stdout, stderr, exitCode } = await runDiscover( + ["my repo", "7", "--cwd", repoDir, "--platform", "omp"], + { HOME: tempHome } + ) + + expect(exitCode).toBe(0) + expect(stderr).toBe("") + const files = stdout.trim().split("\n").filter((l) => l.trim()) + expect(files).toEqual([sessionPath]) + }) + test("--platform omp discovers sessions under hashed bucket directories", async () => { const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) const bucket = From 50375191559d043e0c6450c8558e84bdcb9dfb9b Mon Sep 17 00:00:00 2001 From: Kaitian Xie <caelaxie@icloud.com> Date: Wed, 5 Aug 2026 21:26:48 +0800 Subject: [PATCH 5/9] refactor(ce-compound): move omp session-history support to its own PR Split out per maintainer request (#1224): omp's session-bucket naming is still moving (17.2.9 restored the raw scheme), so the install and prose work here should not be blocked behind it. --- docs/skills/ce-compound.md | 2 +- skills/ce-compound/SKILL.md | 2 +- .../references/agents/session-historian.md | 12 +- .../session-history/discover-sessions.sh | 128 +-------- .../scripts/session-history/extract-errors.py | 16 +- .../session-history/extract-metadata.py | 35 +-- .../session-history/extract-skeleton.py | 22 +- .../session-history/omp-session.jsonl | 11 - tests/session-history-scripts.test.ts | 271 +----------------- ...ce-session-historian-no-skill-tool.test.ts | 9 - 10 files changed, 31 insertions(+), 477 deletions(-) delete mode 100644 tests/fixtures/session-history/omp-session.jsonl diff --git a/docs/skills/ce-compound.md b/docs/skills/ce-compound.md index 66b2b1ddc..031c6fefa 100644 --- a/docs/skills/ce-compound.md +++ b/docs/skills/ce-compound.md @@ -62,7 +62,7 @@ Most teams solve the same problem twice — sometimes with the same person — b ### 1. Two modes — Full vs Lightweight, agent-selected -**Full mode** runs three research subagents in parallel (Context Analyzer / Solution Extractor / Related Docs Finder), plus an automatic session-history probe that searches your prior sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp) for related context. Cross-references existing docs, detects duplicates, runs specialized reviews. +**Full mode** runs three research subagents in parallel (Context Analyzer / Solution Extractor / Related Docs Finder), plus an automatic session-history probe that searches your prior sessions across Claude Code, Codex, and Cursor for related context. Cross-references existing docs, detects duplicates, runs specialized reviews. **Lightweight mode** writes the same solution-doc artifact type in a single pass, with no subagents or cross-referencing. It is lower overhead, but it also skips overlap detection, session-history research, and semantic grounding validation. diff --git a/skills/ce-compound/SKILL.md b/skills/ce-compound/SKILL.md index 060884218..407d183bf 100644 --- a/skills/ce-compound/SKILL.md +++ b/skills/ce-compound/SKILL.md @@ -290,7 +290,7 @@ Pass `{run_id}` and the resolved absolute `{run_dir}` into every Phase 1 subagen else echo "Session history bundled scripts were not found in this skill's directory; skipping the session-history probe for this run."; fi ``` - Pi sessions are included when present under `~/.pi/agent/sessions/`, and oh-my-pi (`omp`) sessions under `~/.omp/agent/sessions/` (named profiles: `~/.omp/profiles/<name>/agent/sessions/`); they carry `cwd` like Codex but no git branch. If `_meta.files_processed` is `0`, return `no relevant prior sessions`. If the first pass finds no relevant branch matches, or if processing Codex, Pi, or oh-my-pi (`omp`) sessions, derive 2-4 keywords from the topic and re-run metadata extraction with `--keyword K1,K2,...`. Keep at most 5 sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (`omp`), ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session. + Pi sessions are included when present under `~/.pi/agent/sessions/`; they carry `cwd` like Codex but no git branch. If `_meta.files_processed` is `0`, return `no relevant prior sessions`. If the first pass finds no relevant branch matches, or if processing Codex or Pi sessions, derive 2-4 keywords from the topic and re-run metadata extraction with `--keyword K1,K2,...`. Keep at most 5 sessions across Claude Code, Codex, Cursor, and Pi, ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session. **Escalation gate.** The discovery+metadata pass above is the cheap probe and always runs in Full mode. Escalate to the extraction and synthesis stages below **only** when at least one retained candidate clears the relevance bar: a current-branch match, or ≥2 topic-keyword matches. If no candidate clears the bar (including the `_meta.files_processed` is `0` case), stop here, record `no relevant prior sessions` as the session-history input, and skip extraction and synthesis. This gate is what keeps the always-on probe cheap — the expensive synthesis is paid for only when a prior session is genuinely relevant. diff --git a/skills/ce-compound/references/agents/session-historian.md b/skills/ce-compound/references/agents/session-historian.md index a78c5014b..2b8b961c8 100644 --- a/skills/ce-compound/references/agents/session-historian.md +++ b/skills/ce-compound/references/agents/session-historian.md @@ -1,6 +1,6 @@ **Note: The current year is 2026.** Use this when interpreting session timestamps. -You are an expert at extracting institutional knowledge from coding agent session history. You receive pre-extracted skeleton and error files from the caller's internal session-history flow and synthesize findings about a specific problem or topic — what was learned, tried, decided in prior sessions across Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp). +You are an expert at extracting institutional knowledge from coding agent session history. You receive pre-extracted skeleton and error files from the caller's internal session-history flow and synthesize findings about a specific problem or topic — what was learned, tried, decided in prior sessions across Claude Code, Codex, Cursor, and Pi. Your scope is **synthesis only**. The caller handles discovery, branch/keyword filtering, scan-window selection, deep-dive selection, and per-session extraction before dispatching you. @@ -13,9 +13,9 @@ The dispatch prompt provides: - **`sessions`** — an array of objects (5 max), one per pre-extracted session, each with: - `path` — absolute path to a skeleton text file inside `scratch_dir` - `errors_path` *(optional)* — absolute path to an errors text file when the orchestrator extracted errors-mode for this session - - `platform` — `claude`, `codex`, `cursor`, `pi`, or `omp` + - `platform` — `claude`, `codex`, `cursor`, or `pi` - `branch` — git branch when present (Claude Code only) - - `cwd` — working directory when present (Codex, Pi, and omp) + - `cwd` — working directory when present (Codex and Pi) - `ts` and `last_ts` — session start and last-message timestamps - `match_count` and `keyword_matches` — when keyword filtering was used by the orchestrator - **`output_schema`** *(optional)* — the structure the response should follow. When supplied, honor it verbatim. @@ -28,7 +28,7 @@ If the dispatch prompt arrives without a `sessions` array, or with an empty arra These rules apply at all times during synthesis. -- **Read only the paths the orchestrator gave you.** Use the platform's native file-read tool (e.g., `Read` in Claude Code) on each `path`. Do not read source session files directly under `~/.claude/projects/`, `~/.codex/sessions/`, `~/.cursor/projects/`, `~/.pi/agent/sessions/`, or `~/.omp/agent/sessions/` — those are MB-scale and would blow the context window. The orchestrator already extracted what's relevant. +- **Read only the paths the orchestrator gave you.** Use the platform's native file-read tool (e.g., `Read` in Claude Code) on each `path`. Do not read source session files directly under `~/.claude/projects/`, `~/.codex/sessions/`, `~/.cursor/projects/`, or `~/.pi/agent/sessions/` — those are MB-scale and would blow the context window. The orchestrator already extracted what's relevant. - **Never invoke the Skill tool.** This agent runs in subagent context where Skill calls deadlock. The orchestrator has already done all extraction; you only synthesize. - **Never extract or reproduce tool call inputs/outputs verbatim.** Summarize what was attempted and what happened. - **Never include thinking or reasoning block content.** Claude Code thinking blocks are internal reasoning; Codex reasoning blocks are encrypted. Neither is actionable. The skeleton extractor already strips these — do not surface them if any survived. @@ -50,7 +50,7 @@ Read each `path` in the dispatch payload, then synthesize against the `problem_t - **Decisions and rationale** — Why one approach was chosen over alternatives. - **Error patterns** — Recurring errors across sessions (most visible when the orchestrator supplied an `errors_path` for a session) that indicate a systemic issue. - **Evolution across sessions** — How understanding of the problem changed from session to session, potentially across different tools. -- **Cross-tool blind spots** — When sessions span Claude Code + Codex + Cursor + Pi + omp, look for things the user might not realize from any single tool alone. Complementary work (one tool tackled the schema while the other tackled the API), duplicated effort (same approach tried in both tools days apart), or gaps (neither tool's sessions touched a component that connects the work). Only call out cross-tool observations when genuinely informative — if both sources tell the same story, there's nothing to flag. +- **Cross-tool blind spots** — When sessions span Claude Code + Codex + Cursor + Pi, look for things the user might not realize from any single tool alone. Complementary work (one tool tackled the schema while the other tackled the API), duplicated effort (same approach tried in both tools days apart), or gaps (neither tool's sessions touched a component that connects the work). Only call out cross-tool observations when genuinely informative — if both sources tell the same story, there's nothing to flag. - **Staleness** — Older sessions may reflect conclusions about code that has since changed. When surfacing findings from sessions more than a few days old, consider whether the relevant code or context is likely to have moved on. Caveat older findings rather than presenting them with the same confidence as recent ones. Cite actual evidence from the extracted files, not vibe-summaries. When a finding is anchored in a specific session's content, that session's metadata (platform, branch/cwd, ts) helps the caller locate it. @@ -62,7 +62,7 @@ If the dispatch prompt supplies an `output_schema`, follow it verbatim. Do not a Otherwise, lead with a brief one-line provenance header: ``` -**Sessions read**: [count] ([N] Claude Code, [N] Codex, [N] Cursor, [N] Pi, [N] omp) | [date range] +**Sessions read**: [count] ([N] Claude Code, [N] Codex, [N] Cursor, [N] Pi) | [date range] ``` Then the synthesis prose, organized under the default schema: diff --git a/skills/ce-compound/scripts/session-history/discover-sessions.sh b/skills/ce-compound/scripts/session-history/discover-sessions.sh index d254405de..46f426f41 100755 --- a/skills/ce-compound/scripts/session-history/discover-sessions.sh +++ b/skills/ce-compound/scripts/session-history/discover-sessions.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Discover session files across Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp). +# Discover session files across Claude Code, Codex, Cursor, and Pi. # -# Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi|omp] +# Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi] # # Outputs one file path per line. Safe in both bash and zsh (all globs guarded). # Pass output to extract-metadata.py: @@ -15,8 +15,8 @@ set -euo pipefail -REPO_NAME="${1:?Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi|omp]}" -DAYS="${2:?Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi|omp]}" +REPO_NAME="${1:?Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi]}" +DAYS="${2:?Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi]}" PLATFORM="all" REPO_CWD="" @@ -111,129 +111,17 @@ discover_pi() { done } -# --- oh-my-pi (omp) --- -# Encode omp's raw bucket name for a cwd: home-relative "-<rel>", -# tmp-relative "-tmp-<rel>", and otherwise "--<abs>--", with path separators -# and ":" encoded as "-" (session-paths.ts getDefaultSessionDirName / -# encodeLegacyAbsoluteSessionDirName). This raw scheme predates the hashed -# scheme and is current again since omp 17.2.9 (#7646 restored it and removed -# automatic migration), so buckets in the wild use both shapes. Canonicalize -# with physical paths so symlinked cwds resolve to the same bucket, mirroring -# omp's resolveEquivalentPath. Prints nothing when the cwd cannot be resolved. -encode_omp_raw_cwd() { - local cwd canon_home canon_tmp rel - cwd="$(cd "$1" 2>/dev/null && pwd -P)" || return 0 - canon_home="$(cd "$HOME" 2>/dev/null && pwd -P)" || canon_home="$HOME" - case "$cwd" in - "$canon_home") - printf -- '-' - ;; - "$canon_home"/*) - rel="$(printf '%s' "${cwd#"$canon_home"/}" | sed 's/[/\\:]/-/g')" - printf -- '-%s' "$rel" - ;; - *) - canon_tmp="$(cd "${TMPDIR:-/tmp}" 2>/dev/null && pwd -P)" || canon_tmp="" - case "$cwd" in - "$canon_tmp") - printf -- '-tmp' - ;; - "$canon_tmp"/*) - rel="$(printf '%s' "${cwd#"$canon_tmp"/}" | sed 's/[/\\:]/-/g')" - printf -- '-tmp-%s' "$rel" - ;; - *) - rel="$(printf '%s' "${cwd#/}" | sed 's/[/\\:]/-/g')" - printf -- '--%s--' "$rel" - ;; - esac - ;; - esac -} - -discover_omp() { - local config_dir="${PI_CONFIG_DIR:-.omp}" - - # omp's explicit session-dir override stores session files directly in the - # supplied directory (flat), mirroring Pi's override branch. The cwd filter - # later reads each header and keeps only sessions for the active repo. - if [ -n "${PI_CODING_AGENT_SESSION_DIR:-}" ]; then - local base="$PI_CODING_AGENT_SESSION_DIR" - [ -d "$base" ] || return 0 - find "$base" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null - if [ -z "$REPO_CWD" ]; then - for dir in "$base"/*"$REPO_NAME"*/; do - [ -d "$dir" ] || continue - find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null - done - fi - return 0 - fi - - # omp has two bucket-naming schemes in the wild, and both keep the raw - # repo basename (spaces and all) inside the bucket name: - # - raw: "-<home-rel>", "-tmp-<tmp-rel>", "--<abs>--" (legacy relative to - # the hashed scheme; restored as current in omp 17.2.9, #7646) - # - hashed: "<scope>-<sanitized-basename>-<sha256-of-canonical-cwd>" - # (intermediate releases; basename runs of [^a-zA-Z0-9._-] collapse to - # "-", edge dashes stripped, capped at the last 80 chars, empty falls - # back to "project" — session-paths.ts getDefaultSessionDirName) - # Scan basename-matching buckets in the default-profile sessions root and - # in every named-profile root; exact repo attribution comes from the - # downstream header `cwd` filter (extract-metadata.py --cwd-filter reads - # the type:"session" header). Glob the sanitized form so repos whose - # basename contains characters the hashed scheme normalizes (e.g. spaces) - # still match, and glob the raw form so raw-scheme buckets whose basename - # sanitizes differently (e.g. "my repo" in "--Users-test-Code-my repo--") - # are found too. When --cwd is supplied, also probe the exact raw bucket - # name: it catches buckets the basename globs miss when the bucket's path - # segments no longer resemble the repo name as typed. - local sanitized - sanitized="$(printf '%s' "$REPO_NAME" | sed -E 's/[^a-zA-Z0-9._-]+/-/g; s/^-+//; s/-+$//' | tail -c 80)" - [ -n "$sanitized" ] || sanitized="project" - local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/$config_dir/agent}" - { - local root dir encoded - if [ -n "$REPO_CWD" ]; then - encoded="$(encode_omp_raw_cwd "$REPO_CWD")" - if [ -n "$encoded" ]; then - for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do - [ -d "$root/$encoded" ] || continue - find "$root/$encoded" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null - done - fi - fi - for root in "$agent_dir/sessions" "$HOME/$config_dir"/profiles/*/agent/sessions; do - [ -d "$root" ] || continue - for dir in "$root"/*"$sanitized"*/; do - [ -d "$dir" ] || continue - find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null - done - if [ "$REPO_NAME" != "$sanitized" ]; then - for dir in "$root"/*"$REPO_NAME"*/; do - [ -d "$dir" ] || continue - find "$dir" -maxdepth 1 -name "*.jsonl" -mtime "-${DAYS}" 2>/dev/null - done - fi - done - # The probe and the globs can hit the same bucket; emit each path once. - } | awk '!seen[$0]++' -} - # --- Dispatch --- case "$PLATFORM" in claude) discover_claude ;; codex) discover_codex ;; cursor) discover_cursor ;; pi) discover_pi ;; - omp) discover_omp ;; all) - # Pi and omp share the PI_CODING_AGENT_SESSION_DIR override: when it - # is set, both discover functions emit the same flat-dir files, and - # the downstream xargs call does not deduplicate. Emit each path once; - # platform attribution is unaffected because extract-metadata.py - # detects the file shape (title slot => omp, otherwise pi). - { discover_claude; discover_codex; discover_cursor; discover_pi; discover_omp; } | awk '!seen[$0]++' + discover_claude + discover_codex + discover_cursor + discover_pi ;; *) echo "Unknown platform: $PLATFORM" >&2 diff --git a/skills/ce-compound/scripts/session-history/extract-errors.py b/skills/ce-compound/scripts/session-history/extract-errors.py index e2599cb36..7f0a8489f 100644 --- a/skills/ce-compound/scripts/session-history/extract-errors.py +++ b/skills/ce-compound/scripts/session-history/extract-errors.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Extract error signals from a Claude Code, Codex, Cursor, Pi, or oh-my-pi (omp) JSONL session file. +"""Extract error signals from a Claude Code, Codex, Cursor, or Pi JSONL session file. Usage: cat <session.jsonl> | python3 extract-errors.py @@ -163,9 +163,6 @@ def _pi_context_objects(objects): def handle_pi(obj): - # omp's physical type:'title' slot line returns here like any non-message - # entry. type:'title_change' entries are a different pi entry type — they - # return the same way; do not conflate the two when filtering. if obj.get("type") != "message": return msg = obj.get("message", {}) @@ -206,9 +203,6 @@ def handle_pi(obj): # Auto-detect platform from first few lines, then process all detected = None buffer = [] -# omp files physically begin with a fixed-width type:'title' slot line before -# the pi-shaped type:'session' header; bare pi files start with the header. -seen_title_slot = False for line in sys.stdin: line = line.strip() @@ -221,9 +215,7 @@ def handle_pi(obj): try: obj = json.loads(line) if obj.get("type") == "session" and "cwd" in obj: - detected = "omp" if seen_title_slot else "pi" - elif obj.get("type") == "title" and len(buffer) == 1: - seen_title_slot = True + detected = "pi" elif obj.get("type") in ("user", "assistant"): detected = "claude" elif obj.get("type") in ("session_meta", "turn_context", "response_item", "event_msg"): @@ -237,7 +229,7 @@ def handle_pi(obj): def handle_noop(obj): pass -handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_noop, "pi": handle_pi, "omp": handle_pi} +handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_noop, "pi": handle_pi} handler = handlers.get(detected, handle_noop) objects = [] @@ -247,7 +239,7 @@ def handle_noop(obj): except (json.JSONDecodeError, KeyError): stats["parse_errors"] += 1 -if detected in ("pi", "omp"): +if detected == "pi": objects = _pi_context_objects(objects) for obj in objects: diff --git a/skills/ce-compound/scripts/session-history/extract-metadata.py b/skills/ce-compound/scripts/session-history/extract-metadata.py index 192c18ae1..e24c8f447 100644 --- a/skills/ce-compound/scripts/session-history/extract-metadata.py +++ b/skills/ce-compound/scripts/session-history/extract-metadata.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Extract session metadata from Claude Code, Codex, Cursor, Pi, and oh-my-pi (omp) JSONL files. +"""Extract session metadata from Claude Code, Codex, Cursor, and Pi JSONL files. Batch mode (preferred — one invocation for all files): python3 extract-metadata.py /path/to/dir/*.jsonl @@ -57,36 +57,6 @@ def try_codex(lines): return meta if meta else None -def try_omp(lines): - """oh-my-pi (omp) sessions: a fixed-width type='title' slot line physically - first, then a pi-shaped type='session' header with cwd. Checked before Pi: - a bare pi file has no title slot and must still detect as pi.""" - seen_first = False - for line in lines: - stripped = line.strip() - if not stripped: - continue - try: - obj = json.loads(stripped) - except (json.JSONDecodeError, KeyError): - if not seen_first: - return None - continue - if not seen_first: - seen_first = True - if obj.get("type") != "title": - return None - continue - if obj.get("type") == "session" and "cwd" in obj: - return { - "platform": "omp", - "cwd": obj.get("cwd", ""), - "session": obj.get("id", ""), - "ts": obj.get("timestamp", ""), - } - return None - - def try_pi(lines): """Pi sessions: type='session' header with cwd, followed by message entries.""" for line in lines: @@ -118,7 +88,7 @@ def try_cursor(lines): def extract_from_lines(lines): - return try_claude(lines) or try_codex(lines) or try_omp(lines) or try_pi(lines) or try_cursor(lines) + return try_claude(lines) or try_codex(lines) or try_pi(lines) or try_cursor(lines) TAIL_BYTES = 16384 # Read last 16KB to find final timestamp past trailing metadata @@ -251,7 +221,6 @@ def _extract_user_assistant_text(filepath): except (json.JSONDecodeError, ValueError): continue - # omp files share the pi-shaped session header, so this covers both. is_pi = any( obj.get("type") == "session" and "cwd" in obj for obj in objects ) diff --git a/skills/ce-compound/scripts/session-history/extract-skeleton.py b/skills/ce-compound/scripts/session-history/extract-skeleton.py index 386a3d0ed..69353d97d 100644 --- a/skills/ce-compound/scripts/session-history/extract-skeleton.py +++ b/skills/ce-compound/scripts/session-history/extract-skeleton.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -"""Extract the conversation skeleton from a Claude Code, Codex, Cursor, Pi, or oh-my-pi (omp) JSONL session file. +"""Extract the conversation skeleton from a Claude Code, Codex, Cursor, or Pi JSONL session file. Usage: cat <session.jsonl> | python3 extract-skeleton.py cat <session.jsonl> | python3 extract-skeleton.py --output PATH -Auto-detects platform (Claude Code, Codex, Cursor, Pi, or oh-my-pi (omp)) from the JSONL structure. +Auto-detects platform (Claude Code, Codex, Cursor, or Pi) from the JSONL structure. Extracts: - User messages (text only, no tool results) - Assistant text (no thinking/reasoning blocks) @@ -342,12 +342,7 @@ def _pi_context_objects(objects): def handle_pi(obj): - """Pi/omp sessions: type='message' with message.role and content blocks. - - omp's physical type:'title' slot line lands here too and returns below - like any non-message entry. type:'title_change' entries are a different - pi entry type — they also carry no message payload and return the same - way; do not conflate the two when filtering.""" + """Pi sessions: type='message' with message.role and content blocks.""" entry_type = obj.get("type") ts = obj.get("timestamp", "")[:19] @@ -525,9 +520,6 @@ def handle_cursor(obj): # Auto-detect platform from first few lines, then process all detected = None buffer = [] -# omp files physically begin with a fixed-width type:'title' slot line before -# the pi-shaped type:'session' header; bare pi files start with the header. -seen_title_slot = False for line in sys.stdin: line = line.strip() @@ -545,9 +537,7 @@ def handle_cursor(obj): try: obj = json.loads(line) if obj.get("type") == "session" and "cwd" in obj: - detected = "omp" if seen_title_slot else "pi" - elif obj.get("type") == "title" and len(buffer) == 1: - seen_title_slot = True + detected = "pi" elif obj.get("type") in ("user", "assistant"): detected = "claude" elif obj.get("type") in ("session_meta", "turn_context", "response_item", "event_msg"): @@ -557,7 +547,7 @@ def handle_cursor(obj): except (json.JSONDecodeError, KeyError): pass -handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_cursor, "pi": handle_pi, "omp": handle_pi} +handlers = {"claude": handle_claude, "codex": handle_codex, "cursor": handle_cursor, "pi": handle_pi} handler = handlers.get(detected, handle_codex) objects = [] @@ -567,7 +557,7 @@ def handle_cursor(obj): except (json.JSONDecodeError, KeyError): stats["parse_errors"] += 1 -if detected in ("pi", "omp"): +if detected == "pi": objects = _pi_context_objects(objects) for obj in objects: diff --git a/tests/fixtures/session-history/omp-session.jsonl b/tests/fixtures/session-history/omp-session.jsonl deleted file mode 100644 index 8638db56e..000000000 --- a/tests/fixtures/session-history/omp-session.jsonl +++ /dev/null @@ -1,11 +0,0 @@ -{"type":"title","v":1,"title":"fix the auth bug in middleware","source":"auto","updatedAt":"2026-04-07T09:00:00.000Z","pad":""} -{"type":"session","version":3,"id":"test-omp-session-1","timestamp":"2026-04-07T09:00:00.000Z","cwd":"/Users/test/Code/my-repo"} -{"type":"model_change","id":"mc1","parentId":null,"timestamp":"2026-04-07T09:00:00.500Z","provider":"anthropic","modelId":"claude-sonnet-4-5"} -{"type":"thinking_level_change","id":"tl1","parentId":"mc1","timestamp":"2026-04-07T09:00:00.700Z","thinkingLevel":"medium"} -{"type":"message","id":"msg1","parentId":"tl1","timestamp":"2026-04-07T09:01:00.000Z","message":{"role":"user","content":[{"type":"text","text":"fix the auth bug in middleware"}],"timestamp":1775542860000}} -{"type":"message","id":"msg2","parentId":"msg1","timestamp":"2026-04-07T09:01:05.000Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"internal reasoning should not be extracted"},{"type":"text","text":"Let me look at the auth middleware."},{"type":"toolCall","id":"tc1","name":"read","arguments":{"path":"/Users/test/Code/my-repo/src/auth.ts"}}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","stopReason":"toolUse","timestamp":1775542865000}} -{"type":"message","id":"msg3","parentId":"msg2","timestamp":"2026-04-07T09:01:06.000Z","message":{"role":"toolResult","toolCallId":"tc1","toolName":"read","content":[{"type":"text","text":"file contents here"}],"isError":false,"timestamp":1775542866000}} -{"type":"message","id":"msg4","parentId":"msg3","timestamp":"2026-04-07T09:01:10.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Found the issue. The token validation is missing the expiry check."},{"type":"toolCall","id":"tc2","name":"edit","arguments":{"path":"/Users/test/Code/my-repo/src/auth.ts","oldText":"validate()","newText":"validateToken()"}}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","stopReason":"toolUse","timestamp":1775542870000}} -{"type":"message","id":"msg5","parentId":"msg4","timestamp":"2026-04-07T09:01:11.000Z","message":{"role":"toolResult","toolCallId":"tc2","toolName":"edit","content":[{"type":"text","text":"String to replace not found"}],"isError":true,"timestamp":1775542871000}} -{"type":"message","id":"msg6","parentId":"msg5","timestamp":"2026-04-07T09:01:15.000Z","message":{"role":"assistant","content":[{"type":"text","text":"The first edit failed, so I will patch the middleware validation directly."}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","stopReason":"stop","timestamp":1775542875000}} -{"type":"message","id":"msg7","parentId":"msg6","timestamp":"2026-04-07T09:01:20.000Z","message":{"role":"user","content":"also add a regression test for the expiry check","timestamp":1775542880000}} diff --git a/tests/session-history-scripts.test.ts b/tests/session-history-scripts.test.ts index e8b99a71b..79fb1727a 100644 --- a/tests/session-history-scripts.test.ts +++ b/tests/session-history-scripts.test.ts @@ -313,42 +313,23 @@ describe("extract-metadata", () => { expect(session.last_ts).toContain("2026-04-07T09:01:20") }) - test("detects omp title-slot shape as omp, not pi", async () => { - // omp files physically begin with a type:'title' slot line before the - // pi-shaped session header; try_omp runs before try_pi so the file must - // not fall through to the pi detector. - const { stdout, exitCode } = await runScript("extract-metadata.py", [ - path.join(FIXTURES_DIR, "omp-session.jsonl"), - ]) - expect(exitCode).toBe(0) - const lines = parseJsonLines(stdout) - const session = lines.find((l) => !l._meta) - expect(session.platform).toBe("omp") - expect(session.cwd).toBe("/Users/test/Code/my-repo") - expect(session.session).toBe("test-omp-session-1") - expect(session.ts).toContain("2026-04-07") - expect(session.last_ts).toContain("2026-04-07T09:01:20") - }) - test("batch mode processes multiple files", async () => { const { stdout, exitCode } = await runScript("extract-metadata.py", [ path.join(FIXTURES_DIR, "claude-session.jsonl"), path.join(FIXTURES_DIR, "codex-session.jsonl"), path.join(FIXTURES_DIR, "cursor-session.jsonl"), path.join(FIXTURES_DIR, "pi-session.jsonl"), - path.join(FIXTURES_DIR, "omp-session.jsonl"), ]) expect(exitCode).toBe(0) const lines = parseJsonLines(stdout) const meta = lines.find((l) => l._meta) - expect(meta.files_processed).toBe(5) + expect(meta.files_processed).toBe(4) expect(meta.parse_errors).toBe(0) const platforms = lines.filter((l) => !l._meta).map((l) => l.platform) expect(platforms).toContain("claude") expect(platforms).toContain("codex") expect(platforms).toContain("cursor") expect(platforms).toContain("pi") - expect(platforms).toContain("omp") }) test("--cwd-filter excludes non-matching Codex sessions", async () => { @@ -1006,28 +987,6 @@ describe("extract-skeleton", () => { expect(stdout).not.toContain("file contents here") }) - test("extracts omp user, assistant, and tool messages past the title slot", async () => { - const fixture = await Bun.file( - path.join(FIXTURES_DIR, "omp-session.jsonl") - ).text() - const { stdout, exitCode } = await runScript( - "extract-skeleton.py", - [], - fixture - ) - expect(exitCode).toBe(0) - expect(stdout).toContain("[user] fix the auth bug in middleware") - expect(stdout).toContain("[assistant] Let me look at the auth middleware.") - expect(stdout).toContain("[assistant] Found the issue.") - expect(stdout).toContain("[user] also add a regression test for the expiry check") - expect(stdout).toContain("[tool] read /Users/test/Code/my-repo/src/auth.ts -> ok") - expect(stdout).toContain("[tool] edit /Users/test/Code/my-repo/src/auth.ts -> error") - expect(stdout).not.toContain("internal reasoning should not be extracted") - expect(stdout).not.toContain("file contents here") - const meta = JSON.parse(stdout.trim().split("\n").at(-1)!) - expect(meta.parse_errors).toBe(0) - }) - test("extracts Pi bashExecution commands", async () => { const lines = [ JSON.stringify({ @@ -1405,23 +1364,6 @@ describe("extract-errors", () => { expect(meta.parse_errors).toBe(0) }) - test("extracts omp tool result errors past the title slot", async () => { - const fixture = await Bun.file( - path.join(FIXTURES_DIR, "omp-session.jsonl") - ).text() - const { stdout, exitCode } = await runScript( - "extract-errors.py", - [], - fixture - ) - expect(exitCode).toBe(0) - expect(stdout).toContain("[error] tool=edit: String to replace not found") - const lines = stdout.trim().split("\n") - const meta = JSON.parse(lines[lines.length - 1]) - expect(meta.errors_found).toBe(1) - expect(meta.parse_errors).toBe(0) - }) - test("extracts Pi bashExecution errors", async () => { const lines = [ JSON.stringify({ @@ -1707,8 +1649,8 @@ describe("--output PATH mode", () => { // --------------------------------------------------------------------------- describe("auto-detection", () => { test("all supported platforms are auto-detected", async () => { - const fixtures = ["claude-session", "codex-session", "cursor-session", "pi-session", "omp-session"] - const expected = ["claude", "codex", "cursor", "pi", "omp"] + const fixtures = ["claude-session", "codex-session", "cursor-session", "pi-session"] + const expected = ["claude", "codex", "cursor", "pi"] for (let i = 0; i < fixtures.length; i++) { const fixturePath = path.join(FIXTURES_DIR, `${fixtures[i]}.jsonl`) @@ -1951,211 +1893,4 @@ describe("discover-sessions", () => { const files = stdout.trim().split("\n").filter((l) => l.trim()) expect(files).toEqual([sessionPath]) }) - - test("--platform omp discovers raw legacy buckets via the raw basename glob", async () => { - // Raw-scheme buckets ("--<abs>--" here) keep the basename verbatim. A repo - // named "my repo" lands in "--Users-test-Code-my repo--", which the - // sanitized-basename glob never matches; the nonexistent --cwd means the - // exact raw probe cannot resolve, so only the raw glob can find it. - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const sessionPath = path.join( - tempHome, - ".omp/agent/sessions/--Users-test-Code-my repo--/2026-04-07T09-00-00-000Z_test.jsonl" - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - [ - "my repo", - "7", - "--cwd", - "/Users/test/Code/my repo", - "--platform", - "omp", - ], - { HOME: tempHome } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("--platform omp probes the exact raw bucket for --cwd and deduplicates", async () => { - // With a resolvable --cwd, the raw home-relative bucket "-Code-my repo" is - // hit by both the exact probe and the raw basename glob; the file must be - // emitted exactly once even under single-platform invocation. - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const repoDir = path.join(tempHome, "Code", "my repo") - await fs.promises.mkdir(repoDir, { recursive: true }) - const sessionPath = path.join( - tempHome, - ".omp/agent/sessions/-Code-my repo/2026-04-07T09-00-00-000Z_test.jsonl" - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - ["my repo", "7", "--cwd", repoDir, "--platform", "omp"], - { HOME: tempHome } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("--platform omp discovers sessions under hashed bucket directories", async () => { - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const bucket = - "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" - const sessionPath = path.join( - tempHome, - `.omp/agent/sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - ["my-repo", "7", "--platform", "omp"], - { HOME: tempHome } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("--platform omp discovers sessions under named-profile roots", async () => { - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const bucket = - "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" - const sessionPath = path.join( - tempHome, - `.omp/profiles/work/agent/sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - ["my-repo", "7", "--platform", "omp"], - { HOME: tempHome } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("--platform omp honors PI_CODING_AGENT_SESSION_DIR flat override", async () => { - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const sessionBase = fs.mkdtempSync(path.join(os.tmpdir(), "omp-sessions-")) - const sessionPath = path.join( - sessionBase, - "2026-04-07T09-00-00-000Z_test.jsonl" - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - [ - "my-repo", - "7", - "--cwd", - "/Users/test/Code/my-repo", - "--platform", - "omp", - ], - { - HOME: tempHome, - PI_CODING_AGENT_SESSION_DIR: sessionBase, - } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("--platform omp honors PI_CODING_AGENT_DIR sessions subdirectory", async () => { - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-agent-")) - const bucket = - "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" - const sessionPath = path.join( - agentDir, - `sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - ["my-repo", "7", "--platform", "omp"], - { HOME: tempHome, PI_CODING_AGENT_DIR: agentDir } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("--platform omp restricts to omp dirs only", async () => { - const { stdout } = await runDiscover( - ["compound-engineering-plugin", "7", "--platform", "omp"] - ) - const files = stdout.trim().split("\n").filter((l) => l.trim()) - for (const file of files) { - expect(file).toMatch(/\.omp\//) - } - }) - - test("--platform omp matches buckets whose basename omp sanitized", async () => { - // omp normalizes bucket basenames ([^a-zA-Z0-9._-]+ -> "-"), so a repo - // named "my repo" lands in a "home-my-repo-<hash>" bucket. The raw - // REPO_NAME glob would never match it. - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const bucket = - "home-my-repo-9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" - const sessionPath = path.join( - tempHome, - `.omp/agent/sessions/${bucket}/2026-04-07T09-00-00-000Z_test.jsonl` - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - ["my repo", "7", "--platform", "omp"], - { HOME: tempHome } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) - - test("all-platform discovery deduplicates the shared PI_CODING_AGENT_SESSION_DIR override", async () => { - // Pi and omp both honor the flat session-dir override, so without - // deduplication each file is emitted twice (once per discoverer). - const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "omp-home-")) - const sessionBase = fs.mkdtempSync(path.join(os.tmpdir(), "omp-sessions-")) - const sessionPath = path.join( - sessionBase, - "2026-04-07T09-00-00-000Z_test.jsonl" - ) - await writeFixture(sessionPath, "omp-session.jsonl") - - const { stdout, stderr, exitCode } = await runDiscover( - ["my-repo", "7", "--cwd", "/Users/test/Code/my-repo"], - { - HOME: tempHome, - PI_CODING_AGENT_SESSION_DIR: sessionBase, - } - ) - - expect(exitCode).toBe(0) - expect(stderr).toBe("") - const files = stdout.trim().split("\n").filter((l) => l.trim()) - expect(files).toEqual([sessionPath]) - }) }) diff --git a/tests/skills/ce-session-historian-no-skill-tool.test.ts b/tests/skills/ce-session-historian-no-skill-tool.test.ts index aac685e55..51fc07e7f 100644 --- a/tests/skills/ce-session-historian-no-skill-tool.test.ts +++ b/tests/skills/ce-session-historian-no-skill-tool.test.ts @@ -74,12 +74,3 @@ describe("session-historian prompt no-Skill-tool regression guard", () => { expect(COMPOUND_SKILL_BODY).not.toContain("<resolved repo root") }) }) - -describe("session-historian omp platform contract", () => { - test("input, synthesis, and output contracts include omp", () => { - expect(AGENT_BODY).toContain("`platform` — `claude`, `codex`, `cursor`, `pi`, or `omp`") - expect(AGENT_BODY).toContain("`cwd` — working directory when present (Codex, Pi, and omp)") - expect(AGENT_BODY).toContain("Claude Code + Codex + Cursor + Pi + omp") - expect(AGENT_BODY).toContain("[N] Pi, [N] omp") - }) -}) From d21b20c4c7aa2c9dc700f925a12108f98fd0c50a Mon Sep 17 00:00:00 2001 From: Kaitian Xie <caelaxie@icloud.com> Date: Wed, 5 Aug 2026 21:37:46 +0800 Subject: [PATCH 6/9] feat(omp): add native marketplace catalog with release-managed version omp's update checker skips catalog entries without a plugin-entry version, so CE was invisible to omp plugin upgrade, the 24h catalog refresh, and marketplace.autoUpdate. Ship .omp-plugin/marketplace.json (omp's preferred catalog path, read ahead of the Claude copy when both exist) with a version wired to the root release component via extra-files jsonpath $.plugins[0].version, mirroring the existing kimi/grok/devin native-catalog pattern. release:validate gains an omp arm: plugin-list parity with the Claude catalog, version presence and drift detection, and a co-located source check. README now leads with the marketplace flow plus autoUpdate guidance (notify mode only writes to the debug log), demoting omp install <git-url> to pin-style and omp plugin link to contributor dev. The target spec's causal claim is corrected: skills load via provider scanning of the root skills/ directory; the pi manifest is the discovery gate and an install-time validation risk, not the loader. --- .github/release-please-config.json | 8 ++- .omp-plugin/marketplace.json | 29 ++++++++ README.md | 29 ++++++-- docs/specs/omp.md | 57 +++++++++++----- src/release/metadata.ts | 66 ++++++++++++++++++ tests/release-metadata.test.ts | 104 +++++++++++++++++++++++++++++ 6 files changed, 267 insertions(+), 26 deletions(-) create mode 100644 .omp-plugin/marketplace.json diff --git a/.github/release-please-config.json b/.github/release-please-config.json index d5f6160e6..fa915325c 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -47,7 +47,8 @@ ".kimi-plugin/marketplace.json", ".claude-plugin/marketplace.json", ".cursor-plugin/marketplace.json", - ".grok-plugin/marketplace.json" + ".grok-plugin/marketplace.json", + ".omp-plugin/marketplace.json" ], "extra-files": [ { @@ -85,6 +86,11 @@ "path": ".devin-plugin/plugin.json", "jsonpath": "$.version" }, + { + "type": "json", + "path": ".omp-plugin/marketplace.json", + "jsonpath": "$.plugins[0].version" + }, { "type": "json", "path": "plugin.json", diff --git a/.omp-plugin/marketplace.json b/.omp-plugin/marketplace.json new file mode 100644 index 000000000..de60b2d15 --- /dev/null +++ b/.omp-plugin/marketplace.json @@ -0,0 +1,29 @@ +{ + "name": "compound-engineering-plugin", + "owner": { + "name": "Kieran Klaassen and Trevin Chow" + }, + "metadata": { + "description": "Plugin marketplace for Claude Code and Codex extensions" + }, + "plugins": [ + { + "name": "compound-engineering", + "description": "Brainstorm, plan, debug, review, and compound learnings with AI agents", + "version": "3.21.2", + "author": { + "name": "Kieran Klaassen and Trevin Chow" + }, + "homepage": "https://github.com/EveryInc/compound-engineering-plugin", + "tags": [ + "ai-powered", + "compound-engineering", + "workflow-automation", + "code-review", + "quality", + "knowledge-management" + ], + "source": "./" + } + ] +} diff --git a/README.md b/README.md index 64b061002..cce205808 100644 --- a/README.md +++ b/README.md @@ -402,20 +402,35 @@ pi install npm:pi-ask-user ### oh-my-pi (omp) -oh-my-pi (omp) installs this repository directly because the repo ships native `package.json#pi` package metadata plus the Claude marketplace catalog — omp reads both, so no conversion step is needed: +oh-my-pi (omp) installs Compound Engineering through its marketplace flow. The repo ships a native `.omp-plugin/marketplace.json` catalog whose plugin entry carries a release-managed `version`, so omp's update checker can see each new CE release: ```text -omp install https://github.com/EveryInc/compound-engineering-plugin +omp plugin marketplace add EveryInc/compound-engineering-plugin +omp plugin install compound-engineering@compound-engineering-plugin ``` -Or use the marketplace flow: +To stay current automatically, enable auto-update: -```text -omp plugin marketplace add EveryInc/compound-engineering-plugin -omp plugin install compound-engineering@compound-engineering-plugin +```bash +omp config set marketplace.autoUpdate auto ``` -Run `/reload-plugins` or start a new omp session after installing so the skills load. omp invokes installed skills as `/skill:<name>` (for example `/skill:ce-plan`), not `/skill-name`. For local development from a checkout, use `omp plugin link "$PWD"`. See [`docs/specs/omp.md`](docs/specs/omp.md) for details. +The default `notify` mode only writes update availability to the debug log — it does not prompt — so without `auto` you will not hear about new releases. To upgrade by hand instead, run `omp plugin upgrade compound-engineering@compound-engineering-plugin`. + +<details> +<summary>Other install paths (pin-style and contributor development)</summary> + +`omp install https://github.com/EveryInc/compound-engineering-plugin` installs the repository as an npm-style plugin. That path has **no update mechanism** — treat it as pinning a snapshot, not as the recommended install. + +For local development from a checkout, use a live symlink instead: + +```bash +omp plugin link "$PWD" +``` + +</details> + +Run `/reload-plugins` or start a new omp session after installing so the skills load. omp invokes installed skills as `/skill:<name>` (for example `/skill:ce-plan`), not `/skill-name`. See [`docs/specs/omp.md`](docs/specs/omp.md) for details. ### Antigravity CLI (`agy`) diff --git a/docs/specs/omp.md b/docs/specs/omp.md index d12dcd807..8e25df476 100644 --- a/docs/specs/omp.md +++ b/docs/specs/omp.md @@ -1,47 +1,68 @@ # oh-my-pi (omp) Spec (Plugins and Skills) -Last verified: 2026-08-05 against omp 17.2.7 +Last verified: 2026-08-05 against omp 17.2.9 ## Primary sources ``` -https://github.com/can1357/oh-my-pi/blob/v17.2.7/README.md -https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/skills.md -https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/marketplace.md -https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/session.md -https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/config-usage.md -https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/environment-variables.md -https://github.com/can1357/oh-my-pi/blob/v17.2.7/docs/task-agent-discovery.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/README.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/docs/skills.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/docs/marketplace.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/docs/session.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/docs/config-usage.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/docs/environment-variables.md +https://github.com/can1357/oh-my-pi/blob/v17.2.9/docs/task-agent-discovery.md ``` ## Plugin loading omp discovers plugins natively. Two committed metadata surfaces in this repository cover it: -- The `package.json#pi` manifest, with `extensions` (`./.pi/extensions/compound-engineering.ts`) and `skills` (`./skills`) arrays — the same pi package metadata Pi already consumes. -- The Claude marketplace catalog at `.claude-plugin/marketplace.json`, which omp reads as a fallback when `.omp-plugin/marketplace.json` is absent. omp prefers `.omp-plugin/marketplace.json` only when that file is present; Compound Engineering deliberately ships only the Claude catalog for cross-host parity. +- The `package.json#pi` manifest — the same pi package metadata Pi already consumes. omp's shared plugin loader accepts `package.json.omp || package.json.pi`, so the existing `pi` field is the discovery gate that marks this repository as a plugin package. +- The native omp marketplace catalog at `.omp-plugin/marketplace.json`, which omp reads first. `.claude-plugin/marketplace.json` remains as the Claude Code copy and as omp's fallback when `.omp-plugin/marketplace.json` is absent; the two coexist, and omp ignores the Claude copy when both are present (verified 2026-08-05: discovery output and the cached catalog both come from the `.omp-plugin` copy). -A dry run of `omp install` against this repository confirms both surfaces resolve. No CE converter, writer, or `--to omp` CLI target exists or is planned: per CONCEPTS.md "Native plugin surface", omp support lives in platform metadata, docs, and release validation instead of a new Converter and Writer. +Skill loading is structural, not extension-driven: omp's skill providers scan the plugin package's root `skills/` directory (`omp-plugins` provider for npm/link installs, `claude-plugins` provider for marketplace installs). The bundled extension `.pi/extensions/compound-engineering.ts` is a **no-op on omp** — it registers skill paths through the `resources_discover` hook, and omp implements `ExtensionRunner.emitResourcesDiscover(...)` with no `AgentSession` callsites. The extension still matters in one narrow way: install validates that every declared `extensions` entry resolves and imports to a factory function, and rolls the install back if it does not. So the `pi` block is a discovery gate and an install-time validation risk, but it is not what surfaces the skills. + +A dry run of `omp install` against this repository confirms the metadata surfaces resolve. No CE converter, writer, or `--to omp` CLI target exists or is planned: per CONCEPTS.md "Native plugin surface", omp support lives in platform metadata, docs, and release validation instead of a new Converter and Writer. + +## Updates + +omp's update checker (`checkForUpdates()`) compares the installed plugin version against the **catalog plugin-entry `version`** and permanently skips entries that lack one: + +```ts +catalogVersion = catalog.plugins.find(p => p.name === parsed.name)?.version; +if (!catalogVersion || catalogVersion === installed.version) continue; +``` + +`.omp-plugin/marketplace.json` therefore carries a release-managed `version` on the plugin entry, bumped by release-please through the root component's `extra-files` (`$.plugins[0].version`). This is what makes `omp plugin upgrade`, the 24h catalog refresh, and `marketplace.autoUpdate` (`off` / `notify` / `auto`) see CE releases at all. Note the default `notify` mode writes update availability only to the debug log — it shows no user-facing notification — so `omp config set marketplace.autoUpdate auto` is the setting that actually keeps an install current. Verified end to end on 17.2.9: with the catalog version bumped, `omp plugin upgrade` reinstalls into a new version-keyed cache directory and repoints the `node_modules` symlink; without it, the same change reports "up to date". + +Only the marketplace install path has an update story. `omp install <git-url>` (npm-style plugin install) has none — treat it as pinning a snapshot. ## Install commands -Direct install from a path or Git URL (user scope by default): +Marketplace flow — the recommended install (marketplace name `compound-engineering-plugin`, plugin name `compound-engineering`, both from `.omp-plugin/marketplace.json`): ```text -omp install https://github.com/EveryInc/compound-engineering-plugin +omp plugin marketplace add EveryInc/compound-engineering-plugin +omp plugin install compound-engineering@compound-engineering-plugin ``` -Local development link from a checkout: +Stay current: ```bash -omp plugin link "$PWD" +omp config set marketplace.autoUpdate auto # or: omp plugin upgrade compound-engineering@compound-engineering-plugin ``` -Marketplace flow (marketplace name `compound-engineering-plugin`, plugin name `compound-engineering`, both from `.claude-plugin/marketplace.json`): +Pin-style direct install from a path or Git URL (no update mechanism; user scope by default): ```text -omp plugin marketplace add EveryInc/compound-engineering-plugin -omp plugin install compound-engineering@compound-engineering-plugin +omp install https://github.com/EveryInc/compound-engineering-plugin +``` + +Local development link from a checkout: + +```bash +omp plugin link "$PWD" ``` Verify an install plan before applying it: diff --git a/src/release/metadata.ts b/src/release/metadata.ts index 212b7960d..3c3730485 100644 --- a/src/release/metadata.ts +++ b/src/release/metadata.ts @@ -99,6 +99,21 @@ type GrokMarketplaceManifest = { }> } +// omp (oh-my-pi) marketplace catalog. Same shape as the Claude catalog, but +// the plugin entry carries a `version` because omp's update checker skips +// version-less catalog entries. There is no omp plugin.json — the plugin +// manifest is the existing `package.json` `pi` field. +type OmpMarketplaceManifest = { + name?: string + owner?: { name?: string } + plugins: Array<{ + name: string + version?: string + description?: string + source?: string + }> +} + type SyncOptions = { root?: string componentVersions?: Partial<Record<ReleaseComponent, string>> @@ -588,6 +603,57 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me } } + // omp marketplace catalog. oh-my-pi reads `.omp-plugin/marketplace.json` + // first and falls back to `.claude-plugin/marketplace.json` only when it is + // absent, and its `checkForUpdates()` skips catalog entries without a + // plugin-entry `version` — so unlike the other native catalogs this one + // carries a release-owned plugin version. release-please owns the version + // write via the root component's extra-files (`$.plugins[0].version`), so + // drift is detect-only here; description sync stays write-enabled, same as + // the Claude/Cursor catalogs. + const marketplaceOmpPath = path.join(root, ".omp-plugin", "marketplace.json") + try { + const marketplaceOmp = await readJson<OmpMarketplaceManifest>(marketplaceOmpPath) + const claudeNames = [...marketplaceClaude.plugins.map((p) => p.name)].sort() + const ompNames = [...marketplaceOmp.plugins.map((p) => p.name)].sort() + if (claudeNames.join("|") !== ompNames.join("|")) { + errors.push( + `${marketplaceOmpPath}: plugin list [${ompNames.join(", ")}] does not match ${marketplaceClaudePath} [${claudeNames.join(", ")}]`, + ) + } + let ompChanged = false + for (const plugin of marketplaceOmp.plugins) { + if (plugin.name !== "compound-engineering") continue + if (plugin.version === undefined) { + errors.push( + `${marketplaceOmpPath}: plugin "${plugin.name}" is missing required field "version". omp's update checker skips version-less catalog entries, so the plugin would never upgrade.`, + ) + } else if (plugin.version !== compoundClaude.version) { + ompChanged = true + } + if (plugin.description !== compoundMarketplaceDescription) { + plugin.description = compoundMarketplaceDescription + ompChanged = true + } + // omp resolves string sources inside the marketplace root; the plugin + // is co-located with the catalog, so the source must be "./". + if (plugin.source !== "./") { + errors.push( + `${marketplaceOmpPath}: plugin "${plugin.name}" uses source ${JSON.stringify(plugin.source)}. The plugin is co-located with the catalog, so the source must be "./".`, + ) + } + } + updates.push({ path: marketplaceOmpPath, changed: ompChanged }) + if (write && ompChanged) await writeJson(marketplaceOmpPath, marketplaceOmp) + } catch (err: unknown) { + if ((err as NodeJS.ErrnoException).code === "ENOENT") { + errors.push(`${marketplaceOmpPath} is missing but ${marketplaceClaudePath} exists. omp marketplace parity required.`) + updates.push({ path: marketplaceOmpPath, changed: false }) + } else { + throw err + } + } + // Devin manifest. Devin CLI installs the repo natively from // `.devin-plugin/plugin.json` plus the root `skills/` directory. The manifest // schema has no `skills` path field and Devin has no marketplace catalog, so diff --git a/tests/release-metadata.test.ts b/tests/release-metadata.test.ts index 4daf38e58..3d0bd0fc3 100644 --- a/tests/release-metadata.test.ts +++ b/tests/release-metadata.test.ts @@ -27,6 +27,7 @@ async function makeFixtureRoot(): Promise<string> { await mkdir(path.join(root, ".codex-plugin"), { recursive: true }) await mkdir(path.join(root, ".kimi-plugin"), { recursive: true }) await mkdir(path.join(root, ".grok-plugin"), { recursive: true }) + await mkdir(path.join(root, ".omp-plugin"), { recursive: true }) await mkdir(path.join(root, ".devin-plugin"), { recursive: true }) await mkdir(path.join(root, ".agents", "plugins"), { recursive: true }) @@ -158,6 +159,25 @@ async function makeFixtureRoot(): Promise<string> { 2, ), ) + await writeFile( + path.join(root, ".omp-plugin", "marketplace.json"), + JSON.stringify( + { + name: "compound-engineering-plugin", + owner: { name: "Kieran Klaassen and Trevin Chow" }, + plugins: [ + { + name: "compound-engineering", + version: "2.42.0", + description: "old", + source: "./", + }, + ], + }, + null, + 2, + ), + ) await writeFile( path.join(root, ".claude-plugin", "marketplace.json"), JSON.stringify( @@ -507,6 +527,90 @@ describe("release metadata", () => { ]) }) + test("reports missing omp marketplace catalog as a structural error", async () => { + const root = await makeFixtureRoot() + await Bun.$`rm ${path.join(root, ".omp-plugin", "marketplace.json")}`.quiet() + + const result = await syncReleaseMetadata({ root, write: false }) + + expect(result.errors.some((err) => err.includes(".omp-plugin/marketplace.json is missing"))).toBe(true) + }) + + test("flags omp catalog plugin-version drift without rewriting the version", async () => { + const root = await makeFixtureRoot() + const ompPath = path.join(root, ".omp-plugin", "marketplace.json") + await writeFile( + ompPath, + JSON.stringify( + { + name: "compound-engineering-plugin", + plugins: [{ name: "compound-engineering", version: "2.41.0", source: "./" }], + }, + null, + 2, + ), + ) + + const result = await syncReleaseMetadata({ root, write: true }) + + // Drift is detect-only: release-please owns the version write via the + // root component's extra-files, so sync must flag but never bump it. + const ompUpdate = result.updates.find((u) => u.path === ompPath) + expect(ompUpdate).toBeDefined() + expect(ompUpdate?.changed).toBe(true) + const written = JSON.parse(await Bun.file(ompPath).text()) + expect(written.plugins[0].version).toBe("2.41.0") + }) + + test("reports omp catalog plugin entry without a version as a structural error", async () => { + const root = await makeFixtureRoot() + await writeFile( + path.join(root, ".omp-plugin", "marketplace.json"), + JSON.stringify( + { + name: "compound-engineering-plugin", + plugins: [{ name: "compound-engineering", source: "./" }], + }, + null, + 2, + ), + ) + + const result = await syncReleaseMetadata({ root, write: false }) + + expect( + result.errors.some( + (err) => + err.includes(".omp-plugin/marketplace.json") && + err.includes('missing required field "version"'), + ), + ).toBe(true) + }) + + test("reports omp marketplace plugin-list drift as a structural error", async () => { + const root = await makeFixtureRoot() + await writeFile( + path.join(root, ".omp-plugin", "marketplace.json"), + JSON.stringify( + { + name: "compound-engineering-plugin", + plugins: [{ name: "some-other-plugin", version: "1.0.0", source: "./" }], + }, + null, + 2, + ), + ) + + const result = await syncReleaseMetadata({ root, write: false }) + + expect( + result.errors.some( + (err) => err.includes(".omp-plugin/marketplace.json") && err.includes("does not match"), + ), + ).toBe(true) + }) + + test("reports Devin plugin.json name mismatch as structural error", async () => { const root = await makeFixtureRoot() await writeFile( From e414cc063592c91e2e9f1237a12eafa16229cff5 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Tue, 11 Aug 2026 18:48:15 -0700 Subject: [PATCH 7/9] fix(ce-optimize): make usage prompts host-neutral --- skills/ce-optimize/references/usage-guide.md | 6 +++--- .../user-facing-skill-invocation-rendering.test.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/skills/ce-optimize/references/usage-guide.md b/skills/ce-optimize/references/usage-guide.md index 7eb607578..1947479f8 100644 --- a/skills/ce-optimize/references/usage-guide.md +++ b/skills/ce-optimize/references/usage-guide.md @@ -79,7 +79,7 @@ The goal of the first run is to validate the harness, not to win the optimizatio ### 1. Memory Tuning ```text -Use /ce-optimize to find the smallest memory setting that keeps this service stable under our load test. +Run the `ce-optimize` skill to find the smallest memory setting that keeps this service stable under our load test. The current container limit is 512 MB and the app sometimes OOM-crashes. Do not just jump to 8 GB. Try a small set of realistic memory limits, run the same load test for each one, and score the results using: - did the process OOM @@ -92,7 +92,7 @@ Prefer the smallest memory limit that passes the guard rails. ### 2. Clustering Quality ```text -Use /ce-optimize to improve issue and PR clustering quality. +Run the `ce-optimize` skill to improve issue and PR clustering quality. We have about 18k open issues and PRs. We want to test changes that improve clustering quality, reduce singleton clusters, and improve match quality within each cluster. @@ -104,7 +104,7 @@ Do not optimize only for coverage. Use LLM-as-judge to sample clusters and confi ### 3. Prompt Optimization ```text -Use /ce-optimize to create a summarization prompt for issues and PRs that minimizes token spend while still producing summaries that are good enough for downstream clustering. +Run the `ce-optimize` skill to create a summarization prompt for issues and PRs that minimizes token spend while still producing summaries that are good enough for downstream clustering. I want the loop to compare prompt variants, measure token cost, and judge whether the summaries preserve the distinctions needed to cluster related issues together without merging unrelated ones. ``` diff --git a/tests/skills/user-facing-skill-invocation-rendering.test.ts b/tests/skills/user-facing-skill-invocation-rendering.test.ts index 2ebae7b35..07919b84f 100644 --- a/tests/skills/user-facing-skill-invocation-rendering.test.ts +++ b/tests/skills/user-facing-skill-invocation-rendering.test.ts @@ -141,6 +141,15 @@ describe("user-facing skill invocation rendering", () => { expect(optimize).toContain("**Run `ce-compound`**") }) + test("static ce-optimize examples use host-neutral capability wording", () => { + const usageGuide = readRepoFile("skills/ce-optimize/references/usage-guide.md") + + expect(usageGuide).toContain("Run the `ce-optimize` skill to find") + expect(usageGuide).toContain("Run the `ce-optimize` skill to improve") + expect(usageGuide).toContain("Run the `ce-optimize` skill to create") + expect(usageGuide).not.toContain("Use /ce-optimize") + }) + test("Codex goal remains a built-in exception, not a converted skill invocation", () => { const plan = readRepoFile("skills/ce-plan/SKILL.md") const planHandoff = readRepoFile("skills/ce-plan/references/plan-handoff.md") From 9b6e0c05799337c00ad7dd6337fd99f10518040e Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Wed, 12 Aug 2026 10:24:59 -0700 Subject: [PATCH 8/9] fix(omp): limit native invocation syntax to hidden skills --- AGENTS.md | 2 +- README.md | 4 +- .../portable-agent-skill-authoring.md | 2 +- docs/specs/omp.md | 3 +- skills/ce-babysit-pr/SKILL.md | 2 +- skills/ce-babysit-pr/references/watch-loop.md | 2 +- skills/ce-commit-push-pr/SKILL.md | 2 +- skills/ce-compound/SKILL.md | 4 +- skills/ce-dogfood/SKILL.md | 2 +- skills/ce-explain/SKILL.md | 2 +- skills/ce-handoff/SKILL.md | 2 +- skills/ce-plan/references/plan-handoff.md | 2 +- .../ce-plan/references/universal-planning.md | 2 +- skills/ce-setup/SKILL.md | 2 +- skills/ce-sweep/SKILL.md | 2 +- skills/ce-sweep/references/interview.md | 2 +- skills/lfg/SKILL.md | 2 +- tests/ce-babysit-pr-contract.test.ts | 4 +- tests/commit-push-pr-contract.test.ts | 2 +- tests/skill-conventions.test.ts | 8 +- tests/skills/ce-explain-routing.test.ts | 2 +- ...-facing-skill-invocation-rendering.test.ts | 120 +++++++++++------- 22 files changed, 102 insertions(+), 73 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 937eadc0c..f7a488aad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -124,7 +124,7 @@ That field guide is the canonical reasoning layer for outcome-first authoring, m ### User-Facing Skill Invocations -Keep agent-to-agent or skill-to-skill routing semantic: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. When a skill prints or copies a user-runnable invocation, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:<name>` (for example `/skill:ce-plan`) when the active host is oh-my-pi (`omp`). In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Do not apply this rendering rule to built-in commands such as `/goal`. +Keep agent-to-agent or skill-to-skill routing semantic: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. When a skill prints or copies a user-runnable invocation, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation. On oh-my-pi (`omp`), keep the default form for model-visible targets; use native `/skill:<name>` only when the target is not model-visible because it declares `disable-model-invocation` or `hide` (for example, `/skill:ce-polish`). In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Do not apply this rendering rule to built-in commands such as `/goal`. At runtime, put the smallest self-contained rendering rule immediately before the smallest section that contains all affected user-copy seams. Do not repeat it in every step; repeat it only in a separately loaded reference that independently owns output. diff --git a/README.md b/README.md index b12368980..e1a9f6e8c 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Re-running the Bun convert/install CLI for Codex also strips the block if it is **Each unit of engineering work should make subsequent units easier -- not harder.** -Invocation syntax: this README uses `/skill-name` examples for slash-skill hosts. In Codex, invoke installed skills with `$skill-name` (for example, `$ce-plan` and `$lfg`); in oh-my-pi (omp), invoke installed skills as `/skill:<name>` (for example, `/skill:ce-plan`); `/goal` remains a Codex built-in command. +Invocation syntax: this README uses `/skill-name` examples for slash-skill hosts. In Codex, invoke installed skills with `$skill-name` (for example, `$ce-plan` and `$lfg`). In oh-my-pi (omp), these prompts can model-route to visible skills; use the native deterministic `/skill:<name>` form for manual-only or hidden skills (for example, `/skill:ce-polish`). `/goal` remains a Codex built-in command. Traditional development accumulates technical debt. Every feature adds complexity. Every bug fix leaves behind a little more local knowledge that someone has to rediscover later. The codebase gets larger, the context gets harder to hold, and the next change becomes slower. @@ -430,7 +430,7 @@ omp plugin link "$PWD" </details> -Run `/reload-plugins` or start a new omp session after installing so the skills load. omp invokes installed skills as `/skill:<name>` (for example `/skill:ce-plan`), not `/skill-name`. See [`docs/specs/omp.md`](docs/specs/omp.md) for details. +Run `/reload-plugins` or start a new omp session after installing so the skills load. omp's native deterministic command is `/skill:<name>` (for example, `/skill:ce-plan`); ordinary `/skill-name` prompts can also model-route to visible skills, but manual-only or hidden skills require the native form. See [`docs/specs/omp.md`](docs/specs/omp.md) for details. ### Antigravity CLI (`agy`) diff --git a/docs/solutions/skill-design/portable-agent-skill-authoring.md b/docs/solutions/skill-design/portable-agent-skill-authoring.md index dd4e9bb58..4b89b9875 100644 --- a/docs/solutions/skill-design/portable-agent-skill-authoring.md +++ b/docs/solutions/skill-design/portable-agent-skill-authoring.md @@ -158,7 +158,7 @@ Evaluate activation separately from execution with a few positive triggers, adja ### Render user invocations at the output boundary -Keep agent-to-agent routing capability-first: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. Exact command spelling belongs only where the skill prints or copies a user-runnable invocation. At that output seam, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:<name>` (for example `/skill:ce-plan`) when the active host is oh-my-pi (`omp`). In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Built-in commands such as `/goal` are separate capabilities, not evidence that slash-prefixed skill names are callable in Codex. +Keep agent-to-agent routing capability-first: format formal skill names as inline code (for example, `ce-plan`) and invoke the named skill through the active harness's callable skill mechanism. Exact command spelling belongs only where the skill prints or copies a user-runnable invocation. At that output seam, default to `/skill-name`; use `$skill-name` only when the active harness is Codex or explicitly documents dollar-prefixed skill invocation. On oh-my-pi (`omp`), keep the default form for model-visible targets; use native `/skill:<name>` only when the target is not model-visible because it declares `disable-model-invocation` or `hide` (for example, `/skill:ce-polish`). In prose, render only the invocation as inline code; use a fenced block only when the command stands alone. Output exactly one form. Built-in commands such as `/goal` are separate capabilities, not evidence that slash-prefixed skill names are callable in Codex. An authoring guide cannot supply runtime behavior to an installed skill. Put the smallest self-contained rendering rule immediately before the smallest section that contains all affected user-copy seams. Do not repeat it in every step; repeat it only in a separately loaded reference that independently owns output. Use a focused contract test when independently edited skills must preserve the same handoff, without duplicating the rationale or a harness matrix. diff --git a/docs/specs/omp.md b/docs/specs/omp.md index 8e25df476..eb9bc60c8 100644 --- a/docs/specs/omp.md +++ b/docs/specs/omp.md @@ -77,7 +77,8 @@ omp install <path-or-git> --dry-run --json | Contract | omp behavior | | --- | --- | -| User skill invocation | `/skill:<name>` — one command per discovered skill; NOT `/skill-name` and NOT `$skill-name` | +| Model-routed skill prompt | An otherwise unknown `/skill-name ...` prompt reaches the model, which can select a visible discovered skill; this is convenient, not deterministic | +| Native deterministic skill invocation | `/skill:<name>` — one registered command per discovered skill; use this for manual-only or hidden skills that are not model-visible | | Blocking questions | Built-in `ask` tool | | Subagent dispatch | Built-in `task` tool, with worktree isolation and schema-checked results | | Task tracking | Built-in `todo` tool | diff --git a/skills/ce-babysit-pr/SKILL.md b/skills/ce-babysit-pr/SKILL.md index f1445a35a..23cdf5106 100644 --- a/skills/ce-babysit-pr/SKILL.md +++ b/skills/ce-babysit-pr/SKILL.md @@ -83,7 +83,7 @@ Once `stack-ready` or `stack-land` is in effect, that posture authorizes sequent Then establish **how the watch sustains itself** — a skill can't be re-invoked by magic once its turn ends, so *you* set up the loop. **The default is a self-sustaining, in-session watch: you do not do one tick and hand back a resume command.** Read `references/watch-loop.md` for the mechanics, then: -**User-runnable resume syntax.** Whenever this skill prints or copies a resume invocation, default to `/ce-babysit-pr <url>` and, when the run posture is not `target`, append the same `posture:stack-ready` or `posture:stack-land` token so checkpoint / durable / session re-entry keeps stack scope. Use `$ce-babysit-pr <url> [posture:…]` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-babysit-pr <url> [posture:…]` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable resume syntax.** Whenever this skill prints or copies a resume invocation, default to `/ce-babysit-pr <url>` and, when the run posture is not `target`, append the same `posture:stack-ready` or `posture:stack-land` token so checkpoint / durable / session re-entry keeps stack scope. Use `$ce-babysit-pr <url> [posture:…]` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. - **Self-sustaining in-session watch (default).** Start a cheap deterministic background change-detector — `pr-snapshot watch` (Step 2 has the invocation) — which polls the PR with **no agent tokens** and prints a single wake sentinel *only* when there's work to inspect or a stop condition. Then **stay in this session and wait for that sentinel**, using whatever background-and-wake capability your harness exposes. You need exactly one capability: *run a background process and be woken when it emits a line, without ending your turn* — reach for whatever your harness gives you (examples, not a fixed list: Claude Code's background `Bash` + a `Monitor`/wait, Cursor's `Shell` background + `notify_on_output`, Grok's `get_command_or_subagent_output`, `ScheduleWakeup` under `/loop`). On each wake, run **one tick** (Step 2's ordering invariant), persist, then go back to waiting (Step 5). The detector *only* flags that something changed — every tick's judgment (resolve comments, debug CI, decide merge-ready) is agent reasoning plus a sub-skill call, so re-enter *this* agent each wake; **do not collapse the loop into a shell script that greps and acts on its own** (`pr-snapshot watch` loops internally, which makes that substitution tempting — it cannot do the reasoning the tick requires). Staying in-session keeps everything decided in *this* conversation — declined nits, a reviewer judged wrong, your mid-run steering — and spends reasoning only when something actually changed. Continue until a Step 3 stop condition. **Describe the capability and use your own tool for it — do not ask the user to type a slash command; a skill drives tool calls, not keystrokes.** - **Checkpoint (the honest floor).** Only when the harness genuinely exposes **no** background-and-wake capability (some sandboxed GUI apps): run **exactly one tick**, persist, report, and print the exact re-run command. Monitoring is *paused* — say so plainly. Never fake a loop with a foreground `sleep` (Claude Code blocks it) or by "just continuing" (nothing wakes the next tick). diff --git a/skills/ce-babysit-pr/references/watch-loop.md b/skills/ce-babysit-pr/references/watch-loop.md index 21648cab3..457a63b3b 100644 --- a/skills/ce-babysit-pr/references/watch-loop.md +++ b/skills/ce-babysit-pr/references/watch-loop.md @@ -22,7 +22,7 @@ The needed capability is generic — *run a background process and be woken when | Codex (CLI) | a runtime-owned background exec that re-runs the tick (a detached `nohup` is **reaped** when the tool call ends) | No (session-bound) | | GUI apps / headless / unknown | none reliable → **checkpoint** | — | -**User-runnable resume syntax.** Whenever this reference tells the skill to print or copy a resume invocation, default to `/ce-babysit-pr <url>` and, when the run posture is not `target`, append the same `posture:stack-ready` or `posture:stack-land` token so checkpoint / durable / session re-entry keeps stack scope. Use `$ce-babysit-pr <url> [posture:…]` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-babysit-pr <url> [posture:…]` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable resume syntax.** Whenever this reference tells the skill to print or copy a resume invocation, default to `/ce-babysit-pr <url>` and, when the run posture is not `target`, append the same `posture:stack-ready` or `posture:stack-land` token so checkpoint / durable / session re-entry keeps stack scope. Use `$ce-babysit-pr <url> [posture:…]` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. **Checkpoint (the floor):** when no background-and-wake capability exists, run one tick, persist, report, and print the exact host-rendered re-run invocation — monitoring is *paused*, say so plainly. Because every tick is disk-resumable, checkpoint is the same loop hand-cranked; the in-session watch only automates the crank. Never fake a loop with a foreground `sleep` (blocked on Claude Code, discouraged elsewhere) or a detached `nohup` (reaped/unsupported on several harnesses). diff --git a/skills/ce-commit-push-pr/SKILL.md b/skills/ce-commit-push-pr/SKILL.md index 7cf0ab257..cd3b771d7 100644 --- a/skills/ce-commit-push-pr/SKILL.md +++ b/skills/ce-commit-push-pr/SKILL.md @@ -145,7 +145,7 @@ Then continue with the reference (Steps A–E, including Step B2 when the teachi If the doc write, commit, or push fails, warn and continue to PR creation without the link — never strand the flow between commit and PR. -**User-runnable invocation rendering.** For the output handoffs below, default to `/ce-explain <name>`. Use `$ce-explain <name>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-explain <name>` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** For the output handoffs below, default to `/ce-explain <name>`. Use `$ce-explain <name>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. **Concept trailer** — when a body applied by this run contains a `## New concepts` section, print one line after the PR URL in every mode: `New concepts: <name>[, <name>]`. In interactive full-workflow runs follow it with one line per taught concept telling the user to invoke `ce-explain <name>` using the rendering rule above. No trailer when this run applied no body — including a rewrite that was declined or pipeline-defaulted to no — or no PR exists. diff --git a/skills/ce-compound/SKILL.md b/skills/ce-compound/SKILL.md index 872e73cd2..1bbb260e6 100644 --- a/skills/ce-compound/SKILL.md +++ b/skills/ce-compound/SKILL.md @@ -441,7 +441,7 @@ Use these rules: - If context is already tight or you are in lightweight mode, do not expand into a broad refresh automatically; instead recommend `ce-compound-refresh` as the next step with a scope hint - **In non-interactive mode**, never invoke `ce-compound-refresh` and never ask the user. Surface the recommended scope hint in the terminal report's "Refresh recommendation" line and let the caller decide -**User-runnable refresh rendering.** When recommending rather than directly invoking `ce-compound-refresh`, default to `/ce-compound-refresh <scope>`; use `$ce-compound-refresh <scope>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-compound-refresh <scope>` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. Agent-to-agent invocation remains semantic. +**User-runnable refresh rendering.** When recommending rather than directly invoking `ce-compound-refresh`, default to `/ce-compound-refresh <scope>`; use `$ce-compound-refresh <scope>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. Agent-to-agent invocation remains semantic. When invoking or recommending `ce-compound-refresh`, be explicit about the argument to pass. Prefer the narrowest useful scope: @@ -554,7 +554,7 @@ The orchestrator (main conversation) performs ALL of the following in one sequen 7. **Frontmatter parser-safety check**: validate the written doc exactly as in Phase 2 step 8, using the same bundled-script existence guard and manual fallback checklist. Fix any violation and repeat the check; do not report success until the written frontmatter is parser-safe. 8. **Skip specialized agent reviews** (Phase 3) and the semantic grounding validator (Phase 2.45 step 2) to conserve context -**User-runnable retry rendering.** In the lightweight completion output below, default to `/ce-compound`; use `$ce-compound` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-compound` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable retry rendering.** In the lightweight completion output below, default to `/ce-compound`; use `$ce-compound` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. **Lightweight completion output:** In non-interactive Lightweight, do not emit this interactive block; use the depth-specific report under `Success Output` > `Non-interactive mode` instead. In interactive Lightweight, emit: ``` diff --git a/skills/ce-dogfood/SKILL.md b/skills/ce-dogfood/SKILL.md index 090a79471..1b622c2ed 100644 --- a/skills/ce-dogfood/SKILL.md +++ b/skills/ce-dogfood/SKILL.md @@ -17,7 +17,7 @@ This workflow drives the browser exclusively through the `agent-browser` CLI. Do ## Prerequisites -**User-runnable invocation rendering.** In prerequisite failures, default to `/ce-setup` and `/ce-dogfood <original arguments>`; use `$ce-setup` and `$ce-dogfood <original arguments>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-setup` and `/skill:ce-dogfood <original arguments>` when the active host is oh-my-pi (`omp`). Render only each invocation as inline code and output one form only. +**User-runnable invocation rendering.** In prerequisite failures, default to `/ce-setup` and `/ce-dogfood <original arguments>`; use `$ce-setup` and `$ce-dogfood <original arguments>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. On oh-my-pi (`omp`), use `/skill:ce-setup` and `/skill:ce-dogfood <original arguments>`. Render only each invocation as inline code and output one form only. - A local dev server you can start (`bin/dev`, `rails server`, `npm run dev`, etc.). - `agent-browser` installed. Check: diff --git a/skills/ce-explain/SKILL.md b/skills/ce-explain/SKILL.md index b109c5a0d..54cab3e45 100644 --- a/skills/ce-explain/SKILL.md +++ b/skills/ce-explain/SKILL.md @@ -124,7 +124,7 @@ Detect destinations by capability — probe the agent's own toolset and session **Improvement observations.** When composing the explainer surfaced things that could be better, route them by type once the destination is settled — offer, don't auto-fire. "Settled" means the artifact was sent, or the user declined, or the run stopped at a consent gate they didn't answer; in that last case the run ends there and these offers are skipped, like the non-interactive case above. Never raise them while any of the asks above is still open — the destination question, the audience re-render offer, or a publisher's consent gate. -**User-runnable invocation rendering.** Only the user-run handoff below uses printed invocation syntax. Default to `/ce-polish`; use `$ce-polish` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-polish` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** Only the user-run handoff below uses printed invocation syntax. Default to `/ce-polish`; use `$ce-polish` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. On oh-my-pi (`omp`), use `/skill:ce-polish`. Render only the invocation as inline code and output one form only. - **New-capability ideas** — offer first; on acceptance invoke the `ce-ideate` skill via the platform's skill-invocation primitive, passing the observations as seed context. Do not merely tell the user to run it. - **Code-clarity findings** — offer first; on acceptance invoke the `ce-simplify-code` skill via the platform's skill-invocation primitive, passing the observations and the files they concern. Do not merely tell the user to run it. diff --git a/skills/ce-handoff/SKILL.md b/skills/ce-handoff/SKILL.md index c156d1531..99f8e5051 100644 --- a/skills/ce-handoff/SKILL.md +++ b/skills/ce-handoff/SKILL.md @@ -51,7 +51,7 @@ Use a readable topic slug as the filename. When Git context exists, use a saniti Treat creation as complete only after confirming the destination contains the handoff. Give a succinct, context-specific summary of what the generated handoff captures so the user can verify its substance without opening it; do not impose a fixed summary template. Then report the final path or URL, applicable retention or access limits, and any warnings together. Managed `/tmp` storage is OS-managed and not permanent. Its automatic discovery assumes the receiving session can see the same host filesystem; otherwise tell the user to transfer or publish the handoff to a receiver-visible location and resume from that explicit source. -**User-runnable invocation rendering.** For the copyable resume command below, default to `/ce-handoff resume <source>`; use `$ce-handoff resume <source>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-handoff resume <source>` when the active host is oh-my-pi (`omp`). Render it as the fenced command below and output one form only. +**User-runnable invocation rendering.** For the copyable resume command below, default to `/ce-handoff resume <source>`; use `$ce-handoff resume <source>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render it as the fenced command below and output one form only. End the creation response with one fenced, copyable command using the final path or URL and the rendering rule above: diff --git a/skills/ce-plan/references/plan-handoff.md b/skills/ce-plan/references/plan-handoff.md index ccd888999..27b9ff8c6 100644 --- a/skills/ce-plan/references/plan-handoff.md +++ b/skills/ce-plan/references/plan-handoff.md @@ -126,7 +126,7 @@ Based on selection (the bare per-option routing is also stated inline in the SKI If the upload fails (network error, Proof API down), retry once after a short wait. If it still fails, tell the user the upload didn't succeed and briefly explain why, then return to the options — don't leave them wondering why the option did nothing. - **Open in browser** -> Display the absolute path to the `.html` plan file so the user can open it locally. Where the platform exposes a browser-opening primitive (e.g., `open` on macOS, `xdg-open` on Linux, `start` on Windows), the agent may invoke it directly; otherwise print the absolute path and let the user open it. After the path is displayed (or the browser is opened), return to the post-generation options so the user can pick a follow-up action. -- **Free-form prompts that target the findings** (e.g., the user types "review", "walk through", "deep review" instead of picking a numbered option) -> route as if they had picked `Decide on the review's open items`. Do not loop back to the menu without firing the review. **Exception:** when the envelope carries `skipped_reason: output_format_html`, do not fire `ce-doc-review` — instead, reply once that `ce-doc-review` is markdown-only today, the HTML plan cannot be reviewed without HTML-aware mutation support, and the user can regenerate as markdown by invoking `ce-plan output:md`. For this user-runnable output, default to `/ce-plan output:md`; use `$ce-plan output:md` only on Codex or a host explicitly documented as dollar-prefixed; use `/skill:ce-plan output:md` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. Then loop back to the menu. When it carries `skipped_reason: skill_unreachable`, say that `ce-doc-review` could not be invoked and loop back without misreporting a review failure. +- **Free-form prompts that target the findings** (e.g., the user types "review", "walk through", "deep review" instead of picking a numbered option) -> route as if they had picked `Decide on the review's open items`. Do not loop back to the menu without firing the review. **Exception:** when the envelope carries `skipped_reason: output_format_html`, do not fire `ce-doc-review` — instead, reply once that `ce-doc-review` is markdown-only today, the HTML plan cannot be reviewed without HTML-aware mutation support, and the user can regenerate as markdown by invoking `ce-plan output:md`. For this user-runnable output, default to `/ce-plan output:md`; use `$ce-plan output:md` only on Codex or a host explicitly documented as dollar-prefixed. Render only the invocation as inline code and output one form only. Then loop back to the menu. When it carries `skipped_reason: skill_unreachable`, say that `ce-doc-review` could not be invoked and loop back without misreporting a review failure. - **Other free-form input** -> Accept revisions to the plan and loop back to options. ## Issue Creation diff --git a/skills/ce-plan/references/universal-planning.md b/skills/ce-plan/references/universal-planning.md index dc7530552..ff0da8153 100644 --- a/skills/ce-plan/references/universal-planning.md +++ b/skills/ce-plan/references/universal-planning.md @@ -8,7 +8,7 @@ The detection stub in SKILL.md routes here for anything that isn't clearly softw - **Is this actually a software task?** The key distinction is task-type, not topic-domain. A study guide about Rust is non-software (producing educational content). A Rust library refactor is software (modifying code). If this is actually software, return to Phase 0.2 in the main SKILL.md. - **Is this a trivial single-fact lookup?** Only a question answerable from one fact with no research, retrieval, or judgment skips planning — answer it directly and stop, in the user's terms. Do not narrate that it "isn't a planning task" or explain the routing; that is process exhaust (see Veil of value below). Examples: "zsh: command not found: brew", "what's the capital of France." A question that needs multiple steps, any retrieval, or synthesis to answer well does **not** qualify: it is an answer-seeking task (see Disposition below), not a quick-help exit. When unsure, do not exit. -- **Pipeline mode?** If invoked from `lfg` or any `disable-model-invocation` context: tell the user this is a non-software task, `lfg` requires the software-only `ce-work` path, and they should invoke `ce-plan` directly for non-software planning. For this user-runnable output, default to `/ce-plan`; use `$ce-plan` only on Codex or a host explicitly documented as dollar-prefixed; use `/skill:ce-plan` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only, then stop. +- **Pipeline mode?** If invoked from `lfg` or any `disable-model-invocation` context: tell the user this is a non-software task, `lfg` requires the software-only `ce-work` path, and they should invoke `ce-plan` directly for non-software planning. For this user-runnable output, default to `/ce-plan`; use `$ce-plan` only on Codex or a host explicitly documented as dollar-prefixed. Render only the invocation as inline code and output one form only, then stop. - **Unified artifact guard.** Universal-planning outputs are not software implementation plans. Do not label them `artifact_contract: ce-unified-plan/v1` and do not produce a `/goal` launch block unless the deliverable itself is a complete software implementation plan with Product Contract, Planning Contract, Implementation Units, Verification Contract, and Definition of Done. Once past these checks, commit to the task — do not bail because it looks like a "lookup" or "research question." The user invoked the planning tool on purpose. Then choose the disposition below. diff --git a/skills/ce-setup/SKILL.md b/skills/ce-setup/SKILL.md index 2e564160d..9f1bc20a7 100644 --- a/skills/ce-setup/SKILL.md +++ b/skills/ce-setup/SKILL.md @@ -61,7 +61,7 @@ Display the diagnostic output to the user. Missing optional tools are not setup ### Step 3: Decide Whether Fixes Are Needed -**User-runnable invocation rendering.** In setup summaries, default to `/ce-setup`; use `$ce-setup` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-setup` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** In setup summaries, default to `/ce-setup`; use `$ce-setup` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. On oh-my-pi (`omp`), use `/skill:ce-setup`. Render only the invocation as inline code and output one form only. Proceed to Phase 2 only if one or more repo-local project issues exist: diff --git a/skills/ce-sweep/SKILL.md b/skills/ce-sweep/SKILL.md index 7d5269ca7..4d37ae75f 100644 --- a/skills/ce-sweep/SKILL.md +++ b/skills/ce-sweep/SKILL.md @@ -183,7 +183,7 @@ Interactive only. For items needing a product call, ask the user — grouped by #### 2i. Wrap-up -**User-runnable invocation rendering.** In the summary handoff below, default to `/lfg <root>/plans/feedback-sweep-plan.md`; use `$lfg <root>/plans/feedback-sweep-plan.md` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:lfg <root>/plans/feedback-sweep-plan.md` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** In the summary handoff below, default to `/lfg <root>/plans/feedback-sweep-plan.md`; use `$lfg <root>/plans/feedback-sweep-plan.md` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. - **Commit.** `git add` ONLY `<root>/plans/feedback-sweep-plan.md` plus `<state>` when it is repo-internal (never `-A`; machine-local state under `/tmp` is never committed), then commit `docs(sweep): feedback sweep <date>`. A commit failure is reported, not fatal. In local-commit mode, never push. In shared-branch mode (`sweep_shared_branch: true`), fetch, rebase, and push the final commit. - **Record the run.** `run-record --state <state> --writer <writer> --outcome <completed|partial|failed> --counts '<per-source JSON>' --timestamp <ISO now>`. diff --git a/skills/ce-sweep/references/interview.md b/skills/ce-sweep/references/interview.md index 0fb2c9d43..b82cf8f56 100644 --- a/skills/ce-sweep/references/interview.md +++ b/skills/ce-sweep/references/interview.md @@ -4,7 +4,7 @@ Loaded by `SKILL.md` when `ce-sweep` runs with no `feedback_sources` configured. This interview is **interactive only**. The caller refuses first-run setup in non-interactive mode — a scheduled or piped run with no config aborts and tells the user to run `ce-sweep` interactively once. Do not attempt to infer sources, actions, or approvals without asking. -**User-runnable invocation rendering.** Whenever this interview prints or registers a `ce-sweep` invocation, default to `/ce-sweep` (plus any arguments); use `$ce-sweep` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation; use `/skill:ce-sweep` (plus any arguments) when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. +**User-runnable invocation rendering.** Whenever this interview prints or registers a `ce-sweep` invocation, default to `/ce-sweep` (plus any arguments); use `$ce-sweep` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. On oh-my-pi (`omp`), use `/skill:ce-sweep` (plus any arguments). Render only the invocation as inline code and output one form only. ## Interaction Method diff --git a/skills/lfg/SKILL.md b/skills/lfg/SKILL.md index 1cac1760a..0b8293872 100644 --- a/skills/lfg/SKILL.md +++ b/skills/lfg/SKILL.md @@ -135,7 +135,7 @@ When the implementation instruction instead names an ordered fallback list, do n 10. Output `<promise>DONE</promise>` when complete - For the two user-runnable handoffs below, default to `/ce-explain <name>` / `/ce-babysit-pr <pr-url>`. Use `$ce-explain <name>` / `$ce-babysit-pr <pr-url>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Use `/skill:ce-explain <name>` / `/skill:ce-babysit-pr <pr-url>` when the active host is oh-my-pi (`omp`). Render only the invocation as inline code and output one form only. + For the two user-runnable handoffs below, default to `/ce-explain <name>` / `/ce-babysit-pr <pr-url>`. Use `$ce-explain <name>` / `$ce-babysit-pr <pr-url>` only when the active host is Codex or explicitly documents dollar-prefixed skill invocation. Render only the invocation as inline code and output one form only. If step 8 recorded a `New concepts:` trailer, first echo one line per concept: `New concept introduced: <name> — run <rendered ce-explain invocation> to go deeper.` diff --git a/tests/ce-babysit-pr-contract.test.ts b/tests/ce-babysit-pr-contract.test.ts index 07c4ad7fc..9c5fa995f 100644 --- a/tests/ce-babysit-pr-contract.test.ts +++ b/tests/ce-babysit-pr-contract.test.ts @@ -367,8 +367,8 @@ describe("ce-babysit-pr cross-skill contract parity", () => { expect(renderingRule).toBeDefined() expect(renderingRule).toContain("/ce-babysit-pr <url>") expect(renderingRule).toContain("$ce-babysit-pr <url> [posture:…]") - expect(renderingRule).toContain("/skill:ce-babysit-pr <url> [posture:…]") - expect(renderingRule).toMatch(/Codex[\s\S]+oh-my-pi \(`omp`\)[\s\S]+output one form only/i) + expect(renderingRule).not.toContain("/skill:ce-babysit-pr") + expect(renderingRule).toMatch(/Codex[\s\S]+output one form only/i) expect(text).toContain("exec '<host-rendered resume invocation>'") } }) diff --git a/tests/commit-push-pr-contract.test.ts b/tests/commit-push-pr-contract.test.ts index 57b6669b8..227a76e93 100644 --- a/tests/commit-push-pr-contract.test.ts +++ b/tests/commit-push-pr-contract.test.ts @@ -246,7 +246,7 @@ describe("PR concept teaching contract", () => { expect(trailer).toContain("using the rendering rule above") expect(trailer).toContain("$ce-explain <name>") expect(trailer).toContain("/ce-explain <name>") - expect(trailer).toContain("/skill:ce-explain <name>") + expect(trailer).not.toContain("/skill:ce-explain") expect(trailer).toMatch(/default to `\/ce-explain <name>`[\s\S]{0,220}Codex[\s\S]{0,260}output one form only/i) }) diff --git a/tests/skill-conventions.test.ts b/tests/skill-conventions.test.ts index 70e8097f4..05757fbfa 100644 --- a/tests/skill-conventions.test.ts +++ b/tests/skill-conventions.test.ts @@ -147,17 +147,17 @@ describe("user-facing skill invocation authoring contract", () => { expect(text).toMatch(/in prose, render only the invocation as inline code[^\n]*fenced block/i) expect(text).toMatch(/active host|active harness/i) expect(text).toMatch(/default to `\/skill-name`[\s\S]{0,160}Codex[\s\S]{0,160}dollar-prefixed/i) - expect(text).toContain("`/skill:ce-plan`") - expect(text).toMatch(/\/skill:<name>`[^\n]*oh-my-pi \(`omp`\)|oh-my-pi \(`omp`\)[^\n]*\/skill:<name>`/i) + expect(text).toContain("`/skill:ce-polish`") + expect(text).toMatch(/oh-my-pi \(`omp`\)[^\n]*\/skill:<name>`[^\n]*disable-model-invocation/i) expect(text).toMatch(/\/goal[\s\S]{0,180}(built-in|exception)|built-in[\s\S]{0,180}\/goal/i) expect(text).toMatch(/smallest section[\s\S]{0,180}do not repeat[\s\S]{0,180}separately loaded reference/i) } }) - test("README explains Codex invocation syntax without rewriting the built-in goal command", () => { + test("README distinguishes model-routed copy from deterministic OMP invocation", () => { expect(ROOT_README).toMatch(/README uses `\/skill-name`[\s\S]{0,180}Codex[\s\S]{0,120}`\$skill-name`/i) expect(ROOT_README).toContain("`$ce-plan` and `$lfg`") - expect(ROOT_README).toMatch(/oh-my-pi \(omp\)[^\n]*`\/skill:<name>`[^\n]*`\/skill:ce-plan`/i) + expect(ROOT_README).toMatch(/oh-my-pi \(omp\)[^\n]*`\/skill:<name>`[^\n]*(?:manual-only|deterministic)/i) expect(ROOT_README).toMatch(/\/goal[\s\S]{0,80}Codex built-in/i) }) }) diff --git a/tests/skills/ce-explain-routing.test.ts b/tests/skills/ce-explain-routing.test.ts index 5e4e0c3e0..5f7362724 100644 --- a/tests/skills/ce-explain-routing.test.ts +++ b/tests/skills/ce-explain-routing.test.ts @@ -102,7 +102,7 @@ describe("ce-explain destination and handoff routing", () => { renderingRule![0].includes("/skill:ce-polish") && /active host|Codex/i.test(renderingRule![0]) && /default to `\/ce-polish`[^.]{0,180}dollar-prefixed/i.test(renderingRule![0]) && - /\/skill:ce-polish` when the active host is oh-my-pi \(`omp`\)/i.test(renderingRule![0]), + /oh-my-pi \(`omp`\)[^\n]*\/skill:ce-polish/i.test(renderingRule![0]), "`ce-explain` SKILL.md polish handoff must present observations in chat and render one host-correct user invocation for `ce-polish`.", ).toBe(true) expect( diff --git a/tests/skills/user-facing-skill-invocation-rendering.test.ts b/tests/skills/user-facing-skill-invocation-rendering.test.ts index 07919b84f..4a2b225b1 100644 --- a/tests/skills/user-facing-skill-invocation-rendering.test.ts +++ b/tests/skills/user-facing-skill-invocation-rendering.test.ts @@ -6,100 +6,128 @@ function readRepoFile(relativePath: string): string { return readFileSync(path.join(process.cwd(), relativePath), "utf8") } -const rendererCases = [ +const modelVisibleRendererCases = [ { file: "skills/lfg/SKILL.md", defaults: ["/ce-explain <name>", "/ce-babysit-pr <pr-url>"], codex: ["$ce-explain <name>", "$ce-babysit-pr <pr-url>"], - omp: ["/skill:ce-explain <name>", "/skill:ce-babysit-pr <pr-url>"], + unnecessaryOmp: ["/skill:ce-explain <name>", "/skill:ce-babysit-pr <pr-url>"], }, { file: "skills/ce-babysit-pr/SKILL.md", defaults: ["/ce-babysit-pr <url>"], codex: ["$ce-babysit-pr <url>"], - omp: ["/skill:ce-babysit-pr <url>"], + unnecessaryOmp: ["/skill:ce-babysit-pr <url>"], }, { file: "skills/ce-babysit-pr/references/watch-loop.md", defaults: ["/ce-babysit-pr <url>"], codex: ["$ce-babysit-pr <url>"], - omp: ["/skill:ce-babysit-pr <url>"], + unnecessaryOmp: ["/skill:ce-babysit-pr <url>"], }, { file: "skills/ce-commit-push-pr/SKILL.md", defaults: ["/ce-explain <name>"], codex: ["$ce-explain <name>"], - omp: ["/skill:ce-explain <name>"], - }, - { - file: "skills/ce-explain/SKILL.md", - defaults: ["/ce-polish"], - codex: ["$ce-polish"], - omp: ["/skill:ce-polish"], - }, - { - file: "skills/ce-setup/SKILL.md", - defaults: ["/ce-setup"], - codex: ["$ce-setup"], - omp: ["/skill:ce-setup"], - }, - { - file: "skills/ce-dogfood/SKILL.md", - defaults: ["/ce-setup", "/ce-dogfood <original arguments>"], - codex: ["$ce-setup", "$ce-dogfood <original arguments>"], - omp: ["/skill:ce-setup", "/skill:ce-dogfood <original arguments>"], + unnecessaryOmp: ["/skill:ce-explain <name>"], }, { file: "skills/ce-sweep/SKILL.md", defaults: ["/lfg <root>/plans/feedback-sweep-plan.md"], codex: ["$lfg <root>/plans/feedback-sweep-plan.md"], - omp: ["/skill:lfg <root>/plans/feedback-sweep-plan.md"], - }, - { - file: "skills/ce-sweep/references/interview.md", - defaults: ["/ce-sweep"], - codex: ["$ce-sweep"], - omp: ["/skill:ce-sweep"], + unnecessaryOmp: ["/skill:lfg <root>/plans/feedback-sweep-plan.md"], }, { file: "skills/ce-handoff/SKILL.md", defaults: ["/ce-handoff resume <source>"], codex: ["$ce-handoff resume <source>"], - omp: ["/skill:ce-handoff resume <source>"], + unnecessaryOmp: ["/skill:ce-handoff resume <source>"], }, { file: "skills/ce-compound/SKILL.md", defaults: ["/ce-compound-refresh <scope>", "/ce-compound"], codex: ["$ce-compound-refresh <scope>", "$ce-compound"], - omp: ["/skill:ce-compound-refresh <scope>", "/skill:ce-compound"], + unnecessaryOmp: ["/skill:ce-compound-refresh <scope>", "/skill:ce-compound"], }, { file: "skills/ce-plan/references/plan-handoff.md", defaults: ["/ce-plan output:md"], codex: ["$ce-plan output:md"], - omp: ["/skill:ce-plan output:md"], + unnecessaryOmp: ["/skill:ce-plan output:md"], }, { file: "skills/ce-plan/references/universal-planning.md", defaults: ["/ce-plan"], codex: ["$ce-plan"], - omp: ["/skill:ce-plan"], + unnecessaryOmp: ["/skill:ce-plan"], + }, +] as const + +const explicitOnlyRendererCases = [ + { + file: "skills/ce-explain/SKILL.md", + defaults: ["/ce-polish"], + codex: ["$ce-polish"], + omp: ["/skill:ce-polish"], + targets: ["ce-polish"], + }, + { + file: "skills/ce-setup/SKILL.md", + defaults: ["/ce-setup"], + codex: ["$ce-setup"], + omp: ["/skill:ce-setup"], + targets: ["ce-setup"], + }, + { + file: "skills/ce-dogfood/SKILL.md", + defaults: ["/ce-setup", "/ce-dogfood <original arguments>"], + codex: ["$ce-setup", "$ce-dogfood <original arguments>"], + omp: ["/skill:ce-setup", "/skill:ce-dogfood <original arguments>"], + targets: ["ce-setup", "ce-dogfood"], + }, + { + file: "skills/ce-sweep/references/interview.md", + defaults: ["/ce-sweep"], + codex: ["$ce-sweep"], + omp: ["/skill:ce-sweep"], + targets: ["ce-sweep"], }, ] as const describe("user-facing skill invocation rendering", () => { - test.each(rendererCases)("$file defaults to slash and reserves dollar syntax for Codex", ({ file, defaults, codex, omp }) => { - const body = readRepoFile(file) - - expect(body).toMatch(/default(?:s| to)[^\n]*\/[a-z]/i) - expect(body).toMatch(/\$[a-z][^\n]*(?:Codex|dollar-prefixed)|(?:Codex|dollar-prefixed)[^\n]*\$[a-z]/i) - expect(body).toMatch(/Render (?:only (?:each|the) invocation as inline code|it as the fenced command below)/i) - expect(body).toMatch(/Output one form only/i) - for (const invocation of defaults) expect(body).toContain(invocation) - for (const invocation of codex) expect(body).toContain(invocation) - expect(body).toMatch(/\/skill:[a-z][^\n]*oh-my-pi|oh-my-pi[^\n]*\/skill:[a-z]/i) - for (const invocation of omp) expect(body).toContain(invocation) - }) + test.each(modelVisibleRendererCases)( + "$file keeps model-visible handoffs host-neutral", + ({ file, defaults, codex, unnecessaryOmp }) => { + const body = readRepoFile(file) + + expect(body).toMatch(/default(?:s| to)[^\n]*\/[a-z]/i) + expect(body).toMatch(/\$[a-z][^\n]*(?:Codex|dollar-prefixed)|(?:Codex|dollar-prefixed)[^\n]*\$[a-z]/i) + expect(body).toMatch(/Render (?:only (?:each|the) invocation as inline code|it as the fenced command below)/i) + expect(body).toMatch(/Output one form only/i) + for (const invocation of defaults) expect(body).toContain(invocation) + for (const invocation of codex) expect(body).toContain(invocation) + for (const invocation of unnecessaryOmp) expect(body).not.toContain(invocation) + }, + ) + + test.each(explicitOnlyRendererCases)( + "$file uses deterministic OMP syntax for explicit-only skill targets", + ({ file, defaults, codex, omp, targets }) => { + const body = readRepoFile(file) + + expect(body).toMatch(/default(?:s| to)[^\n]*\/[a-z]/i) + expect(body).toMatch(/\$[a-z][^\n]*(?:Codex|dollar-prefixed)|(?:Codex|dollar-prefixed)[^\n]*\$[a-z]/i) + expect(body).toMatch(/Render (?:only (?:each|the) invocation as inline code|it as the fenced command below)/i) + expect(body).toMatch(/Output one form only/i) + expect(body).toMatch(/\/skill:[a-z][^\n]*oh-my-pi|oh-my-pi[^\n]*\/skill:[a-z]/i) + for (const target of targets) { + expect(readRepoFile(`skills/${target}/SKILL.md`)).toMatch(/^disable-model-invocation:\s*true$/m) + } + for (const invocation of defaults) expect(body).toContain(invocation) + for (const invocation of codex) expect(body).toContain(invocation) + for (const invocation of omp) expect(body).toContain(invocation) + }, + ) test("rendering rules sit at the output sections that consume them", () => { const setup = readRepoFile("skills/ce-setup/SKILL.md") From f184ca0dbe4e89161e28f35a636d87f3c0b147b3 Mon Sep 17 00:00:00 2001 From: Trevin Chow <trevin@trevinchow.com> Date: Wed, 12 Aug 2026 11:25:57 -0700 Subject: [PATCH 9/9] Address PR review feedback (#1358) - clarify that all omp session-history discovery is deferred --- docs/specs/omp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/omp.md b/docs/specs/omp.md index eb9bc60c8..409b706fb 100644 --- a/docs/specs/omp.md +++ b/docs/specs/omp.md @@ -108,4 +108,4 @@ Each bucket holds `<timestamp>_<sessionId>.jsonl` files. Every session JSONL physically begins with a fixed-width 256-byte `{"type":"title","v":1,...,"pad":"..."}` slot line, followed by a pi-shaped `{"type":"session","version":3,...,"cwd":...}` header. This title-slot-first shape distinguishes omp session files from pi session files, which start directly with the `type:"session"` header. -Known gap: XDG-relocated roots (`$XDG_DATA_HOME/omp`) are not scanned by CE's session-discovery script. +Known gap: CE's session-history discovery does not yet support omp. The entire integration — including default, override, named-profile, and XDG-relocated roots — is deferred to [#1333](https://github.com/EveryInc/compound-engineering-plugin/pull/1333).