refactor(v3.0 Part B/2): add correct structure — Utilities/, VoiceServer/, PAI root docs, voice ID fix#83
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR adds extensive PAI 4.0 documentation (Pipelines, Plugin and Hook systems), new VoiceServer install/management scripts and configs, skill-indexing and validation tooling, reorganizes skills into hierarchical categories, and standardizes voice notifications to use a "default" voice profile. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Moved 6 flat-level skills into Utilities/ for PAI 4.0.3 parity: - AudioEditor → Utilities/AudioEditor (was missing from Utilities) - CodeReview → Utilities/CodeReview (OpenCode-specific) - OpenCodeSystem → Utilities/OpenCodeSystem (OpenCode-specific) - Sales → Utilities/Sales (OpenCode-specific) - System → Utilities/System (OpenCode-specific) - WriteStory → Utilities/WriteStory (OpenCode-specific) OpenCode-specific skills preserved in Utilities/ category.
Added 3 files missing from PAI/ root: - doc-dependencies.json (documentation dependency graph) - PIPELINES.md (pipeline system documentation) - THEHOOKSYSTEM.md (hooks/plugin system documentation) All .claude/ references replaced with .opencode/.
Renamed: voice-server/ → VoiceServer/ (CamelCase, matches PAI 4.0.3) Added missing files from PAI 4.0.3: - install.sh, start.sh, stop.sh, restart.sh, status.sh, uninstall.sh - voices.json, pronunciations.json - menubar/ directory All .claude/ references replaced with .opencode/ in copied files.
…eletion Restored from git history (accidentally deleted with skills/PAI/): - PAI/THEPLUGINSYSTEM.md (OpenCode replacement for THEHOOKSYSTEM.md) - PAI/Tools/GenerateSkillIndex.ts + ValidateSkillStructure.ts + SkillSearch.ts - PAI/PIPELINES.md, PAI/doc-dependencies.json - PAISECURITYSYSTEM/HOOKS.md Removed legacy files NOT in 4.0.3 upstream: - PAI/BACKUPS.md, BROWSERAUTOMATION.md, CONSTITUTION.md, RESPONSEFORMAT.md, SCRAPINGREFERENCE.md, TERMINALTABS.md (all pre-v3.0 artifacts) - PAI/UPDATES/ directory (not in 4.0.3) - PAI/Workflows/ (11 legacy workflows not in 4.0.3 root) - PAI/USER/ reset to 4.0.3 template (README.md placeholders only, removed 47 personal template files that don't belong in public repo) Fixed references: THEHOOKSYSTEM → THEPLUGINSYSTEM in SKILL.md and DOCUMENTATIONINDEX.md Regenerated skill-index.json (52 skills, 7 categories) PAI/ root now matches 4.0.3 exactly, with only 2 justified deviations: - THEPLUGINSYSTEM.md (replaces THEHOOKSYSTEM.md per ADR-001) - MINIMAL_BOOTSTRAP.md (OpenCode lazy loading, created in v3.0 WP-C)
VoiceServer sendNotification() now uses title (agent name) instead of voiceId for voice resolution. This enables dynamic provider switching via TTS_PROVIDER env var (google/elevenlabs/macos). Changed: voice_id:'fTtv3eikoepIosk8dTZ5' → voice_id:'default' + title:'AgentName' Files affected: - VoiceServer/server.ts (line 425: voiceId → safeTitle) - PAI/SKILL.md (7 phase curls) - PAI/Algorithm/v3.7.0.md (2 voice curls) - agents/Algorithm.md (frontmatter + 3 references) - PAI/Tools/algorithm.ts (VOICE_ID constant) Zero hardcoded ElevenLabs IDs remain in the codebase.
1d85d4a to
2bd381c
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟠 Major comments (19)
.opencode/VoiceServer/stop.sh-31-35 (1)
31-35:⚠️ Potential issue | 🟠 MajorDo not kill arbitrary listeners on port
8888.This cleanup will SIGKILL any process bound to
:8888, even if the LaunchAgent failed and the port belongs to another app. That makes a normal stop capable of taking down unrelated local services; the same block is duplicated in.opencode/VoiceServer/uninstall.sh:55-58.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/stop.sh around lines 31 - 35, The script currently SIGKILLs any process on :8888 which can kill unrelated apps; change the cleanup to only stop the VoiceServer LaunchAgent process by either (a) reading and using a stored PID file (e.g., create/use a pidfile written when starting the server) or (b) resolving PIDs from lsof -ti :8888 and verifying each PID's command line via ps -p <pid> -o args= contains a known identifier (e.g., the VoiceServer/LaunchAgent job label or the server binary name) before sending a kill; apply the same guarded logic to the duplicate block in uninstall.sh so only matching processes are terminated..opencode/VoiceServer/uninstall.sh-36-43 (1)
36-43:⚠️ Potential issue | 🟠 MajorCheck
launchctl unloadbefore claiming the service is stopped.Unlike
.opencode/VoiceServer/stop.sh:21-25, this path prints success unconditionally. Iflaunchctl unloadfails, the uninstall continues with a loaded LaunchAgent and ends with a falseUninstall Completestate.🐛 Proposed fix
# Stop the service if running echo -e "${YELLOW}> Stopping voice server...${NC}" if launchctl list | grep -q "$SERVICE_NAME" 2>/dev/null; then - launchctl unload "$PLIST_PATH" 2>/dev/null - echo -e "${GREEN}OK Voice server stopped${NC}" + if launchctl unload "$PLIST_PATH" 2>/dev/null; then + echo -e "${GREEN}OK Voice server stopped${NC}" + else + echo -e "${RED}X Failed to stop voice server${NC}" + exit 1 + fi else echo -e "${YELLOW} Service was not running${NC}" fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/uninstall.sh around lines 36 - 43, The uninstall script currently calls launchctl unload "$PLIST_PATH" and then always prints "OK Voice server stopped"; change it to check the exit status of launchctl unload (or test with launchctl list after unload) and only print the success message when unload succeeds; if unload fails, print an error (including SERVICE_NAME and PLIST_PATH) and stop or fail the uninstall flow accordingly so the script does not claim "Uninstall Complete" while the LaunchAgent remains loaded; locate this logic around the block referencing SERVICE_NAME, PLIST_PATH and launchctl unload in uninstall.sh..opencode/PAI/doc-dependencies.json-98-132 (1)
98-132:⚠️ Potential issue | 🟠 MajorTrack
THEPLUGINSYSTEM.mdin the dependency manifest.This PR adds
.opencode/PAI/THEPLUGINSYSTEM.md, butauthoritative_docsstill only enumeratesTHEHOOKSYSTEM.md. Any integrity tooling driven by this file will miss the new plugin-system doc, despiteDOCUMENTATIONINDEX.mdbeing markedtracks_all.📝 Proposed fix
"THEHOOKSYSTEM.md": { "description": "Hook system detailed documentation", "tier": 2, "upstream": "PAISYSTEMARCHITECTURE.md" }, + + "THEPLUGINSYSTEM.md": { + "description": "Plugin system detailed documentation", + "tier": 2, + "upstream": "PAISYSTEMARCHITECTURE.md" + }, "PAIAGENTSYSTEM.md": { "description": "Agent system detailed documentation", "tier": 2,If
PAISYSTEMARCHITECTURE.mdnow has a dedicated plugin-system section as part of the Hooks → Plugins ADR, wire that dependent relationship in here as well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/doc-dependencies.json around lines 98 - 132, The dependency manifest is missing an entry for THEPLUGINSYSTEM.md so integrity tooling will ignore the new doc; add a new object keyed "THEPLUGINSYSTEM.md" in .opencode/PAI/doc-dependencies.json with a short "description" (e.g., "Plugin system documentation"), "tier": 2, and set "upstream": "PAISYSTEMARCHITECTURE.md" to reflect the Hooks → Plugins relationship (or omit upstream only if PAISYSTEMARCHITECTURE.md does not contain the plugin section); ensure this mirrors the style of existing entries like THEHOOKSYSTEM.md and that DOCUMENTATIONINDEX.md's tracks_all remains unchanged..opencode/VoiceServer/restart.sh-15-23 (1)
15-23:⚠️ Potential issue | 🟠 MajorPropagate stop/start failures before reporting success.
This always reaches the
restartedmessage, so a failingstop.shorstart.shis converted into a false-positive restart..opencode/VoiceServer/stop.sh:21-25already exits non-zero on failure, but that signal is ignored here.🐛 Proposed fix
# Stop the server -"$SCRIPT_DIR/stop.sh" +if ! "$SCRIPT_DIR/stop.sh"; then + exit 1 +fi # Wait a moment sleep 2 # Start the server -"$SCRIPT_DIR/start.sh" +if ! "$SCRIPT_DIR/start.sh"; then + exit 1 +fi echo -e "${GREEN}OK Voice server restarted${NC}"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/restart.sh around lines 15 - 23, The restart script currently ignores failures from "$SCRIPT_DIR/stop.sh" and "$SCRIPT_DIR/start.sh" and always prints success; update restart.sh to propagate failures by checking and acting on each command's exit status (or enable strict failure handling), e.g., run "$SCRIPT_DIR/stop.sh" and if it exits non‑zero immediately exit with that code, only proceed to sleep and "$SCRIPT_DIR/start.sh" when stop succeeds, and likewise exit with start.sh's exit code on failure before printing the final "OK Voice server restarted" message; reference the existing "$SCRIPT_DIR/stop.sh", "$SCRIPT_DIR/start.sh", and the echo at the end to locate where to add the checks..opencode/VoiceServer/menubar/install-menubar.sh-62-69 (1)
62-69:⚠️ Potential issue | 🟠 MajorThe installed plugin still points menu actions at the default
~/.claudetree.
pai-voice.5s.shbuilds its Start/Stop/Restart actions fromVOICE_SERVER_DIR, but.opencode/VoiceServer/menubar/pai-voice.5s.sh:7-8defaultsPAI_DIRto$HOME/.claudeand.opencode/VoiceServer/menubar/pai-voice.5s.sh:31-49expands that into.../VoiceServer/*.shcommands. Because.opencode/VoiceServer/install.sh:198-205calls this installer without exporting an override, the raw symlink here makes the menubar actions target the wrong directory.🐛 Proposed fix
# Remove existing plugin if it exists rm -f "$PLUGIN_DIR/pai-voice.5s.sh" 2>/dev/null || true -# Create symbolic link to our script -ln -s "$MENUBAR_SCRIPT" "$PLUGIN_DIR/pai-voice.5s.sh" +# Install a wrapper so the menubar process keeps the real PAI root +PAI_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +cat > "$PLUGIN_DIR/pai-voice.5s.sh" <<EOF +#!/bin/bash +export PAI_DIR="$PAI_DIR" +exec "$MENUBAR_SCRIPT" +EOF +chmod +x "$PLUGIN_DIR/pai-voice.5s.sh"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/menubar/install-menubar.sh around lines 62 - 69, The installer currently creates a raw symlink so the menubar plugin (pai-voice.5s.sh) picks up its default PAI_DIR ($HOME/.claude) instead of the real VOICE_SERVER_DIR; instead of ln -s to PLUGIN_DIR, copy MENUBAR_SCRIPT into PLUGIN_DIR/pai-voice.5s.sh and inject or prepend an explicit export/assignment of PAI_DIR="$VOICE_SERVER_DIR" (or otherwise ensure VOICE_SERVER_DIR is exported) so the plugin builds Start/Stop/Restart from the correct path; ensure you still rm -f the old plugin, make the copied file executable, and keep references to MENUBAR_SCRIPT, PLUGIN_DIR, pai-voice.5s.sh and VOICE_SERVER_DIR to locate the changes..opencode/PAI/THEHOOKSYSTEM.md-374-403 (1)
374-403:⚠️ Potential issue | 🟠 MajorThe voice configuration examples weren't updated for default routing.
This PR moves notifications to
voice_id: "default"with provider-side resolution, but these sections still teach an explicitvoiceId/ElevenLabs-ID setup and troubleshoot invalid hardcoded IDs. That sends readers back to the deprecated configuration model instead of the new default-routing flow.Also applies to: 461-485, 824-843, 1150-1154
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/THEHOOKSYSTEM.md around lines 374 - 403, Update the voice configuration examples and explanatory text to use the new default-routing model instead of hardcoded ElevenLabs IDs: replace references showing a direct ElevenLabs voiceId (e.g., the JSON daidentity.voiceId example and any usage showing getVoiceId() returning an ElevenLabs-ID) with guidance to set voice_id: "default" and note that provider-side resolution will pick the actual voice; update associated examples and troubleshooting that mention invalid hardcoded IDs (affecting the sections around the getVoiceId/getIdentity usage and the ranges called out) so they instruct readers to rely on "default" routing and provider configuration rather than embedding an ElevenLabs voiceId..opencode/PAI/PIPELINES.md-9-9 (1)
9-9:⚠️ Potential issue | 🟠 MajorThe documented pipeline location is inconsistent.
Line 9 says personal definitions live under
USER/PIPELINES/, but Line 86 and Lines 161-165 tell users to create them under~/.opencode/PAI/PIPELINES/. Only one location can be correct, so this guide currently sends readers to different trees depending on which section they follow.Also applies to: 86-86, 161-165
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/PIPELINES.md at line 9, The doc contains two inconsistent paths for personal pipeline definitions: the string "USER/PIPELINES/" (appearing in the header/line 9) and the string "~/.opencode/PAI/PIPELINES/" (appearing around lines 86 and 161-165); pick the correct canonical location and update all occurrences so they match (replace every "USER/PIPELINES/" or "~/.opencode/PAI/PIPELINES/" instance with the chosen path), and ensure any examples or instructions that reference creating directories use that same canonical path throughout the file..opencode/PAI/Tools/ValidateSkillStructure.ts-51-64 (1)
51-64:⚠️ Potential issue | 🟠 Major
visitedPathsis never populated before recursion.The cycle check at Lines 63-64 always sees the original empty set because no canonical path is added before Line 141 recurses. A symlink loop will still recurse indefinitely despite the guard.
Also applies to: 140-141
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/Tools/ValidateSkillStructure.ts around lines 51 - 64, The cycle-guard Set visitedPaths is never populated before recursion in scanDirectory, so symlinked directory cycles still recurse; fix by adding the resolved canonical path (the value from realpath assigned to canonical) to visitedPaths before recursing into scanDirectory (and optionally remove it after returning if you want per-branch tracking), and ensure the same pattern is applied at the other recursion site around lines that call scanDirectory (references: function scanDirectory, variables visitedPaths and canonical, and the recursive scanDirectory(...) calls)..opencode/PAISECURITYSYSTEM/HOOKS.md-60-71 (1)
60-71:⚠️ Potential issue | 🟠 MajorThe YAML pattern-loading section is not backed by the implementation.
Lines 62-66 and 244-254 say the validator loads
patterns.yamlandpatterns.example.yaml, but the handler currently uses in-codeDANGEROUS_PATTERNSandWARNING_PATTERNS, andpatterns.example.yamlis not consumed. Leaving this here suggests a customization path that has no effect.Also applies to: 75-117, 242-254
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAISECURITYSYSTEM/HOOKS.md around lines 60 - 71, The docs describe cascading pattern loading but the code uses hard-coded DANGEROUS_PATTERNS and WARNING_PATTERNS and never reads patterns.example.yaml; update the handler to load patterns from USER/PAISECURITYSYSTEM/patterns.yaml first, fall back to PAISECURITYSYSTEM/patterns.example.yaml if the user file is missing, and fail-open (allow all) only if neither file exists. Concretely, add a YAML load routine (using your existing YAML parser) invoked during handler initialization (the function/method that currently references DANGEROUS_PATTERNS and WARNING_PATTERNS) to populate those pattern variables from the loaded file, keep the in-code arrays only as last-resort defaults or remove them, and ensure the code path that checks patterns references the loaded pattern structures so patterns.example.yaml is actually consumed..opencode/VoiceServer/start.sh-24-29 (1)
24-29:⚠️ Potential issue | 🟠 MajorDon't return success before
/healthis actually up.Line 25 short-circuits on the LaunchAgent label, but the real readiness signal in this file is the
/healthprobe at Lines 39-45. That means this script can exit 0 without ever proving the server is reachable, and Lines 43-46 also keep a failed startup as success. Make the health check the success condition in both branches and fail if it never comes up.Also applies to: 38-46
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/start.sh around lines 24 - 29, The current early return when the LaunchAgent label ($SERVICE_NAME) is present lets the script succeed before the actual readiness probe (/health) is verified; change both the "already running" branch and the post-start branch to actively poll the /health endpoint (with a short sleep/retry loop and a timeout) and only exit 0 once /health returns success, otherwise exit non-zero after the timeout; update the checks around launchctl list/launchctl start and the health probe logic at the existing /health polling block so both branches reuse the same polling routine and fail if the probe never becomes healthy..opencode/VoiceServer/menubar/pai-voice.5s.sh-7-8 (1)
7-8:⚠️ Potential issue | 🟠 MajorThe fallback path still points at the pre-rename install root.
Lines 7-8 default
VOICE_SERVER_DIRto~/.claude/VoiceServer, but this PR moved the service to.opencode/VoiceServer. If SwiftBar doesn't inheritPAI_DIR, Start/Stop/Restart/Status will all target scripts that are no longer there.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/menubar/pai-voice.5s.sh around lines 7 - 8, The fallback PAI_DIR/VOICE_SERVER_DIR values still point to the old install root; update the defaults so PAI_DIR falls back to "$HOME/.opencode" (or set VOICE_SERVER_DIR to "$HOME/.opencode/VoiceServer" when PAI_DIR is unset) so Start/Stop/Restart/Status target the moved service; modify the PAI_DIR and/or VOICE_SERVER_DIR assignments (symbols: PAI_DIR and VOICE_SERVER_DIR) to use the new .opencode location..opencode/PAI/Tools/GenerateSkillIndex.ts-52-87 (1)
52-87:⚠️ Potential issue | 🟠 MajorSymlinked directory walks need cycle detection here too.
Lines 63-87 follow directory symlinks recursively, but this walker never records canonical paths. A loop or duplicate symlink under
skills/can re-enter the same tree forever and hang index generation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/Tools/GenerateSkillIndex.ts around lines 52 - 87, findSkillFiles can infinite-loop when following directory symlinks because it never tracks canonical paths; modify findSkillFiles to accept or create a visited Set<string> of realpaths and, before recursing into fullPath (and before processing the directory), call fs.promises.realpath(fullPath) to get its canonical path, skip if that canonical path is already in visited, otherwise add it to visited and then recurse using the same visited set; reference symbols: findSkillFiles, fullPath, entry.isSymbolicLink, stat, and the recursive call findSkillFiles(fullPath) so you replace that recursion with a checked call that passes the visited set to prevent cycles and duplicate traversal..opencode/PAI/Tools/GenerateSkillIndex.ts-374-374 (1)
374-374:⚠️ Potential issue | 🟠 MajorUnexpected generator failures still report success.
Line 374 only prints the rejection. If scanning or
writeFile()fails, automation will keep the staleskill-index.jsonbecause the process still exits 0.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/Tools/GenerateSkillIndex.ts at line 374, The current top-level call main().catch(console.error) only logs rejections and leaves the process exit code as 0, so failures in main (including writeFile() errors when generating skill-index.json) appear successful; change the catch to log the error and terminate the process with a non-zero exit (e.g., in the main().catch handler call process.exit(1) after console.error) so any thrown errors from main or writeFile() cause CI/automation to fail and prevent stale skill-index.json from being considered a success..opencode/PAI/Tools/ValidateSkillStructure.ts-97-105 (1)
97-105:⚠️ Potential issue | 🟠 MajorCategory metadata is being counted as a flat skill.
Once a top-level category adds the
Category/SKILL.mdrequired by Lines 112-120, thepathParts.length === 1branch at Lines 102-105 incrementsflatSkillsand validates that category as a normal skill. That makes the flat/hierarchical totals unreliable for any categorized tree.Also applies to: 112-120
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/Tools/ValidateSkillStructure.ts around lines 97 - 105, The flat-skill branch is counting top-level category metadata (the Category/SKILL.md files) as regular skills; update the logic around pathParts, flatSkills and the validateSkill call to skip category metadata files — e.g., detect and ignore files whose basename is "SKILL.md" or whose first path part equals "Category" (or otherwise matches your category metadata pattern) before incrementing flatSkills and calling validateSkill so category metadata isn't included in flat/hierarchical totals..opencode/PAI/Tools/SkillSearch.ts-203-203 (1)
203-203:⚠️ Potential issue | 🟠 MajorUnexpected failures still exit 0.
Line 203 only logs the rejection. If
readFile()orJSON.parse()fails, wrappers and CI jobs will still see a successful run.Suggested fix
-main().catch(console.error); +main().catch((error) => { + console.error(error); + process.exit(1); +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/Tools/SkillSearch.ts at line 203, The top-level promise rejection handler currently only logs errors (main().catch(console.error)), so unexpected failures (e.g., readFile()/JSON.parse() inside main) still return exit code 0; change the rejection handler to log the error and exit non‑zero (for example, replace the simple console.error handler with a callback that calls console.error(err) and then process.exit(1)) so the process reflects failure when main() rejects..opencode/PAISECURITYSYSTEM/HOOKS.md-9-17 (1)
9-17:⚠️ Potential issue | 🟠 MajorThis doc describes a hook entrypoint that the codebase no longer exposes.
The current implementation lives in
.opencode/plugins/handlers/security-validator.tsand is wired through.opencode/plugins/pai-unified.ts, so the stdin/exit-code contract here, thesettings.jsonregistration in Lines 160-191, and the directbun ~/.opencode/hooks/SecurityValidator.hook.tstests in Lines 221-235 all point to the wrong integration surface.Also applies to: 48-57, 156-191, 219-236
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAISECURITYSYSTEM/HOOKS.md around lines 9 - 17, The HOOKS.md entry for SecurityValidator.hook.ts is stale: update the doc to reference the actual implementation at .opencode/plugins/handlers/security-validator.ts and its registration/wiring via .opencode/plugins/pai-unified.ts instead of the old stdin/exit-code contract, remove or correct the incorrect settings.json registration and direct bun hook test instructions, and make sure the Trigger/Matchers section and any example invocation reflect how .opencode/plugins/pai-unified.ts wires the hook into PreToolUse so readers follow the real integration surface..opencode/PAI/THEHOOKSYSTEM.md-354-368 (1)
354-368:⚠️ Potential issue | 🟠 MajorThe copied
PAI_DIRexample still points to the old install root.This file consistently documents
~/.opencode, but Lines 357-362 tell users to setPAI_DIR="$HOME/.claude". If they paste that block, every${PAI_DIR}/hooks/...example resolves to the wrong directory.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/THEHOOKSYSTEM.md around lines 354 - 368, The PAI_DIR example currently points to the old install root ("$HOME/.claude"); update the JSON snippet so PAI_DIR matches the rest of the document (e.g., "PAI_DIR": "$HOME/.opencode" or "~/.opencode") so that all ${PAI_DIR}/hooks/... paths resolve correctly; change the example value for the PAI_DIR environment variable in the block that shows the env section to the new install root used throughout the file..opencode/VoiceServer/menubar/pai-voice.5s.sh-44-48 (1)
44-48:⚠️ Potential issue | 🟠 MajorSwiftBar
bash=parameter must reference an executable path, not inline command strings.Lines 44, 46, and 48 embed full shell commands as string values in
bash=. SwiftBar'sbash=parameter accepts only an absolute file path to an executable; parameters are passed separately viaparam0=,param1=, etc. The correct pattern isbash=/bin/bash param0=-c param1="your command here".To fix:
- Line 44 (tail):
bash=/bin/bash param0=-c param1="tail -f ~/Library/Logs/pai-voice-server.log"- Line 46 (curl):
bash=/bin/bash param0=-c param1='curl -X POST http://localhost:8888/notify -H "Content-Type: application/json" -d "{\"message\":\"Testing voice server\"}"'- Line 48 (open):
bash=/usr/bin/open param1="$VOICE_SERVER_DIR"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/menubar/pai-voice.5s.sh around lines 44 - 48, Replace the SwiftBar menu item lines that use inline commands inside bash= (the three echo lines containing "tail -f ~/Library/Logs/pai-voice-server.log", the curl POST command, and "open $VOICE_SERVER_DIR") with the proper SwiftBar invocation form: set bash to an absolute executable path (e.g., bash=/bin/bash for tail and curl, bash=/usr/bin/open for open), pass -c via param0 when using /bin/bash, and put the full command in param1 (ensuring proper quoting/escaping for the curl JSON payload); for the open action use bash=/usr/bin/open and pass the directory via param1="$VOICE_SERVER_DIR"..opencode/VoiceServer/install.sh-136-149 (1)
136-149:⚠️ Potential issue | 🟠 MajorHarden startup verification to avoid flaky installs and false-positive smoke tests.
Line 137 uses a fixed delay plus a single health check, and Lines 146-149 do not fail on HTTP errors from
/notify. This can mark installs as failed/successful incorrectly.Suggested reliability fix
-# Wait for server to start -sleep 2 - -# Test the server -echo -e "${YELLOW}> Testing voice server...${NC}" -if curl -s -f -X GET http://localhost:8888/health > /dev/null 2>&1; then +echo -e "${YELLOW}> Testing voice server...${NC}" +max_attempts=15 +attempt=1 +until curl -s -f http://localhost:8888/health > /dev/null 2>&1; do + if [ "$attempt" -ge "$max_attempts" ]; then + echo -e "${RED}X Voice server is not responding${NC}" + echo " Check logs at: $LOG_PATH" + echo " Try running manually: bun run $SCRIPT_DIR/server.ts" + exit 1 + fi + sleep 1 + attempt=$((attempt + 1)) +done + +if curl -s -f http://localhost:8888/health > /dev/null 2>&1; then echo -e "${GREEN}OK Voice server is running${NC}" # Send test notification echo -e "${YELLOW}> Sending test notification...${NC}" - curl -s -X POST http://localhost:8888/notify \ + curl -s -f -X POST http://localhost:8888/notify \ -H "Content-Type: application/json" \ -d '{"message": "Voice server installed successfully"}' > /dev/null echo -e "${GREEN}OK Test notification sent${NC}" else🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/install.sh around lines 136 - 149, The startup verification is flaky because it uses a fixed sleep then a single curl health check and ignores HTTP errors on /notify; replace the fixed sleep + one-shot curl against /health with a retry loop (e.g., attempt curl -f to http://localhost:8888/health with a short delay between tries and an overall timeout) so the script only proceeds when the service actually responds 200, and also make the POST to /notify use a failing curl flag and check its exit status (fail the install with a non-zero exit if the notify request fails); update the health check and notify invocations in the install.sh snippet (the curl calls to /health and /notify) accordingly.
🟡 Minor comments (3)
.opencode/VoiceServer/pronunciations.json-1-6 (1)
1-6:⚠️ Potential issue | 🟡 MinorClarify or update the source of truth reference.
The
_commentreferencesskills/PAI/USER/PRONUNCIATIONS.mdas the source of truth, but this file does not exist in the repository. If a source documentation file is intended, ensure it exists at the correct path. If planning to create it, use.opencode/skills/PAI/USER/PRONUNCIATIONS.mdfor consistency with the project's directory structure.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/pronunciations.json around lines 1 - 6, Update the _comment entry in pronunciations.json to point to the correct source-of-truth path or create the referenced file: either change "_comment" to reference ".opencode/skills/PAI/USER/PRONUNCIATIONS.md" or add that file under .opencode/skills/PAI/USER with the documentation for the entries in the "replacements" array (e.g., PAI and ISC); ensure the string in "_comment" matches the actual file location so the repository reference is accurate..opencode/VoiceServer/status.sh-59-75 (1)
59-75:⚠️ Potential issue | 🟡 MinorStatus output still assumes ElevenLabs-only routing.
The PR switches voice resolution to
TTS_PROVIDERplus dynamic"default"voices, but this block still keys entirely offELEVENLABS_API_KEY/ELEVENLABS_VOICE_IDand otherwise reports macOSsay. Any non-ElevenLabs provider will now look like fallback or misconfiguration even when it is working.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/status.sh around lines 59 - 75, Update the status block to prefer TTS_PROVIDER (and the chosen voice variable, e.g., TTS_VOICE or dynamic "default") rather than only checking ELEVENLABS_API_KEY/VOICE_ID: read TTS_PROVIDER from ENV_FILE (falling back to checking ELEVENLABS_API_KEY for compatibility), print "Provider: $TTS_PROVIDER" and the resolved voice id/name (e.g., TTS_VOICE or ELEVENLABS_VOICE_ID when provider is elevenlabs), and only show the macOS 'say' fallback when no provider is configured; adjust the conditional logic around ENV_FILE, ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, TTS_PROVIDER and TTS_VOICE to reflect this..opencode/PAI/THEPLUGINSYSTEM.md-248-260 (1)
248-260:⚠️ Potential issue | 🟡 MinorThe library count is inconsistent.
This section says 8 libraries, but the table here lists 9, and the header / architecture sections also say 9.
📝 Suggested fix
-## Library Reference (8 Libraries) +## Library Reference (9 Libraries)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/THEPLUGINSYSTEM.md around lines 248 - 260, Update the mismatched library count by changing the header "Library Reference (8 Libraries)" to the correct number "Library Reference (9 Libraries)" and verify any other occurrences of the count in this document (e.g., architecture/summary sections) to match; confirm the table entries (file-logger.ts, paths.ts, identity.ts, time.ts, sanitizer.ts, injection-patterns.ts, learning-utils.ts, model-config.ts, db-utils.ts) are all intended and if not remove the extra row instead of changing the header. Ensure the header text and any summary mentions use the same numeric value so the document is consistent.
🧹 Nitpick comments (1)
.opencode/VoiceServer/install.sh (1)
54-56: MakeELEVENLABS_API_KEYparsing more robust.Lines 54-56 can misread quoted values, whitespace, or multiple entries. Prefer extracting the last exact-key match and trimming quotes.
Suggested parsing refactor
-if [ -f "$ENV_FILE" ] && grep -q "ELEVENLABS_API_KEY=" "$ENV_FILE"; then - API_KEY=$(grep "ELEVENLABS_API_KEY=" "$ENV_FILE" | cut -d'=' -f2) +if [ -f "$ENV_FILE" ] && grep -qE '^[[:space:]]*ELEVENLABS_API_KEY=' "$ENV_FILE"; then + API_KEY=$( + grep -E '^[[:space:]]*ELEVENLABS_API_KEY=' "$ENV_FILE" \ + | tail -n 1 \ + | sed -E 's/^[[:space:]]*ELEVENLABS_API_KEY=[[:space:]]*//; s/^[\"\x27]//; s/[\"\x27]$//' + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/VoiceServer/install.sh around lines 54 - 56, The current parsing of ELEVENLABS_API_KEY in ENV_FILE can mis-handle quoted values, whitespace, or multiple occurrences; update the logic that sets API_KEY to: search only for lines that begin with the exact key (use a caret-anchored match for ELEVENLABS_API_KEY=), take the last matching line if multiple exist, split on the first '=' to get the full RHS, then trim leading/trailing whitespace and any surrounding single or double quotes before assigning to API_KEY and performing the existing checks (API_KEY != "your_api_key_here" and -n check). Ensure these steps are applied where ENV_FILE and API_KEY are referenced so quoted or spaced values and duplicate keys are handled robustly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.opencode/PAI/Tools/SkillSearch.ts:
- Line 22: The INDEX_FILE constant currently points to '..', 'Skills',
'skill-index.json' but the generator writes the index to
'.opencode/skills/skill-index.json'; update the INDEX_FILE definition (the join
call that constructs the path for INDEX_FILE) to point to the generator's
location (use 'skills' lowercase and the same relative path used by
GenerateSkillIndex.ts) so the code that reads the index can find the generated
file.
In @.opencode/PAI/Tools/ValidateSkillStructure.ts:
- Line 22: The SKILLS_DIR constant in ValidateSkillStructure.ts is pointing
three parents up (join(import.meta.dir, '..', '..', '..', 'skills')) which
causes the validator to scan the wrong skills tree; update SKILLS_DIR to resolve
two parents up to match the generator (i.e., use join(import.meta.dir, '..',
'..', 'skills')) so the validator and GenerateSkillIndex.ts reference the same
skills/ directory (confirm by comparing with the path used in
GenerateSkillIndex.ts and adjust any related imports or tests).
---
Major comments:
In @.opencode/PAI/doc-dependencies.json:
- Around line 98-132: The dependency manifest is missing an entry for
THEPLUGINSYSTEM.md so integrity tooling will ignore the new doc; add a new
object keyed "THEPLUGINSYSTEM.md" in .opencode/PAI/doc-dependencies.json with a
short "description" (e.g., "Plugin system documentation"), "tier": 2, and set
"upstream": "PAISYSTEMARCHITECTURE.md" to reflect the Hooks → Plugins
relationship (or omit upstream only if PAISYSTEMARCHITECTURE.md does not contain
the plugin section); ensure this mirrors the style of existing entries like
THEHOOKSYSTEM.md and that DOCUMENTATIONINDEX.md's tracks_all remains unchanged.
In @.opencode/PAI/PIPELINES.md:
- Line 9: The doc contains two inconsistent paths for personal pipeline
definitions: the string "USER/PIPELINES/" (appearing in the header/line 9) and
the string "~/.opencode/PAI/PIPELINES/" (appearing around lines 86 and 161-165);
pick the correct canonical location and update all occurrences so they match
(replace every "USER/PIPELINES/" or "~/.opencode/PAI/PIPELINES/" instance with
the chosen path), and ensure any examples or instructions that reference
creating directories use that same canonical path throughout the file.
In @.opencode/PAI/THEHOOKSYSTEM.md:
- Around line 374-403: Update the voice configuration examples and explanatory
text to use the new default-routing model instead of hardcoded ElevenLabs IDs:
replace references showing a direct ElevenLabs voiceId (e.g., the JSON
daidentity.voiceId example and any usage showing getVoiceId() returning an
ElevenLabs-ID) with guidance to set voice_id: "default" and note that
provider-side resolution will pick the actual voice; update associated examples
and troubleshooting that mention invalid hardcoded IDs (affecting the sections
around the getVoiceId/getIdentity usage and the ranges called out) so they
instruct readers to rely on "default" routing and provider configuration rather
than embedding an ElevenLabs voiceId.
- Around line 354-368: The PAI_DIR example currently points to the old install
root ("$HOME/.claude"); update the JSON snippet so PAI_DIR matches the rest of
the document (e.g., "PAI_DIR": "$HOME/.opencode" or "~/.opencode") so that all
${PAI_DIR}/hooks/... paths resolve correctly; change the example value for the
PAI_DIR environment variable in the block that shows the env section to the new
install root used throughout the file.
In @.opencode/PAI/Tools/GenerateSkillIndex.ts:
- Around line 52-87: findSkillFiles can infinite-loop when following directory
symlinks because it never tracks canonical paths; modify findSkillFiles to
accept or create a visited Set<string> of realpaths and, before recursing into
fullPath (and before processing the directory), call
fs.promises.realpath(fullPath) to get its canonical path, skip if that canonical
path is already in visited, otherwise add it to visited and then recurse using
the same visited set; reference symbols: findSkillFiles, fullPath,
entry.isSymbolicLink, stat, and the recursive call findSkillFiles(fullPath) so
you replace that recursion with a checked call that passes the visited set to
prevent cycles and duplicate traversal.
- Line 374: The current top-level call main().catch(console.error) only logs
rejections and leaves the process exit code as 0, so failures in main (including
writeFile() errors when generating skill-index.json) appear successful; change
the catch to log the error and terminate the process with a non-zero exit (e.g.,
in the main().catch handler call process.exit(1) after console.error) so any
thrown errors from main or writeFile() cause CI/automation to fail and prevent
stale skill-index.json from being considered a success.
In @.opencode/PAI/Tools/SkillSearch.ts:
- Line 203: The top-level promise rejection handler currently only logs errors
(main().catch(console.error)), so unexpected failures (e.g.,
readFile()/JSON.parse() inside main) still return exit code 0; change the
rejection handler to log the error and exit non‑zero (for example, replace the
simple console.error handler with a callback that calls console.error(err) and
then process.exit(1)) so the process reflects failure when main() rejects.
In @.opencode/PAI/Tools/ValidateSkillStructure.ts:
- Around line 51-64: The cycle-guard Set visitedPaths is never populated before
recursion in scanDirectory, so symlinked directory cycles still recurse; fix by
adding the resolved canonical path (the value from realpath assigned to
canonical) to visitedPaths before recursing into scanDirectory (and optionally
remove it after returning if you want per-branch tracking), and ensure the same
pattern is applied at the other recursion site around lines that call
scanDirectory (references: function scanDirectory, variables visitedPaths and
canonical, and the recursive scanDirectory(...) calls).
- Around line 97-105: The flat-skill branch is counting top-level category
metadata (the Category/SKILL.md files) as regular skills; update the logic
around pathParts, flatSkills and the validateSkill call to skip category
metadata files — e.g., detect and ignore files whose basename is "SKILL.md" or
whose first path part equals "Category" (or otherwise matches your category
metadata pattern) before incrementing flatSkills and calling validateSkill so
category metadata isn't included in flat/hierarchical totals.
In @.opencode/PAISECURITYSYSTEM/HOOKS.md:
- Around line 60-71: The docs describe cascading pattern loading but the code
uses hard-coded DANGEROUS_PATTERNS and WARNING_PATTERNS and never reads
patterns.example.yaml; update the handler to load patterns from
USER/PAISECURITYSYSTEM/patterns.yaml first, fall back to
PAISECURITYSYSTEM/patterns.example.yaml if the user file is missing, and
fail-open (allow all) only if neither file exists. Concretely, add a YAML load
routine (using your existing YAML parser) invoked during handler initialization
(the function/method that currently references DANGEROUS_PATTERNS and
WARNING_PATTERNS) to populate those pattern variables from the loaded file, keep
the in-code arrays only as last-resort defaults or remove them, and ensure the
code path that checks patterns references the loaded pattern structures so
patterns.example.yaml is actually consumed.
- Around line 9-17: The HOOKS.md entry for SecurityValidator.hook.ts is stale:
update the doc to reference the actual implementation at
.opencode/plugins/handlers/security-validator.ts and its registration/wiring via
.opencode/plugins/pai-unified.ts instead of the old stdin/exit-code contract,
remove or correct the incorrect settings.json registration and direct bun hook
test instructions, and make sure the Trigger/Matchers section and any example
invocation reflect how .opencode/plugins/pai-unified.ts wires the hook into
PreToolUse so readers follow the real integration surface.
In @.opencode/VoiceServer/install.sh:
- Around line 136-149: The startup verification is flaky because it uses a fixed
sleep then a single curl health check and ignores HTTP errors on /notify;
replace the fixed sleep + one-shot curl against /health with a retry loop (e.g.,
attempt curl -f to http://localhost:8888/health with a short delay between tries
and an overall timeout) so the script only proceeds when the service actually
responds 200, and also make the POST to /notify use a failing curl flag and
check its exit status (fail the install with a non-zero exit if the notify
request fails); update the health check and notify invocations in the install.sh
snippet (the curl calls to /health and /notify) accordingly.
In @.opencode/VoiceServer/menubar/install-menubar.sh:
- Around line 62-69: The installer currently creates a raw symlink so the
menubar plugin (pai-voice.5s.sh) picks up its default PAI_DIR ($HOME/.claude)
instead of the real VOICE_SERVER_DIR; instead of ln -s to PLUGIN_DIR, copy
MENUBAR_SCRIPT into PLUGIN_DIR/pai-voice.5s.sh and inject or prepend an explicit
export/assignment of PAI_DIR="$VOICE_SERVER_DIR" (or otherwise ensure
VOICE_SERVER_DIR is exported) so the plugin builds Start/Stop/Restart from the
correct path; ensure you still rm -f the old plugin, make the copied file
executable, and keep references to MENUBAR_SCRIPT, PLUGIN_DIR, pai-voice.5s.sh
and VOICE_SERVER_DIR to locate the changes.
In @.opencode/VoiceServer/menubar/pai-voice.5s.sh:
- Around line 7-8: The fallback PAI_DIR/VOICE_SERVER_DIR values still point to
the old install root; update the defaults so PAI_DIR falls back to
"$HOME/.opencode" (or set VOICE_SERVER_DIR to "$HOME/.opencode/VoiceServer" when
PAI_DIR is unset) so Start/Stop/Restart/Status target the moved service; modify
the PAI_DIR and/or VOICE_SERVER_DIR assignments (symbols: PAI_DIR and
VOICE_SERVER_DIR) to use the new .opencode location.
- Around line 44-48: Replace the SwiftBar menu item lines that use inline
commands inside bash= (the three echo lines containing "tail -f
~/Library/Logs/pai-voice-server.log", the curl POST command, and "open
$VOICE_SERVER_DIR") with the proper SwiftBar invocation form: set bash to an
absolute executable path (e.g., bash=/bin/bash for tail and curl,
bash=/usr/bin/open for open), pass -c via param0 when using /bin/bash, and put
the full command in param1 (ensuring proper quoting/escaping for the curl JSON
payload); for the open action use bash=/usr/bin/open and pass the directory via
param1="$VOICE_SERVER_DIR".
In @.opencode/VoiceServer/restart.sh:
- Around line 15-23: The restart script currently ignores failures from
"$SCRIPT_DIR/stop.sh" and "$SCRIPT_DIR/start.sh" and always prints success;
update restart.sh to propagate failures by checking and acting on each command's
exit status (or enable strict failure handling), e.g., run "$SCRIPT_DIR/stop.sh"
and if it exits non‑zero immediately exit with that code, only proceed to sleep
and "$SCRIPT_DIR/start.sh" when stop succeeds, and likewise exit with start.sh's
exit code on failure before printing the final "OK Voice server restarted"
message; reference the existing "$SCRIPT_DIR/stop.sh", "$SCRIPT_DIR/start.sh",
and the echo at the end to locate where to add the checks.
In @.opencode/VoiceServer/start.sh:
- Around line 24-29: The current early return when the LaunchAgent label
($SERVICE_NAME) is present lets the script succeed before the actual readiness
probe (/health) is verified; change both the "already running" branch and the
post-start branch to actively poll the /health endpoint (with a short
sleep/retry loop and a timeout) and only exit 0 once /health returns success,
otherwise exit non-zero after the timeout; update the checks around launchctl
list/launchctl start and the health probe logic at the existing /health polling
block so both branches reuse the same polling routine and fail if the probe
never becomes healthy.
In @.opencode/VoiceServer/stop.sh:
- Around line 31-35: The script currently SIGKILLs any process on :8888 which
can kill unrelated apps; change the cleanup to only stop the VoiceServer
LaunchAgent process by either (a) reading and using a stored PID file (e.g.,
create/use a pidfile written when starting the server) or (b) resolving PIDs
from lsof -ti :8888 and verifying each PID's command line via ps -p <pid> -o
args= contains a known identifier (e.g., the VoiceServer/LaunchAgent job label
or the server binary name) before sending a kill; apply the same guarded logic
to the duplicate block in uninstall.sh so only matching processes are
terminated.
In @.opencode/VoiceServer/uninstall.sh:
- Around line 36-43: The uninstall script currently calls launchctl unload
"$PLIST_PATH" and then always prints "OK Voice server stopped"; change it to
check the exit status of launchctl unload (or test with launchctl list after
unload) and only print the success message when unload succeeds; if unload
fails, print an error (including SERVICE_NAME and PLIST_PATH) and stop or fail
the uninstall flow accordingly so the script does not claim "Uninstall Complete"
while the LaunchAgent remains loaded; locate this logic around the block
referencing SERVICE_NAME, PLIST_PATH and launchctl unload in uninstall.sh.
---
Minor comments:
In @.opencode/PAI/THEPLUGINSYSTEM.md:
- Around line 248-260: Update the mismatched library count by changing the
header "Library Reference (8 Libraries)" to the correct number "Library
Reference (9 Libraries)" and verify any other occurrences of the count in this
document (e.g., architecture/summary sections) to match; confirm the table
entries (file-logger.ts, paths.ts, identity.ts, time.ts, sanitizer.ts,
injection-patterns.ts, learning-utils.ts, model-config.ts, db-utils.ts) are all
intended and if not remove the extra row instead of changing the header. Ensure
the header text and any summary mentions use the same numeric value so the
document is consistent.
In @.opencode/VoiceServer/pronunciations.json:
- Around line 1-6: Update the _comment entry in pronunciations.json to point to
the correct source-of-truth path or create the referenced file: either change
"_comment" to reference ".opencode/skills/PAI/USER/PRONUNCIATIONS.md" or add
that file under .opencode/skills/PAI/USER with the documentation for the entries
in the "replacements" array (e.g., PAI and ISC); ensure the string in "_comment"
matches the actual file location so the repository reference is accurate.
In @.opencode/VoiceServer/status.sh:
- Around line 59-75: Update the status block to prefer TTS_PROVIDER (and the
chosen voice variable, e.g., TTS_VOICE or dynamic "default") rather than only
checking ELEVENLABS_API_KEY/VOICE_ID: read TTS_PROVIDER from ENV_FILE (falling
back to checking ELEVENLABS_API_KEY for compatibility), print "Provider:
$TTS_PROVIDER" and the resolved voice id/name (e.g., TTS_VOICE or
ELEVENLABS_VOICE_ID when provider is elevenlabs), and only show the macOS 'say'
fallback when no provider is configured; adjust the conditional logic around
ENV_FILE, ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, TTS_PROVIDER and TTS_VOICE to
reflect this.
---
Nitpick comments:
In @.opencode/VoiceServer/install.sh:
- Around line 54-56: The current parsing of ELEVENLABS_API_KEY in ENV_FILE can
mis-handle quoted values, whitespace, or multiple occurrences; update the logic
that sets API_KEY to: search only for lines that begin with the exact key (use a
caret-anchored match for ELEVENLABS_API_KEY=), take the last matching line if
multiple exist, split on the first '=' to get the full RHS, then trim
leading/trailing whitespace and any surrounding single or double quotes before
assigning to API_KEY and performing the existing checks (API_KEY !=
"your_api_key_here" and -n check). Ensure these steps are applied where ENV_FILE
and API_KEY are referenced so quoted or spaced values and duplicate keys are
handled robustly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5dcbd132-a341-406d-83b8-fb019056c3df
📒 Files selected for processing (74)
.opencode/PAI/Algorithm/v3.7.0.md.opencode/PAI/DOCUMENTATIONINDEX.md.opencode/PAI/PIPELINES.md.opencode/PAI/SKILL.md.opencode/PAI/THEHOOKSYSTEM.md.opencode/PAI/THEPLUGINSYSTEM.md.opencode/PAI/Tools/GenerateSkillIndex.ts.opencode/PAI/Tools/SkillSearch.ts.opencode/PAI/Tools/ValidateSkillStructure.ts.opencode/PAI/Tools/algorithm.ts.opencode/PAI/doc-dependencies.json.opencode/PAISECURITYSYSTEM/HOOKS.md.opencode/VoiceServer/README.md.opencode/VoiceServer/install.sh.opencode/VoiceServer/logs/README.md.opencode/VoiceServer/menubar/install-menubar.sh.opencode/VoiceServer/menubar/pai-voice.5s.sh.opencode/VoiceServer/pronunciations.json.opencode/VoiceServer/restart.sh.opencode/VoiceServer/server.ts.opencode/VoiceServer/start.sh.opencode/VoiceServer/status.sh.opencode/VoiceServer/stop.sh.opencode/VoiceServer/uninstall.sh.opencode/VoiceServer/voices.json.opencode/agents/Algorithm.md.opencode/skills/Utilities/AudioEditor/SKILL.md.opencode/skills/Utilities/AudioEditor/Tools/Analyze.help.md.opencode/skills/Utilities/AudioEditor/Tools/Analyze.ts.opencode/skills/Utilities/AudioEditor/Tools/Edit.help.md.opencode/skills/Utilities/AudioEditor/Tools/Edit.ts.opencode/skills/Utilities/AudioEditor/Tools/Pipeline.help.md.opencode/skills/Utilities/AudioEditor/Tools/Pipeline.ts.opencode/skills/Utilities/AudioEditor/Tools/Polish.help.md.opencode/skills/Utilities/AudioEditor/Tools/Polish.ts.opencode/skills/Utilities/AudioEditor/Tools/Transcribe.help.md.opencode/skills/Utilities/AudioEditor/Tools/Transcribe.ts.opencode/skills/Utilities/AudioEditor/Workflows/Clean.md.opencode/skills/Utilities/CodeReview/SKILL.md.opencode/skills/Utilities/OpenCodeSystem/SKILL.md.opencode/skills/Utilities/Sales/SKILL.md.opencode/skills/Utilities/Sales/Workflows/CreateNarrative.md.opencode/skills/Utilities/Sales/Workflows/CreateSalesPackage.md.opencode/skills/Utilities/Sales/Workflows/CreateVisual.md.opencode/skills/Utilities/System/SKILL.md.opencode/skills/Utilities/System/Tools/CreateUpdate.ts.opencode/skills/Utilities/System/Tools/SecretScan.ts.opencode/skills/Utilities/System/Tools/UpdateIndex.ts.opencode/skills/Utilities/System/Tools/UpdateSearch.ts.opencode/skills/Utilities/System/Workflows/CrossRepoValidation.md.opencode/skills/Utilities/System/Workflows/DocumentRecent.md.opencode/skills/Utilities/System/Workflows/DocumentSession.md.opencode/skills/Utilities/System/Workflows/GitPush.md.opencode/skills/Utilities/System/Workflows/IntegrityCheck.md.opencode/skills/Utilities/System/Workflows/PrivacyCheck.md.opencode/skills/Utilities/System/Workflows/SecretScanning.md.opencode/skills/Utilities/System/Workflows/WorkContextRecall.md.opencode/skills/Utilities/WriteStory/AestheticProfiles.md.opencode/skills/Utilities/WriteStory/AntiCliche.md.opencode/skills/Utilities/WriteStory/Critics.md.opencode/skills/Utilities/WriteStory/RhetoricalFigures.md.opencode/skills/Utilities/WriteStory/SKILL.md.opencode/skills/Utilities/WriteStory/StorrFramework.md.opencode/skills/Utilities/WriteStory/StoryLayers.md.opencode/skills/Utilities/WriteStory/StoryStructures.md.opencode/skills/Utilities/WriteStory/Workflows/BuildBible.md.opencode/skills/Utilities/WriteStory/Workflows/Explore.md.opencode/skills/Utilities/WriteStory/Workflows/Interview.md.opencode/skills/Utilities/WriteStory/Workflows/Revise.md.opencode/skills/Utilities/WriteStory/Workflows/WriteChapter.md.opencode/skills/skill-index.json.prd/PRD-20260309-coderabbit-pr47-fixes.md.prd/PRD-20260309-installer-refactor.mddocs/architecture/INSTALLER-REFACTOR-PLAN.md
💤 Files with no reviewable changes (3)
- .prd/PRD-20260309-coderabbit-pr47-fixes.md
- .prd/PRD-20260309-installer-refactor.md
- docs/architecture/INSTALLER-REFACTOR-PLAN.md
Fixes verified against actual code: Tools path resolution: - SkillSearch.ts: INDEX_FILE pointed to PAI/Skills/ instead of skills/ - ValidateSkillStructure.ts: SKILLS_DIR went 3 levels up instead of 2 - Both now match GenerateSkillIndex.ts (.opencode/skills/) Symlink cycle prevention: - GenerateSkillIndex.ts: findSkillFiles now tracks visited canonical paths - ValidateSkillStructure.ts: scanDirectory now populates visitedPaths Set Error handling: - GenerateSkillIndex.ts + SkillSearch.ts: main().catch now exits non-zero Documentation accuracy: - doc-dependencies.json: add missing THEPLUGINSYSTEM.md entry - THEHOOKSYSTEM.md: PAI_DIR example $HOME/.claude → $HOME/.opencode - THEPLUGINSYSTEM.md: library count 8 → 9 (matches table) - pronunciations.json: stale skills/PAI/USER/ → PAI/USER/ path Stats accuracy: - ValidateSkillStructure.ts: category SKILL.md files no longer counted as flat skills - pai-voice.5s.sh: PAI_DIR default $HOME/.claude → $HOME/.opencode Skipped (verified not applicable): - PIPELINES.md paths: upstream semantics, both paths correct in context - VoiceServer shell script hardening: upstream 4.0.3 files, out of scope - PAISECURITYSYSTEM/HOOKS.md: doc is factually accurate for hook architecture - THEHOOKSYSTEM.md voiceId examples: settings.json config docs, not hardcoded IDs
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
.opencode/PAI/Tools/GenerateSkillIndex.ts (1)
86-90: Consider using asyncstatfor consistency.The function uses async file operations throughout but switches to synchronous
existsSynchere. While not a bug, usingaccessfromfs/promiseswould maintain consistency.♻️ Optional refactor for async consistency
+import { readdir, readFile, writeFile, stat, realpath, access } from 'fs/promises'; +import { constants } from 'fs'; import { join, relative, sep } from 'path'; -import { existsSync } from 'fs';// Check for SKILL.md in this directory const skillMdPath = join(fullPath, 'SKILL.md'); - if (existsSync(skillMdPath)) { + try { + await access(skillMdPath, constants.F_OK); skillFiles.push(skillMdPath); + } catch { + // SKILL.md doesn't exist in this directory }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/PAI/Tools/GenerateSkillIndex.ts around lines 86 - 90, The code currently uses the synchronous existsSync to check SKILL.md (skillMdPath) which breaks the async pattern; replace the existsSync check with an async fs.promises access/stat call (e.g., await access(skillMdPath) or await stat(skillMdPath)) and only push to skillFiles when the promise resolves, handling failures with a try/catch so the function remains fully async; also update imports to use fs/promises or destructure access/stat from 'fs/promises' and adjust the surrounding function to await this check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.opencode/PAI/THEHOOKSYSTEM.md:
- Around line 493-495: The markdown has fenced code blocks missing language
identifiers (MD040) for the blocks that contain the template line
"YYYY-MM-DD-HHMMSS_TYPE_description.md" and the "HOOK LIFECYCLE:" section;
update each opening fence from ``` to ```text (or another appropriate language
label) so the blocks become ```text ... ``` to satisfy the linter and keep the
content unchanged inside the fences; ensure the same change is applied to the
other block referenced (the one beginning with "HOOK LIFECYCLE:" around the
second range).
- Around line 381-485: Update the doc text and examples that currently instruct
hardcoding an ElevenLabs voice ID to instead show the new
default/provider-resolution flow: state that daidentity.voiceId may be "default"
(or a provider-specific id), that runtime provider is selected via the
TTS_PROVIDER env var or per-agent voice registry, and update the example calls
and the VoiceNotification pattern (references: getIdentity, getVoiceId,
identity.voiceId, handlers/VoiceNotification.ts) to use voice_id: "default" and
include a short note about provider resolution and where to configure
provider-specific IDs if needed; apply the same change to all referenced
sections (including the earlier identity examples and the VoiceNotification
payload) so examples no longer instruct hardcoding ElevenLabs IDs.
- Around line 360-368: Update the conflicting PAI_DIR defaults by choosing one
canonical value and making both occurrences consistent: replace the JSON example
value "$HOME/.opencode" and the descriptive text "typically `~/.claude`" so they
both state the same default (e.g., "$HOME/.opencode" or "~/.claude"); ensure the
documentation still notes that hook scripts reference ${PAI_DIR} in command
paths and that the chosen value is used throughout the section and examples
(search for the PAI_DIR example and the "typically" sentence to locate the
edits).
In @.opencode/PAI/Tools/GenerateSkillIndex.ts:
- Around line 358-368: The token-estimate math can divide by zero when
index.totalSkills is 0 because currentTokens = index.totalSkills * avgFullTokens
may be 0; update the calculation around currentTokens/newTokens/savings
(referencing avgFullTokens, avgMinimalTokens, currentTokens, newTokens, savings,
and index.totalSkills) to guard against division by zero—compute savings only if
currentTokens > 0 (otherwise set savings to "0.0" or "0%") and ensure the
console output uses that safe value.
---
Nitpick comments:
In @.opencode/PAI/Tools/GenerateSkillIndex.ts:
- Around line 86-90: The code currently uses the synchronous existsSync to check
SKILL.md (skillMdPath) which breaks the async pattern; replace the existsSync
check with an async fs.promises access/stat call (e.g., await
access(skillMdPath) or await stat(skillMdPath)) and only push to skillFiles when
the promise resolves, handling failures with a try/catch so the function remains
fully async; also update imports to use fs/promises or destructure access/stat
from 'fs/promises' and adjust the surrounding function to await this check.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 65ce1a29-c3ec-4c8c-9781-89c08b34e896
📒 Files selected for processing (8)
.opencode/PAI/THEHOOKSYSTEM.md.opencode/PAI/THEPLUGINSYSTEM.md.opencode/PAI/Tools/GenerateSkillIndex.ts.opencode/PAI/Tools/SkillSearch.ts.opencode/PAI/Tools/ValidateSkillStructure.ts.opencode/PAI/doc-dependencies.json.opencode/VoiceServer/menubar/pai-voice.5s.sh.opencode/VoiceServer/pronunciations.json
✅ Files skipped from review due to trivial changes (4)
- .opencode/PAI/doc-dependencies.json
- .opencode/PAI/THEPLUGINSYSTEM.md
- .opencode/PAI/Tools/SkillSearch.ts
- .opencode/VoiceServer/menubar/pai-voice.5s.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- .opencode/VoiceServer/pronunciations.json
- .opencode/PAI/Tools/ValidateSkillStructure.ts
.opencode/PAI/THEHOOKSYSTEM.md
Outdated
| "PAI_DIR": "$HOME/.opencode", | ||
| "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "64000" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Key Variables:** | ||
| - `PAI_DIR` - PAI installation directory (typically `~/.claude`) | ||
| - Hook scripts reference `${PAI_DIR}` in command paths |
There was a problem hiding this comment.
Conflicting PAI_DIR defaults in the same section
Line 360 documents PAI_DIR as $HOME/.opencode, but Line 367 says it is typically ~/.claude. This contradiction can lead to broken hook paths during setup. Please keep one canonical default across both lines.
Suggested doc fix
- - `PAI_DIR` - PAI installation directory (typically `~/.claude`)
+ - `PAI_DIR` - PAI installation directory (typically `~/.opencode`)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/PAI/THEHOOKSYSTEM.md around lines 360 - 368, Update the
conflicting PAI_DIR defaults by choosing one canonical value and making both
occurrences consistent: replace the JSON example value "$HOME/.opencode" and the
descriptive text "typically `~/.claude`" so they both state the same default
(e.g., "$HOME/.opencode" or "~/.claude"); ensure the documentation still notes
that hook scripts reference ${PAI_DIR} in command paths and that the chosen
value is used throughout the section and examples (search for the PAI_DIR
example and the "typically" sentence to locate the edits).
.opencode/PAI/THEHOOKSYSTEM.md
Outdated
| "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. | ||
|
|
There was a problem hiding this comment.
Voice configuration guidance appears stale vs dynamic provider routing
This section still instructs explicit ElevenLabs voice-ID configuration (e.g., Line 381, Line 472, Line 484, Line 828), but this PR’s objective states voice_id: "default" with provider resolution via TTS_PROVIDER. The current text can misconfigure new installs.
Suggested doc alignment update
- "voiceId": "{YourElevenLabsVoiceId}"
+ "voiceId": "default"- voice_id: identity.voiceId // From settings.json
+ voice_id: "default" // Provider-specific voice resolved server-side via TTS_PROVIDER-Each agent can have a unique ElevenLabs voice configured. See the Agents skill for voice registry.
+Voice resolution is provider-aware and handled by VoiceServer using `TTS_PROVIDER`.
+Use `"default"` unless overriding with a provider-specific mapping.Also applies to: 828-843, 1151-1154
🧰 Tools
🪛 LanguageTool
[grammar] ~483-~483: Ensure spelling is correct
Context: ...Configure voice IDs via settings.json daidentity section or environment variables. Each ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/PAI/THEHOOKSYSTEM.md around lines 381 - 485, Update the doc text
and examples that currently instruct hardcoding an ElevenLabs voice ID to
instead show the new default/provider-resolution flow: state that
daidentity.voiceId may be "default" (or a provider-specific id), that runtime
provider is selected via the TTS_PROVIDER env var or per-agent voice registry,
and update the example calls and the VoiceNotification pattern (references:
getIdentity, getVoiceId, identity.voiceId, handlers/VoiceNotification.ts) to use
voice_id: "default" and include a short note about provider resolution and where
to configure provider-specific IDs if needed; apply the same change to all
referenced sections (including the earlier identity examples and the
VoiceNotification payload) so examples no longer instruct hardcoding ElevenLabs
IDs.
.opencode/PAI/THEHOOKSYSTEM.md
Outdated
| ``` | ||
| YYYY-MM-DD-HHMMSS_TYPE_description.md | ||
| ``` |
There was a problem hiding this comment.
Add language identifiers to fenced code blocks
The fenced blocks starting at Line 493 and Line 1078 are missing language tags (MD040). Add explicit languages to keep lint clean.
Suggested markdown fix
-```
+```text
YYYY-MM-DD-HHMMSS_TYPE_description.md
-```
+```-```
+```text
HOOK LIFECYCLE:
...
-```
+```Also applies to: 1078-1155
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 493-493: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/PAI/THEHOOKSYSTEM.md around lines 493 - 495, The markdown has
fenced code blocks missing language identifiers (MD040) for the blocks that
contain the template line "YYYY-MM-DD-HHMMSS_TYPE_description.md" and the "HOOK
LIFECYCLE:" section; update each opening fence from ``` to ```text (or another
appropriate language label) so the blocks become ```text ... ``` to satisfy the
linter and keep the content unchanged inside the fences; ensure the same change
is applied to the other block referenced (the one beginning with "HOOK
LIFECYCLE:" around the second range).
…tion (ADR-001) - Delete .opencode/PAI/THEHOOKSYSTEM.md (superseded by THEPLUGINSYSTEM.md) - Delete .opencode/PAISECURITYSYSTEM/HOOKS.md (superseded by PLUGINS.md) - Update doc-dependencies.json: remove THEHOOKSYSTEM.md entry, rename hook-system section to plugin-system referencing THEPLUGINSYSTEM.md - Update PAISECURITYSYSTEM/ARCHITECTURE.md: all hook→plugin terminology, execution flow updated for plugin model (throw Error, not exit codes) - Update PAISECURITYSYSTEM/README.md: hook→plugin in prose and file tree - Fix GenerateSkillIndex.ts division by zero when totalSkills == 0
…ver/, PAI root docs, voice ID fix (#83) * refactor: move orphan skills into Utilities/ category Moved 6 flat-level skills into Utilities/ for PAI 4.0.3 parity: - AudioEditor → Utilities/AudioEditor (was missing from Utilities) - CodeReview → Utilities/CodeReview (OpenCode-specific) - OpenCodeSystem → Utilities/OpenCodeSystem (OpenCode-specific) - Sales → Utilities/Sales (OpenCode-specific) - System → Utilities/System (OpenCode-specific) - WriteStory → Utilities/WriteStory (OpenCode-specific) OpenCode-specific skills preserved in Utilities/ category. * feat: add missing PAI/ root docs from 4.0.3 Added 3 files missing from PAI/ root: - doc-dependencies.json (documentation dependency graph) - PIPELINES.md (pipeline system documentation) - THEHOOKSYSTEM.md (hooks/plugin system documentation) All .claude/ references replaced with .opencode/. * refactor: rename voice-server/ to VoiceServer/ + add missing files Renamed: voice-server/ → VoiceServer/ (CamelCase, matches PAI 4.0.3) Added missing files from PAI 4.0.3: - install.sh, start.sh, stop.sh, restart.sh, status.sh, uninstall.sh - voices.json, pronunciations.json - menubar/ directory All .claude/ references replaced with .opencode/ in copied files. * fix: restore missing files + remove legacy content from skills/PAI/ deletion Restored from git history (accidentally deleted with skills/PAI/): - PAI/THEPLUGINSYSTEM.md (OpenCode replacement for THEHOOKSYSTEM.md) - PAI/Tools/GenerateSkillIndex.ts + ValidateSkillStructure.ts + SkillSearch.ts - PAI/PIPELINES.md, PAI/doc-dependencies.json - PAISECURITYSYSTEM/HOOKS.md Removed legacy files NOT in 4.0.3 upstream: - PAI/BACKUPS.md, BROWSERAUTOMATION.md, CONSTITUTION.md, RESPONSEFORMAT.md, SCRAPINGREFERENCE.md, TERMINALTABS.md (all pre-v3.0 artifacts) - PAI/UPDATES/ directory (not in 4.0.3) - PAI/Workflows/ (11 legacy workflows not in 4.0.3 root) - PAI/USER/ reset to 4.0.3 template (README.md placeholders only, removed 47 personal template files that don't belong in public repo) Fixed references: THEHOOKSYSTEM → THEPLUGINSYSTEM in SKILL.md and DOCUMENTATIONINDEX.md Regenerated skill-index.json (52 skills, 7 categories) PAI/ root now matches 4.0.3 exactly, with only 2 justified deviations: - THEPLUGINSYSTEM.md (replaces THEHOOKSYSTEM.md per ADR-001) - MINIMAL_BOOTSTRAP.md (OpenCode lazy loading, created in v3.0 WP-C) * chore: remove INSTALLER-REFACTOR-PLAN.md (internal planning doc) * fix: replace all hardcoded ElevenLabs voice IDs with dynamic routing VoiceServer sendNotification() now uses title (agent name) instead of voiceId for voice resolution. This enables dynamic provider switching via TTS_PROVIDER env var (google/elevenlabs/macos). Changed: voice_id:'fTtv3eikoepIosk8dTZ5' → voice_id:'default' + title:'AgentName' Files affected: - VoiceServer/server.ts (line 425: voiceId → safeTitle) - PAI/SKILL.md (7 phase curls) - PAI/Algorithm/v3.7.0.md (2 voice curls) - agents/Algorithm.md (frontmatter + 3 references) - PAI/Tools/algorithm.ts (VOICE_ID constant) Zero hardcoded ElevenLabs IDs remain in the codebase. * fix: address CodeRabbit review findings on PR #83 Fixes verified against actual code: Tools path resolution: - SkillSearch.ts: INDEX_FILE pointed to PAI/Skills/ instead of skills/ - ValidateSkillStructure.ts: SKILLS_DIR went 3 levels up instead of 2 - Both now match GenerateSkillIndex.ts (.opencode/skills/) Symlink cycle prevention: - GenerateSkillIndex.ts: findSkillFiles now tracks visited canonical paths - ValidateSkillStructure.ts: scanDirectory now populates visitedPaths Set Error handling: - GenerateSkillIndex.ts + SkillSearch.ts: main().catch now exits non-zero Documentation accuracy: - doc-dependencies.json: add missing THEPLUGINSYSTEM.md entry - THEHOOKSYSTEM.md: PAI_DIR example $HOME/.claude → $HOME/.opencode - THEPLUGINSYSTEM.md: library count 8 → 9 (matches table) - pronunciations.json: stale skills/PAI/USER/ → PAI/USER/ path Stats accuracy: - ValidateSkillStructure.ts: category SKILL.md files no longer counted as flat skills - pai-voice.5s.sh: PAI_DIR default $HOME/.claude → $HOME/.opencode Skipped (verified not applicable): - PIPELINES.md paths: upstream semantics, both paths correct in context - VoiceServer shell script hardening: upstream 4.0.3 files, out of scope - PAISECURITYSYSTEM/HOOKS.md: doc is factually accurate for hook architecture - THEHOOKSYSTEM.md voiceId examples: settings.json config docs, not hardcoded IDs * fix: delete THEHOOKSYSTEM.md and HOOKS.md, complete hook→plugin migration (ADR-001) - Delete .opencode/PAI/THEHOOKSYSTEM.md (superseded by THEPLUGINSYSTEM.md) - Delete .opencode/PAISECURITYSYSTEM/HOOKS.md (superseded by PLUGINS.md) - Update doc-dependencies.json: remove THEHOOKSYSTEM.md entry, rename hook-system section to plugin-system referencing THEPLUGINSYSTEM.md - Update PAISECURITYSYSTEM/ARCHITECTURE.md: all hook→plugin terminology, execution flow updated for plugin model (throw Error, not exit codes) - Update PAISECURITYSYSTEM/README.md: hook→plugin in prose and file tree - Fix GenerateSkillIndex.ts division by zero when totalSkills == 0
…ver/, PAI root docs, voice ID fix (#83) * refactor: move orphan skills into Utilities/ category Moved 6 flat-level skills into Utilities/ for PAI 4.0.3 parity: - AudioEditor → Utilities/AudioEditor (was missing from Utilities) - CodeReview → Utilities/CodeReview (OpenCode-specific) - OpenCodeSystem → Utilities/OpenCodeSystem (OpenCode-specific) - Sales → Utilities/Sales (OpenCode-specific) - System → Utilities/System (OpenCode-specific) - WriteStory → Utilities/WriteStory (OpenCode-specific) OpenCode-specific skills preserved in Utilities/ category. * feat: add missing PAI/ root docs from 4.0.3 Added 3 files missing from PAI/ root: - doc-dependencies.json (documentation dependency graph) - PIPELINES.md (pipeline system documentation) - THEHOOKSYSTEM.md (hooks/plugin system documentation) All .claude/ references replaced with .opencode/. * refactor: rename voice-server/ to VoiceServer/ + add missing files Renamed: voice-server/ → VoiceServer/ (CamelCase, matches PAI 4.0.3) Added missing files from PAI 4.0.3: - install.sh, start.sh, stop.sh, restart.sh, status.sh, uninstall.sh - voices.json, pronunciations.json - menubar/ directory All .claude/ references replaced with .opencode/ in copied files. * fix: restore missing files + remove legacy content from skills/PAI/ deletion Restored from git history (accidentally deleted with skills/PAI/): - PAI/THEPLUGINSYSTEM.md (OpenCode replacement for THEHOOKSYSTEM.md) - PAI/Tools/GenerateSkillIndex.ts + ValidateSkillStructure.ts + SkillSearch.ts - PAI/PIPELINES.md, PAI/doc-dependencies.json - PAISECURITYSYSTEM/HOOKS.md Removed legacy files NOT in 4.0.3 upstream: - PAI/BACKUPS.md, BROWSERAUTOMATION.md, CONSTITUTION.md, RESPONSEFORMAT.md, SCRAPINGREFERENCE.md, TERMINALTABS.md (all pre-v3.0 artifacts) - PAI/UPDATES/ directory (not in 4.0.3) - PAI/Workflows/ (11 legacy workflows not in 4.0.3 root) - PAI/USER/ reset to 4.0.3 template (README.md placeholders only, removed 47 personal template files that don't belong in public repo) Fixed references: THEHOOKSYSTEM → THEPLUGINSYSTEM in SKILL.md and DOCUMENTATIONINDEX.md Regenerated skill-index.json (52 skills, 7 categories) PAI/ root now matches 4.0.3 exactly, with only 2 justified deviations: - THEPLUGINSYSTEM.md (replaces THEHOOKSYSTEM.md per ADR-001) - MINIMAL_BOOTSTRAP.md (OpenCode lazy loading, created in v3.0 WP-C) * chore: remove INSTALLER-REFACTOR-PLAN.md (internal planning doc) * fix: replace all hardcoded ElevenLabs voice IDs with dynamic routing VoiceServer sendNotification() now uses title (agent name) instead of voiceId for voice resolution. This enables dynamic provider switching via TTS_PROVIDER env var (google/elevenlabs/macos). Changed: voice_id:'fTtv3eikoepIosk8dTZ5' → voice_id:'default' + title:'AgentName' Files affected: - VoiceServer/server.ts (line 425: voiceId → safeTitle) - PAI/SKILL.md (7 phase curls) - PAI/Algorithm/v3.7.0.md (2 voice curls) - agents/Algorithm.md (frontmatter + 3 references) - PAI/Tools/algorithm.ts (VOICE_ID constant) Zero hardcoded ElevenLabs IDs remain in the codebase. * fix: address CodeRabbit review findings on PR #83 Fixes verified against actual code: Tools path resolution: - SkillSearch.ts: INDEX_FILE pointed to PAI/Skills/ instead of skills/ - ValidateSkillStructure.ts: SKILLS_DIR went 3 levels up instead of 2 - Both now match GenerateSkillIndex.ts (.opencode/skills/) Symlink cycle prevention: - GenerateSkillIndex.ts: findSkillFiles now tracks visited canonical paths - ValidateSkillStructure.ts: scanDirectory now populates visitedPaths Set Error handling: - GenerateSkillIndex.ts + SkillSearch.ts: main().catch now exits non-zero Documentation accuracy: - doc-dependencies.json: add missing THEPLUGINSYSTEM.md entry - THEHOOKSYSTEM.md: PAI_DIR example $HOME/.claude → $HOME/.opencode - THEPLUGINSYSTEM.md: library count 8 → 9 (matches table) - pronunciations.json: stale skills/PAI/USER/ → PAI/USER/ path Stats accuracy: - ValidateSkillStructure.ts: category SKILL.md files no longer counted as flat skills - pai-voice.5s.sh: PAI_DIR default $HOME/.claude → $HOME/.opencode Skipped (verified not applicable): - PIPELINES.md paths: upstream semantics, both paths correct in context - VoiceServer shell script hardening: upstream 4.0.3 files, out of scope - PAISECURITYSYSTEM/HOOKS.md: doc is factually accurate for hook architecture - THEHOOKSYSTEM.md voiceId examples: settings.json config docs, not hardcoded IDs * fix: delete THEHOOKSYSTEM.md and HOOKS.md, complete hook→plugin migration (ADR-001) - Delete .opencode/PAI/THEHOOKSYSTEM.md (superseded by THEPLUGINSYSTEM.md) - Delete .opencode/PAISECURITYSYSTEM/HOOKS.md (superseded by PLUGINS.md) - Update doc-dependencies.json: remove THEHOOKSYSTEM.md entry, rename hook-system section to plugin-system referencing THEPLUGINSYSTEM.md - Update PAISECURITYSYSTEM/ARCHITECTURE.md: all hook→plugin terminology, execution flow updated for plugin model (throw Error, not exit codes) - Update PAISECURITYSYSTEM/README.md: hook→plugin in prose and file tree - Fix GenerateSkillIndex.ts division by zero when totalSkills == 0
#83–#85) (#87) * refactor(v3.0 Part B/2): add correct structure — Utilities/, VoiceServer/, PAI root docs, voice ID fix (#83) * refactor: move orphan skills into Utilities/ category Moved 6 flat-level skills into Utilities/ for PAI 4.0.3 parity: - AudioEditor → Utilities/AudioEditor (was missing from Utilities) - CodeReview → Utilities/CodeReview (OpenCode-specific) - OpenCodeSystem → Utilities/OpenCodeSystem (OpenCode-specific) - Sales → Utilities/Sales (OpenCode-specific) - System → Utilities/System (OpenCode-specific) - WriteStory → Utilities/WriteStory (OpenCode-specific) OpenCode-specific skills preserved in Utilities/ category. * feat: add missing PAI/ root docs from 4.0.3 Added 3 files missing from PAI/ root: - doc-dependencies.json (documentation dependency graph) - PIPELINES.md (pipeline system documentation) - THEHOOKSYSTEM.md (hooks/plugin system documentation) All .claude/ references replaced with .opencode/. * refactor: rename voice-server/ to VoiceServer/ + add missing files Renamed: voice-server/ → VoiceServer/ (CamelCase, matches PAI 4.0.3) Added missing files from PAI 4.0.3: - install.sh, start.sh, stop.sh, restart.sh, status.sh, uninstall.sh - voices.json, pronunciations.json - menubar/ directory All .claude/ references replaced with .opencode/ in copied files. * fix: restore missing files + remove legacy content from skills/PAI/ deletion Restored from git history (accidentally deleted with skills/PAI/): - PAI/THEPLUGINSYSTEM.md (OpenCode replacement for THEHOOKSYSTEM.md) - PAI/Tools/GenerateSkillIndex.ts + ValidateSkillStructure.ts + SkillSearch.ts - PAI/PIPELINES.md, PAI/doc-dependencies.json - PAISECURITYSYSTEM/HOOKS.md Removed legacy files NOT in 4.0.3 upstream: - PAI/BACKUPS.md, BROWSERAUTOMATION.md, CONSTITUTION.md, RESPONSEFORMAT.md, SCRAPINGREFERENCE.md, TERMINALTABS.md (all pre-v3.0 artifacts) - PAI/UPDATES/ directory (not in 4.0.3) - PAI/Workflows/ (11 legacy workflows not in 4.0.3 root) - PAI/USER/ reset to 4.0.3 template (README.md placeholders only, removed 47 personal template files that don't belong in public repo) Fixed references: THEHOOKSYSTEM → THEPLUGINSYSTEM in SKILL.md and DOCUMENTATIONINDEX.md Regenerated skill-index.json (52 skills, 7 categories) PAI/ root now matches 4.0.3 exactly, with only 2 justified deviations: - THEPLUGINSYSTEM.md (replaces THEHOOKSYSTEM.md per ADR-001) - MINIMAL_BOOTSTRAP.md (OpenCode lazy loading, created in v3.0 WP-C) * chore: remove INSTALLER-REFACTOR-PLAN.md (internal planning doc) * fix: replace all hardcoded ElevenLabs voice IDs with dynamic routing VoiceServer sendNotification() now uses title (agent name) instead of voiceId for voice resolution. This enables dynamic provider switching via TTS_PROVIDER env var (google/elevenlabs/macos). Changed: voice_id:'fTtv3eikoepIosk8dTZ5' → voice_id:'default' + title:'AgentName' Files affected: - VoiceServer/server.ts (line 425: voiceId → safeTitle) - PAI/SKILL.md (7 phase curls) - PAI/Algorithm/v3.7.0.md (2 voice curls) - agents/Algorithm.md (frontmatter + 3 references) - PAI/Tools/algorithm.ts (VOICE_ID constant) Zero hardcoded ElevenLabs IDs remain in the codebase. * fix: address CodeRabbit review findings on PR #83 Fixes verified against actual code: Tools path resolution: - SkillSearch.ts: INDEX_FILE pointed to PAI/Skills/ instead of skills/ - ValidateSkillStructure.ts: SKILLS_DIR went 3 levels up instead of 2 - Both now match GenerateSkillIndex.ts (.opencode/skills/) Symlink cycle prevention: - GenerateSkillIndex.ts: findSkillFiles now tracks visited canonical paths - ValidateSkillStructure.ts: scanDirectory now populates visitedPaths Set Error handling: - GenerateSkillIndex.ts + SkillSearch.ts: main().catch now exits non-zero Documentation accuracy: - doc-dependencies.json: add missing THEPLUGINSYSTEM.md entry - THEHOOKSYSTEM.md: PAI_DIR example $HOME/.claude → $HOME/.opencode - THEPLUGINSYSTEM.md: library count 8 → 9 (matches table) - pronunciations.json: stale skills/PAI/USER/ → PAI/USER/ path Stats accuracy: - ValidateSkillStructure.ts: category SKILL.md files no longer counted as flat skills - pai-voice.5s.sh: PAI_DIR default $HOME/.claude → $HOME/.opencode Skipped (verified not applicable): - PIPELINES.md paths: upstream semantics, both paths correct in context - VoiceServer shell script hardening: upstream 4.0.3 files, out of scope - PAISECURITYSYSTEM/HOOKS.md: doc is factually accurate for hook architecture - THEHOOKSYSTEM.md voiceId examples: settings.json config docs, not hardcoded IDs * fix: delete THEHOOKSYSTEM.md and HOOKS.md, complete hook→plugin migration (ADR-001) - Delete .opencode/PAI/THEHOOKSYSTEM.md (superseded by THEPLUGINSYSTEM.md) - Delete .opencode/PAISECURITYSYSTEM/HOOKS.md (superseded by PLUGINS.md) - Update doc-dependencies.json: remove THEHOOKSYSTEM.md entry, rename hook-system section to plugin-system referencing THEPLUGINSYSTEM.md - Update PAISECURITYSYSTEM/ARCHITECTURE.md: all hook→plugin terminology, execution flow updated for plugin model (throw Error, not exit codes) - Update PAISECURITYSYSTEM/README.md: hook→plugin in prose and file tree - Fix GenerateSkillIndex.ts division by zero when totalSkills == 0 * feat(installer): add anthropic-max preset for Max/Pro OAuth subscription (#84) * feat(installer): add anthropic-max preset for Max/Pro OAuth subscription Adds 'anthropic-max' as a built-in installer preset so users with an existing Anthropic Max or Pro subscription can use PAI-OpenCode without paying extra for API keys. ## What's new ### Provider preset - provider-models.ts: add 'anthropic-max' to ProviderName union, PROVIDER_MODELS, and PROVIDER_LABELS (label: 'Anthropic Max/Pro (OAuth)') - Model tiers: haiku-4-5 / sonnet-4-6 / opus-4-6 — same models as 'anthropic' but routed through OAuth instead of an API key ### Installer engine (steps-fresh.ts) - installAnthropicMaxBridge(): new helper function that: 1. Copies .opencode/plugins/anthropic-max-bridge.js to the local plugins dir 2. Extracts the OAuth token from macOS Keychain (service: Claude Code-credentials) 3. Parses claudeAiOauth.{accessToken,refreshToken,expiresAt} 4. Writes token to ~/.local/share/opencode/auth.json under the 'anthropic' key 5. Returns success + hours remaining, non-throwing (install continues on failure) - stepInstallPAI(): call installAnthropicMaxBridge() when provider === 'anthropic-max' - runFreshInstall(): skip API key prompt for anthropic-max, show Claude CLI check message ### CLI (quick-install.ts) - --preset anthropic-max works without --api-key (all other presets still require it) - Inline instructions printed when anthropic-max selected - Updated help text and examples ### New files - .opencode/plugins/anthropic-max-bridge.js: 80-line minimal plugin (3 API fixes only) Fix 1: system prompt array-of-objects format (prevents HTTP 400) Fix 2: anthropic-beta: oauth-2025-04-20 header (prevents HTTP 401) Fix 3: Authorization: Bearer <token> instead of x-api-key - PAI-Install/anthropic-max-refresh.sh: one-command token refresh after expiry - docs/providers/anthropic-max.md: user-facing setup guide, troubleshooting, tech details ## Usage Interactive: bash install.sh → choose 'Anthropic Max/Pro (OAuth)' Headless: bun PAI-Install/cli/quick-install.ts --preset anthropic-max --name 'User' Token refresh (every ~8-12 hours): bash PAI-Install/anthropic-max-refresh.sh ## Notes - macOS only (requires Keychain access) - Requires Claude Code CLI installed and authenticated - Using OAuth tokens in third-party tools may violate Anthropic ToS - Non-fatal: if Keychain extraction fails, install continues with a warning * fix(anthropic-max): address CodeRabbit findings + add standalone contrib package CodeRabbit fixes (PR #84): - anthropic-max-refresh.sh: use quoted heredoc ('PYEOF') and export tokens as env vars to prevent shell injection via token values - anthropic-max-refresh.sh: add validation for REFRESH_TOKEN (non-empty) and EXPIRES_AT (numeric, >0) before writing auth.json - steps-fresh.ts: fix progress regression — onProgress(92) called after 95% steps; corrected to 96 - steps-fresh.ts: replace brittle import.meta.url.replace('file://', '') with fileURLToPath(new URL(import.meta.url)) from node:url - anthropic-max-bridge.js: add explicit comment explaining the authorize block returns { type: 'failed' } intentionally (tokens come from auth.json, not from an OAuth redirect flow) - docs/providers/anthropic-max.md: add YAML frontmatter (title, tags, published, type, summary) and convert tip block to Obsidian callout syntax Goal 2 — standalone extractable package: - contrib/anthropic-max-bridge/ — self-contained, no Bun/PAI required - install.sh (bash only, injection-safe heredoc, validates all tokens) - refresh-token.sh (same safety fixes as PAI-Install version) - plugins/anthropic-max-bridge.js (identical to .opencode/plugins/) - README.md (quick-start focused, team-sharing notes) - TECHNICAL.md (curl proof, token structure, what we ruled out) - docs/providers/anthropic-max.md: Option B now points to contrib/ in this repo instead of the external jeremy-opencode repository * fix(anthropic-max): address second-round CodeRabbit findings - anthropic-max-bridge.js: guard experimental.chat.system.transform against output.system being undefined/null/non-array before calling .map(); normalise to [] if missing, wrap bare string/object into array, then apply existing string->{type,text} conversion - contrib/anthropic-max-bridge/install.sh: chmod auth.json to 0o600 (owner read/write only) immediately after writing via os.chmod + stat module; mirrors the chmodSync(authFile, 0o600) already present in steps-fresh.ts - steps-fresh.ts: add zero-case message when hoursRemaining === 0 ('Token installed — expired or valid for less than 1 hour. Run anthropic-max-refresh.sh now.'); Math.max(0, ...) clamp was already present * fix(anthropic-max): check Bun.spawn exit code for Keychain lookup Previously proc.exited was awaited but its return value discarded, so a non-zero exit from the 'security' command (e.g. item not found) was only caught via an empty-stdout check. Stdout and stderr are now read concurrently with proc.exited via Promise.all to avoid pipe deadlock; the exit code is inspected before accepting keychainJson, and stderr is surfaced in the error message when present. * nitpick(steps-fresh): add warning when refresh token is missing When extracting OAuth tokens from Keychain, warn users if the refresh token is undefined. This alerts them that they'll need to re-authenticate with 'claude' when the access token expires. * feat(oauth): add runtime auto-refresh token bridge (#85) * feat(oauth): add runtime auto-refresh token bridge - Add anthropic-token-bridge.js/.ts: checks token every 5 messages, auto-refreshes from macOS Keychain when <2h remaining - Add lib/token-utils.ts + lib/refresh-manager.ts: token lifecycle logic (3 strategies: Keychain → claude setup-token → exchange API) - Add info()/warn()/error() wrappers to file-logger.ts (needed by token bridge) - Add anthropic-token-bridge.js to contrib/anthropic-max-bridge/plugins/ - Update contrib/install.sh: copies both plugins, updates token refresh note * docs(contrib): update README + TECHNICAL for auto-refresh token bridge - README: Token Expiry → auto-refresh primary, manual fallback secondary - README: File Reference → add anthropic-token-bridge.js entry - README: install.sh step → mention both plugins copied - README: token flow diagram → show both plugins - README: HTTP 401 troubleshooting → mention auto-refresh failed - TECHNICAL: Token lifetime → replace manual-only with auto-refresh details * fix(oauth): address CI findings in token bridge - token-utils.ts: os.homedir() instead of process.env.HOME (avoids unexpanded ~) - refresh-manager.ts: remove dead promisify(spawn), add execCommand timeout (15s), add pragma: allowlist secret on Keychain extractions, mask token in logs - anthropic-token-bridge.ts/.js: read role from output.message instead of input - Regenerate compiled .js from fixed TS sources (both plugin + contrib copy) * fix(oauth): parse claudeAiOauth nested structure from Keychain The macOS Keychain stores credentials as { claudeAiOauth: { accessToken, refreshToken } } but the plugin was looking for flat structure. This caused auto-refresh to fail with hasAccess: false, hasRefresh: false errors, falling back to browser OAuth flow. Now correctly extracts from nested structure with fallback for legacy formats. * fix: address CodeRabbit review findings on PR #87 Security: - auth.json now written with mode 0600 (token-utils, compiled JS, contrib) - Validate access token and expires field before reporting healthy status - Refresh scripts check for already-expired Keychain tokens before writing - Refresh scripts restore chmod 600 after rewriting auth.json - quick-install.ts fails fast on non-macOS for anthropic-max preset Correctness: - Use actual Keychain expiresAt instead of hardcoded 8h TTL - Token bridge attempts refresh on no_anthropic_config (not just expiry) - Installer copies anthropic-token-bridge.js alongside the bridge plugin - contrib anthropic-max-bridge.js gets defensive output.system null-safety - Remove non-existent FEEDSYSTEM.md from doc-dependencies.json - GenerateSkillIndex frontmatter regex accepts CRLF line endings - SkillSearch normalizes PascalCase names for natural language queries Shell scripts: - VoiceServer: sed replaces cut -d= -f2 to handle = in values - VoiceServer: remove unused RED/PLIST_PATH variables - restart.sh: check exit codes from stop.sh/start.sh Docs: - Add language tags to fenced code blocks in contrib README/TECHNICAL * fix: CodeRabbit round 2 - session-start validation, platform guards, token expiry logic Session-start improvements: - GenerateSkillIndex.ts: case-insensitive ALWAYS_LOADED_SKILLS matching - anthropic-token-bridge: enhanced session-start token validation - refresh-manager: improved expiresAt handling with null/undefined/past checks Platform guards: - VoiceServer/install.sh: curl --fail for notify endpoint - VoiceServer/uninstall.sh: Darwin platform guard + safe process kill - steps-fresh.ts: macOS platform gate for anthropic-max + expiresAt validation Token logic fixes: - steps-fresh.ts: skip provider API key for anthropic-max in .env - refresh-manager: AbortController 10s timeout for fetch - contrib plugin: mirror all fixes * fix(token-bridge): CRITICAL - synchronous token refresh at session start The 5-second setTimeout delay caused API calls to fail with expired tokens before refresh completed. Now refreshes immediately and synchronously when session starts with an expired/soon-expiring token. - Changed from setTimeout(5000) to immediate await - Added fallback async retry on failure - Applied to: .ts source + compiled .js + contrib plugin * fix(token-bridge): always sync Keychain→auth.json at session start Root cause fix for stale token issue: the session-start hook previously only checked auth.json's `expires` timestamp, which can be valid even when Anthropic has invalidated the token (e.g. after re-authentication). Now the hook always reads the Keychain first (source of truth) and writes it to auth.json before any expiry logic runs. If Keychain and auth.json tokens differ, a warning is logged and the fresh token is synced. Fallback path (Keychain unavailable or expired) still triggers full refreshAnthropicToken() as before. Also exports extractFromKeychain() from refresh-manager.ts so it can be used directly in the bridge hook. * fix: address PR #87 review comments contrib/anthropic-max-bridge/plugins/anthropic-token-bridge.js - Session-start hook now syncs Keychain→auth.json first (same pattern as main plugin); extractFromKeychain() result is awaited and wrapped in its own try/catch with fileLog/fileLogError; existing refreshAnthropicToken() / isRefreshInProgress() fallback path is unchanged .opencode/PAI/Tools/GenerateSkillIndex.ts - Duplicate skill name detection now throws an Error instead of warn+continue, making the build fail fast with a clear message .opencode/VoiceServer/install.sh - API_KEY extraction strips surrounding quotes, inline comments, and whitespace before comparing against sentinel values - ProgramArguments now point to a runtime wrapper (run-server.sh) that resolves Bun via PATH at launch time instead of baking in /Users/steffen/.bun/bin/bun - ELEVENLABS_API_KEY plist entry is only emitted when API_KEY is non-empty - Fixed sleep 2 with a polling health-check loop (60s timeout, 1s interval) that exits with an error if the server never responds .opencode/VoiceServer/uninstall.sh - Port cleanup now uses lsof -ti for PID-only output (no header line risk) and verifies each PID's command via ps before killing - Script directory echo now resolves symlinks via readlink -f / realpath * fix: address second round of PR #87 review comments .opencode/VoiceServer/install.sh - ENV_FILE now matches the server's path resolution: ${OPENCODE_DIR:-${PAI_DIR:-$HOME/.opencode}}/.env (was $HOME/.env) - API_KEY extraction uses grep -m1 to avoid concatenating multiple matching lines; inline-comment stripping now only removes trailing '#[^"']*$' (unquoted comments) so '#' inside a key value is preserved .opencode/plugins/lib/refresh-manager.ts .opencode/plugins/anthropic-token-bridge.js contrib/anthropic-max-bridge/plugins/anthropic-token-bridge.js - extractFromKeychain: destructure { stdout, stderr, exitCode } from execCommand and pass the real stderr into the error() call (was incorrectly passing stdout as stderr)
Summary
This is Part 2 of 2 of the v3.0 structure cleanup (split from #81 for CodeRabbit review — max ~100 files per PR).
Stacked on: #82 (Part A — must merge first)
What & Why (6 commits, 74 files)
1. Move orphan skills into
Utilities/category (44 files — renames)Skills that had no category home were moved into the new
Utilities/grouping:AudioEditor,CodeReview,OpenCodeSystem,Sales,System,WriteStory2. Add missing PAI/ root docs from 4.0.3 (3 files)
PIPELINES.md— pipeline documentationdoc-dependencies.json— dependency graphTHEPLUGINSYSTEM.md— restored from git history (ADR-001: Hooks → Plugins)3. Rename
voice-server/→VoiceServer/+ add 8 missing files (13 files)CamelCase rename to match PAI 4.0.3 convention, plus missing shell scripts and configs:
install.sh,restart.sh,start.sh,stop.sh,status.sh,uninstall.sh,pronunciations.json,voices.json, menubar scripts4. Restore missing files + remove legacy content (8 files)
PAISECURITYSYSTEM/HOOKS.md.opencode/PAI/Tools/scripts:GenerateSkillIndex.ts,SkillSearch.ts,ValidateSkillStructure.ts5. Remove internal planning docs (3 files — deletions)
.prd/PRD-20260309-coderabbit-pr47-fixes.md.prd/PRD-20260309-installer-refactor.mddocs/architecture/INSTALLER-REFACTOR-PLAN.md6. Fix hardcoded ElevenLabs voice IDs → dynamic routing (5 files)
Replace hardcoded
fTtv3eikoepIosk8dTZ5withvoice_id:"default"+title:"AgentName".VoiceServer resolves dynamically via
TTS_PROVIDERenv var.Files:
PAI/SKILL.md,PAI/Algorithm/v3.7.0.md,PAI/Tools/algorithm.ts,VoiceServer/server.ts,agents/Algorithm.mdFiles Changed: 74
Related
Summary by CodeRabbit
New Features
Documentation
Configuration