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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/skills/ce-compound.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion skills/ce-compound/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<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.

Expand Down
12 changes: 6 additions & 6 deletions skills/ce-compound/references/agents/session-historian.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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:
Expand Down
128 changes: 120 additions & 8 deletions skills/ce-compound/scripts/session-history/discover-sessions.sh
Original file line number Diff line number Diff line change
@@ -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 <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi]
# Usage: discover-sessions.sh <repo-name> <days> [--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:
Expand All @@ -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]}"
DAYS="${2:?Usage: discover-sessions.sh <repo-name> <days> [--cwd /abs/repo/root] [--platform claude|codex|cursor|pi]}"
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]}"
PLATFORM="all"
REPO_CWD=""

Expand Down Expand Up @@ -111,17 +111,129 @@ 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}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Search omp's XDG session roots

When an omp install has migrated to XDG storage (for example $XDG_DATA_HOME/omp exists, so getSessionsDir() resolves sessions to $XDG_DATA_HOME/omp/sessions and flattens the agent/ prefix), this fallback only searches the legacy $HOME/.omp/agent/sessions tree. In that environment the new omp support reports files_processed: 0 even though recent omp sessions exist, so Full-mode session history silently misses the platform it is adding; include the XDG data/profile session roots in the root list before globbing.

Useful? React with 👍 / 👎.

{
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)
discover_claude
discover_codex
discover_cursor
discover_pi
# 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
Expand Down
16 changes: 12 additions & 4 deletions skills/ce-compound/scripts/session-history/extract-errors.py
Original file line number Diff line number Diff line change
@@ -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 <session.jsonl> | python3 extract-errors.py
Expand Down Expand Up @@ -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", {})
Expand Down Expand Up @@ -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()
Expand All @@ -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"):
Expand All @@ -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 = []
Expand All @@ -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:
Expand Down
Loading