diff --git a/.opencode/PAI/ACTIONS.md b/.opencode/PAI/ACTIONS.md index d281d1a1..afb8ad2b 100644 --- a/.opencode/PAI/ACTIONS.md +++ b/.opencode/PAI/ACTIONS.md @@ -88,7 +88,7 @@ Actions run in two environments with identical behavior: ## Action Structure -Each action is a flat directory under `~/.claude/PAI/ACTIONS/`: +Each action is a flat directory under `~/.opencode/PAI/ACTIONS/`: ``` A_LABEL_AND_RATE/ @@ -195,7 +195,7 @@ const capabilities = { ### Local Execution ```bash -cd ~/.claude/PAI/ACTIONS +cd ~/.opencode/PAI/ACTIONS # Run a single action bun lib/runner.v2.ts run A_LABEL_AND_RATE --input '{"content": "Your text here"}' @@ -258,7 +258,7 @@ Authorization: Bearer YOUR_AUTH_TOKEN ```bash # Personal actions go in USER/ACTIONS/ -mkdir ~/.claude/PAI/USER/ACTIONS/A_YOUR_ACTION +mkdir ~/.opencode/PAI/USER/ACTIONS/A_YOUR_ACTION ``` ### Step 2: Define Manifest (action.json) @@ -389,10 +389,10 @@ Same input should produce same output (for LLM actions, use temperature 0). ## Related Documentation -- **Pipelines:** `~/.claude/PAI/PIPELINES.md` -- **Flows:** `~/.claude/PAI/FLOWS.md` -- **Architecture:** `~/.claude/PAI/PAISYSTEMARCHITECTURE.md` -- **Personal Actions:** `~/.claude/PAI/USER/ACTIONS/` +- **Pipelines:** `~/.opencode/PAI/PIPELINES.md` +- **Flows:** `~/.opencode/PAI/FLOWS.md` +- **Architecture:** `~/.opencode/PAI/PAISYSTEMARCHITECTURE.md` +- **Personal Actions:** `~/.opencode/PAI/USER/ACTIONS/` - **Source code:** `~/Projects/arbol/` --- diff --git a/.opencode/PAI/Algorithm/v3.7.0.md b/.opencode/PAI/Algorithm/v3.7.0.md index d1b0b230..e326c9ea 100644 --- a/.opencode/PAI/Algorithm/v3.7.0.md +++ b/.opencode/PAI/Algorithm/v3.7.0.md @@ -115,7 +115,7 @@ The coarse version has 3 criteria that each hide 6+ verifiable sub-requirements. **ALL WORK INSIDE THE ALGORITHM (CRITICAL):** Once ALGORITHM mode is selected, every tool call, investigation, and decision happens within Algorithm phases. No work outside the phase structure until the Algorithm completes. -**Entry banner was already printed by CLAUDE.md** before this file was loaded. The user has already seen: +**Entry banner was already printed by AGENTS.md** before this file was loaded. The user has already seen: ``` ♻︎ Entering the PAI ALGORITHM… (v3.7.0) ═════════════ 🗒️ TASK: [8 word description] @@ -165,7 +165,7 @@ OUTPUT: 💪🏼 EFFORT LEVEL: [EFFORT LEVEL based on the reverse engineering step above] | [8 word reasoning]` - IDEAL STATE Criteria Generation — write criteria directly into the PRD: -- Edit the stub PRD.md (already created at Algorithm entry) to add full content — update frontmatter `effort` field with the determined effort level, and add sections (Context, Criteria, Decisions, Verification) per `~/.claude/PAI/PRDFORMAT.md` +- Edit the stub PRD.md (already created at Algorithm entry) to add full content — update frontmatter `effort` field with the determined effort level, and add sections (Context, Criteria, Decisions, Verification) per `~/.opencode/PAI/PRDFORMAT.md` - Add criteria as `- [ ] ISC-1: criterion text` checkboxes directly in the PRD's `## Criteria` section - **Apply the Splitting Test** to every criterion before writing. Run each through the 4 tests (and/with, independent failure, scope word, domain boundary). Split any compound criteria into atomics. - Set frontmatter `progress: 0/N` where N = total criteria count @@ -348,7 +348,7 @@ OUTPUT: - **WRITE REFLECTION JSONL (MANDATORY for Standard+ effort):** After outputting the learning reflections above, append a structured JSONL entry to the reflections log. This feeds MineReflections, AlgorithmUpgrade, and Upgrade workflows. ```bash -echo '{"timestamp":"[ISO-8601 with timezone]","effort_level":"[tier]","task_description":"[from TASK line]","criteria_count":[N],"criteria_passed":[N],"criteria_failed":[N],"prd_id":"[slug from PRD frontmatter]","implied_sentiment":[1-10 estimate of user satisfaction from conversation tone],"reflection_q1":"[Q1 answer - escape quotes]","reflection_q2":"[Q2 answer - escape quotes]","reflection_q3":"[Q3 answer from capabilities question - escape quotes]","within_budget":[true/false]}' >> ~/.claude/MEMORY/LEARNING/REFLECTIONS/algorithm-reflections.jsonl +echo '{"timestamp":"[ISO-8601 with timezone]","effort_level":"[tier]","task_description":"[from TASK line]","criteria_count":[N],"criteria_passed":[N],"criteria_failed":[N],"prd_id":"[slug from PRD frontmatter]","implied_sentiment":[1-10 estimate of user satisfaction from conversation tone],"reflection_q1":"[Q1 answer - escape quotes]","reflection_q2":"[Q2 answer - escape quotes]","reflection_q3":"[Q3 answer from capabilities question - escape quotes]","within_budget":[true/false]}' >> ~/.opencode/MEMORY/LEARNING/REFLECTIONS/algorithm-reflections.jsonl ``` Fill in all bracketed values from the current session. `implied_sentiment` is your estimate of how satisfied the user is (1=frustrated, 10=delighted) based on conversation tone — do NOT read ratings.jsonl. Escape double quotes in reflection text with `\"`. @@ -358,7 +358,7 @@ Fill in all bracketed values from the current session. `implied_sentiment` is yo ### Critical Rules (Zero Exceptions) -- **Mandatory output format** — Every response MUST use exactly one of the output formats defined in the Execution Modes section of CLAUDE.md (ALGORITHM, NATIVE, ITERATION, or MINIMAL). No freeform output. No exceptions. If you completed algorithm work, wrap results in the ALGORITHM format. If iterating, use ITERATION. Choose the right format and use it. +- **Mandatory output format** — Every response MUST use exactly one of the output formats defined in the Execution Modes section of AGENTS.md (ALGORITHM, NATIVE, ITERATION, or MINIMAL). No freeform output. No exceptions. If you completed algorithm work, wrap results in the ALGORITHM format. If iterating, use ITERATION. Choose the right format and use it. - **Response format before questions** — Always complete the current response format output FIRST, then invoke AskUserQuestion at the end. Never interrupt or replace the response format to ask questions. Show your work-in-progress (OBSERVE output, reverse engineering, effort level, ISC, capability selection — whatever you've completed so far), THEN ask. The user sees your thinking AND your questions together. Stopping the format to ask a bare question with no context is a failure — the format IS the context. - **Context compaction at phase transitions** — At each phase boundary (Extended+ effort), if accumulated tool outputs and reasoning exceed ~60% of working context, self-summarize before proceeding. Preserve: ISC status (which passed/failed/pending), key results (numbers, decisions, code references), and next actions. Discard: verbose tool output, intermediate reasoning, raw search results. Format: 1-3 paragraphs replacing prior phase content. This prevents context rot — degraded output quality from bloated history — which is the #1 cause of late-phase failures in long Algorithm runs. Inspired by RLM (Zhang/Kraska/Khattab 2025). - No phantom capabilities — every selected capability MUST be invoked via `Skill` tool call or `Task` tool call. Text-only output is NOT invocation. Selection without a tool call is dishonest and a CRITICAL FAILURE. @@ -374,12 +374,12 @@ If after compaction you don't know your current phase or criteria status: 1. Read the most recent PRD from `MEMORY/WORK/` (by mtime) — it has all state 2. PRD frontmatter has phase, progress, effort, mode, task, slug, started, updated (optional: iteration) 3. PRD body has criteria checkboxes, decisions, verification evidence -4. `~/.claude/MEMORY/STATE/work.json` has the registry of all sessions (populated by read-only PRDSync + PRDStateSync hooks) +4. `~/.opencode/MEMORY/STATE/work.json` has the registry of all sessions (populated by read-only PRDSync + PRDStateSync hooks) ### PRD.md Format **Frontmatter:** 8 fields — `task`, `slug`, `effort`, `phase`, `progress`, `mode`, `started`, `updated`. Optional: `iteration` (for rework). **Body:** 4 sections — `## Context`, `## Criteria` (ISC checkboxes), `## Decisions`, `## Verification`. Sections appear only when populated. -**Full spec:** `~/.claude/PAI/PRDFORMAT.md` (read during OBSERVE if needed for field details or continuation rules). +**Full spec:** `~/.opencode/PAI/PRDFORMAT.md` (read during OBSERVE if needed for field details or continuation rules). --- diff --git a/.opencode/PAI/CLI.md b/.opencode/PAI/CLI.md index e3660e37..27a113b4 100644 --- a/.opencode/PAI/CLI.md +++ b/.opencode/PAI/CLI.md @@ -23,7 +23,7 @@ The Algorithm CLI executes the PAI Algorithm (Observe → Think → Plan → Bui # Run the Algorithm in autonomous loop mode bun ~/.opencode/PAI/Tools/algorithm.ts -m loop -p -n 20 -# Run in interactive mode (launches a claude session with PRD context) +# Run in interactive mode (launches an OpenCode session with PRD context) bun ~/.opencode/PAI/Tools/algorithm.ts -m interactive -p # Check status of all PRDs @@ -57,7 +57,7 @@ algorithm stop -p Stop a loop Loop mode runs the Algorithm iteratively without human interaction. Each iteration: 1. Reads the PRD and identifies failing Ideal State Criteria -2. Spawns a `claude -p` session focused on the failing criteria +2. Spawns an OpenCode session via Task tool focused on the failing criteria 3. The session makes progress, updates the PRD checkboxes 4. Re-reads the PRD to check progress 5. Repeats until all criteria pass or max iterations reached @@ -88,13 +88,13 @@ bun ~/.opencode/PAI/Tools/algorithm.ts -m loop -p PRD-20260213-auth.md -n 20 -a #### Interactive Mode -Interactive mode launches a full `claude` session with the PRD context pre-loaded. You work with Claude directly to make progress on criteria. +Interactive mode launches a full OpenCode session with the PRD context pre-loaded. You work with OpenCode directly to make progress on criteria. ```bash bun ~/.opencode/PAI/Tools/algorithm.ts -m interactive -p PRD-20260213-feature.md ``` -This opens an interactive Claude session with: +This opens an interactive OpenCode session with: - The PRD path and title - Current progress (passing/total) - List of failing criteria diff --git a/.opencode/PAI/MEMORYSYSTEM.md b/.opencode/PAI/MEMORYSYSTEM.md index 34230c5f..e582be8b 100755 --- a/.opencode/PAI/MEMORYSYSTEM.md +++ b/.opencode/PAI/MEMORYSYSTEM.md @@ -3,20 +3,20 @@ **The unified system memory - what happened, what we learned, what we're working on.** **Version:** 7.0 (Projects-native architecture, 2026-01-12) -**Location:** `~/.claude/MEMORY/` +**Location:** `~/.opencode/MEMORY/` --- ## Architecture -**Claude Code's `projects/` is the source of truth. Hooks capture domain-specific events directly. Harvesting tools extract learnings from session transcripts.** +**OpenCode's `projects/` is the source of truth. Plugins capture domain-specific events directly. Harvesting tools extract learnings from session transcripts.** ``` User Request ↓ -Claude Code projects/ (native transcript storage - 30-day retention) +OpenCode projects/ (native session storage - SQLite database, 30-day retention) ↓ -Hook Events trigger domain-specific captures: +Plugin Events trigger domain-specific captures: ├── Algorithm (AI) → WORK/ ├── RatingCapture → LEARNING/SIGNALS/ ├── WorkCompletionLearning → LEARNING/ @@ -27,14 +27,14 @@ Harvesting (periodic): └── LearningPatternSynthesis → LEARNING/SYNTHESIS/ (aggregates ratings) ``` -**Key insight:** Hooks write directly to specialized directories. There is no intermediate "firehose" layer - Claude Code's `projects/` serves that purpose natively. +**Key insight:** Plugins write directly to specialized directories. There is no intermediate "firehose" layer - OpenCode's `projects/` serves that purpose natively. --- ## Directory Structure ``` -~/.claude/MEMORY/ +~/.opencode/MEMORY/ ├── WORK/ # PRIMARY work tracking │ └── {timestamp}_{slug}/ │ └── PRD.md # Single source of truth (metadata + ISC + decisions + changelog) @@ -65,8 +65,8 @@ Harvesting (periodic): │ ├── algorithms/ # Per-session algorithm state (phase, criteria, effort level) │ ├── kitty-sessions/ # Per-session Kitty terminal env (listenOn, windowId) │ ├── tab-titles/ # Per-window tab state (title, color, phase) -│ ├── events.jsonl # Unified event log (append-only, typed events from hooks) -│ ├── session-names.json # Auto-generated session names (from SessionAutoName hook) +│ ├── events.jsonl # Unified event log (append-only, typed events from plugins) +│ ├── session-names.json # Auto-generated session names (from SessionAutoName plugin) │ ├── current-work.json │ ├── format-streak.json │ ├── algorithm-streak.json @@ -84,14 +84,13 @@ Harvesting (periodic): ## Directory Details -### Claude Code projects/ - Native Session Storage +### OpenCode projects/ - Native Session Storage -**Location:** `~/.claude/projects/-Users-{username}--claude/` -*(Replace `{username}` with your system username, e.g., `-Users-john--claude`)* -**What populates it:** Claude Code automatically (every conversation) -**Content:** Complete session transcripts in JSONL format -**Format:** `{uuid}.jsonl` - one file per session -**Retention:** 30 days (Claude Code manages cleanup) +**Location:** `~/.opencode/projects/` (SQLite database) +**What populates it:** OpenCode automatically (every conversation) +**Content:** Complete session transcripts in SQLite format +**Format:** Session data stored in SQLite database with structured tables +**Retention:** 30 days (OpenCode manages cleanup) **Purpose:** Source of truth for all session data; harvesting tools read from here This is the actual "firehose" - every message, tool call, and response. PAI leverages this native storage rather than duplicating it. @@ -100,8 +99,8 @@ This is the actual "firehose" - every message, tool call, and response. PAI leve **What populates it:** - Algorithm (AI) creates work dir with PRD.md during execution -- `WorkCompletionLearning.hook.ts` on Stop (updates PRD/THREAD) -- `SessionCleanup.hook.ts` on SessionEnd (marks COMPLETED) +- `WorkCompletionLearning.plugin.ts` on Stop (updates PRD/THREAD) +- `SessionCleanup.plugin.ts` on SessionEnd (marks COMPLETED) **Content:** Flat work directories with a single PRD.md as source of truth **Format:** `WORK/{timestamp}_{slug}/PRD.md` — consolidated metadata + ISC + decisions + changelog @@ -128,8 +127,8 @@ This is the actual "firehose" - every message, tool call, and response. PAI leve ### LEARNING/ - Categorized Learnings **What populates it:** -- `RatingCapture.hook.ts` (explicit ratings + implicit sentiment + low-rating learnings) -- `WorkCompletionLearning.hook.ts` (significant work session completions) +- `RatingCapture.plugin.ts` (explicit ratings + implicit sentiment + low-rating learnings) +- `WorkCompletionLearning.plugin.ts` (significant work session completions) - `SessionHarvester.ts` (periodic extraction from projects/ transcripts) - `LearningPatternSynthesis.ts` (aggregates ratings into pattern reports) @@ -143,7 +142,7 @@ This is the actual "firehose" - every message, tool call, and response. PAI leve **Categorization logic:** | Directory | When Used | Example Triggers | |-----------|-----------|------------------| -| `SYSTEM/` | Tooling/infrastructure failures | hook crash, config error, deploy failure | +| `SYSTEM/` | Tooling/infrastructure failures | plugin crash, config error, deploy failure | | `ALGORITHM/` | Task execution issues | wrong approach, over-engineered, missed the point | | `FAILURES/` | Full context for low ratings (1-3) | severe frustration, repeated errors | | `REFLECTIONS/` | Algorithm performance analysis | per-session 3-question reflection from LEARN phase | @@ -152,7 +151,7 @@ This is the actual "firehose" - every message, tool call, and response. PAI leve ### LEARNING/FAILURES/ - Full Context Failure Analysis **What populates it:** -- `RatingCapture.hook.ts` via `FailureCapture.ts` (for ratings 1-3) +- `RatingCapture.plugin.ts` via `FailureCapture.ts` (for ratings 1-3) - Manual migration via `bun FailureCapture.ts --migrate` **Content:** Complete context dumps for low-sentiment events @@ -194,14 +193,14 @@ This is the actual "firehose" - every message, tool call, and response. PAI leve ### SECURITY/ - Security Events -**What populates it:** `SecurityValidator.hook.ts` on tool validation +**What populates it:** `SecurityValidator.plugin.ts` on tool validation **Content:** Security audit events (blocks, confirmations, alerts) **Format:** `SECURITY/security-events.jsonl` **Purpose:** Security decision audit trail ### STATE/ - Fast Runtime Data -**What populates it:** Various tools and hooks +**What populates it:** Various tools and plugins **Content:** High-frequency read/write JSON files for runtime state **Key Property:** Ephemeral - can be rebuilt from RAW or other sources. Optimized for speed, not permanence. @@ -209,7 +208,7 @@ This is the actual "firehose" - every message, tool call, and response. PAI leve - `algorithms/` - Per-session algorithm state files (`{sessionId}.json` — phase, criteria, effort level, active flag) - `kitty-sessions/` - Per-session Kitty terminal env (`{sessionId}.json` — listenOn, windowId for tab control and voice gating) - `tab-titles/` - Per-window tab state (`{windowId}.json` — title, color, phase for daemon recovery) -- `session-names.json` - Auto-generated session names from SessionAutoName hook +- `session-names.json` - Auto-generated session names from SessionAutoName plugin - `current-work.json` - Active work directory pointer - `format-streak.json`, `algorithm-streak.json` - Performance metrics - `progress/` - Multi-session project tracking @@ -219,7 +218,7 @@ This is mutable state that changes during execution - not historical records. If **`events.jsonl` - Unified Event Log:** -An append-only JSONL file where hooks emit structured, typed events alongside their normal state writes. Each line is a JSON object with `timestamp`, `session_id`, `source`, `type`, and type-specific fields. The type field uses a dot-separated topic hierarchy (e.g., `algorithm.phase`, `work.created`, `rating.captured`, `voice.sent`). This file is an observability layer -- it does NOT replace any of the mutable state files listed above. Events are written by `${PAI_DIR}/hooks/lib/event-emitter.ts` using synchronous append, and errors are silently swallowed so the event log never disrupts hook execution. Consumers can tail or `fs.watch` this file for real-time visibility into PAI activity. +An append-only JSONL file where plugins emit structured, typed events alongside their normal state writes. Each line is a JSON object with `timestamp`, `session_id`, `source`, `type`, and type-specific fields. The type field uses a dot-separated topic hierarchy (e.g., `algorithm.phase`, `work.created`, `rating.captured`, `voice.sent`). This file is an observability layer -- it does NOT replace any of the mutable state files listed above. Events are written by `${PAI_DIR}/plugins/lib/event-emitter.ts` using synchronous append, and errors are silently swallowed so the event log never disrupts plugin execution. Consumers can tail or `fs.watch` this file for real-time visibility into PAI activity. ### PAISYSTEMUPDATES/ - Change History @@ -229,18 +228,18 @@ An append-only JSONL file where hooks emit structured, typed events alongside th --- -## Hook Integration +## Plugin Integration -| Hook | Trigger | Writes To | -|------|---------|-----------| +| Plugin | Trigger | Writes To | +|--------|---------|-----------| | Algorithm (AI) | During execution | WORK/PRD.md, STATE/current-work-{sessionId}.json | -| PRDSync.hook.ts | PostToolUse (Write/Edit) | STATE/work.json (syncs PRD frontmatter) | -| WorkCompletionLearning.hook.ts | SessionEnd | LEARNING/ (significant work) | -| SessionCleanup.hook.ts | SessionEnd | WORK/PRD.md (status→COMPLETED), clears STATE | -| RatingCapture.hook.ts | UserPromptSubmit | LEARNING/SIGNALS/, LEARNING/, FAILURES/ (1-3) | -| SecurityValidator.hook.ts | PreToolUse | SECURITY/ | +| PRDSync.plugin.ts | PostToolUse (Write/Edit) | STATE/work.json (syncs PRD frontmatter) | +| WorkCompletionLearning.plugin.ts | SessionEnd | LEARNING/ (significant work) | +| SessionCleanup.plugin.ts | SessionEnd | WORK/PRD.md (status→COMPLETED), clears STATE | +| RatingCapture.plugin.ts | UserPromptSubmit | LEARNING/SIGNALS/, LEARNING/, FAILURES/ (1-3) | +| SecurityValidator.plugin.ts | PreToolUse | SECURITY/ | -> **Note:** All hooks listed above also emit typed events to `STATE/events.jsonl` via `appendEvent()`. See [THEHOOKSYSTEM.md § Unified Event System](THEHOOKSYSTEM.md) for event types and consumer details. +> **Note:** All plugins listed above also emit typed events to `STATE/events.jsonl` via `appendEvent()`. See [THEPLUGINSYSTEM.md § Unified Event System](THEPLUGINSYSTEM.md) for event types and consumer details. ## Harvesting Tools @@ -258,7 +257,7 @@ An append-only JSONL file where hooks emit structured, typed events alongside th ``` User Request ↓ -Claude Code → projects/{uuid}.jsonl (native transcript) +OpenCode → projects/ (SQLite session storage) ↓ Algorithm (AI) → WORK/{timestamp}_{slug}/PRD.md + STATE/current-work-{sessionId}.json ↓ @@ -282,56 +281,56 @@ LearningPatternSynthesis → analyzes SIGNALS/ → writes SYNTHESIS/ ### Check current work ```bash -cat ~/.claude/MEMORY/STATE/current-work.json -ls ~/.claude/MEMORY/WORK/ | tail -5 +cat ~/.opencode/MEMORY/STATE/current-work.json +ls ~/.opencode/MEMORY/WORK/ | tail -5 ``` ### Check ratings ```bash -tail ~/.claude/MEMORY/LEARNING/SIGNALS/ratings.jsonl +tail ~/.opencode/MEMORY/LEARNING/SIGNALS/ratings.jsonl ``` ### View session transcripts ```bash -# List recent sessions (newest first) -# Replace {username} with your system username -ls -lt ~/.claude/projects/-Users-{username}--claude/*.jsonl | head -5 +# OpenCode stores sessions in SQLite database +# Use opencode CLI to list recent sessions +opencode sessions list --recent 5 -# View last session events -tail ~/.claude/projects/-Users-{username}--claude/$(ls -t ~/.claude/projects/-Users-{username}--claude/*.jsonl | head -1) | jq . +# Or query the database directly +sqlite3 ~/.opencode/projects/sessions.db "SELECT id, created_at FROM sessions ORDER BY created_at DESC LIMIT 5" ``` ### Check learnings ```bash -ls ~/.claude/MEMORY/LEARNING/SYSTEM/ -ls ~/.claude/MEMORY/LEARNING/ALGORITHM/ -ls ~/.claude/MEMORY/LEARNING/SYNTHESIS/ +ls ~/.opencode/MEMORY/LEARNING/SYSTEM/ +ls ~/.opencode/MEMORY/LEARNING/ALGORITHM/ +ls ~/.opencode/MEMORY/LEARNING/SYNTHESIS/ ``` ### Check failures ```bash # List recent failure captures -ls -lt ~/.claude/MEMORY/LEARNING/FAILURES/$(date +%Y-%m)/ 2>/dev/null | head -10 +ls -lt ~/.opencode/MEMORY/LEARNING/FAILURES/$(date +%Y-%m)/ 2>/dev/null | head -10 # View a specific failure -cat ~/.claude/MEMORY/LEARNING/FAILURES/2026-01/*/CONTEXT.md | head -100 +cat ~/.opencode/MEMORY/LEARNING/FAILURES/2026-01/*/CONTEXT.md | head -100 # Migrate historical low ratings to FAILURES -bun run ~/.claude/PAI/Tools/FailureCapture.ts --migrate +bun run ~/.opencode/PAI/Tools/FailureCapture.ts --migrate ``` ### Check multi-session progress ```bash -ls ~/.claude/MEMORY/STATE/progress/ +ls ~/.opencode/MEMORY/STATE/progress/ ``` ### Run harvesting tools ```bash # Harvest learnings from recent sessions -bun run ~/.claude/PAI/Tools/SessionHarvester.ts --recent 10 +bun run ~/.opencode/PAI/Tools/SessionHarvester.ts --recent 10 # Generate pattern synthesis -bun run ~/.claude/PAI/Tools/LearningPatternSynthesis.ts --week +bun run ~/.opencode/PAI/Tools/LearningPatternSynthesis.ts --week ``` --- @@ -343,31 +342,31 @@ bun run ~/.claude/PAI/Tools/LearningPatternSynthesis.ts --week - PRD.md frontmatter now holds session metadata (title, session_id, status, completed_at) - ISC section in PRD (checkbox markdown) is the system of record for criteria - CHANGELOG section in PRD replaces THREAD.md -- All hooks updated: SessionCleanup, WorkCompletionLearning, LoadContext +- All plugins updated: SessionCleanup, WorkCompletionLearning, LoadContext - Legacy fallback preserved: consumers check PRD.md first, fall back to META.yaml/ISC.json - Dropped never-populated sections: NON-SCOPE, ASSUMPTIONS, OPEN QUESTIONS **2026-01-17:** v7.1 - Full Context Failure Analysis - Added LEARNING/FAILURES/ directory for comprehensive failure captures - Created FailureCapture.ts tool for generating context dumps -- Updated RatingCapture.hook.ts to create failure captures for ratings 1-3 +- Updated RatingCapture.plugin.ts to create failure captures for ratings 1-3 - Each failure gets its own directory with transcript, sentiment, tool-calls, and context - Directory names use 8-word descriptions generated by fast inference - Added migration capability via `bun FailureCapture.ts --migrate` **2026-01-12:** v7.0 - Projects-native architecture -- Eliminated RAW/ directory entirely - Claude Code's `projects/` is the source of truth -- Removed EventLogger.hook.ts (was duplicating what projects/ already captures) +- Eliminated RAW/ directory entirely - OpenCode's `projects/` is the source of truth +- Removed EventLogger.plugin.ts (was duplicating what projects/ already captures) - Created SessionHarvester.ts to extract learnings from projects/ transcripts -- Created WorkCompletionLearning.hook.ts for session-end learning capture +- Created WorkCompletionLearning.plugin.ts for session-end learning capture - Created LearningPatternSynthesis.ts for rating pattern aggregation - Added LEARNING/SYNTHESIS/ for pattern reports - Updated ActivityParser.ts to use projects/ as data source -- Removed archive functionality from pai.ts (Claude Code handles 30-day cleanup) +- Removed archive functionality from pai.ts (OpenCode handles 30-day cleanup) **2026-01-11:** v6.1 - Removed RECOVERY system - Deleted RECOVERY/ directory (5GB of redundant snapshots) -- Removed RecoveryJournal.hook.ts, recovery-engine.ts, snapshot-manager.ts +- Removed RecoveryJournal.plugin.ts, recovery-engine.ts, snapshot-manager.ts - Git provides all necessary rollback capability **2026-01-11:** v6.0 - Major consolidation @@ -376,25 +375,25 @@ bun run ~/.claude/PAI/Tools/LearningPatternSynthesis.ts --week - Merged SIGNALS/ into LEARNING/SIGNALS/ - Merged PROGRESS/ into STATE/progress/ - Merged integrity-checks/ into STATE/integrity/ -- Fixed AutoWorkCreation hook (prompt vs user_prompt field) -- Updated all hooks to use correct paths +- Fixed AutoWorkCreation plugin (prompt vs user_prompt field) +- Updated all plugins to use correct paths **2026-01-10:** v5.0 - Documentation consolidation - Consolidated WORKSYSTEM.md into MEMORYSYSTEM.md **2026-01-09:** v4.0 - Major restructure -- Moved BACKUPS to `~/.claude/BACKUPS/` (outside MEMORY) +- Moved BACKUPS to `~/.opencode/BACKUPS/` (outside MEMORY) - Renamed RAW-OUTPUTS to RAW - All directories now ALL CAPS **2026-01-05:** v1.0 - Unified Memory System migration -- Previous: `~/.claude/history/`, `~/.claude/context/`, `~/.claude/progress/` -- Current: `~/.claude/MEMORY/` +- Previous: `~/.opencode/history/`, `~/.opencode/context/`, `~/.opencode/progress/` +- Current: `~/.opencode/MEMORY/` - Files migrated: 8,415+ --- ## Related Documentation -- **Hook System:** `THEHOOKSYSTEM.md` +- **Plugin System:** `THEPLUGINSYSTEM.md` - **Architecture:** `PAISYSTEMARCHITECTURE.md` diff --git a/.opencode/PAI/PRDFORMAT.md b/.opencode/PAI/PRDFORMAT.md index 4b99d601..fbcce457 100644 --- a/.opencode/PAI/PRDFORMAT.md +++ b/.opencode/PAI/PRDFORMAT.md @@ -101,7 +101,7 @@ Evidence for each criterion. Written during VERIFY phase. ## File Location ``` -~/.claude/MEMORY/WORK/{slug}/PRD.md +~/.opencode/MEMORY/WORK/{slug}/PRD.md ``` Directory created with `mkdir -p MEMORY/WORK/{slug}/` during OBSERVE. @@ -140,4 +140,4 @@ Key design choices: - **Checkboxes over EARS/BDD**: Simpler to parse, write, and verify. ISC pattern proven over 48 PRDs. - **YAML frontmatter over JSON**: Universal standard (Jekyll, Hugo, Astro, Kiro, spec-kit all use it). - **Convention-based sections**: Sections appear when needed, not as empty boilerplate. -- **Reference file pattern**: This spec lives at `~/.claude/PAI/PRDFORMAT.md`, not inline in CLAUDE.md. Saves ~2,500 tokens/response. +- **Reference file pattern**: This spec lives at `~/.opencode/PAI/PRDFORMAT.md`, not inline in AGENTS.md. Saves ~2,500 tokens/response. diff --git a/.opencode/PAI/README.md b/.opencode/PAI/README.md index e75a3413..a996fc7f 100644 --- a/.opencode/PAI/README.md +++ b/.opencode/PAI/README.md @@ -1,19 +1,19 @@ # PAI — Personal AI Infrastructure -PAI is a general problem-solving system that magnifies human capabilities. It runs inside Claude Code as an interconnected set of skills, hooks, tools, memory, and configuration — all orchestrated by The Algorithm. +PAI is a general problem-solving system that magnifies human capabilities. It runs inside OpenCode as an interconnected set of skills, hooks, tools, memory, and configuration — all orchestrated by The Algorithm. ## How It Works -**CLAUDE.md** is the master config — generated from `CLAUDE.md.template` via `BuildCLAUDE.ts`. It defines execution modes, The Algorithm, and the context routing table. Claude Code loads it natively every session. A SessionStart hook keeps it fresh automatically. +**AGENTS.md** is the master config — generated from `AGENTS.md.template` via `BuildAGENTS.ts`. It defines execution modes, The Algorithm, and the context routing table. OpenCode loads it natively every session. A SessionStart plugin keeps it fresh automatically. **This directory (`PAI/`)** contains all system documentation, tools, user context, and the SKILL.md that defines PAI as a skill. The rest of the system lives alongside it under `~/.opencode/` (hooks, skills, settings, memory). ## Directory Structure -``` +```text ~/.opencode/ - CLAUDE.md # Master config (generated from template) - CLAUDE.md.template # Source template with variables + AGENTS.md # Master config (generated from template) + AGENTS.md.template # Source template with variables settings.json # Single source of truth for all configuration hooks/ # Event lifecycle hooks (21+) skills/ # 12 categories, 49 skills — each with SKILL.md @@ -30,8 +30,8 @@ The 7-phase execution engine: Observe, Think, Plan, Build, Execute, Verify, Lear ### Skills (`SKILLSYSTEM.md`) 12 hierarchical categories with 49 total skills in `~/.opencode/skills/`, each with a `SKILL.md` defining triggers, workflows, and tools. Skills are the primary capability unit. -### Hooks (`THEHOOKSYSTEM.md`) -21+ event hooks across the session lifecycle: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionEnd. Defined in `settings.json`, implemented in `~/.opencode/hooks/`. +### Plugins (`THEPLUGINSYSTEM.md`) +Event-driven plugin system across the session lifecycle: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionEnd. Defined in `settings.json`, implemented in `~/.opencode/plugins/`. ### Memory (`MEMORYSYSTEM.md`) Persistent storage across sessions: @@ -42,7 +42,7 @@ Persistent storage across sessions: - **WISDOM/** — Domain knowledge frames that compound over time ### Tools (`Tools/`) -TypeScript utilities in `PAI/Tools/`: `BuildCLAUDE.ts` (generate CLAUDE.md from template), `Inference.ts` (AI calls), `GenerateSkillIndex.ts`, `SessionProgress.ts`, `Banner.ts`, and more. +TypeScript utilities in `PAI/Tools/`: `BuildAGENTS.ts` (generate AGENTS.md from template), `Inference.ts` (AI calls), `GenerateSkillIndex.ts`, `SessionProgress.ts`, `Banner.ts`, and more. ### Agents (`PAIAGENTSYSTEM.md`) 14 specialized agent types (Algorithm, Engineer, Architect, Designer, Researcher variants). Custom agents via the Agents skill. Agent teams for coordinated multi-agent work. @@ -69,17 +69,17 @@ Personal data directory. See `USER/README.md` for full index: ## Startup & Context Loading At session start, three things happen: -1. **CLAUDE.md** loads natively (identity, algorithm, routing table) +1. **AGENTS.md** loads natively (identity, algorithm, routing table) 2. **`loadAtStartup` files** from `settings.json` are force-loaded by `LoadContext.hook.ts` 3. **Dynamic context** injected by `LoadContext.hook.ts`: relationship context, learning readback, active work summary (each toggleable in `settings.json → dynamicContext`) -All other documentation loads on-demand based on the routing table in CLAUDE.md. +All other documentation loads on-demand based on the routing table in AGENTS.md. ## Build System | Target | Source | Builder | Trigger | |--------|--------|---------|---------| -| `CLAUDE.md` | `CLAUDE.md.template` + `settings.json` + `PAI/Algorithm/LATEST` | `bun PAI/Tools/BuildCLAUDE.ts` | SessionStart hook + manual | +| `AGENTS.md` | `AGENTS.md.template` + `settings.json` + `PAI/Algorithm/LATEST` | `bun PAI/Tools/BuildAGENTS.ts` | SessionStart plugin + manual | ## Extending PAI diff --git a/.opencode/PAI/SKILLSYSTEM.md b/.opencode/PAI/SKILLSYSTEM.md index 01cbd2ed..18c003fe 100755 --- a/.opencode/PAI/SKILLSYSTEM.md +++ b/.opencode/PAI/SKILLSYSTEM.md @@ -47,7 +47,7 @@ If a skill does not follow this structure, it is not properly configured and wil ### System Skills (Shareable via PAI Packs) - Use **TitleCase** naming: `Browser`, `Research`, `Development` - Contain NO personal data (contacts, API keys, team members) -- Reference `~/.claude/PAI/USER/` for any personalization +- Reference `~/.opencode/PAI/USER/` for any personalization - Can be exported to the public PAI repository ### Personal Skills (Never Shared) @@ -60,7 +60,7 @@ If a skill does not follow this structure, it is not properly configured and wil Personal skills are identified by their `_ALLCAPS` naming convention. To list current personal skills: ```bash -ls -1 ~/.claude/skills/ | grep "^_" +ls -1 ~/.opencode/skills/ | grep "^_" ``` This ensures documentation never drifts from reality. The underscore prefix ensures: @@ -73,8 +73,8 @@ System skills should reference PAI/USER files for personal data: ```markdown ## Configuration Personal configuration loaded from: -- `~/.claude/PAI/USER/CONTACTS.md` - Contact information -- `~/.claude/PAI/USER/TECHSTACKPREFERENCES.md` - Tech preferences +- `~/.opencode/PAI/USER/CONTACTS.md` - Contact information +- `~/.opencode/PAI/USER/TECHSTACKPREFERENCES.md` - Tech preferences ``` **NEVER hardcode personal data in system skills.** @@ -95,7 +95,7 @@ All skills include this standard instruction block after the YAML frontmatter: ## Customization **Before executing, check for user customizations at:** -`~/.claude/PAI/USER/SKILLCUSTOMIZATIONS/{SkillName}/` +`~/.opencode/PAI/USER/SKILLCUSTOMIZATIONS/{SkillName}/` If this directory exists, load and apply: - `PREFERENCES.md` - User preferences and configuration @@ -107,7 +107,7 @@ These define user-specific preferences. If the directory does not exist, proceed ### Directory Structure ``` -~/.claude/PAI/USER/SKILLCUSTOMIZATIONS/ +~/.opencode/PAI/USER/SKILLCUSTOMIZATIONS/ ├── README.md # Documentation for this system ├── Art/ # Art skill customizations │ ├── EXTEND.yaml # Extension manifest @@ -160,7 +160,7 @@ description: "What this customization adds" ### Creating a Customization -1. **Create directory**: `mkdir -p ~/.claude/PAI/USER/SKILLCUSTOMIZATIONS/SkillName` +1. **Create directory**: `mkdir -p ~/.opencode/PAI/USER/SKILLCUSTOMIZATIONS/SkillName` 2. **Create EXTEND.yaml**: Define what files to load and merge strategy 3. **Create PREFERENCES.md**: User preferences for this skill 4. **Add additional files**: Any skill-specific configurations @@ -192,7 +192,7 @@ science_cycle_time: meso # Optional: micro | meso | macro **Rules:** - `name` uses **TitleCase** - `description` is a **single line** (not multi-line with `|`) -- `USE WHEN` keyword is **MANDATORY** (Claude Code parses this for skill activation) +- `USE WHEN` keyword is **MANDATORY** (OpenCode parses this for skill activation) - Use intent-based triggers with `OR` for multiple conditions - Max 1024 characters (Anthropic hard limit) - **NO separate `triggers:` or `workflows:` arrays in YAML** @@ -224,7 +224,7 @@ science_cycle_time: meso - **Research** - Investigation through hypotheses and evidence gathering - **Council** - Debate as parallel hypothesis testing -**See:** `~/.claude/skills/Science/Protocol.md` for the full protocol interface +**See:** `~/.opencode/skills/Science/Protocol.md` for the full protocol interface ### 2. Markdown Body (Workflow Routing + Examples + Documentation) @@ -250,11 +250,11 @@ science_cycle_time: meso Running the **WorkflowName** workflow in the **SkillName** skill to ACTION... ``` -**Full documentation:** `~/.claude/PAI/THENOTIFICATIONSYSTEM.md` +**Full documentation:** `~/.opencode/PAI/THENOTIFICATIONSYSTEM.md` ## Workflow Routing -The notification announces workflow execution. The routing table tells Claude which workflow to execute: +The notification announces workflow execution. The routing table determines which workflow to execute: | Workflow | Trigger | File | |----------|---------|------| @@ -425,7 +425,7 @@ SkillSearch('art tools') # Loads Tools.md from skill root Or reference them directly: ```bash # Read specific context file -Read ~/.claude/skills/Media/Art/Aesthetic.md +Read ~/.opencode/skills/Media/Art/Aesthetic.md ``` Context files can reference workflows and tools: @@ -510,7 +510,7 @@ Don't bother for: Use the Createskill skill's CanonicalizeSkill workflow: ``` -~/.claude/skills/Createskill/Workflows/CanonicalizeSkill.md +~/.opencode/skills/Createskill/Workflows/CanonicalizeSkill.md ``` Or manually: @@ -518,7 +518,7 @@ Or manually: 2. Update YAML frontmatter to single-line description 3. Add `## Workflow Routing` table 4. Add `## Examples` section -5. Move backups to `~/.claude/MEMORY/Backups/` +5. Move backups to `~/.opencode/MEMORY/Backups/` 6. Verify against checklist --- @@ -580,7 +580,7 @@ description: Complete blog workflow. USE WHEN user mentions doing anything with ## Complete Canonical Example: Blogging Skill -**Reference:** `~/.claude/skills/_PERSONAL/_MYSKILL/SKILL.md` +**Reference:** `~/.opencode/skills/_PERSONAL/_MYSKILL/SKILL.md` ```yaml --- @@ -609,7 +609,7 @@ Complete blog workflow. Running the **WorkflowName** workflow in the **Blogging** skill to ACTION... ``` -**Full documentation:** `~/.claude/PAI/THENOTIFICATIONSYSTEM.md` +**Full documentation:** `~/.opencode/PAI/THENOTIFICATIONSYSTEM.md` ## Core Paths @@ -829,7 +829,7 @@ bun ToolName.ts \ \`\`\` ``` -**See:** `~/.claude/PAI/CLIFIRSTARCHITECTURE.md` (Workflow-to-Tool Integration section) +**See:** `~/.opencode/PAI/CLIFIRSTARCHITECTURE.md` (Workflow-to-Tool Integration section) --- @@ -934,7 +934,7 @@ bun Generate.ts \ 4. **Value flags**: `--flag ` for choices 5. **Composable**: Flags should combine logically -**See:** `~/.claude/PAI/CLIFIRSTARCHITECTURE.md` (Configuration Flags section) for full documentation +**See:** `~/.opencode/PAI/CLIFIRSTARCHITECTURE.md` (Configuration Flags section) for full documentation ### Tool Structure @@ -944,7 +944,7 @@ bun Generate.ts \ * ToolName.ts - Brief description * * Usage: - * bun ~/.claude/skills/SkillName/Tools/ToolName.ts [options] + * bun ~/.opencode/skills/SkillName/Tools/ToolName.ts [options] * * Commands: * start Start the thing @@ -962,7 +962,7 @@ bun Generate.ts \ ## How It Works -1. **Skill Activation**: Claude Code reads skill descriptions at startup. The `USE WHEN` clause in the description determines when the skill activates based on user intent. +1. **Skill Activation**: OpenCode reads skill descriptions at startup. The `USE WHEN` clause in the description determines when the skill activates based on user intent. 2. **Workflow Routing**: Once the skill is active, the `## Workflow Routing` section determines which workflow file to execute. diff --git a/.opencode/PAI/THEHOOKSYSTEM.md b/.opencode/PAI/THEHOOKSYSTEM.md deleted file mode 100755 index bd379fab..00000000 --- a/.opencode/PAI/THEHOOKSYSTEM.md +++ /dev/null @@ -1,1327 +0,0 @@ -# Hook System - -> **PAI 4.0** — This system is under active development. APIs, configuration formats, and features may change without notice. - -**Event-Driven Automation Infrastructure** - -**Location:** `~/.claude/hooks/` -**Configuration:** `~/.claude/settings.json` -**Status:** Active - 20 hooks running in production - ---- - -## Overview - -The PAI hook system is an event-driven automation infrastructure built on Claude Code's native hook support. Hooks are executable scripts (TypeScript/Python) that run automatically in response to specific events during Claude Code sessions. - -**Core Capabilities:** -- **Session Management** - Auto-load context, capture summaries, manage state -- **Voice Notifications** - Text-to-speech announcements for task completions -- **History Capture** - Automatic work/learning documentation to `~/.claude/MEMORY/` -- **Multi-Agent Support** - Agent-specific hooks with voice routing -- **Tab Titles** - Dynamic terminal tab updates with task context -- **Unified Event Stream** - All hooks emit structured events to `events.jsonl` for real-time observability - -**Key Principle:** Hooks run asynchronously and fail gracefully. They enhance the user experience but never block Claude Code's core functionality. - ---- - -## Available Hook Types - -Claude Code supports the following hook events: - -### 1. **SessionStart** -**When:** Claude Code session begins (new conversation) -**Use Cases:** -- Load PAI context from `PAI/SKILL.md` -- Initialize session state -- Capture session metadata - -**Current Hooks:** -```json -{ - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/KittyEnvPersist.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/LoadContext.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** -- `KittyEnvPersist.hook.ts` - Persists Kitty terminal env vars to disk and resets tab title to clean state -- `LoadContext.hook.ts` - Injects dynamic context (relationship, learning, work summary) as `` at session start - ---- - -### 2. **SessionEnd** -**When:** Claude Code session terminates (conversation ends) -**Use Cases:** -- Capture work completions and learning moments -- Generate session summaries -- Record relationship context -- Update system counts (skills, hooks, signals) -- Run integrity checks - -**Current Hooks:** -```json -{ - "SessionEnd": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/WorkCompletionLearning.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/SessionCleanup.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/RelationshipMemory.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/UpdateCounts.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/IntegrityCheck.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** -- `WorkCompletionLearning.hook.ts` - Reads PRD.md frontmatter for work metadata and ISC section for criteria status, captures learning to `MEMORY/LEARNING/` for significant work sessions -- `SessionCleanup.hook.ts` - Marks PRD.md frontmatter status→COMPLETED and sets completed_at timestamp, clears session state, resets tab, cleans session names -- `RelationshipMemory.hook.ts` - Captures relationship context (observations, behaviors) to `MEMORY/RELATIONSHIP/` -- `UpdateCounts.hook.ts` - Updates system counts (skills, hooks, signals, workflows, files) displayed in the startup banner -- `IntegrityCheck.hook.ts` - Runs DocCrossRefIntegrity and SystemIntegrity checks at session end - ---- - -### 3. **UserPromptSubmit** -**When:** User submits a new prompt to Claude -**Use Cases:** -- Update UI indicators -- Pre-process user input -- Capture prompts for analysis -- Detect ratings and sentiment - -**Current Hooks:** -```json -{ - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/RatingCapture.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/UpdateTabTitle.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/SessionAutoName.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** - -**RatingCapture.hook.ts** - Unified Rating Detection -- Handles both explicit ratings ("7", "8 - good work") and implicit sentiment analysis -- Explicit path: Pattern match first (no inference needed), writes to `ratings.jsonl` -- Implicit path: Haiku inference for sentiment if no explicit match -- Low ratings (<6) auto-capture as learning opportunities -- Writes to `~/.claude/MEMORY/SIGNALS/ratings.jsonl` -- Uses shared libraries: `hooks/lib/learning-utils.ts`, `hooks/lib/time.ts` -- **Inference:** `import { inference } from '../PAI/Tools/Inference'` → `inference({ level: 'fast', expectJson: true })` - -**UpdateTabTitle.hook.ts** - Tab Title + Working State -- Updates Kitty terminal tab title with task summary + `…` suffix -- Sets tab to **orange background** (working state) -- Announces via voice server with context-appropriate gerund -- See `TERMINALTABS.md` for full state system documentation -- **Inference:** `import { inference } from '../PAI/Tools/Inference'` → `inference({ level: 'fast' })` - -**SessionAutoName.hook.ts** - Automatic Session Naming -- Infers a short descriptive name for the session from the first substantive prompt -- Updates `MEMORY/STATE/session-names.json` with the session ID → name mapping -- Used by the startup banner and session management tools -- **Inference:** `import { inference } from '../PAI/Tools/Inference'` → `inference({ level: 'fast' })` - ---- - -### 4. **Stop** -**When:** Main agent ({DAIDENTITY.NAME}) completes a response -**Use Cases:** -- Voice notifications for task completion -- Capture work summaries and learnings -- **Update terminal tab with final state** (color + suffix based on outcome) - -**Current Hooks:** -```json -{ - "Stop": [ - { - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/LastResponseCache.hook.ts" }, - { "type": "command", "command": "${PAI_DIR}/hooks/ResponseTabReset.hook.ts" }, - { "type": "command", "command": "${PAI_DIR}/hooks/VoiceCompletion.hook.ts" }, - { "type": "command", "command": "${PAI_DIR}/hooks/DocIntegrity.hook.ts" }, - { "type": "command", "command": "${PAI_DIR}/hooks/AlgorithmTab.hook.ts" } - ] - } - ] -} -``` - -**What They Do:** - -Each Stop hook is a self-contained `.hook.ts` file that reads stdin via shared `hooks/lib/hook-io.ts`, calls its handler, and exits. Handlers in `hooks/handlers/` are unchanged — each hook is a thin wrapper. - -**`LastResponseCache.hook.ts`** — Cache last response for RatingCapture bridge -- Writes `last_assistant_message` (or transcript fallback) to `MEMORY/STATE/last-response.txt` -- RatingCapture reads this on the next UserPromptSubmit to access the previous response - -**`ResponseTabReset.hook.ts`** — Reset Kitty tab title/color after response -- Calls `handlers/TabState.ts` to set completed state -- Converts working gerund title to past tense - -**`VoiceCompletion.hook.ts`** — Send 🗣️ voice line to TTS server -- Calls `handlers/VoiceNotification.ts` for voice delivery -- Voice gate: only main sessions (checks `kitty-sessions/{sessionId}.json`) -- Subagents have no kitty-sessions file → voice blocked - -**`AlgorithmTab.hook.ts`** — Show Algorithm phase + progress in Kitty tab title -- Reads `work.json`, finds most recently updated active session, sets tab title - -**`DocIntegrity.hook.ts`** — Cross-reference + semantic drift checks -- Calls `handlers/DocCrossRefIntegrity.ts` — deterministic + inference-powered doc updates -- Self-gating: returns instantly when no system files were modified - -**Tab State System:** See `TERMINALTABS.md` for complete documentation - ---- - -### 5. **PreToolUse** -**When:** Before Claude executes any tool -**Use Cases:** -- Voice curl gating (prevent background agents from speaking) -- Security validation across file operations (Bash, Edit, Write, Read) -- Tab state updates on questions -- Agent execution guardrails -- Skill invocation validation - -**Current Hooks:** -```json -{ - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/SecurityValidator.hook.ts" } - ] - }, - { - "matcher": "Edit", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/SecurityValidator.hook.ts" } - ] - }, - { - "matcher": "Write", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/SecurityValidator.hook.ts" } - ] - }, - { - "matcher": "Read", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/SecurityValidator.hook.ts" } - ] - }, - { - "matcher": "AskUserQuestion", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/SetQuestionTab.hook.ts" } - ] - }, - { - "matcher": "Task", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/AgentExecutionGuard.hook.ts" } - ] - }, - { - "matcher": "Skill", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/SkillGuard.hook.ts" } - ] - } - ] -} -``` - -**What They Do:** -- `SecurityValidator.hook.ts` - Validates operations against security patterns. Runs on **4 matchers**: Bash (dangerous commands), Edit (sensitive file protection), Write (sensitive file protection), Read (sensitive path access) -- `SetQuestionTab.hook.ts` - Updates tab state to "awaiting input" when AskUserQuestion is invoked -- `AgentExecutionGuard.hook.ts` - Validates agent spawning (Task tool) against execution policies -- `SkillGuard.hook.ts` - Prevents false skill invocations (e.g., blocks keybindings-help unless explicitly requested) - ---- - -### 6. **PostToolUse** -**When:** After Claude executes any tool -**Status:** Active - Algorithm state tracking - -**Current Hooks:** -```json -{ - "PostToolUse": [ - { - "matcher": "AskUserQuestion", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/QuestionAnswered.hook.ts" } - ] - }, - { - "matcher": "Write", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/PRDSync.hook.ts" } - ] - }, - { - "matcher": "Edit", - "hooks": [ - { "type": "command", "command": "${PAI_DIR}/hooks/PRDSync.hook.ts" } - ] - } - ] -} -``` - -**What They Do:** - -**QuestionAnswered.hook.ts** - Post-Question Processing -- Fires after AskUserQuestion completes (user has answered) -- Captures the question and answer for session context -- Used for analytics and learning from user preferences - -**PRDSync.hook.ts** - PRD Frontmatter → work.json Sync -- Fires after Write/Edit to PRD files in `MEMORY/WORK/` -- Syncs PRD frontmatter (status, title, effort) to `MEMORY/STATE/work.json` -- Keeps work registry in sync without manual updates -- Non-blocking, fire-and-forget - ---- - -### 7. **PreCompact** -**When:** Before Claude compacts context (long conversations) -**Status:** Not currently configured - -**Potential Use Cases:** -- Preserve important context before compaction -- Log compaction events - ---- - -## Configuration - -### Location -**File:** `~/.claude/settings.json` -**Section:** `"hooks": { ... }` - -### Environment Variables -Hooks have access to all environment variables from `~/.claude/settings.json` `"env"` section: - -```json -{ - "env": { - "PAI_DIR": "$HOME/.claude", - "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "64000" - } -} -``` - -**Key Variables:** -- `PAI_DIR` - PAI installation directory (typically `~/.claude`) -- Hook scripts reference `${PAI_DIR}` in command paths - -### Identity Configuration (Central to Install Wizard) - -**settings.json is the single source of truth for all daidentity/configuration.** - -```json -{ - "daidentity": { - "name": "PAI", - "fullName": "Personal AI", - "displayName": "PAI", - "color": "#3B82F6", - "voiceId": "{YourElevenLabsVoiceId}" - }, - "principal": { - "name": "{YourName}", - "pronunciation": "{YourName}", - "timezone": "America/Los_Angeles" - } -} -``` - -**Using the Identity Module:** -```typescript -import { getIdentity, getPrincipal, getDAName, getPrincipalName, getVoiceId } from './lib/identity'; - -// Get full identity objects -const identity = getIdentity(); // { name, fullName, displayName, voiceId, color } -const principal = getPrincipal(); // { name, pronunciation, timezone } - -// Convenience functions -const DA_NAME = getDAName(); // "PAI" -const USER_NAME = getPrincipalName(); // "{YourName}" -const VOICE_ID = getVoiceId(); // from settings.json daidentity.voiceId -``` - -**Why settings.json?** -- Programmatic access via `JSON.parse()` - no regex parsing markdown -- Central to the PAI install wizard -- Single source of truth for all configuration -- Tool-friendly: easy to read/write from any language - -### Hook Configuration Structure - -```json -{ - "hooks": { - "HookEventName": [ - { - "matcher": "pattern", // Optional: filter which tools/events trigger hook - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/my-hook.ts --arg value" - } - ] - } - ] - } -} -``` - -**Fields:** -- `HookEventName` - One of: SessionStart, SessionEnd, UserPromptSubmit, Stop, PreToolUse, PostToolUse, PreCompact -- `matcher` - Pattern to match (use `"*"` for all tools, or specific tool names) -- `type` - Always `"command"` (executes external script) -- `command` - Path to executable hook script (TypeScript/Python/Bash) - -### Hook Input (stdin) -All hooks receive JSON data on stdin: - -```typescript -{ - session_id: string; // Unique session identifier - transcript_path: string; // Path to JSONL transcript - hook_event_name: string; // Event that triggered hook - prompt?: string; // User prompt (UserPromptSubmit only) - tool_name?: string; // Tool name (PreToolUse/PostToolUse) - tool_input?: any; // Tool parameters (PreToolUse) - tool_output?: any; // Tool result (PostToolUse) - // ... event-specific fields -} -``` - ---- - -## Common Patterns - -### 1. Voice Notifications - -**Pattern:** Extract completion message → Send to voice server - -```typescript -// handlers/VoiceNotification.ts pattern -import { getIdentity } from './lib/identity'; - -const identity = getIdentity(); -const completionMessage = extractCompletionMessage(lastMessage); - -const payload = { - title: identity.name, - message: completionMessage, - voice_enabled: true, - voice_id: identity.voiceId // From settings.json -}; - -await fetch('http://localhost:8888/notify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) -}); -``` - -**Agent-Specific Voices:** -Configure voice IDs via `settings.json` daidentity section or environment variables. -Each agent can have a unique ElevenLabs voice configured. See the Agents skill for voice registry. - ---- - -### 2. History Capture (UOCS Pattern) - -**Pattern:** Parse structured response → Save to appropriate history directory - -**File Naming Convention:** -``` -YYYY-MM-DD-HHMMSS_TYPE_description.md -``` - -**Types:** -- `WORK` - General task completions -- `LEARNING` - Problem-solving learnings -- `SESSION` - Session summaries -- `RESEARCH` - Research findings (from agents) -- `FEATURE` - Feature implementations (from agents) -- `DECISION` - Architectural decisions (from agents) - -**Example pattern (from WorkCompletionLearning.hook.ts):** -```typescript -import { getLearningCategory, isLearningCapture } from './lib/learning-utils'; -import { getPSTTimestamp, getYearMonth } from './lib/time'; - -const structured = extractStructuredSections(lastMessage); -const isLearning = isLearningCapture(text, structured.summary, structured.analysis); - -// If learning content detected, capture to LEARNING/ -if (isLearning) { - const category = getLearningCategory(text); // 'SYSTEM' or 'ALGORITHM' - const targetDir = join(baseDir, 'MEMORY', 'LEARNING', category, getYearMonth()); - const filename = generateFilename(description, 'LEARNING'); - writeFileSync(join(targetDir, filename), content); -} -``` - -**Structured Sections Parsed:** -- `📋 SUMMARY:` - Brief overview -- `🔍 ANALYSIS:` - Key findings -- `⚡ ACTIONS:` - Steps taken -- `✅ RESULTS:` - Outcomes -- `📊 STATUS:` - Current state -- `➡️ NEXT:` - Follow-up actions -- `🎯 COMPLETED:` - **Voice notification line** - ---- - -### 3. Agent Type Detection - -**Pattern:** Identify which agent is executing → Route appropriately - -```typescript -// Agent detection pattern -let agentName = getAgentForSession(sessionId); - -// Detect from Task tool -if (hookData.tool_name === 'Task' && hookData.tool_input?.subagent_type) { - agentName = hookData.tool_input.subagent_type; - setAgentForSession(sessionId, agentName); -} - -// Detect from CLAUDE_CODE_AGENT env variable -else if (process.env.CLAUDE_CODE_AGENT) { - agentName = process.env.CLAUDE_CODE_AGENT; -} - -// Detect from path (subagents run in /agents/name/) -else if (hookData.cwd && hookData.cwd.includes('/agents/')) { - const agentMatch = hookData.cwd.match(/\/agents\/([^\/]+)/); - if (agentMatch) agentName = agentMatch[1]; -} -``` - -**Session Mapping:** `~/.claude/MEMORY/STATE/agent-sessions.json` -```json -{ - "session-id-abc123": "engineer", - "session-id-def456": "researcher" -} -``` - ---- - -### 4. Tab Title + Color State Architecture - -**Pattern:** Visual state feedback through tab colors and title suffixes - -**State Flow:** - -| Event | Hook | Tab Title | Inactive Color | State | -|-------|------|-----------|----------------|-------| -| UserPromptSubmit | `UpdateTabTitle.hook.ts` | `⚙️ Summary…` | Orange `#B35A00` | Working | -| Inference | `UpdateTabTitle.hook.ts` | `🧠 Analyzing…` | Orange `#B35A00` | Inference | -| Stop (success) | `handlers/TabState.ts` | `Summary` | Green `#022800` | Completed | -| Stop (question) | `handlers/TabState.ts` | `Summary?` | Teal `#0D4F4F` | Awaiting Input | -| Stop (error) | `handlers/TabState.ts` | `Summary!` | Orange `#B35A00` | Error | - -**Active Tab:** Always Dark Blue `#002B80` (state colors only affect inactive tabs) - -**Why This Design:** -- **Instant visual feedback** - See state at a glance without reading -- **Color-coded priority** - Teal tabs need attention, green tabs are done -- **Suffix as state indicator** - Works even in narrow tab bars -- **Haiku only on user input** - One AI call per prompt (not per tool) - -**State Detection (in Stop hook):** -1. Check transcript for `AskUserQuestion` tool → `awaitingInput` -2. Check `📊 STATUS:` for error patterns → `error` -3. Default → `completed` - -**Text Colors:** -- Active tab: White `#FFFFFF` (always) -- Inactive tab: Gray `#A0A0A0` (always) - -**Active Tab Background:** Dark Blue `#002B80` (always - state colors only affect inactive tabs) - -**Tab Icons:** -- 🧠 Brain - AI inference in progress (Haiku/Sonnet thinking) -- ⚙️ Gear - Processing/working state - -**Full Documentation:** See `~/.claude/PAI/TERMINALTABS.md` - ---- - -### 5. Async Non-Blocking Execution - -**Pattern:** Hook executes quickly → Launch background processes for slow operations - -```typescript -// update-tab-titles.ts pattern -// Set immediate tab title (fast) -execSync(`printf '\\033]0;${titleWithEmoji}\\007' >&2`); - -// Launch background process for Haiku summary (slow) -Bun.spawn(['bun', `${paiDir}/hooks/UpdateTabTitle.ts`, prompt], { - stdout: 'ignore', - stderr: 'ignore', - stdin: 'ignore' -}); - -process.exit(0); // Exit immediately -``` - -**Key Principle:** Hooks must never block Claude Code. Always exit quickly, use background processes for slow work. - ---- - -### 6. Graceful Failure - -**Pattern:** Wrap everything in try/catch → Log errors → Exit successfully - -```typescript -async function main() { - try { - // Hook logic here - } catch (error) { - // Log but don't fail - console.error('Hook error:', error); - } - - process.exit(0); // Always exit 0 -} -``` - -**Why:** If hooks crash, Claude Code may freeze. Always exit cleanly. - ---- - -## Creating Custom Hooks - -### Step 1: Choose Hook Event -Decide which event should trigger your hook (SessionStart, Stop, PostToolUse, etc.) - -### Step 2: Create Hook Script -**Location:** `~/.claude/hooks/my-custom-hook.ts` - -**Template:** -```typescript -#!/usr/bin/env bun - -interface HookInput { - session_id: string; - transcript_path: string; - hook_event_name: string; - // ... event-specific fields -} - -async function main() { - try { - // Read stdin - const input = await Bun.stdin.text(); - const data: HookInput = JSON.parse(input); - - // Your hook logic here - console.log(`Hook triggered: ${data.hook_event_name}`); - - // Example: Read transcript - const fs = require('fs'); - const transcript = fs.readFileSync(data.transcript_path, 'utf-8'); - - // Do something with the data - - } catch (error) { - // Log but don't fail - console.error('Hook error:', error); - } - - process.exit(0); // Always exit 0 -} - -main(); -``` - -### Step 3: Make Executable -```bash -chmod +x ~/.claude/hooks/my-custom-hook.ts -``` - -### Step 4: Add to settings.json -```json -{ - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/my-custom-hook.ts" - } - ] - } - ] - } -} -``` - -### Step 5: Test -```bash -# Test hook directly -echo '{"session_id":"test","transcript_path":"/tmp/test.jsonl","hook_event_name":"Stop"}' | bun ~/.claude/hooks/my-custom-hook.ts -``` - -### Step 6: Restart Claude Code -Hooks are loaded at startup. Restart to apply changes. - ---- - -## Hook Development Best Practices - -### 1. **Fast Execution** -- Hooks should complete in < 500ms -- Use background processes for slow work (Haiku API calls, file processing) -- Exit immediately after launching background work - -### 2. **Graceful Failure** -- Always wrap in try/catch -- Log errors to stderr (available in hook debug logs) -- Always `process.exit(0)` - never throw or exit(1) - -### 3. **Non-Blocking** -- Never wait for external services (unless they respond quickly) -- Use `.catch(() => {})` for async operations -- Fail silently if optional services are offline - -### 4. **Stdin Reading** -- Use timeout when reading stdin (Claude Code may not send data immediately) -- Handle empty/invalid input gracefully - -```typescript -const decoder = new TextDecoder(); -const reader = Bun.stdin.stream().getReader(); - -const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 500); // 500ms timeout -}); - -await Promise.race([readPromise, timeoutPromise]); -``` - -### 5. **File I/O** -- Check `existsSync()` before reading files -- Create directories with `{ recursive: true }` -- Use PST timestamps for consistency - -### 6. **Environment Access** -- All `settings.json` env vars available via `process.env` -- Use `${PAI_DIR}` in settings.json for portability -- Access in code via `process.env.PAI_DIR` - -### 7. **Logging** -- Log useful debug info to stderr for troubleshooting -- Include relevant metadata (session_id, tool_name, etc.) -- Never log sensitive data (API keys, user content) - ---- - -## Troubleshooting - -### Hook Not Running - -**Check:** -1. Is hook script executable? `chmod +x ~/.claude/hooks/my-hook.ts` -2. Is path correct in settings.json? Use `${PAI_DIR}/hooks/...` -3. Is settings.json valid JSON? `jq . ~/.claude/settings.json` -4. Did you restart Claude Code after editing settings.json? - -**Debug:** -```bash -# Test hook directly -echo '{"session_id":"test","transcript_path":"/tmp/test.jsonl","hook_event_name":"Stop"}' | bun ~/.claude/hooks/my-hook.ts - -# Check hook logs (stderr output) -tail -f ~/.claude/hooks/debug.log # If you add logging -``` - ---- - -### Hook Hangs/Freezes Claude Code - -**Cause:** Hook not exiting (infinite loop, waiting for input, blocking operation) - -**Fix:** -1. Add timeouts to all blocking operations -2. Ensure `process.exit(0)` is always reached -3. Use background processes for long operations -4. Check stdin reading has timeout - -**Prevention:** -```typescript -// Always use timeout -setTimeout(() => { - console.error('Hook timeout - exiting'); - process.exit(0); -}, 5000); // 5 second max -``` - ---- - -### Voice Notifications Not Working - -**Check:** -1. Is voice server running? `curl http://localhost:8888/health` -2. Is voice_id correct? See `PAI/SKILL.md` for mappings -3. Is message format correct? `{"message":"...", "voice_id":"...", "title":"..."}` -4. Is ElevenLabs API key in `${PAI_DIR}/.env`? - -**Debug:** -```bash -# Test voice server directly -curl -X POST http://localhost:8888/notify \ - -H "Content-Type: application/json" \ - -d '{"message":"Test message","voice_id":"[YOUR_VOICE_ID]","title":"Test"}' -``` - -**Common Issues:** -- Wrong voice_id → Silent failure (invalid ID) -- Voice server offline → Hook continues (graceful failure) -- No `🎯 COMPLETED:` line → No voice notification extracted - ---- - -### Work Not Capturing - -**Check:** -1. Does `~/.claude/MEMORY/` directory exist? -2. Does current-work file exist? Check `~/.claude/MEMORY/STATE/current-work.json` -3. Is hook actually running? Check `~/.claude/MEMORY/RAW/` for events -4. File permissions? `ls -la ~/.claude/MEMORY/WORK/` - -**Debug:** -```bash -# Check current work -cat ~/.claude/MEMORY/STATE/current-work.json - -# Check recent work directories -ls -lt ~/.claude/MEMORY/WORK/ | head -10 -ls -lt ~/.claude/MEMORY/LEARNING/$(date +%Y-%m)/ | head -10 - -# Check raw events -tail ~/.claude/MEMORY/RAW/$(date +%Y-%m)/$(date +%Y-%m-%d)_all-events.jsonl -``` - -**Common Issues:** -- Missing current-work.json → Work not being tracked for this session -- Work not updating → capture handler not finding current work -- Learning detection too strict → Adjust `isLearningCapture()` logic - ---- - -### Stop Event Not Firing (RESOLVED) - -**Original Issue:** Stop events were not firing consistently in earlier Claude Code versions, causing voice notifications and work capture to fail silently. - -**Resolution:** Fixed in Claude Code updates. The Stop hooks now fires reliably. The unified orchestrator pattern (`Stop hooks.hook.ts` delegating to `handlers/`) was implemented in part to work around this — and remains the production architecture. - -**Status:** RESOLVED — Stop events now fire reliably. Stop hooks handles all post-response work. - ---- - -### Agent Detection Failing - -**Check:** -1. Is `~/.claude/MEMORY/STATE/agent-sessions.json` writable? -2. Is `[AGENT:type]` tag in `🎯 COMPLETED:` line? -3. Is agent running from correct directory? (`/agents/name/`) - -**Debug:** -```bash -# Check session mappings -cat ~/.claude/MEMORY/STATE/agent-sessions.json | jq . - -# Check subagent-stop debug log -tail -f ~/.claude/hooks/subagent-stop-debug.log -``` - -**Fix:** -- Ensure agents include `[AGENT:type]` in completion line -- Verify Task tool passes `subagent_type` parameter -- Check cwd includes `/agents/` in path - ---- - -### Transcript Type Mismatch (Fixed 2026-01-11) - -**Symptom:** Context reading functions return empty results even though transcript has data - -**Root Cause:** Claude Code transcripts use `type: "user"` but hooks were checking for `type: "human"`. - -**Affected Hooks:** -- `UpdateTabTitle.hook.ts` - Couldn't read user messages for context -- `RatingCapture.hook.ts` - Same issue - -**Fix Applied:** -1. Changed `entry.type === 'human'` → `entry.type === 'user'` -2. Improved content extraction to skip `tool_result` blocks and only capture actual text - -**Verification:** -```bash -# Check transcript type field -grep '"type":"user"' ~/.claude/projects/-Users-username--claude/*.jsonl | head -1 | jq '.type' -# Should output: "user" (not "human") -``` - -**Prevention:** When parsing transcripts, always verify the actual JSON structure first. - ---- - -### Context Loading Issues (SessionStart) - -**Check:** -1. Does `~/.claude/PAI/SKILL.md` exist? -2. Is `LoadContext.hook.ts` executable? -3. Is `PAI_DIR` env variable set correctly? - -**Debug:** -```bash -# Test context loading directly -bun ~/.claude/hooks/LoadContext.hook.ts - -# Should output with SKILL.md content -``` - -**Common Issues:** -- Subagent sessions loading main context → Fixed (subagent detection in hook) -- File not found → Check `PAI_DIR` environment variable -- Permission denied → `chmod +x ~/.claude/hooks/LoadContext.hook.ts` - ---- - -## Advanced Topics - -### Multi-Hook Execution Order - -Hooks in same event execute **sequentially** in order defined in settings.json: - -```json -{ - "Stop": [ - { - "hooks": [ - { "command": "${PAI_DIR}/hooks/Stop hooks.hook.ts" } // Single orchestrator - ] - } - ] -} -``` - -**Note:** If first hook hangs, second won't run. Keep hooks fast! - ---- - -### Matcher Patterns - -`"matcher"` field filters which events trigger hook: - -```json -{ - "PostToolUse": [ - { - "matcher": "Bash", // Only Bash tool executions - "hooks": [...] - }, - { - "matcher": "*", // All tool executions - "hooks": [...] - } - ] -} -``` - -**Patterns:** -- `"*"` - All events -- `"Bash"` - Specific tool name -- `""` - Empty (all events, same as `*`) - ---- - -### Hook Data Payloads by Event Type - -**SessionStart:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "SessionStart"; - cwd: string; -} -``` - -**UserPromptSubmit:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "UserPromptSubmit"; - prompt: string; // The user's prompt text -} -``` - -**PreToolUse:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "PreToolUse"; - tool_name: string; - tool_input: any; // Tool parameters -} -``` - -**PostToolUse:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "PostToolUse"; - tool_name: string; - tool_input: any; - tool_output: any; // Tool result - error?: string; // If tool failed -} -``` - -**Stop:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "Stop"; -} -``` - -**SessionEnd:** -```typescript -{ - conversation_id: string; // Note: different field name - timestamp: string; -} -``` - ---- - -## Related Documentation - -- **Voice System:** `~/.claude/VoiceServer/SKILL.md` -- **Agent System:** `~/.claude/skills/Agents/SKILL.md` -- **History/Memory:** `~/.claude/PAI/MEMORYSYSTEM.md` - ---- - -## Quick Reference Card - -``` -HOOK LIFECYCLE: -1. Event occurs (SessionStart, Stop, etc.) -2. Claude Code writes hook data to stdin -3. Hook script executes -4. Hook reads stdin (with timeout) -5. Hook performs actions (voice, capture, etc.) -6. Hook exits 0 (always succeeds) -7. Claude Code continues - -HOOKS BY EVENT (22 hooks total): - -SESSION START (2 hooks): - KittyEnvPersist.hook.ts Persist Kitty env vars + tab reset - LoadContext.hook.ts Dynamic context injection (relationship, learning, work) - -SESSION END (5 hooks): - WorkCompletionLearning.hook.ts Work/learning capture to MEMORY/ - SessionCleanup.hook.ts Mark WORK dir complete, clear state, reset tab - RelationshipMemory.hook.ts Relationship context to MEMORY/RELATIONSHIP/ - UpdateCounts.hook.ts Refresh system counts (skills, hooks, signals) - IntegrityCheck.hook.ts System integrity checks - -USER PROMPT SUBMIT (3 hooks): - RatingCapture.hook.ts Unified rating capture (explicit + implicit) - UpdateTabTitle.hook.ts Tab title + working state (orange) - SessionAutoName.hook.ts Auto-name session from first prompt - -STOP (5 hooks): - LastResponseCache.hook.ts Cache response for RatingCapture bridge - ResponseTabReset.hook.ts Tab title/color reset after response - VoiceCompletion.hook.ts Voice TTS (main sessions only) - DocIntegrity.hook.ts Cross-ref + semantic drift checks - AlgorithmTab.hook.ts Algorithm phase + progress in tab - -PRE TOOL USE (4 hooks): - SecurityValidator.hook.ts Security validation [Bash, Edit, Write, Read] - SetQuestionTab.hook.ts Tab state on question [AskUserQuestion] - AgentExecutionGuard.hook.ts Agent spawn guardrails [Task] - SkillGuard.hook.ts Skill invocation validation [Skill] - -POST TOOL USE (2 hooks): - QuestionAnswered.hook.ts Post-question tab reset [AskUserQuestion] - PRDSync.hook.ts PRD → work.json sync [Write, Edit] - -KEY FILES: -~/.claude/settings.json Hook configuration -~/.claude/hooks/ Hook scripts (22 files) -~/.claude/hooks/handlers/ Handler modules (6 files) -~/.claude/hooks/lib/ Shared libraries (13 files) -~/.claude/hooks/lib/learning-utils.ts Learning categorization -~/.claude/hooks/lib/time.ts PST timestamp utilities -~/.claude/hooks/lib/event-types.ts Typed event definitions (22 interfaces) -~/.claude/hooks/lib/event-emitter.ts appendEvent() → events.jsonl -~/.claude/MEMORY/WORK/ Work tracking -~/.claude/MEMORY/LEARNING/ Learning captures -~/.claude/MEMORY/STATE/ Runtime state -~/.claude/MEMORY/STATE/events.jsonl Unified event log (append-only) - -INFERENCE TOOL (for hooks needing AI): -Path: ~/.claude/PAI/Tools/Inference.ts -Import: import { inference } from '../PAI/Tools/Inference' -Levels: fast (haiku/15s) | standard (sonnet/30s) | smart (opus/90s) - -TAB STATE SYSTEM: -Inference: 🧠… Orange #B35A00 (AI thinking) -Working: ⚙️… Orange #B35A00 (processing) -Completed: Green #022800 (task done) -Awaiting: ? Teal #0D4F4F (needs input) -Error: ! Orange #B35A00 (problem detected) -Active Tab: Always Dark Blue #002B80 (state colors = inactive only) - -VOICE SERVER: -URL: http://localhost:8888/notify -Payload: {"message":"...", "voice_id":"...", "title":"..."} -Configure voice IDs in individual agent files (`agents/*.md` persona frontmatter) - -``` - ---- - -## Shared Libraries - -The hook system uses shared TypeScript libraries to eliminate code duplication: - -### `hooks/lib/learning-utils.ts` -Shared learning categorization logic. - -```typescript -import { getLearningCategory, isLearningCapture } from './lib/learning-utils'; - -// Categorize learning as SYSTEM (tooling/infra) or ALGORITHM (task execution) -const category = getLearningCategory(content, comment); -// Returns: 'SYSTEM' | 'ALGORITHM' - -// Check if response contains learning indicators -const isLearning = isLearningCapture(text, summary, analysis); -// Returns: boolean (true if 2+ learning indicators found) -``` - -**Used by:** RatingCapture, WorkCompletionLearning - -### `hooks/lib/time.ts` -Shared PST timestamp utilities. - -```typescript -import { - getPSTTimestamp, // "2026-01-10 20:30:00 PST" - getPSTDate, // "2026-01-10" - getYearMonth, // "2026-01" - getISOTimestamp, // ISO8601 with offset - getFilenameTimestamp, // "2026-01-10-203000" - getPSTComponents // { year, month, day, hours, minutes, seconds } -} from './lib/time'; -``` - -**Used by:** RatingCapture, WorkCompletionLearning, SessionSummary - -### `hooks/lib/identity.ts` -Identity and principal configuration from settings.json. - -```typescript -import { getIdentity, getPrincipal, getDAName, getPrincipalName, getVoiceId } from './lib/identity'; - -const identity = getIdentity(); // { name, fullName, displayName, voiceId, color } -const principal = getPrincipal(); // { name, pronunciation, timezone } -``` - -**Used by:** handlers/VoiceNotification.ts, RatingCapture, handlers/TabState.ts - -### `PAI/Tools/Inference.ts` -Unified AI inference with three run levels. - -```typescript -import { inference } from '../PAI/Tools/Inference'; - -// Fast (Haiku) - quick tasks, 15s timeout -const result = await inference({ - systemPrompt: 'Summarize in 3 words', - userPrompt: text, - level: 'fast', -}); - -// Standard (Sonnet) - balanced reasoning, 30s timeout -const result = await inference({ - systemPrompt: 'Analyze sentiment', - userPrompt: text, - level: 'standard', - expectJson: true, -}); - -// Smart (Opus) - deep reasoning, 90s timeout -const result = await inference({ - systemPrompt: 'Strategic analysis', - userPrompt: text, - level: 'smart', -}); - -// Result shape -interface InferenceResult { - success: boolean; - output: string; - parsed?: unknown; // if expectJson: true - error?: string; - latencyMs: number; - level: 'fast' | 'standard' | 'smart'; -} -``` - -**Used by:** RatingCapture, UpdateTabTitle, SessionAutoName - ---- - -## Unified Event System - -Alongside existing filesystem state writes (algorithm-state JSON, PRDs, session-names.json, etc.), hooks can emit structured events to a single append-only JSONL log. This provides a unified observability layer without replacing any existing state management. - -### Components - -| File | Purpose | -|------|---------| -| `${PAI_DIR}/hooks/lib/event-types.ts` | TypeScript discriminated union of all PAI event types (22 interfaces covering algorithm, work, session, rating, learning, voice, PRD, doc, build, system, tab, hook error, and custom events) | -| `${PAI_DIR}/hooks/lib/event-emitter.ts` | `appendEvent()` utility that writes typed events to `${PAI_DIR}/MEMORY/STATE/events.jsonl` | - -### Usage in Hooks - -Hooks call `appendEvent()` as a secondary write **alongside** their existing state writes. The emitter is synchronous, fire-and-forget, and silently swallows errors so it never blocks or crashes a hook. - -```typescript -import { appendEvent } from './lib/event-emitter'; - -// Inside an existing hook, AFTER the normal state write: -appendEvent({ type: 'work.created', source: 'PRDSync', slug: 'my-task' }); -``` - -### Event Structure - -Every event has a common base shape plus type-specific fields: -- `timestamp` (ISO 8601) -- auto-injected by `appendEvent()` -- `session_id` -- auto-injected from `CLAUDE_SESSION_ID` env -- `source` -- the hook or handler name that emitted the event -- `type` -- dot-separated topic (e.g., `algorithm.phase`, `work.created`, `voice.sent`, `rating.captured`) - -Events use a dot-separated topic hierarchy for filtering. A `custom.*` escape hatch allows arbitrary extension without modifying the type system. - -### Event Type Categories - -| Category | Types | Emitting Hooks | -|----------|-------|----------------| -| `work.*` | created, completed | PRDSync, SessionCleanup | -| `session.*` | named, completed | SessionCleanup | -| `rating.*` | captured | RatingCapture | -| `learning.*` | captured | WorkCompletionLearning | -| `voice.*` | sent | VoiceNotification | -| `prd.*` | synced | PRDSync | -| `doc.*` | integrity | DocIntegrity | -| `build.*` | rebuild | BuildCLAUDE (SessionStart handler) | -| `system.*` | integrity | IntegrityCheck | -| `settings.*` | counts_updated | UpdateCounts | -| `tab.*` | updated | TabState, UpdateTabTitle | -| `hook.*` | error | Any hook (error reporting) | -| `custom.*` | user-defined | Extensibility escape hatch | - -### Consuming Events - -```bash -# Live tail (real-time monitoring) -tail -f ~/.claude/MEMORY/STATE/events.jsonl | jq - -# Filter by type -tail -f ~/.claude/MEMORY/STATE/events.jsonl | jq 'select(.type | startswith("algorithm."))' - -# Programmatic (Node/Bun fs.watch) -import { watch } from 'fs'; -import { getEventsPath } from './hooks/lib/event-emitter'; -watch(getEventsPath(), (eventType) => { /* read new lines */ }); -``` - -### Key Principles - -- **Additive only** -- events supplement existing state files, they never replace them -- **Append-only** -- `events.jsonl` is an immutable log, never rewritten or truncated by hooks -- **Graceful failure** -- write errors are swallowed; events are observability, not critical path -- **One file** -- all event types go to a single `events.jsonl` for simple tailing and watching - ---- - -**Last Updated:** 2026-02-25 -**Status:** Production - 15 hooks emitting 22 event types across 14 categories -**Maintainer:** PAI System diff --git a/.opencode/PAI/TOOLS.md b/.opencode/PAI/TOOLS.md index 549d50e6..a146345d 100755 --- a/.opencode/PAI/TOOLS.md +++ b/.opencode/PAI/TOOLS.md @@ -10,26 +10,26 @@ This file documents single-purpose CLI utilities that have been consolidated fro ## Inference.ts - Unified AI Inference Tool -**Location:** `~/.claude/PAI/Tools/Inference.ts` +**Location:** `~/.opencode/PAI/Tools/Inference.ts` Single inference tool with three run levels for different speed/capability trade-offs. **Usage:** ```bash # Fast (Haiku) - quick tasks, simple generation -bun ~/.claude/PAI/Tools/Inference.ts --level fast "System prompt" "User prompt" +bun ~/.opencode/PAI/Tools/Inference.ts --level fast "System prompt" "User prompt" # Standard (Sonnet) - balanced reasoning, typical analysis -bun ~/.claude/PAI/Tools/Inference.ts --level standard "System prompt" "User prompt" +bun ~/.opencode/PAI/Tools/Inference.ts --level standard "System prompt" "User prompt" # Smart (Opus) - deep reasoning, strategic decisions -bun ~/.claude/PAI/Tools/Inference.ts --level smart "System prompt" "User prompt" +bun ~/.opencode/PAI/Tools/Inference.ts --level smart "System prompt" "User prompt" # With JSON output -bun ~/.claude/PAI/Tools/Inference.ts --json --level fast "Return JSON" "Input" +bun ~/.opencode/PAI/Tools/Inference.ts --json --level fast "Return JSON" "Input" # Custom timeout -bun ~/.claude/PAI/Tools/Inference.ts --level standard --timeout 60000 "Prompt" "Input" +bun ~/.opencode/PAI/Tools/Inference.ts --level standard --timeout 60000 "Prompt" "Input" ``` **Run Levels:** @@ -64,7 +64,7 @@ if (result.success) { - Hooks use this for sentiment analysis, tab titles, work classification **Technical Details:** -- Uses Claude CLI with subscription (not API key) +- Uses OpenCode CLI with subscription (not API key) - Disables tools and hooks to prevent recursion - Returns latency metrics for monitoring @@ -72,20 +72,20 @@ if (result.success) { ## RemoveBg.ts - Remove Image Backgrounds -**Location:** `~/.claude/PAI/Tools/RemoveBg.ts` +**Location:** `~/.opencode/PAI/Tools/RemoveBg.ts` Remove backgrounds from images using the remove.bg API. **Usage:** ```bash # Remove background from single image (overwrites original) -bun ~/.claude/PAI/Tools/RemoveBg.ts /path/to/image.png +bun ~/.opencode/PAI/Tools/RemoveBg.ts /path/to/image.png # Remove background and save to different path -bun ~/.claude/PAI/Tools/RemoveBg.ts /path/to/input.png /path/to/output.png +bun ~/.opencode/PAI/Tools/RemoveBg.ts /path/to/input.png /path/to/output.png # Process multiple images -bun ~/.claude/PAI/Tools/RemoveBg.ts image1.png image2.png image3.png +bun ~/.opencode/PAI/Tools/RemoveBg.ts image1.png image2.png image3.png ``` **Environment Variables:** @@ -100,17 +100,17 @@ bun ~/.claude/PAI/Tools/RemoveBg.ts image1.png image2.png image3.png ## AddBg.ts - Add Background Color -**Location:** `~/.claude/PAI/Tools/AddBg.ts` +**Location:** `~/.opencode/PAI/Tools/AddBg.ts` Add solid background color to transparent images. **Usage:** ```bash # Add specific background color -bun ~/.claude/PAI/Tools/AddBg.ts /path/to/transparent.png "#EAE9DF" /path/to/output.png +bun ~/.opencode/PAI/Tools/AddBg.ts /path/to/transparent.png "#EAE9DF" /path/to/output.png # Add brand background color -bun ~/.claude/PAI/Tools/AddBg.ts /path/to/transparent.png --brand /path/to/output.png +bun ~/.opencode/PAI/Tools/AddBg.ts /path/to/transparent.png --brand /path/to/output.png ``` **When to Use:** @@ -124,17 +124,17 @@ bun ~/.claude/PAI/Tools/AddBg.ts /path/to/transparent.png --brand /path/to/outpu ## GetTranscript.ts - Extract YouTube Transcripts -**Location:** `~/.claude/PAI/Tools/GetTranscript.ts` +**Location:** `~/.opencode/PAI/Tools/GetTranscript.ts` Extract transcripts from YouTube videos using yt-dlp (via fabric). **Usage:** ```bash # Extract transcript to stdout -bun ~/.claude/PAI/Tools/GetTranscript.ts "https://www.youtube.com/watch?v=VIDEO_ID" +bun ~/.opencode/PAI/Tools/GetTranscript.ts "https://www.youtube.com/watch?v=VIDEO_ID" # Save transcript to file -bun ~/.claude/PAI/Tools/GetTranscript.ts "https://www.youtube.com/watch?v=VIDEO_ID" --save /path/to/transcript.txt +bun ~/.opencode/PAI/Tools/GetTranscript.ts "https://www.youtube.com/watch?v=VIDEO_ID" --save /path/to/transcript.txt ``` **Supported URL Formats:** @@ -193,7 +193,7 @@ sleep 2 - "perform this" **Technical Details:** -- Voice server must be running (`~/.claude/skills/VoiceServer/`) +- Voice server must be running (`~/.opencode/skills/VoiceServer/`) - Segments longer than 450 chars should be split - Natural 2-second pauses between segments for storytelling flow - Uses ElevenLabs API under the hood @@ -202,14 +202,14 @@ sleep 2 ## extract-transcript.py - Transcribe Audio/Video Files -**Location:** `~/.claude/PAI/Tools/extract-transcript.py` +**Location:** `~/.opencode/PAI/Tools/extract-transcript.py` Local transcription using faster-whisper (4x faster than OpenAI Whisper, 50% less memory). Self-contained UV script for offline transcription. **Usage:** ```bash # Transcribe single file (base.en model - recommended) -cd ~/.claude/PAI/Tools/ +cd ~/.opencode/PAI/Tools/ uv run extract-transcript.py /path/to/audio.m4a # Use different model @@ -263,20 +263,20 @@ uv run extract-transcript.py /path/to/folder/ --batch --model base.en ## YouTubeApi.ts - YouTube Channel & Video Stats -**Location:** `~/.claude/PAI/Tools/YouTubeApi.ts` +**Location:** `~/.opencode/PAI/Tools/YouTubeApi.ts` Wrapper around YouTube Data API v3 for channel statistics and video metrics. **Usage:** ```bash # Get channel statistics -bun ~/.claude/PAI/Tools/YouTubeApi.ts --channel-stats +bun ~/.opencode/PAI/Tools/YouTubeApi.ts --channel-stats # Get video statistics -bun ~/.claude/PAI/Tools/YouTubeApi.ts --video-stats VIDEO_ID +bun ~/.opencode/PAI/Tools/YouTubeApi.ts --video-stats VIDEO_ID # Get latest uploads -bun ~/.claude/PAI/Tools/YouTubeApi.ts --latest-videos +bun ~/.opencode/PAI/Tools/YouTubeApi.ts --latest-videos ``` **Environment Variables:** @@ -376,12 +376,12 @@ brew install trufflehog When adding a new utility tool to this system: -1. **Add tool file:** Place `.ts` or `.py` file directly in `~/.claude/PAI/Tools/` +1. **Add tool file:** Place `.ts` or `.py` file directly in `~/.opencode/PAI/Tools/` - Use **Title Case** for filenames (e.g., `GetTranscript.ts`, not `get-transcript.ts`) - Keep the directory flat - NO subdirectories 2. **Document here:** Add section to this file with: - - Tool location (e.g., `~/.claude/PAI/Tools/ToolName.ts`) + - Tool location (e.g., `~/.opencode/PAI/Tools/ToolName.ts`) - Usage examples - When to use triggers - Environment variables (if any) diff --git a/.opencode/PAI/Tools/ActivityParser.ts b/.opencode/PAI/Tools/ActivityParser.ts index eaf399ab..218a3b4c 100755 --- a/.opencode/PAI/Tools/ActivityParser.ts +++ b/.opencode/PAI/Tools/ActivityParser.ts @@ -21,10 +21,10 @@ import * as path from "path"; // Configuration // ============================================================================ -const CLAUDE_DIR = path.join(process.env.HOME!, ".claude"); -const MEMORY_DIR = path.join(CLAUDE_DIR, "MEMORY"); +const OPENCODE_DIR = path.join(process.env.HOME!, ".opencode"); +const MEMORY_DIR = path.join(OPENCODE_DIR, "MEMORY"); const USERNAME = process.env.USER || require("os").userInfo().username; -const PROJECTS_DIR = path.join(CLAUDE_DIR, "projects", `-Users-${USERNAME}--claude`); // Claude Code native storage +const PROJECTS_DIR = path.join(OPENCODE_DIR, "projects"); // OpenCode session storage const SYSTEM_UPDATES_DIR = path.join(MEMORY_DIR, "PAISYSTEMUPDATES"); // Canonical system change history // ============================================================================ @@ -86,7 +86,7 @@ function shouldSkip(filePath: string): boolean { function categorizeFile(filePath: string): keyof ParsedActivity["categories"] | null { if (shouldSkip(filePath)) return null; - if (!filePath.includes("/.claude/")) return null; + if (!filePath.includes("/.opencode/")) return null; if (PATTERNS.skills.test(filePath)) return "skills"; if (PATTERNS.workflows.test(filePath)) return "workflows"; @@ -104,16 +104,16 @@ function extractSkillName(filePath: string): string | null { } function getRelativePath(filePath: string): string { - const claudeIndex = filePath.indexOf("/.claude/"); - if (claudeIndex === -1) return filePath; - return filePath.substring(claudeIndex + 9); // Skip "/.claude/" + const opencodeIndex = filePath.indexOf("/.opencode/"); + if (opencodeIndex === -1) return filePath; + return filePath.substring(opencodeIndex + 11); // Skip "/.opencode/" } // ============================================================================ // Event Parsing // ============================================================================ -// Projects/ format from Claude Code native storage +// Projects/ format from OpenCode native storage interface ProjectsEntry { sessionId?: string; type?: "user" | "assistant" | "summary"; @@ -203,7 +203,7 @@ async function parseEvents(sessionFilter?: string): Promise { // Write tool = new files if (contentItem.name === "Write" && contentItem.input?.file_path) { const filePath = contentItem.input.file_path; - if (filePath.includes("/.claude/")) { + if (filePath.includes("/.opencode/")) { filesCreated.add(filePath); } } @@ -211,7 +211,7 @@ async function parseEvents(sessionFilter?: string): Promise { // Edit tool = modified files if (contentItem.name === "Edit" && contentItem.input?.file_path) { const filePath = contentItem.input.file_path; - if (filePath.includes("/.claude/")) { + if (filePath.includes("/.opencode/")) { filesModified.add(filePath); } } diff --git a/.opencode/PAI/Tools/BuildCLAUDE.ts b/.opencode/PAI/Tools/BuildAGENTS.ts similarity index 85% rename from .opencode/PAI/Tools/BuildCLAUDE.ts rename to .opencode/PAI/Tools/BuildAGENTS.ts index 3e54b3f9..87a8275a 100644 --- a/.opencode/PAI/Tools/BuildCLAUDE.ts +++ b/.opencode/PAI/Tools/BuildAGENTS.ts @@ -1,23 +1,23 @@ #!/usr/bin/env bun /** - * BuildCLAUDE.ts — Generate CLAUDE.md from template + settings + * BuildAGENTS.ts — Generate AGENTS.md from template + settings * - * Reads CLAUDE.md.template, resolves variables from settings.json - * and PAI/Algorithm/LATEST, writes CLAUDE.md. + * Reads AGENTS.md.template, resolves variables from settings.json + * and PAI/Algorithm/LATEST, writes AGENTS.md. * * Called by: * - PAI installer (first install) - * - SessionStart hook (keeps fresh automatically) - * - Manual: bun PAI/Tools/BuildCLAUDE.ts + * - SessionStart plugin (keeps fresh automatically) + * - Manual: bun PAI/Tools/BuildAGENTS.ts */ import { readFileSync, writeFileSync, existsSync } from "fs"; import { join } from "path"; -const PAI_DIR = join(process.env.HOME!, ".claude"); -const TEMPLATE_PATH = join(PAI_DIR, "CLAUDE.md.template"); -const OUTPUT_PATH = join(PAI_DIR, "CLAUDE.md"); +const PAI_DIR = join(process.env.HOME!, ".opencode"); +const TEMPLATE_PATH = join(PAI_DIR, "AGENTS.md.template"); +const OUTPUT_PATH = join(PAI_DIR, "AGENTS.md"); const SETTINGS_PATH = join(PAI_DIR, "settings.json"); const ALGORITHM_DIR = join(PAI_DIR, "PAI/Algorithm"); const LATEST_PATH = join(ALGORITHM_DIR, "LATEST"); @@ -89,7 +89,7 @@ export function needsRebuild(): boolean { export function build(): { rebuilt: boolean; reason?: string } { if (!existsSync(TEMPLATE_PATH)) { - return { rebuilt: false, reason: "No CLAUDE.md.template found" }; + return { rebuilt: false, reason: "No AGENTS.md.template found" }; } let content = readFileSync(TEMPLATE_PATH, "utf-8"); @@ -103,7 +103,7 @@ export function build(): { rebuilt: boolean; reason?: string } { if (existsSync(OUTPUT_PATH)) { const existing = readFileSync(OUTPUT_PATH, "utf-8"); if (existing === content) { - return { rebuilt: false, reason: "CLAUDE.md already current" }; + return { rebuilt: false, reason: "AGENTS.md already current" }; } } @@ -117,7 +117,7 @@ if (import.meta.main) { const result = build(); if (result.rebuilt) { const vars = loadVariables(); - console.log("✅ Built CLAUDE.md from template"); + console.log("✅ Built AGENTS.md from template"); console.log(` Algorithm: ${vars["{{ALGO_VERSION}}"]}`); console.log(` DA: ${vars["{DAIDENTITY.NAME}"]}`); console.log(` Principal: ${vars["{PRINCIPAL.NAME}"]}`); diff --git a/.opencode/PAI/Tools/GetTranscript.ts b/.opencode/PAI/Tools/GetTranscript.ts index 4f88d5f9..5feb7232 100755 --- a/.opencode/PAI/Tools/GetTranscript.ts +++ b/.opencode/PAI/Tools/GetTranscript.ts @@ -4,12 +4,12 @@ * GetTranscript.ts - Extract transcript from YouTube video * * Usage: - * bun ~/.claude/skills/Videotranscript/Tools/GetTranscript.ts - * bun ~/.claude/skills/Videotranscript/Tools/GetTranscript.ts --save + * bun ~/.opencode/skills/Videotranscript/Tools/GetTranscript.ts + * bun ~/.opencode/skills/Videotranscript/Tools/GetTranscript.ts --save * * Examples: - * bun ~/.claude/skills/Videotranscript/Tools/GetTranscript.ts "https://www.youtube.com/watch?v=A1b2C3d4E5F" - * bun ~/.claude/skills/Videotranscript/Tools/GetTranscript.ts "https://youtu.be/A1b2C3d4E5F" --save transcript.txt + * bun ~/.opencode/skills/Videotranscript/Tools/GetTranscript.ts "https://www.youtube.com/watch?v=A1b2C3d4E5F" + * bun ~/.opencode/skills/Videotranscript/Tools/GetTranscript.ts "https://youtu.be/A1b2C3d4E5F" --save transcript.txt * * @author PAI System * @version 1.0.0 diff --git a/.opencode/PAI/Tools/LoadSkillConfig.ts b/.opencode/PAI/Tools/LoadSkillConfig.ts index 1f164ff9..5236b85c 100755 --- a/.opencode/PAI/Tools/LoadSkillConfig.ts +++ b/.opencode/PAI/Tools/LoadSkillConfig.ts @@ -7,11 +7,11 @@ * base config with user customizations from SKILLCUSTOMIZATIONS directory. * * Usage: - * import { loadSkillConfig } from '~/.claude/PAI/Tools/LoadSkillConfig'; + * import { loadSkillConfig } from '~/.opencode/PAI/Tools/LoadSkillConfig'; * const config = loadSkillConfig(__dirname, 'config.json'); * * Or CLI: - * bun ~/.claude/PAI/Tools/LoadSkillConfig.ts + * bun ~/.opencode/PAI/Tools/LoadSkillConfig.ts */ import { readFileSync, existsSync, readdirSync } from 'fs'; @@ -35,7 +35,7 @@ interface ExtendManifest { // Constants const HOME = homedir(); -const CUSTOMIZATION_DIR = join(HOME, '.claude', 'PAI', 'USER', 'SKILLCUSTOMIZATIONS'); +const CUSTOMIZATION_DIR = join(HOME, '.opencode', 'PAI', 'USER', 'SKILLCUSTOMIZATIONS'); /** * Deep merge two objects recursively @@ -250,7 +250,7 @@ Usage: bun LoadSkillConfig.ts --check Check if skill has customizations Examples: - bun LoadSkillConfig.ts ~/.claude/skills/PAIUpgrade sources.json + bun LoadSkillConfig.ts ~/.opencode/skills/PAIUpgrade sources.json bun LoadSkillConfig.ts --list bun LoadSkillConfig.ts --check PAIUpgrade `); diff --git a/.opencode/PAI/Tools/SecretScan.ts b/.opencode/PAI/Tools/SecretScan.ts index 4ab81b35..3eefba9b 100755 --- a/.opencode/PAI/Tools/SecretScan.ts +++ b/.opencode/PAI/Tools/SecretScan.ts @@ -7,11 +7,11 @@ * Part of PAI CORE Tools. * * Usage: - * bun ~/.claude/PAI/Tools/SecretScan.ts - * bun ~/.claude/PAI/Tools/SecretScan.ts . --verbose - * bun ~/.claude/PAI/Tools/SecretScan.ts . --verify + * bun ~/.opencode/PAI/Tools/SecretScan.ts + * bun ~/.opencode/PAI/Tools/SecretScan.ts . --verbose + * bun ~/.opencode/PAI/Tools/SecretScan.ts . --verify * - * @see ~/.claude/skills/_SYSTEM/Workflows/SecretScanning.md + * @see ~/.opencode/skills/_SYSTEM/Workflows/SecretScanning.md */ /* diff --git a/.opencode/skills/Agents/Tools/LoadAgentContext.ts b/.opencode/skills/Agents/Tools/LoadAgentContext.ts index fca614d2..cd89bdea 100755 --- a/.opencode/skills/Agents/Tools/LoadAgentContext.ts +++ b/.opencode/skills/Agents/Tools/LoadAgentContext.ts @@ -21,12 +21,12 @@ interface AgentContext { } export class AgentContextLoader { - private claudeHome: string; + private opencodeHome: string; private agentsDir: string; constructor() { - this.claudeHome = join(homedir(), ".opencode"); - this.agentsDir = join(this.claudeHome, "Skills", "Agents"); + this.opencodeHome = join(homedir(), ".opencode"); + this.agentsDir = join(this.opencodeHome, "skills", "Agents"); } /** diff --git a/.opencode/skills/PAI/SYSTEM/THEHOOKSYSTEM.md b/.opencode/skills/PAI/SYSTEM/THEHOOKSYSTEM.md deleted file mode 100755 index 18ad91a9..00000000 --- a/.opencode/skills/PAI/SYSTEM/THEHOOKSYSTEM.md +++ /dev/null @@ -1,1323 +0,0 @@ -# Hook System - -**Event-Driven Automation Infrastructure** - -**Location:** `~/.opencode/hooks/` -**Configuration:** `~/.opencode/settings.json` -**Status:** Active - All hooks running in production - ---- - -## Overview - -The PAI hook system is an event-driven automation infrastructure built on OpenCode's native hook support. Hooks are executable scripts (TypeScript/Python) that run automatically in response to specific events during OpenCode sessions. - -**Core Capabilities:** -- **Session Management** - Auto-load context, capture summaries, manage state -- **Voice Notifications** - Text-to-speech announcements for task completions -- **History Capture** - Automatic work/learning documentation to `~/.opencode/MEMORY/` -- **Multi-Agent Support** - Agent-specific hooks with voice routing -- **Observability** - Real-time event streaming to dashboard -- **Tab Titles** - Dynamic terminal tab updates with task context - -**Key Principle:** Hooks run asynchronously and fail gracefully. They enhance the user experience but never block OpenCode's core functionality. - ---- - -## Available Hook Types - -OpenCode supports the following hook events (from `~/.opencode/hooks/lib/observability.ts`): - -### 1. **SessionStart** -**When:** OpenCode session begins (new conversation) -**Use Cases:** -- Load PAI context from `skills/PAI/SKILL.md` -- Initialize session state -- Capture session metadata - -**Current Hooks:** -```typescript -{ - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/LoadContext.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/CheckVersion.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** -- `LoadContext.hook.ts` - Reads `skills/PAI/SKILL.md` and injects PAI context as `` at session start -- `CheckVersion.hook.ts` - Checks OpenCode version and notifies of updates - ---- - -### 2. **SessionEnd** -**When:** OpenCode session terminates (conversation ends) -**Use Cases:** -- Generate session summaries -- Save session metadata -- Cleanup temporary state - -**Current Hooks:** -```typescript -{ - "SessionEnd": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/SessionSummary.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** -- `SessionSummary.hook.ts` - Marks current WORK directory as COMPLETED and clears session state -- Captures: work completion status, session duration - ---- - -### 3. **UserPromptSubmit** -**When:** User submits a new prompt to Claude -**Use Cases:** -- Update UI indicators -- Pre-process user input -- Capture prompts for analysis -- Detect ratings and sentiment - -**Current Hooks:** -```typescript -{ - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/ExplicitRatingCapture.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/ImplicitSentimentCapture.hook.ts" - }, - { - "type": "command", - "command": "${PAI_DIR}/hooks/UpdateTabTitle.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** - -**ExplicitRatingCapture.hook.ts** - Explicit Rating Detection -- Detects when user types explicit ratings like "7" or "8 - good work" -- Pattern: Single number (1-10) optionally followed by comment -- Writes to `~/.opencode/MEMORY/SIGNALS/ratings.jsonl` -- Low ratings (<6) auto-capture as learning opportunities -- Uses shared library: `hooks/lib/learning-utils.ts` - -**ImplicitSentimentCapture.hook.ts** - Implicit Sentiment Detection -- Analyzes user messages for emotional sentiment -- Detects frustration ("What the fuck, you broke it!") → rating 1-2 -- Detects excitement ("Oh my god, this is amazing!") → rating 9-10 -- Neutral messages return null (not logged) -- Writes to same `ratings.jsonl` with `source: "implicit"` -- Low ratings (<6) trigger learning capture with detailed context -- Includes confidence score (0.0-1.0) -- Uses shared libraries: `hooks/lib/learning-utils.ts`, `hooks/lib/time.ts` -- **Inference:** `import { inference } from '../skills/PAI/Tools/Inference'` → `inference({ level: 'standard', expectJson: true })` - -**UpdateTabTitle.hook.ts** - Tab Title + Working State -- Updates Kitty terminal tab title with task summary + `…` suffix -- Sets tab to **orange background** (working state) -- Announces via voice server with context-appropriate gerund -- See `TERMINALTABS.md` for full state system documentation -- **Inference:** `import { inference } from '../skills/PAI/Tools/Inference'` → `inference({ level: 'fast' })` - ---- - -### 4. **Stop** -**When:** Main agent ({DAIDENTITY.NAME}) completes a response -**Use Cases:** -- Voice notifications for task completion -- Capture work summaries and learnings -- **Update terminal tab with final state** (color + suffix based on outcome) - -**Current Hooks:** -```typescript -{ - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/StopOrchestrator.hook.ts" - } - ] - } - ] -} -``` - -**What It Does:** - -**StopOrchestrator.hook.ts** - Unified Stop Event Handler -- Single orchestrator that delegates to specialized handlers in `${PAI_DIR}/hooks/handlers/`: - - `voice.ts` - Voice TTS delivery (extracts `🗣️ {DAIDENTITY.NAME}:` line, POSTs to voice server) - - `capture.ts` - Work/learning capture (updates WORK items, writes learnings, sends observability) - - `tab-state.ts` - Tab color/title state (sets completed/awaiting/error visual state) - -**Architecture Benefits:** -- Single transcript read (parsed once, shared across handlers) -- Isolated failures (one handler failing doesn't break others) -- Clean orchestration (handlers are pure functions) - -**Handler Details:** - -`handlers/voice.ts` - Voice TTS Delivery -- Extracts `🗣️ {DAIDENTITY.NAME}:` line from response -- POSTs to `http://localhost:8888/notify` with configured voice ID -- Voice server handles sanitization and TTS conversion - -`handlers/capture.ts` - Work/Learning Capture -- Extracts structured sections (SUMMARY, ANALYSIS, etc.) -- Updates current WORK items with response summaries -- Writes learnings to `${PAI_DIR}/MEMORY/LEARNING//YYYY-MM/` when applicable -- Sends event to observability dashboard -- Sends push notification for long tasks (>5 min) - -`handlers/tab-state.ts` - Tab Color/Title State -- Sets final tab state (color + suffix): - - Completed: Green `#022800`, no suffix - - Awaiting Input: Teal `#0D6969`, `?` suffix (AskUserQuestion detected) - - Error: Orange `#B35A00`, `!` suffix (error patterns detected) - -**Learning Detection:** Automatically identifies learning moments (2+ indicators: problem/issue/bug, fixed/solved, troubleshoot/debug, lesson/takeaway) - -**Tab State System:** See `TERMINALTABS.md` for complete documentation - ---- - -### 5. **SubagentStop** -**When:** Subagent (Task tool) completes execution -**Use Cases:** -- Capture agent outputs to UOCS history -- Track multi-agent workflows -- Send events to observability dashboard - -**Current Hooks:** -```typescript -{ - "SubagentStop": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/AgentOutputCapture.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** -- `AgentOutputCapture.hook.ts` - Agent output capture and observability - - Waits for Task tool result in parent transcript (up to 6 attempts, 7.5s max) - - Extracts `[AGENT:type]` tag and completion message - - Captures agent output to appropriate history category (`research/`, `execution/`, etc.) - - Sends event to observability dashboard - -**Tab Title Updates (Simplified Approach):** -- Tab title updates are now handled by the **parent session** directly -- After receiving Task result, parent calls: `bun ~/.opencode/skills/PAI/Tools/UpdateTabTitle.ts "Message"` -- This avoids transcript parsing race conditions -- See `~/.opencode/skills/PAI/Tools/UpdateTabTitle.ts` for implementation - -**Agent-Specific Routing:** -- `[AGENT:engineer]` → Captured to `execution/features/` -- `[AGENT:researcher]` → Captured to `research/` -- `[AGENT:pentester]` → Captured to `research/` -- `[AGENT:intern]` → Captured to `research/` -- etc. - ---- - -### 6. **PreToolUse** -**When:** Before Claude executes any tool -**Use Cases:** -- Security validation (e.g., block dangerous commands) -- Tool-specific pre-processing - -**Current Hooks:** -```typescript -{ - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/SecurityValidator.hook.ts" - } - ] - }, - { - "matcher": "AskUserQuestion", - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/SetQuestionTab.hook.ts" - } - ] - } - ] -} -``` - -**What They Do:** -- `SecurityValidator.hook.ts` - Validates Bash commands against security patterns -- `SetQuestionTab.hook.ts` - Updates tab state when question is asked - ---- - -### 7. **PostToolUse** -**When:** After Claude executes any tool -**Status:** Not currently configured - -**Potential Use Cases:** -- Capture tool outputs for analytics -- Error tracking -- Performance metrics - ---- - -### 8. **PreCompact** -**When:** Before Claude compacts context (long conversations) -**Status:** Not currently configured - -**Potential Use Cases:** -- Preserve important context before compaction -- Log compaction events - ---- - -## Configuration - -### Location -**File:** `~/.opencode/settings.json` -**Section:** `"hooks": { ... }` - -### Environment Variables -Hooks have access to all environment variables from `~/.opencode/settings.json` `"env"` section: - -```json -{ - "env": { - "PAI_DIR": "$HOME/.claude", - "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "64000" - } -} -``` - -**Key Variables:** -- `PAI_DIR` - PAI installation directory (typically `~/.claude`) -- Hook scripts reference `${PAI_DIR}` in command paths - -### Identity Configuration (Central to Install Wizard) - -**settings.json is the single source of truth for all daidentity/configuration.** - -```json -{ - "daidentity": { - "name": "PAI", - "fullName": "Personal AI", - "displayName": "PAI", - "color": "#3B82F6", - "voiceId": "s3TPKV1kjDlVtZbl4Ksh" - }, - "principal": { - "name": "{YourName}", - "pronunciation": "{YourName}", - "timezone": "America/Los_Angeles" - } -} -``` - -**Using the Identity Module:** -```typescript -import { getIdentity, getPrincipal, getDAName, getPrincipalName, getVoiceId } from './lib/identity'; - -// Get full identity objects -const identity = getIdentity(); // { name, fullName, displayName, voiceId, color } -const principal = getPrincipal(); // { name, pronunciation, timezone } - -// Convenience functions -const DA_NAME = getDAName(); // "PAI" -const USER_NAME = getPrincipalName(); // "{YourName}" -const VOICE_ID = getVoiceId(); // "s3TPKV1kjDlVtZbl4Ksh" -``` - -**Why settings.json?** -- Programmatic access via `JSON.parse()` - no regex parsing markdown -- Central to the PAI install wizard -- Single source of truth for all configuration -- Tool-friendly: easy to read/write from any language - -### Hook Configuration Structure - -```json -{ - "hooks": { - "HookEventName": [ - { - "matcher": "pattern", // Optional: filter which tools/events trigger hook - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/my-hook.ts --arg value" - } - ] - } - ] - } -} -``` - -**Fields:** -- `HookEventName` - One of: SessionStart, SessionEnd, UserPromptSubmit, Stop, SubagentStop, PreToolUse, PostToolUse, PreCompact -- `matcher` - Pattern to match (use `"*"` for all tools, or specific tool names) -- `type` - Always `"command"` (executes external script) -- `command` - Path to executable hook script (TypeScript/Python/Bash) - -### Hook Input (stdin) -All hooks receive JSON data on stdin: - -```typescript -{ - session_id: string; // Unique session identifier - transcript_path: string; // Path to JSONL transcript - hook_event_name: string; // Event that triggered hook - prompt?: string; // User prompt (UserPromptSubmit only) - tool_name?: string; // Tool name (PreToolUse/PostToolUse) - tool_input?: any; // Tool parameters (PreToolUse) - tool_output?: any; // Tool result (PostToolUse) - // ... event-specific fields -} -``` - ---- - -## Common Patterns - -### 1. Voice Notifications - -**Pattern:** Extract completion message → Send to voice server - -```typescript -// handlers/voice.ts pattern -import { getIdentity } from './lib/identity'; - -const identity = getIdentity(); -const completionMessage = extractCompletionMessage(lastMessage); - -const payload = { - title: identity.name, - message: completionMessage, - voice_enabled: true, - voice_id: identity.voiceId // From settings.json -}; - -await fetch('http://localhost:8888/notify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) -}); -``` - -**Agent-Specific Voices:** -Configure voice IDs in `skills/PAI/SYSTEM/AGENTPERSONALITIES.md` or via environment variables. -Each agent can have a unique ElevenLabs voice configured. - ---- - -### 2. History Capture (UOCS Pattern) - -**Pattern:** Parse structured response → Save to appropriate history directory - -**File Naming Convention:** -``` -YYYY-MM-DD-HHMMSS_TYPE_description.md -``` - -**Types:** -- `WORK` - General task completions -- `LEARNING` - Problem-solving learnings -- `SESSION` - Session summaries -- `RESEARCH` - Research findings (from agents) -- `FEATURE` - Feature implementations (from agents) -- `DECISION` - Architectural decisions (from agents) - -**Example from handlers/capture.ts:** -```typescript -import { getLearningCategory, isLearningCapture } from './lib/learning-utils'; -import { getPSTTimestamp, getYearMonth } from './lib/time'; - -const structured = extractStructuredSections(lastMessage); -const isLearning = isLearningCapture(text, structured.summary, structured.analysis); - -// Update current work item -const currentWork = readCurrentWork(); // from STATE/current-work.json -if (currentWork) { - updateWorkItem(currentWork.work_dir, structured); -} - -// If learning content detected, also capture to LEARNING/ -if (isLearning) { - const category = getLearningCategory(text); // 'SYSTEM' or 'ALGORITHM' - const targetDir = join(baseDir, 'MEMORY', 'LEARNING', category, getYearMonth()); - const filename = generateFilename(description, 'LEARNING'); - writeFileSync(join(targetDir, filename), content); -} -``` - -**Structured Sections Parsed:** -- `📋 SUMMARY:` - Brief overview -- `🔍 ANALYSIS:` - Key findings -- `⚡ ACTIONS:` - Steps taken -- `✅ RESULTS:` - Outcomes -- `📊 STATUS:` - Current state -- `➡️ NEXT:` - Follow-up actions -- `🎯 COMPLETED:` - **Voice notification line** - ---- - -### 3. Agent Type Detection - -**Pattern:** Identify which agent is executing → Route appropriately - -```typescript -// Agent detection pattern -let agentName = getAgentForSession(sessionId); - -// Detect from Task tool -if (hookData.tool_name === 'Task' && hookData.tool_input?.subagent_type) { - agentName = hookData.tool_input.subagent_type; - setAgentForSession(sessionId, agentName); -} - -// Detect from CLAUDE_CODE_AGENT env variable -else if (process.env.CLAUDE_CODE_AGENT) { - agentName = process.env.CLAUDE_CODE_AGENT; -} - -// Detect from path (subagents run in /agents/name/) -else if (hookData.cwd && hookData.cwd.includes('/agents/')) { - const agentMatch = hookData.cwd.match(/\/agents\/([^\/]+)/); - if (agentMatch) agentName = agentMatch[1]; -} -``` - -**Session Mapping:** `~/.opencode/MEMORY/STATE/agent-sessions.json` -```json -{ - "session-id-abc123": "engineer", - "session-id-def456": "researcher" -} -``` - ---- - -### 4. Observability Integration - -**Pattern:** Send event to dashboard → Fail silently if offline - -```typescript -import { sendEventToObservability, getCurrentTimestamp, getSourceApp } from './lib/observability'; - -await sendEventToObservability({ - source_app: getSourceApp(), // 'PAI' or agent name - session_id: hookInput.session_id, - hook_event_type: 'Stop', - timestamp: getCurrentTimestamp(), - transcript_path: hookInput.transcript_path, - summary: completionMessage, - // ... additional fields -}).catch(() => { - // Silently fail - dashboard may not be running -}); -``` - -**Dashboard URLs:** -- Server: `http://localhost:4000` -- Client: `http://localhost:5173` - ---- - -### 5. Tab Title + Color State Architecture - -**Pattern:** Visual state feedback through tab colors and title suffixes - -**State Flow:** - -| Event | Hook | Tab Title | Inactive Color | State | -|-------|------|-----------|----------------|-------| -| UserPromptSubmit | `UpdateTabTitle.hook.ts` | `⚙️ Summary…` | Orange `#B35A00` | Working | -| Inference | `UpdateTabTitle.hook.ts` | `🧠 Analyzing…` | Orange `#B35A00` | Inference | -| Stop (success) | `handlers/tab-state.ts` | `Summary` | Green `#022800` | Completed | -| Stop (question) | `handlers/tab-state.ts` | `Summary?` | Teal `#0D6969` | Awaiting Input | -| Stop (error) | `handlers/tab-state.ts` | `Summary!` | Orange `#B35A00` | Error | - -**Active Tab:** Always Dark Blue `#002B80` (state colors only affect inactive tabs) - -**Why This Design:** -- **Instant visual feedback** - See state at a glance without reading -- **Color-coded priority** - Teal tabs need attention, green tabs are done -- **Suffix as state indicator** - Works even in narrow tab bars -- **Haiku only on user input** - One AI call per prompt (not per tool) - -**State Detection (in Stop hook):** -1. Check transcript for `AskUserQuestion` tool → `awaitingInput` -2. Check `📊 STATUS:` for error patterns → `error` -3. Default → `completed` - -**Text Colors:** -- Active tab: White `#FFFFFF` (always) -- Inactive tab: Gray `#A0A0A0` (always) - -**Active Tab Background:** Dark Blue `#002B80` (always - state colors only affect inactive tabs) - -**Tab Icons:** -- 🧠 Brain - AI inference in progress (Haiku/Sonnet thinking) -- ⚙️ Gear - Processing/working state - -**Full Documentation:** See `~/.opencode/skills/PAI/SYSTEM/TERMINALTABS.md` - ---- - -### 6. Async Non-Blocking Execution - -**Pattern:** Hook executes quickly → Launch background processes for slow operations - -```typescript -// update-tab-titles.ts pattern -// Set immediate tab title (fast) -execSync(`printf '\\033]0;${titleWithEmoji}\\007' >&2`); - -// Launch background process for Haiku summary (slow) -Bun.spawn(['bun', `${paiDir}/hooks/UpdateTabTitle.ts`, prompt], { - stdout: 'ignore', - stderr: 'ignore', - stdin: 'ignore' -}); - -process.exit(0); // Exit immediately -``` - -**Key Principle:** Hooks must never block Claude Code. Always exit quickly, use background processes for slow work. - ---- - -### 6. Graceful Failure - -**Pattern:** Wrap everything in try/catch → Log errors → Exit successfully - -```typescript -async function main() { - try { - // Hook logic here - } catch (error) { - // Log but don't fail - console.error('Hook error:', error); - } - - process.exit(0); // Always exit 0 -} -``` - -**Why:** If hooks crash, Claude Code may freeze. Always exit cleanly. - ---- - -## Creating Custom Hooks - -### Step 1: Choose Hook Event -Decide which event should trigger your hook (SessionStart, Stop, PostToolUse, etc.) - -### Step 2: Create Hook Script -**Location:** `~/.opencode/hooks/my-custom-hook.ts` - -**Template:** -```typescript -#!/usr/bin/env bun - -interface HookInput { - session_id: string; - transcript_path: string; - hook_event_name: string; - // ... event-specific fields -} - -async function main() { - try { - // Read stdin - const input = await Bun.stdin.text(); - const data: HookInput = JSON.parse(input); - - // Your hook logic here - console.log(`Hook triggered: ${data.hook_event_name}`); - - // Example: Read transcript - const fs = require('fs'); - const transcript = fs.readFileSync(data.transcript_path, 'utf-8'); - - // Do something with the data - - } catch (error) { - // Log but don't fail - console.error('Hook error:', error); - } - - process.exit(0); // Always exit 0 -} - -main(); -``` - -### Step 3: Make Executable -```bash -chmod +x ~/.opencode/hooks/my-custom-hook.ts -``` - -### Step 4: Add to settings.json -```json -{ - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${PAI_DIR}/hooks/my-custom-hook.ts" - } - ] - } - ] - } -} -``` - -### Step 5: Test -```bash -# Test hook directly -echo '{"session_id":"test","transcript_path":"/tmp/test.jsonl","hook_event_name":"Stop"}' | bun ~/.opencode/hooks/my-custom-hook.ts -``` - -### Step 6: Restart OpenCode -Hooks are loaded at startup. Restart to apply changes. - ---- - -## Hook Development Best Practices - -### 1. **Fast Execution** -- Hooks should complete in < 500ms -- Use background processes for slow work (Haiku API calls, file processing) -- Exit immediately after launching background work - -### 2. **Graceful Failure** -- Always wrap in try/catch -- Log errors to stderr (available in hook debug logs) -- Always `process.exit(0)` - never throw or exit(1) - -### 3. **Non-Blocking** -- Never wait for external services (unless they respond quickly) -- Use `.catch(() => {})` for async operations -- Fail silently if optional services are offline - -### 4. **Stdin Reading** -- Use timeout when reading stdin (OpenCode may not send data immediately) -- Handle empty/invalid input gracefully - -```typescript -const decoder = new TextDecoder(); -const reader = Bun.stdin.stream().getReader(); - -const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 500); // 500ms timeout -}); - -await Promise.race([readPromise, timeoutPromise]); -``` - -### 5. **File I/O** -- Check `existsSync()` before reading files -- Create directories with `{ recursive: true }` -- Use PST timestamps for consistency - -### 6. **Environment Access** -- All `settings.json` env vars available via `process.env` -- Use `${PAI_DIR}` in settings.json for portability -- Access in code via `process.env.PAI_DIR` - -### 7. **Observability** -- Send events to dashboard for visibility -- Include all relevant metadata (session_id, tool_name, etc.) -- Use `.catch(() => {})` - dashboard may be offline - ---- - -## Troubleshooting - -### Hook Not Running - -**Check:** -1. Is hook script executable? `chmod +x ~/.opencode/hooks/my-hook.ts` -2. Is path correct in settings.json? Use `${PAI_DIR}/hooks/...` -3. Is settings.json valid JSON? `jq . ~/.opencode/settings.json` -4. Did you restart OpenCode after editing settings.json? - -**Debug:** -```bash -# Test hook directly -echo '{"session_id":"test","transcript_path":"/tmp/test.jsonl","hook_event_name":"Stop"}' | bun ~/.opencode/hooks/my-hook.ts - -# Check hook logs (stderr output) -tail -f ~/.opencode/hooks/debug.log # If you add logging -``` - ---- - -### Hook Hangs/Freezes OpenCode - -**Cause:** Hook not exiting (infinite loop, waiting for input, blocking operation) - -**Fix:** -1. Add timeouts to all blocking operations -2. Ensure `process.exit(0)` is always reached -3. Use background processes for long operations -4. Check stdin reading has timeout - -**Prevention:** -```typescript -// Always use timeout -setTimeout(() => { - console.error('Hook timeout - exiting'); - process.exit(0); -}, 5000); // 5 second max -``` - ---- - -### Voice Notifications Not Working - -**Check:** -1. Is voice server running? `curl http://localhost:8888/health` -2. Is voice_id correct? See `skills/PAI/SKILL.md` for mappings -3. Is message format correct? `{"message":"...", "voice_id":"...", "title":"..."}` -4. Is ElevenLabs API key in `${PAI_DIR}/.env`? - -**Debug:** -```bash -# Test voice server directly -curl -X POST http://localhost:8888/notify \ - -H "Content-Type: application/json" \ - -d '{"message":"Test message","voice_id":"[YOUR_VOICE_ID]","title":"Test"}' -``` - -**Common Issues:** -- Wrong voice_id → Silent failure (invalid ID) -- Voice server offline → Hook continues (graceful failure) -- No `🎯 COMPLETED:` line → No voice notification extracted - ---- - -### Work Not Capturing - -**Check:** -1. Does `~/.opencode/MEMORY/` directory exist? -2. Is AutoWorkCreation hook running? Check `~/.opencode/MEMORY/STATE/current-work.json` -3. Is hook actually running? Check `~/.opencode/MEMORY/RAW/` for events -4. File permissions? `ls -la ~/.opencode/MEMORY/WORK/` - -**Debug:** -```bash -# Check current work -cat ~/.opencode/MEMORY/STATE/current-work.json - -# Check recent work directories -ls -lt ~/.opencode/MEMORY/WORK/ | head -10 -ls -lt ~/.opencode/MEMORY/LEARNING/$(date +%Y-%m)/ | head -10 - -# Check raw events -tail ~/.opencode/MEMORY/RAW/$(date +%Y-%m)/$(date +%Y-%m-%d)_all-events.jsonl -``` - -**Common Issues:** -- Missing current-work.json → AutoWorkCreation hook not running -- Work not updating → capture handler not finding current work -- Learning detection too strict → Adjust `isLearningCapture()` logic - ---- - -### Stop Event Not Firing (CRITICAL KNOWN ISSUE) - -**Symptom:** Stop hook configured and working, but Stop events not firing consistently - -**Evidence:** -```bash -# Check if Stop events fired today -grep '"event_type":"Stop"' ~/.opencode/MEMORY/RAW/$(date +%Y-%m)/$(date +%Y-%m-%d)_all-events.jsonl -# Result: 0 matches (no Stop events) - -# But other hooks ARE working -grep '"event_type":"PostToolUse"' ~/.opencode/MEMORY/RAW/$(date +%Y-%m)/$(date +%Y-%m-%d)_all-events.jsonl -# Result: 80+ matches (PostToolUse working fine) -``` - -**Impact:** -- Automatic work summaries NOT captured to history (despite Stop hook logic being correct) -- Learning moments NOT auto-detected -- Voice notifications from main agent responses NOT sent -- Manual verification and capture REQUIRED - -**Root Cause:** -- OpenCode event trigger issue (external to hook system) -- Stop event not being emitted when main agent completes responses -- Hook configuration is correct, hook script works, event just never fires -- Other event types (PostToolUse, SessionEnd, UserPromptSubmit) work fine - -**Workaround (MANDATORY):** - -1. **Added CAPTURE field to response format** (see `~/.opencode/skills/PAI/SKILL.md`) - - MANDATORY field in every response - - Forces verification before completing responses - - Must document: "Auto-captured" / "Manually saved" / "N/A" - -2. **Added MANDATORY VERIFICATION GATE** to file organization section - - Before completing valuable work, MUST run verification commands - - Check if auto-capture happened (ls -lt history directories) - - If not, manually save to appropriate history location - -3. **Verification Commands:** - ```bash - # Check if work is being tracked - cat ~/.opencode/MEMORY/STATE/current-work.json - ls -lt ~/.opencode/MEMORY/WORK/ | head -5 - ls -lt ~/.opencode/MEMORY/LEARNING/$(date +%Y-%m)/ | head -5 - - # If no current work or work items empty → Check AutoWorkCreation hook - ``` - -**Status:** UNRESOLVED (OpenCode issue, not hook configuration) -**Mitigation:** Structural enforcement via response format (cannot complete valuable work without verification) -**Tracking:** Documented in `~/.opencode/skills/PAI/SKILL.md` (History Capture System section) - -**Long-term Fix:** -- Report to Anthropic (OpenCode team) as Stop event reliability issue -- Monitor future OpenCode updates for fix -- Keep workaround in place until Stop events fire reliably - ---- - -### Agent Detection Failing - -**Check:** -1. Is `~/.opencode/MEMORY/STATE/agent-sessions.json` writable? -2. Is `[AGENT:type]` tag in `🎯 COMPLETED:` line? -3. Is agent running from correct directory? (`/agents/name/`) - -**Debug:** -```bash -# Check session mappings -cat ~/.opencode/MEMORY/STATE/agent-sessions.json | jq . - -# Check subagent-stop debug log -tail -f ~/.opencode/hooks/subagent-stop-debug.log -``` - -**Fix:** -- Ensure agents include `[AGENT:type]` in completion line -- Verify Task tool passes `subagent_type` parameter -- Check cwd includes `/agents/` in path - ---- - -### Observability Dashboard Not Receiving Events - -**Check:** -1. Is dashboard server running? `curl http://localhost:4000/health` -2. Are hooks sending events? Check `sendEventToObservability()` calls -3. Network issues? `netstat -an | grep 4000` - -**Debug:** -```bash -# Start dashboard server -cd ~/.opencode/skills/system/observability/dashboard/apps/server -bun run dev - -# Check server logs -# Events should appear in real-time -``` - -**Note:** Hooks fail silently if dashboard offline (by design). Not critical for operation. - ---- - -### Transcript Type Mismatch (Fixed 2026-01-11) - -**Symptom:** Context reading functions return empty results even though transcript has data - -**Root Cause:** OpenCode transcripts use `type: "user"` but hooks were checking for `type: "human"`. - -**Affected Hooks:** -- `UpdateTabTitle.hook.ts` - Couldn't read user messages for context -- `ImplicitSentimentCapture.hook.ts` - Same issue - -**Fix Applied:** -1. Changed `entry.type === 'human'` → `entry.type === 'user'` -2. Improved content extraction to skip `tool_result` blocks and only capture actual text - -**Verification:** -```bash -# Check transcript type field -grep '"type":"user"' ~/.opencode/projects/-Users-daniel--opencode/*.jsonl | head -1 | jq '.type' -# Should output: "user" (not "human") -``` - -**Prevention:** When parsing transcripts, always verify the actual JSON structure first. - ---- - -### Context Loading Issues (SessionStart) - -**Check:** -1. Does `~/.opencode/skills/PAI/SKILL.md` exist? -2. Is `LoadContext.hook.ts` executable? -3. Is `PAI_DIR` env variable set correctly? - -**Debug:** -```bash -# Test context loading directly -bun ~/.opencode/hooks/LoadContext.hook.ts - -# Should output with SKILL.md content -``` - -**Common Issues:** -- Subagent sessions loading main context → Fixed (subagent detection in hook) -- File not found → Check `PAI_DIR` environment variable -- Permission denied → `chmod +x ~/.opencode/hooks/LoadContext.hook.ts` - ---- - -## Advanced Topics - -### Multi-Hook Execution Order - -Hooks in same event execute **sequentially** in order defined in settings.json: - -```json -{ - "Stop": [ - { - "hooks": [ - { "command": "${PAI_DIR}/hooks/StopOrchestrator.hook.ts" } // Single orchestrator - ] - } - ] -} -``` - -**Note:** If first hook hangs, second won't run. Keep hooks fast! - ---- - -### Matcher Patterns - -`"matcher"` field filters which events trigger hook: - -```json -{ - "PostToolUse": [ - { - "matcher": "Bash", // Only Bash tool executions - "hooks": [...] - }, - { - "matcher": "*", // All tool executions - "hooks": [...] - } - ] -} -``` - -**Patterns:** -- `"*"` - All events -- `"Bash"` - Specific tool name -- `""` - Empty (all events, same as `*`) - ---- - -### Hook Data Payloads by Event Type - -**SessionStart:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "SessionStart"; - cwd: string; -} -``` - -**UserPromptSubmit:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "UserPromptSubmit"; - prompt: string; // The user's prompt text -} -``` - -**PreToolUse:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "PreToolUse"; - tool_name: string; - tool_input: any; // Tool parameters -} -``` - -**PostToolUse:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "PostToolUse"; - tool_name: string; - tool_input: any; - tool_output: any; // Tool result - error?: string; // If tool failed -} -``` - -**Stop:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "Stop"; -} -``` - -**SubagentStop:** -```typescript -{ - session_id: string; - transcript_path: string; - hook_event_name: "SubagentStop"; -} -``` - -**SessionEnd:** -```typescript -{ - conversation_id: string; // Note: different field name - timestamp: string; -} -``` - ---- - -## Related Documentation - -- **Voice System:** `~/.opencode/VoiceServer/SKILL.md` -- **Agent System:** `~/.opencode/skills/PAI/SYSTEM/AGENTPERSONALITIES.md` -- **History/Memory:** `~/.opencode/skills/PAI/SYSTEM/MEMORYSYSTEM.md` -- **Observability Dashboard:** `~/.opencode/Observability/` - ---- - -## Quick Reference Card - -``` -HOOK LIFECYCLE: -1. Event occurs (SessionStart, Stop, etc.) -2. OpenCode writes hook data to stdin -3. Hook script executes -4. Hook reads stdin (with timeout) -5. Hook performs actions (voice, capture, etc.) -6. Hook exits 0 (always succeeds) -7. OpenCode continues - -KEY FILES: -~/.opencode/settings.json Hook configuration (daidentity, principal, env vars) -~/.opencode/hooks/ Hook scripts -~/.opencode/hooks/lib/identity.ts Identity loader (reads from settings.json) -~/.opencode/hooks/lib/observability.ts Observability helper library -~/.opencode/hooks/lib/learning-utils.ts Learning categorization (SYSTEM/ALGORITHM) -~/.opencode/hooks/lib/time.ts PST timestamp utilities -~/.opencode/MEMORY/RAW/ Event logs (JSONL) - source of truth -~/.opencode/MEMORY/WORK/ Primary work tracking (work directories) -~/.opencode/MEMORY/LEARNING/ Learning captures (SYSTEM/, ALGORITHM/, SIGNALS/) -~/.opencode/MEMORY/STATE/ Runtime state (current-work.json, progress/, etc.) -~/.opencode/MEMORY/STATE/agent-sessions.json Session→Agent mapping - -STOP HOOKS (main agent completion): -StopOrchestrator.hook.ts Unified Stop handler with internal handlers: - → handlers/voice.ts Voice TTS delivery - → handlers/capture.ts Session/learning capture + observability - → handlers/tab-state.ts Tab color/title state - -USER PROMPT HOOKS: -ExplicitRatingCapture.hook.ts Explicit ratings ("7", "8 - good") -ImplicitSentimentCapture.hook.ts Sentiment analysis (level: standard) -UpdateTabTitle.hook.ts Tab title + working state (level: fast) - -INFERENCE TOOL (for hooks needing AI): -Path: ~/.opencode/skills/PAI/Tools/Inference.ts -Import: import { inference } from '../skills/PAI/Tools/Inference' -Levels: fast (haiku/15s) | standard (sonnet/30s) | smart (opus/90s) -Usage: inference({ systemPrompt, userPrompt, level: 'fast', expectJson: true }) - -OTHER CRITICAL HOOKS: -AgentOutputCapture.hook.ts Agent output capture (subagents) -LoadContext.hook.ts PAI context loading -SecurityValidator.hook.ts Security validation for Bash commands - -TAB STATE SYSTEM: -Inference: 🧠… Orange #B35A00 (AI thinking) -Working: ⚙️… Orange #B35A00 (processing) -Completed: Green #022800 (task done) -Awaiting: ? Teal #0D6969 (needs input) -Error: ! Orange #B35A00 (problem detected) -Active Tab: Always Dark Blue #002B80 (state colors = inactive only) - -VOICE SERVER: -URL: http://localhost:8888/notify -Payload: {"message":"...", "voice_id":"...", "title":"..."} -Configure voice IDs in AgentPersonalities.md - -OBSERVABILITY: -Server: http://localhost:4000 -Client: http://localhost:5173 -Events: All hooks send to /events endpoint -``` - ---- - -## Shared Libraries - -The hook system uses shared TypeScript libraries to eliminate code duplication: - -### `hooks/lib/learning-utils.ts` -Shared learning categorization logic. - -```typescript -import { getLearningCategory, isLearningCapture } from './lib/learning-utils'; - -// Categorize learning as SYSTEM (tooling/infra) or ALGORITHM (task execution) -const category = getLearningCategory(content, comment); -// Returns: 'SYSTEM' | 'ALGORITHM' - -// Check if response contains learning indicators -const isLearning = isLearningCapture(text, summary, analysis); -// Returns: boolean (true if 2+ learning indicators found) -``` - -**Used by:** ExplicitRatingCapture, ImplicitSentimentCapture, handlers/capture.ts - -### `hooks/lib/time.ts` -Shared PST timestamp utilities. - -```typescript -import { - getPSTTimestamp, // "2026-01-10 20:30:00 PST" - getPSTDate, // "2026-01-10" - getYearMonth, // "2026-01" - getISOTimestamp, // ISO8601 with offset - getFilenameTimestamp, // "2026-01-10-203000" - getPSTComponents // { year, month, day, hours, minutes, seconds } -} from './lib/time'; -``` - -**Used by:** ExplicitRatingCapture, ImplicitSentimentCapture, handlers/capture.ts, SessionSummary - -### `hooks/lib/identity.ts` -Identity and principal configuration from settings.json. - -```typescript -import { getIdentity, getPrincipal, getDAName, getPrincipalName, getVoiceId } from './lib/identity'; - -const identity = getIdentity(); // { name, fullName, displayName, voiceId, color } -const principal = getPrincipal(); // { name, pronunciation, timezone } -``` - -**Used by:** handlers/voice.ts, ImplicitSentimentCapture, handlers/capture.ts, handlers/tab-state.ts - -### `skills/PAI/Tools/Inference.ts` -Unified AI inference with three run levels. - -```typescript -import { inference } from '../skills/PAI/Tools/Inference'; - -// Fast (Haiku) - quick tasks, 15s timeout -const result = await inference({ - systemPrompt: 'Summarize in 3 words', - userPrompt: text, - level: 'fast', -}); - -// Standard (Sonnet) - balanced reasoning, 30s timeout -const result = await inference({ - systemPrompt: 'Analyze sentiment', - userPrompt: text, - level: 'standard', - expectJson: true, -}); - -// Smart (Opus) - deep reasoning, 90s timeout -const result = await inference({ - systemPrompt: 'Strategic analysis', - userPrompt: text, - level: 'smart', -}); - -// Result shape -interface InferenceResult { - success: boolean; - output: string; - parsed?: unknown; // if expectJson: true - error?: string; - latencyMs: number; - level: 'fast' | 'standard' | 'smart'; -} -``` - -**Used by:** ImplicitSentimentCapture, UpdateTabTitle, AutoWorkCreation - ---- - -**Last Updated:** 2026-01-13 -**Status:** Production - All hooks active and tested (refactored for SRP) -**Maintainer:** PAI System diff --git a/.opencode/skills/PAI/SYSTEM/THEPLUGINSYSTEM.md b/.opencode/skills/PAI/SYSTEM/THEPLUGINSYSTEM.md index bb088e0a..ddaa3e22 100644 --- a/.opencode/skills/PAI/SYSTEM/THEPLUGINSYSTEM.md +++ b/.opencode/skills/PAI/SYSTEM/THEPLUGINSYSTEM.md @@ -4,22 +4,31 @@ **Location:** `~/.opencode/plugins/` **Configuration:** `~/.opencode/opencode.json` -**Status:** Active - All plugins running in production +**Status:** Active — All plugins running in production +**Version:** v3.0 (27 handlers, 9 libraries) --- ## Overview -The PAI plugin system is an event-driven automation infrastructure built on OpenCode's native plugin API. Plugins are TypeScript modules that run automatically in response to specific events during OpenCode sessions. +The PAI plugin system is an event-driven automation infrastructure built on OpenCode's native plugin API. A single unified plugin (`pai-unified.ts`) routes all events to specialized handler modules across two layers: -**Core Capabilities:** -- **Session Management** - Auto-load context, capture summaries, manage state -- **Security Validation** - Block dangerous commands before execution -- **Tool Lifecycle** - Pre/post processing for tool executions -- **Voice Notifications** - Text-to-speech announcements for task completions -- **History Capture** - Automatic work/learning documentation to `~/.opencode/MEMORY/` +**Layer 1 — Hooks (active, can block/modify):** +- Context injection (bootstrap loading) +- Security validation (block dangerous commands) +- Permission override (deny/allow decisions) +- Work tracking (session management) +- Tool lifecycle (pre/post processing) +- Shell environment injection +- Compaction intelligence (context preservation) -**Key Principle:** Plugins run asynchronously and fail gracefully. They enhance the user experience but never block OpenCode's core functionality. +**Layer 2 — Event Bus (passive, observe-only):** +- Session lifecycle (created, ended, error, compacted, updated) +- Message processing (ISC validation, voice, ratings, sentiment) +- Permission audit logging +- Command tracking + +**Key Principle:** Plugins run asynchronously and fail gracefully. They enhance the user experience but never block OpenCode's core functionality. All logging uses `file-logger.ts` — NEVER `console.log` (corrupts TUI). --- @@ -36,184 +45,278 @@ PAI-OpenCode translates Claude Code hook concepts to OpenCode plugin hooks: | UserPromptSubmit | `chat.message` | Filter `role === "user"` | | Stop | `event` | Filter `session.ended` | | SubagentStop | `tool.execute.after` | Filter `tool === "Task"` | +| — (new in OpenCode) | `experimental.session.compacting` | Context injection during compaction | +| — (new in OpenCode) | `shell.env` | Environment variable injection | +| — (new in OpenCode) | `tool` (custom tools) | Register `session_registry`, `session_results`, `code_review` | **Reference Implementation:** `plugins/pai-unified.ts` **Type Definitions:** `plugins/adapters/types.ts` (includes `PAI_TO_OPENCODE_HOOKS` mapping) --- -## Available Plugin Hooks - -OpenCode supports the following plugin hooks: +## Available Plugin Hooks (9 Active) -### 1. **experimental.chat.system.transform** (SessionStart equivalent) +### 1. `experimental.chat.system.transform` (SessionStart) **When:** At the start of each chat/session -**Purpose:** Inject system context into the conversation +**Purpose:** Inject minimal bootstrap context (~15KB) into the conversation -**Example:** -```typescript -"experimental.chat.system.transform": async (input, output) => { - const result = await loadContext(); - if (result.success && result.context) { - output.system.push(result.context); - } -} -``` +Loads `MINIMAL_BOOTSTRAP.md` (core Algorithm + Steering Rules), System `AISTEERINGRULES.md`, and User identity files (ABOUTME, TELOS, DAIDENTITY). Skills load on-demand via OpenCode's native skill tool — no full 233KB context dump. -**Current Implementation:** -- `context-loader.ts` - Reads `skills/PAI/SKILL.md` and injects PAI context -- Loads SYSTEM/*.md files for architecture documentation -- Loads USER/TELOS/*.md for personal context +**Emits:** `session.start`, `context.loaded` --- -### 2. **tool.execute.before** (PreToolUse equivalent) +### 2. `permission.ask` (Security Blocking) -**When:** Before any tool execution -**Purpose:** Security validation, can BLOCK by throwing an error +**When:** When OpenCode asks for permission on a tool +**Purpose:** Override permission decisions — deny dangerous, confirm risky -**Example:** -```typescript -"tool.execute.before": async (input, output) => { - const result = await validateSecurity({ - tool: input.tool, - args: output.args ?? {}, - }); - - if (result.action === "block") { - throw new Error(`[PAI Security] ${result.message}`); - } -} -``` +**Handler:** `security-validator.ts` +**Actions:** `block` → `output.status = "deny"` | `confirm` → `output.status = "ask"` | `allow` → no change -**Current Implementation:** -- `security-validator.ts` - Validates Bash commands against security patterns -- Blocks destructive commands (rm -rf /, reverse shells, etc.) -- See `plugins/adapters/types.ts` for `DANGEROUS_PATTERNS` +**Note:** Not reliably called for all tools, so security validation also runs in `tool.execute.before`. + +**Emits:** `security.block`, `security.warn` --- -### 3. **permission.ask** (PreToolUse blocking equivalent) +### 3. `tool.execute.before` (PreToolUse) -**When:** When OpenCode asks for permission on a tool -**Purpose:** Override permission decisions +**When:** Before every tool execution +**Purpose:** Security validation + agent execution guard + skill guard -**Example:** -```typescript -"permission.ask": async (input, output) => { - const result = await validateSecurity({ tool, args }); - - switch (result.action) { - case "block": - output.status = "deny"; - break; - case "confirm": - output.status = "ask"; - break; - case "allow": - // Don't modify - let it proceed - break; - } -} -``` +**Handlers:** +- `security-validator.ts` — Validates Bash commands against dangerous/warning patterns. **Throws error to block.** +- `agent-execution-guard.ts` — Warns when agents spawn without proper capability selection (non-blocking) +- `skill-guard.ts` — Validates skill invocations match USE WHEN triggers (non-blocking) -**Note:** `permission.ask` is not reliably called for all tools, so security validation is also done in `tool.execute.before`. +**Emits:** `security.block`, `security.warn` --- -### 4. **tool.execute.after** (PostToolUse equivalent) +### 4. `tool.execute.after` (PostToolUse) **When:** After tool execution completes -**Purpose:** Observe results, capture learnings +**Purpose:** Capture outputs, track state, sync PRDs, track questions -**Example:** -```typescript -"tool.execute.after": async (input, output) => { - // Check for Task tool (subagent) completion - if (input.tool === "Task") { - // Capture subagent learnings (future) - } -} -``` +**Handlers:** +- `agent-capture.ts` — Captures subagent (Task tool) outputs to `MEMORY/RESEARCH/` +- `algorithm-tracker.ts` — Tracks Algorithm phase, validates transitions, tracks ISC criteria and agent spawns +- `prd-sync.ts` — When a PRD.md is written/edited, syncs frontmatter to `work-registry.json` for dashboard +- `question-tracking.ts` — Records AskUserQuestion Q&A pairs to `MEMORY/STATE/questions.jsonl` +- `session-registry.ts` — Captures subagent session IDs to registry for compaction recovery -**Current Implementation:** -- Logs tool completions for debugging -- Future: Learning capture, signal processing, work session tracking +**Emits:** `tool.execute`, `agent.complete` --- -### 5. **chat.message** (UserPromptSubmit equivalent) +### 5. `chat.message` (UserPromptSubmit) -**When:** When a chat message is sent -**Purpose:** Process user input, format enforcement, rating capture +**When:** When user submits a message +**Purpose:** Work session creation, rating capture, effort level detection -**Example:** -```typescript -"chat.message": async (input, output) => { - const role = input.message?.role || "unknown"; - const content = input.message?.content || ""; +**Handlers:** +- `work-tracker.ts` — Creates work sessions in `MEMORY/WORK/` on first non-trivial user message, appends to thread +- `rating-capture.ts` — Detects explicit ratings (1-10) and persists to `MEMORY/LEARNING/SIGNALS/ratings.jsonl` +- `format-reminder.ts` — Detects effort level from user prompts using 8-tier system (Instant→Loop) - // Only process user messages - if (role !== "user") return; +**Features:** +- Message deduplication cache (5s TTL) prevents double-processing between `chat.message` and `message.updated` +- Trivial message detection (greetings, ratings, acknowledgments) skips work session creation +- Session-scoped message buffers for relationship memory analysis - // Format enforcement, rating capture, etc. -} -``` +**Emits:** `user.message` + +--- + +### 6. `event` (Session Lifecycle + Message Processing) + +**When:** All session lifecycle events and message updates +**Purpose:** Orchestrates 15+ handlers across session start, end, and message processing + +#### Session Start (`session.created`) +- `skill-restore.ts` — Restores SKILL.md files modified by OpenCode's normalization +- `check-version.ts` — Checks for PAI-OpenCode updates via GitHub releases + +#### Session End (`session.ended` / `session.idle`) +- `learning-capture.ts` — Extracts learnings from work session, bridges `MEMORY/WORK/` to `MEMORY/LEARNING/` +- `integrity-check.ts` — Validates system health (required files, configs, MEMORY dirs, plugins) +- `work-tracker.ts` — Completes work session +- `update-counts.ts` — Updates `settings.json` with fresh system counts for banner/statusline +- `session-cleanup.ts` — Marks work directory COMPLETED, clears `current-work.json`, cleans `session-names.json` +- `relationship-memory.ts` — Analyzes session messages to extract relationship notes (W/B/O types) to `MEMORY/RELATIONSHIP/` + +#### Assistant Messages (`message.updated`, role=assistant) +- `isc-validator.ts` — Validates Algorithm format, counts ISC criteria, warns on missing elements +- `voice-notification.ts` — Extracts 🗣️ voice line, sends to TTS service (ElevenLabs or Google Cloud) +- `tab-state.ts` — Updates Kitty terminal tab title with 3-5 word completion summary +- `response-capture.ts` — Captures responses for work tracking, extracts ISC to `ISC.json` +- `last-response-cache.ts` — Caches last assistant response to `MEMORY/STATE/` for implicit sentiment context -**Current Implementation:** -- Filters for user messages only -- Future: Auto-work creation, rating capture, skill trigger detection +#### User Messages (`message.updated`, role=user) +- `rating-capture.ts` — Explicit rating detection (backup path via event bus) +- `implicit-sentiment.ts` — AI-powered sentiment analysis (1-10 scale) when no explicit rating given +- `work-tracker.ts` — Auto-work creation (backup path via event bus) + +#### Session Compacted (`session.compacted`) +- `learning-capture.ts` — Rescues learnings after compaction (POST-compaction, complementary to PRE-compaction hook) + +#### Other Events +- `session.error` — Error diagnostics logging +- `session.updated` — Session title tracking +- `permission.asked` — Full permission audit log +- `command.executed` — `/command` usage tracking +- `installation.update.available` — Native OpenCode update notification + +**Emits:** `session.start`, `session.end`, `assistant.message`, `explicit.rating`, `implicit.sentiment`, `isc.validated`, `voice.sent`, `learning.captured` --- -### 6. **event** (Stop/SessionEnd equivalent) +### 7. `experimental.session.compacting` (Compaction Intelligence) -**When:** Session lifecycle events -**Purpose:** Handle session start/end, cleanup +**When:** During LLM summary generation for context compaction +**Purpose:** Inject PAI-critical context so the compaction summary preserves it -**Example:** -```typescript -event: async (input) => { - const eventType = input.event?.type || ""; +**Handler:** `compaction-intelligence.ts` — Reads active PRD status, subagent registry, and ISC criteria, then appends to `output.context` so the LLM includes them in the compaction summary. - if (eventType.includes("session.created")) { - // Session initialization - } +**Complements:** `session.compacted` event (post-compaction learning rescue). Both are needed: one influences WHAT the LLM summarizes, the other rescues data AFTER compaction. - if (eventType.includes("session.ended") || eventType.includes("session.idle")) { - // Session cleanup, save state - } -} -``` +--- + +### 8. `tool` (Custom Tools) -**Current Implementation:** -- Logs session lifecycle events -- Future: Session cleanup, work session state persistence +**When:** Always available — registers custom tools for the AI to call +**Purpose:** Provide session recovery and code review tools + +**Tools registered:** +- `session_registry` — Lists all subagent sessions with metadata for the current session (compaction recovery) +- `session_results` — Gets registry metadata for a specific subagent + resume instructions +- `code_review` — Runs roborev for AI-powered code review (dirty, last-commit, fix, refine modes) + +**Handlers:** `session-registry.ts`, `roborev-trigger.ts` + +--- + +### 9. `shell.env` (Shell Environment Injection) + +**When:** Before every Bash tool call +**Purpose:** Inject PAI runtime context into stateless shell processes + +OpenCode Bash is **stateless** — every call spawns a fresh process. This hook injects: +- `PAI_CONTEXT=1`, `PAI_SESSION_ID`, `PAI_WORK_DIR`, `PAI_VERSION` +- Explicit passthrough of keys: `PAI_OBSERVABILITY_PORT`, `GOOGLE_API_KEY`, `TTS_PROVIDER`, `DA`, `TIME_ZONE` + +**Two-layer system:** Layer 1 (`.opencode/.env` loaded by Bun) handles API keys in TypeScript. Layer 2 (this hook) handles Bash child processes needing runtime context. + +--- + +## Handler Reference (27 Handlers) + +| Handler | Hook | Purpose | +|---------|------|---------| +| `agent-capture.ts` | tool.execute.after | Captures subagent outputs to MEMORY/RESEARCH/ | +| `agent-execution-guard.ts` | tool.execute.before | Validates agent spawning patterns (non-blocking) | +| `algorithm-tracker.ts` | tool.execute.after | Tracks Algorithm phase, ISC criteria, agent spawns | +| `check-version.ts` | event (session.created) | Checks for PAI-OpenCode updates via GitHub | +| `compaction-intelligence.ts` | experimental.session.compacting | Injects PRD/ISC/registry into compaction summary | +| `format-reminder.ts` | chat.message | Detects effort level (8-tier: Instant→Loop) | +| `implicit-sentiment.ts` | event (message.updated) | AI sentiment analysis on user messages (1-10) | +| `integrity-check.ts` | event (session.ended) | System health validation (files, configs, MEMORY) | +| `isc-validator.ts` | event (message.updated) | Validates Algorithm format, counts ISC criteria | +| `last-response-cache.ts` | event (message.updated) | Caches last response for sentiment context | +| `learning-capture.ts` | event (session.ended, compacted) | Extracts learnings, bridges WORK→LEARNING | +| `observability-emitter.ts` | (all hooks) | Fire-and-forget event emission to observability server | +| `prd-sync.ts` | tool.execute.after | Syncs PRD frontmatter to work-registry.json | +| `question-tracking.ts` | tool.execute.after | Records AskUserQuestion Q&A pairs | +| `rating-capture.ts` | chat.message, event | Detects explicit ratings (1-10) | +| `relationship-memory.ts` | event (session.ended) | Extracts relationship notes (W/B/O types) | +| `response-capture.ts` | event (message.updated) | Captures responses, extracts ISC to ISC.json | +| `roborev-trigger.ts` | tool (custom) | AI code review via roborev CLI | +| `security-validator.ts` | permission.ask, tool.execute.before | Pattern-based security validation (block/confirm/allow) | +| `session-cleanup.ts` | event (session.ended) | Marks COMPLETED, clears state, cleans session-names | +| `session-registry.ts` | tool (custom), tool.execute.after | Tracks subagent sessions for compaction recovery | +| `skill-guard.ts` | tool.execute.before | Validates skill invocations match triggers | +| `skill-restore.ts` | event (session.created) | Restores SKILL.md files modified by OpenCode | +| `tab-state.ts` | event (message.updated) | Updates Kitty terminal tab title/color | +| `update-counts.ts` | event (session.ended) | Refreshes settings.json system counts | +| `voice-notification.ts` | event (message.updated) | Sends 🗣️ voice line to TTS service | +| `work-tracker.ts` | chat.message, event | Creates/manages work sessions in MEMORY/WORK/ | + +--- + +## Library Reference (8 Libraries) + +| Library | Purpose | +|---------|---------| +| `file-logger.ts` | TUI-safe file logging — NEVER use `console.log` in plugins | +| `paths.ts` | Canonical path construction for MEMORY, WORK, LEARNING directories | +| `identity.ts` | Central identity loader (DA name, Principal name from settings.json) | +| `time.ts` | Consistent timestamp formatting (ISO, PST/PDT) | +| `sanitizer.ts` | Input normalization before security pattern matching (base64 decode, Unicode, spacing) | +| `injection-patterns.ts` | Comprehensive prompt injection pattern library (7 categories) | +| `learning-utils.ts` | Learning categorization (SYSTEM vs ALGORITHM) shared across handlers | +| `model-config.ts` | PAI model configuration schema and ZEN provider definitions | +| `db-utils.ts` | Database health checks, size monitoring, session archiving | --- ## Plugin Architecture -``` +```text plugins/ -├── pai-unified.ts # Main plugin (combines all functionality) -├── handlers/ -│ ├── context-loader.ts # SessionStart → PAI context injection -│ └── security-validator.ts # PreToolUse → Security validation +├── pai-unified.ts # Main plugin — routes all 9 hooks to handlers +├── handlers/ # 27 specialized handler modules +│ ├── agent-capture.ts # Subagent output capture +│ ├── agent-execution-guard.ts # Agent spawning validation +│ ├── algorithm-tracker.ts # Algorithm phase tracking +│ ├── check-version.ts # Update checking +│ ├── compaction-intelligence.ts # Compaction context injection +│ ├── format-reminder.ts # Effort level detection +│ ├── implicit-sentiment.ts # AI sentiment analysis +│ ├── integrity-check.ts # System health checks +│ ├── isc-validator.ts # ISC format validation +│ ├── last-response-cache.ts # Response caching for sentiment +│ ├── learning-capture.ts # Learning extraction +│ ├── observability-emitter.ts # Event emission (fire-and-forget) +│ ├── prd-sync.ts # PRD frontmatter sync +│ ├── question-tracking.ts # Q&A pair tracking +│ ├── rating-capture.ts # Explicit rating detection +│ ├── relationship-memory.ts # Session relationship notes +│ ├── response-capture.ts # Response capture + ISC extraction +│ ├── roborev-trigger.ts # AI code review tool +│ ├── security-validator.ts # Security pattern matching +│ ├── session-cleanup.ts # Session finalization +│ ├── session-registry.ts # Subagent session tracking +│ ├── skill-guard.ts # Skill invocation validation +│ ├── skill-restore.ts # SKILL.md git restore +│ ├── tab-state.ts # Terminal tab management +│ ├── update-counts.ts # Settings.json count refresh +│ ├── voice-notification.ts # TTS voice output +│ └── work-tracker.ts # Work session management ├── adapters/ -│ └── types.ts # Shared types + PAI_TO_OPENCODE_HOOKS mapping -└── lib/ - ├── file-logger.ts # Logging (avoids TUI corruption) - └── model-config.js # Model configuration +│ └── types.ts # Shared types + PAI_TO_OPENCODE_HOOKS mapping +└── lib/ # 9 shared libraries + ├── db-utils.ts # Database health + ├── file-logger.ts # TUI-safe logging + ├── identity.ts # DA/Principal identity + ├── injection-patterns.ts # Security patterns (7 categories) + ├── learning-utils.ts # Learning categorization + ├── model-config.ts # Model/provider config + ├── paths.ts # Path utilities + ├── sanitizer.ts # Input normalization + └── time.ts # Timestamp formatting ``` **Key Design Decisions:** -1. **Single Plugin File** - `pai-unified.ts` exports all hooks from one plugin -2. **Handler Separation** - Complex logic in `handlers/` for maintainability -3. **File Logging** - Never use `console.log` (corrupts OpenCode TUI), use `file-logger.ts` -4. **Fail-Open Security** - On error, don't block (avoid hanging OpenCode) +1. **Single Plugin File** — `pai-unified.ts` exports all hooks from one plugin (OpenCode auto-discovers it) +2. **Handler Separation** — Complex logic in `handlers/` for maintainability and testability +3. **File Logging** — Never use `console.log` (corrupts OpenCode TUI), use `file-logger.ts` +4. **Fail-Open Security** — On handler error, don't block (avoid hanging OpenCode) +5. **Message Deduplication** — 5s cache prevents double-processing between `chat.message` and `message.updated` +6. **Session-Scoped Buffers** — Message buffers keyed by sessionId prevent cross-session contamination +7. **Two-Layer Compaction** — `experimental.session.compacting` (PRE) + `session.compacted` event (POST) --- @@ -221,18 +324,17 @@ plugins/ ### Plugin Registration (Auto-Discovery) -OpenCode **automatically discovers** plugins from the `plugins/` directory - **no config entry needed!** +OpenCode **automatically discovers** plugins from the `plugins/` directory — **no config entry needed!** -``` +```text .opencode/ plugins/ - pai-unified.ts # ✅ Auto-discovered and loaded - my-plugin.ts # ✅ Also auto-discovered + pai-unified.ts # Auto-discovered and loaded ``` OpenCode scans `{plugin,plugins}/*.{ts,js}` and loads all matching files automatically. -**Important:** Do NOT add relative paths to `opencode.json` - this causes `BunInstallFailedError`. +**Important:** Do NOT add relative paths to `opencode.json` — this causes `BunInstallFailedError`. If you must explicitly register a plugin (e.g., from npm or absolute path), use: @@ -256,9 +358,41 @@ PAI-specific identity configuration is handled via: --- +## Security Patterns + +Security validation uses multi-layer pattern matching against dangerous commands: + +**Blocked Patterns (DANGEROUS_PATTERNS):** +- `rm -rf /` — Root-level deletion +- `rm -rf ~/` — Home directory deletion +- `mkfs.` — Filesystem formatting +- `bash -i >&` — Reverse shells +- `curl | bash` — Remote code execution +- `cat .ssh/id_` — Credential theft +- `eval $(echo ... | base64 -d)` — Obfuscated RCE +- `printenv | curl` — Environment exfiltration +- `python -c "import os; os.system()"` — Python RCE one-liners +- `node -e "require('child_process')"` — Node RCE one-liners + +**Warning Patterns (WARNING_PATTERNS):** +- `git push --force` — Force push +- `git reset --hard` — Hard reset +- `npm install -g` — Global installs +- `docker rm` — Container removal + +**Enhanced in v3.0 (WP-B):** +- 7-category injection pattern detection (`injection-patterns.ts`) +- Input sanitization before matching (`sanitizer.ts` — base64 decode, Unicode normalization) +- Security audit logging to `security-audit.jsonl` +- Multi-field scanning (not just `args.content`) + +See `plugins/adapters/types.ts` for full pattern definitions. + +--- + ## Logging -**CRITICAL:** Never use `console.log` in plugins - it corrupts the OpenCode TUI. +**CRITICAL:** Never use `console.log` in plugins — it corrupts the OpenCode TUI. Use the file logger instead: @@ -274,25 +408,24 @@ Log file location: `~/.opencode/plugins/debug.log` --- -## Security Patterns +## Observability -Security validation uses pattern matching against dangerous commands: +The `observability-emitter.ts` handler sends events to the PAI Observability Server for real-time monitoring: -**Blocked Patterns (DANGEROUS_PATTERNS):** -- `rm -rf /` - Root-level deletion -- `rm -rf ~/` - Home directory deletion -- `mkfs.` - Filesystem formatting -- `bash -i >&` - Reverse shells -- `curl | bash` - Remote code execution -- `cat .ssh/id_` - Credential theft +**Design:** Fire-and-forget with 1-second timeout. Server unavailability is not an error. -**Warning Patterns (WARNING_PATTERNS):** -- `git push --force` - Force push -- `git reset --hard` - Hard reset -- `npm install -g` - Global installs -- `docker rm` - Container removal +**Events emitted:** +- `session.start`, `session.end` +- `context.loaded` +- `user.message`, `assistant.message` +- `tool.execute`, `agent.complete` +- `security.block`, `security.warn` +- `explicit.rating`, `implicit.sentiment` +- `isc.validated` +- `voice.sent` +- `learning.captured` -See `plugins/adapters/types.ts` for full pattern definitions. +Configure via `PAI_OBSERVABILITY_PORT` and `PAI_OBSERVABILITY_ENABLED` environment variables. --- @@ -300,7 +433,6 @@ See `plugins/adapters/types.ts` for full pattern definitions. ### Plugin Not Loading -**Check:** 1. Is the plugin file in `.opencode/plugins/`? (Auto-discovery location) 2. Can Bun parse the TypeScript? `bun run .opencode/plugins/pai-unified.ts` 3. Are there TypeScript errors? Check `~/.opencode/plugins/debug.log` @@ -309,22 +441,20 @@ See `plugins/adapters/types.ts` for full pattern definitions. ### Context Not Injecting -**Check:** -1. Does `skills/PAI/SKILL.md` exist? +1. Does `MINIMAL_BOOTSTRAP.md` exist in `.opencode/PAI/`? 2. Check `~/.opencode/plugins/debug.log` for loading errors -3. Verify `context-loader.ts` can find the PAI skill directory +3. Verify bootstrap loader can find PAI skill directory ### Security Blocking Everything -**Check:** 1. Review `debug.log` for which pattern matched 2. Verify command is actually safe 3. Check for false positives in pattern matching +4. Review `security-audit.jsonl` for audit trail ### TUI Corruption **Cause:** Using `console.log` in plugin code - **Fix:** Replace all `console.log` with `fileLog` from `lib/file-logger.ts` --- @@ -340,12 +470,16 @@ If migrating from PAI's Claude Code implementation: | Exit code 2 to block | `throw Error()` | Different mechanism | | Reads stdin for input | Function parameters | Different API | | Multiple hook files | Single unified plugin | Recommended pattern | +| No custom tools | `tool` hook | New: register custom tools | +| No compaction hook | `experimental.session.compacting` | New: influence compaction | +| No shell env hook | `shell.env` | New: inject env vars | **Key Differences:** 1. OpenCode plugins use async functions, not external scripts 2. Blocking uses `throw Error()` instead of `exit(2)` 3. Input comes from function parameters, not stdin 4. All hooks can be combined in one plugin file +5. Three new hooks available (custom tools, compaction, shell.env) --- @@ -355,9 +489,10 @@ If migrating from PAI's Claude Code implementation: - **Agent System:** `SYSTEM/PAIAGENTSYSTEM.md` - **Architecture:** `SYSTEM/PAISYSTEMARCHITECTURE.md` - **Security Patterns:** `plugins/adapters/types.ts` +- **Observability:** `plugins/handlers/observability-emitter.ts` --- -**Last Updated:** 2026-01-22 -**Status:** Production - All plugins active and tested +**Last Updated:** 2026-03-17 +**Status:** Production — 27 handlers, 9 libraries, 9 hooks active **Maintainer:** PAI System