feat(opencode): add 6 XPowers plugins for enhanced TUI experience#54
feat(opencode): add 6 XPowers plugins for enhanced TUI experience#54
Conversation
Adds a new OpenCode plugin that sends desktop notifications when: - AI agents finish responding (session.idle) - Subagent tasks complete with status (success/error/warning) - Long-running build/test commands finish (bash tool) Supports multiple notification backends: - OSC 777: Ghostty, iTerm2, WezTerm, rxvt-unicode - OSC 99: Kitty terminal - osascript: macOS native notifications - notify-send: Linux desktop notifications - growlnotify: legacy macOS Also shows toast notifications in the OpenCode TUI for all events. Includes configurable notify-config.json for customizing: - Enable/disable specific event types - Notification duration - Title prefix - Backend priority order Inspired by pi-agent-extensions notify plugin.
Adds a review gate for write and edit tool calls that:
- Captures original file content before edits (tool.execute.before)
- Computes line-by-line diff after edits complete (tool.execute.after)
- Shows toast notifications with change stats and optional diff preview
- Logs all changes to .opencode/cache/slow-mode/{sessionId}/review.log
- Blocks edits to protected paths (.env, .git/hooks, keys, etc.)
- Injects review reminder on session stop
Features:
- Configurable auto-approve threshold (lines changed)
- Configurable protected path globs
- Per-session change tracking with cleanup
- Session summary on idle showing total files/lines changed
Inspired by pi-agent-extensions slow-mode.
Adds live polling of tm ready with toast notifications: - Background polling every 60s (configurable via pollIntervalMs) - Toast notifications when new tasks become available - Task count summary on session idle - Configurable priority filtering (default: P0-P2) - Seen-task tracking with TTL to avoid notification spam - Custom tool xpowers_task_status for AI to query task board Features: - Parses tm ready text output for task IDs, priorities, titles - Persistent seen-task cache in .opencode/cache/task-monitor/ - Cleanup of old seen tasks on load - Safe error handling for tm command failures Includes task-monitor-config.json for customizing behavior.
Tracks file modifications and git commits during OpenCode sessions. Warns via toast notifications when uncommitted changes exist: - On session.idle: shows git status with file list and diff stats - On stop: injects reminder prompt and optionally blocks exit - Tracks which files were modified vs committed per session - Detects protected file modifications (.env, .git/hooks, keys) Features: - Configurable auto-commit on session end - Configurable commit message template - Git status parsing (modified, staged, untracked, deleted, ahead/behind) - Diff stat for change summaries - blockStopIfUncommitted option to enforce commits - Per-session state tracking with cleanup Includes git-guard-config.json for customizing behavior.
Monitors estimated context window usage during OpenCode sessions: - Tracks message additions via message.updated events - Estimates tokens from message content (3.2-4.0 chars/token heuristic) - Warns at 70% usage (warning toast) - Critical alert at 90% usage (error toast + /compact suggestion) - Resets counter on session.compacted event - Detects model from message metadata to use correct context limit - Shows context summary on session.idle Supports configurable model context limits for: - OpenAI: GPT-4/4o/4.1/o1/o3/gpt-5 series - Anthropic: Claude 3/3.5/3.7/4 series - Google: Gemini 2.0/2.5 series - Groq: Llama 3.3/4 series Includes context-gauge-config.json for customizing thresholds and limits.
Runs project-appropriate linters after write/edit tool calls: - Auto-detects linter from project config files (.eslintrc, pyproject.toml, etc.) - Supports: ESLint, Prettier, Flake8, Black, Rustfmt, Gofmt, ShellCheck - Shows toast with lint results (errors, warnings, style issues) - Configurable auto-fix (--fix flag) - Optionally blocks on lint errors (blockOnError) - Suppresses duplicate toasts within quietDurationMs Linter detection: - TypeScript/JavaScript: ESLint (with --format compact) or Prettier - Python: Flake8 or Black - Rust: Rustfmt - Go: Gofmt - Shell: ShellCheck Includes lint-gate-config.json for customizing behavior and per-extension linter overrides.
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ 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: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughAdds six new XPowers plugins (context gauge, git guard, lint gate, notify, slow mode, task monitor) plus corresponding ChangesContext Gauge
Git Guard
Lint Gate
Notify
Slow Mode (Review Gate)
Task Monitor
Sequence DiagramsequenceDiagram
participant Session
participant PluginMgr as Plugin Manager
participant Plugin as XPowers Plugin
participant Tool as Tool Executor
participant Shell as Shell/Git/Linter
participant UI as Toast/UI
participant Host as Desktop Notify
Session->>PluginMgr: session.created
PluginMgr->>Plugin: loadConfig + init state
Tool->>PluginMgr: tool.execute.before (write/edit)
PluginMgr->>Plugin: capture pre-state (orig file, protected check)
Tool->>Tool: run operation (write/edit/git/lint)
Tool->>PluginMgr: tool.execute.after (output, args)
PluginMgr->>Plugin: parse output, update session state
Plugin->>Shell: run git/status or linter or tm ready
Shell-->>Plugin: stdout/exit
Plugin->>Plugin: parse results, decide actions
alt notification needed
Plugin->>UI: showToast(...)
Plugin->>Host: send desktop notification (optional)
end
Session->>PluginMgr: session.idle / session.deleted / stop
PluginMgr->>Plugin: summarize state, show toasts/logs, inject prompts or block
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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. Review rate limit: 0/1 reviews remaining, refill in 24 minutes and 19 seconds.Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1b30b30b1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| normalized.includes(normalizedKey) || | ||
| normalizedKey.includes(normalized) | ||
| ) { | ||
| return limit |
There was a problem hiding this comment.
Match model IDs by specificity before substring fallback
The substring fallback returns the first matching key in insertion order, so provider-qualified model IDs like openai/gpt-4o match gpt-4 before gpt-4o and get an 8k limit instead of 128k. That causes the gauge to over-report usage and fire critical/compact warnings far too early for common OpenAI model naming formats. Please prefer longest/specific matches (or split provider prefixes) before this generic includes fallback.
Useful? React with 👍 / 👎.
| if (event.type === "session.deleted" && pollTimer) { | ||
| clearInterval(pollTimer) | ||
| pollTimer = null |
There was a problem hiding this comment.
Keep task polling running after a session is deleted
The background setInterval is started once at plugin init but gets cleared on the first session.deleted event and is never restarted on later sessions. In long-lived OpenCode processes that create a new session after deleting one, task notifications silently stop working for the rest of the app lifetime.
Useful? React with 👍 / 👎.
| const args = autoFix && linter.fixArgs | ||
| ? [...linter.args, ...linter.fixArgs, filePath] | ||
| : [...linter.args, filePath] |
There was a problem hiding this comment.
Drop check-mode flags when auto-fix is enabled
Auto-fix currently appends fix args to the normal check args, which breaks/falsifies fix behavior for linters whose base args are check-only (for example black uses --check, so running black --check --quiet <file> never applies fixes). When autoFix is on, the command should use fix-mode arguments instead of combining both modes.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR adds a new suite of project-local OpenCode plugins under .opencode/plugins plus matching JSON config files, aiming to improve the in-terminal workflow with notifications, edit review, task polling, git reminders, context monitoring, and post-edit lint checks. It extends the repo’s existing OpenCode integration by adding more automation around session/tool events.
Changes:
- Add six new OpenCode plugins for notifications, slow-mode edit review, task monitoring, git guardrails, context usage tracking, and lint gating.
- Add per-plugin config files in
.opencode/with defaults for enabling behavior, thresholds, polling, protected paths, and logging directories. - Expose one new custom tool (
xpowers_task_status) and add multiple session/tool lifecycle hooks to surface status and reminders in the TUI.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
.opencode/task-monitor-config.json |
Default settings for task polling cadence, priorities, seen-task tracking, and toast behavior. |
.opencode/slow-mode-config.json |
Default slow-mode settings for protected paths, diff preview limits, and review logging. |
.opencode/plugins/xpowers-task-monitor.ts |
Implements tm ready polling, task parsing, seen-task cache, idle/start notifications, and a task-status tool. |
.opencode/plugins/xpowers-slow-mode.ts |
Implements pre/post edit hooks to capture originals, diff changes, show review toasts, and log review summaries. |
.opencode/plugins/xpowers-notify.ts |
Implements desktop/TUI notifications for idle sessions, task completions/errors, and long-running bash commands. |
.opencode/plugins/xpowers-lint-gate.ts |
Detects per-file linters, runs them after edits, parses output, and optionally blocks on lint errors. |
.opencode/plugins/xpowers-git-guard.ts |
Tracks modified files and commits across a session, warns on idle/stop, and supports optional auto-commit. |
.opencode/plugins/xpowers-context-gauge.ts |
Estimates context-window usage from message events and warns near configured thresholds. |
.opencode/notify-config.json |
Default notification settings and backend order. |
.opencode/lint-gate-config.json |
Default lint-gate settings for warnings, autofix, quiet period, and log directory. |
.opencode/git-guard-config.json |
Default git-guard settings for warnings, optional blocking/autocommit, and protected paths. |
.opencode/context-gauge-config.json |
Default context thresholds and model context-limit mappings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!config.onAgentIdle) return | ||
|
|
||
| if (event.type === "session.idle") { |
| if (variant === "error" && config.onTaskError) { | ||
| await notify(title, message, variant) | ||
| return | ||
| } | ||
|
|
||
| if (config.onTaskComplete) { | ||
| await notify(title, message, variant) |
| // Match: [indicator] [id] [status] P[priority] [title] | ||
| // ○ hyper-5ct ● P1 [epic] Project: Rename repository to xpowers | ||
| const match = trimmed.match( | ||
| /^[○◐●✓❄]\s+([a-z]+-[a-z0-9]+)\s+.*?P(\d)\s+(.+)/, | ||
| ) | ||
| if (match) { | ||
| tasks.push({ | ||
| id: match[1], | ||
| priority: parseInt(match[2], 10), | ||
| title: match[3].trim(), | ||
| }) | ||
| } |
| const notifyNewTasks = async (tasks: ParsedTask[]) => { | ||
| const newTasks = tasks.filter((t) => !seenTasks.has(t.id)) | ||
|
|
| const createESLintParser = (linterName: string): LinterConfig["parseOutput"] => { | ||
| return (output: string, stderr: string) => { | ||
| const issues: LintIssue[] = [] | ||
| const text = output || stderr | ||
|
|
||
| // ESLint format: /path/to/file.ts | ||
| // 42:5 error Missing semicolon semi | ||
| // 43:1 warning Unused variable @typescript-eslint/no-unused-vars | ||
| const lines = text.split("\n") | ||
| let currentFile = "" | ||
|
|
||
| for (const line of lines) { | ||
| const fileMatch = line.match(/^\s*(\S+\.(?:ts|tsx|js|jsx|mjs|cjs))\s*$/) | ||
| if (fileMatch) { | ||
| currentFile = fileMatch[1] | ||
| continue | ||
| } | ||
|
|
||
| const issueMatch = line.match( | ||
| /^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s+([\w@/-]+)\s*$/, | ||
| ) | ||
| if (issueMatch) { | ||
| issues.push({ | ||
| line: parseInt(issueMatch[1], 10), | ||
| column: parseInt(issueMatch[2], 10), | ||
| severity: issueMatch[3] as "error" | "warning", | ||
| message: issueMatch[4].trim(), | ||
| rule: issueMatch[5].trim(), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return issues | ||
| } |
| const checkBackend = async ($: any, backend: Backend): Promise<boolean> => { | ||
| try { | ||
| switch (backend) { | ||
| case "osc777": | ||
| case "osc99": | ||
| // OSC sequences work in most modern terminals; always try them | ||
| return true |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (3)
.opencode/plugins/xpowers-task-monitor.ts (1)
277-330: 💤 Low valueUnused variable
sessionId.The
sessionIdis extracted on line 278 but never used for validation or session-specific logic. Consider either removing the extraction or using it to guard against events from other sessions, similar to thesessions.delete(sessionId)pattern inxpowers-git-guard.ts.♻️ Suggested cleanup
If sessionId validation isn't needed due to plugin architecture:
event: async ({ event }) => { - const sessionId = (event as any).session_id ?? (event as any).sessionID - if (event.type === "session.created" && config.notifyOnSessionStart) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-task-monitor.ts around lines 277 - 330, The extracted but unused sessionId in the event handler (sessionId from event as any).session_id / sessionID should be removed or actually used to guard session-scoped behavior; either delete the sessionId line, or implement the same session-check pattern used in xpowers-git-guard.ts: store the current session id when handling "session.created" (e.g., trackedSessionId = sessionId), verify incoming events match trackedSessionId before running fetchTasks/showToast for "session.created" and "session.idle", and use that same id when cleaning up pollTimer on "session.deleted" (e.g., if (event.type === "session.deleted" && sessionId === trackedSessionId) { clearInterval(pollTimer); pollTimer = null; trackedSessionId = null }). Ensure references are to the event handler function where fetchTasks and showToast are called and to the pollTimer cleanup block..opencode/plugins/xpowers-git-guard.ts (1)
208-218: ⚖️ Poor tradeoffReDoS potential from user-configured glob patterns.
The glob-to-regex conversion produces patterns like
.*from**, which can cause catastrophic backtracking on adversarial inputs (e.g.,a**b**c**d). WhileprotectedPathsis user-controlled (self-inflicted risk), consider using a dedicated glob library likemicromatchorpicomatchfor safer, more complete glob handling.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-git-guard.ts around lines 208 - 218, The current custom glob-to-regex in matchGlob produces vulnerable patterns (e.g., replacing ** with .*), risking ReDoS from malicious protectedPaths; replace the implementation of matchGlob in xpowers-git-guard.ts with a tested glob library (e.g., micromatch or picomatch): add the dependency, import the library, and use its matching API (e.g., micromatch.isMatch or picomatch(pattern)(path)) with options to handle path separators and case-insensitivity so protectedPaths are matched safely; keep all callers of matchGlob unchanged..opencode/plugins/xpowers-lint-gate.ts (1)
349-356: ⚖️ Poor tradeoff
whichcommand is not portable to Windows.The
which eslint,which prettier, etc. checks on lines 349, 355, 370, 376, 394, 401, and 408 will fail on Windows wherewhichis unavailable. Consider usingwhereon Windows or a cross-platform approach.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-lint-gate.ts around lines 349 - 356, The platform check using backtick commands like `which eslint`/`which prettier` is not portable to Windows; replace those calls in the blocks that decide linter/formatter availability (the checks surrounding LINTER_REGISTRY and hasPrettierConfig) with a cross-platform existence check — e.g., use a small helper that tries the Node-friendly approach (prefer a lightweight npm utility such as the "which" package or implement a wrapper that runs `which` on POSIX and `where` on Windows, or use child_process to attempt spawning the binary directly and catch ENOENT) and then use that helper instead of `$` command strings; update the checks that currently inspect check.exitCode to call this helper so availability detection works on Windows and POSIX alike.
🤖 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/plugins/xpowers-context-gauge.ts:
- Around line 247-282: In the "message.updated" handler, you're currently adding
estimateTokens(content) on every update which double-counts streamed/edited
messages; change it to track per-message previous content (e.g., add a map on
state like state.messageContents keyed by message.id from (event as
any).properties?.message.id), compute deltaTokens = estimateTokens(newContent) -
estimateTokens(prevContent || "") (or zero if negative), add only deltaTokens to
state.estimatedTokens, and only increment state.messageCount when this is the
first time you see that message id (i.e., prevContent was undefined); also
update state.messageContents[message.id] = newContent.
- Around line 83-86: The current shallow merge ({ ...DEFAULT_CONFIG, ...parsed
}) replaces DEFAULT_MODEL_LIMITS when parsed.modelLimits exists; change to
deep-merge modelLimits by building mergedModelLimits = {
...DEFAULT_MODEL_LIMITS, ...(parsed.modelLimits ?? {}) } and then return {
...DEFAULT_CONFIG, ...parsed, modelLimits: mergedModelLimits } so
DEFAULT_MODEL_LIMITS entries (e.g., gpt-4.5, claude-3-5-haiku,
gemini-2.0-flash-lite) are preserved unless explicitly overridden; refer to
DEFAULT_CONFIG, DEFAULT_MODEL_LIMITS, and ContextGaugeConfig to locate
variables/types to update.
- Around line 321-354: The condition gate is wrong: it compares
config.suggestCompactAt to config.dangerThreshold (a static config-vs-config
check) so the compact suggestion either always or never runs when newLevel ===
"danger"; change the check to compare the live usage against the configured
threshold. Update the if inside the newLevel === "danger" block from checking
config.suggestCompactAt <= config.dangerThreshold to something like if
(!state.compactSuggested && usagePercent >= config.suggestCompactAt) (or convert
units as needed), so state.compactSuggested is set and the session prompt
injection (ctx.client.session.prompt) only happens when current usagePercent
meets or exceeds suggestCompactAt.
In @.opencode/plugins/xpowers-git-guard.ts:
- Around line 277-299: The logic in the input.tool === "bash" branch captures
committed files by calling getGitStatus after the commit, but staged files are
cleared after commit so stagedFiles is empty; update the flow to record staged
files before the commit executes (e.g., in the tool.execute.before hook) or
extract file names from the commit output instead of calling getGitStatus
post-commit; specifically, capture the staged file list and add them to
state.filesCommitted (and remove from state.filesModified) and set
state.commitMade when detecting /git\s+commit/ in the command, then keep the
existing showToast call for success.
- Around line 165-176: The current regex requires files, insertions and
deletions to be present so lines like "1 file changed, 5 insertions(+)" return
null; update the regex used on lastLine to make the insertions and deletions
groups optional (e.g.
/(\d+)\s+files?\s+changed(?:.*?(\d+)\s+insertions?)?(?:.*?(\d+)\s+deletions?)?/
) and then when building the return object for files/insertions/deletions use
parseInt(match[1],10) for files and parseInt(match[2] || '0',10) and
parseInt(match[3] || '0',10) for insertions/deletions so missing groups default
to 0.
In @.opencode/plugins/xpowers-lint-gate.ts:
- Around line 218-229: The code currently casts ShellCheck severities directly
from match[4] to "error" | "warning", which loses/invalidates values like "info"
and "style"; update the parser in the loop that builds issues (the for-of over
lines that pushes into issues) to normalize match[4]. Lowercase the captured
severity and map "error" => "error", map "warning","info","style" => "warning"
(or a chosen normalized value), and default to "warning" for unknowns, then
assign that normalized value to the LintIssue.severity instead of the direct
cast; this ensures formatLintResult sees only expected severities.
- Around line 236-250: The ESLint entries in LINTER_REGISTRY (e.g., the ".ts"
and ".tsx" objects) pass "--format compact" but createESLintParser("eslint")
expects the default "stylish" output; fix by removing the args entry that sets
"--format", i.e., delete the "--format", "compact" pair from the args arrays for
eslint entries (apply same change to ".ts", ".tsx", ".js", ".jsx", ".mjs",
".cjs" entries) so the linter uses ESLint's default stylish format, or
alternatively update createESLintParser's regex to parse the compact format if
you prefer compact output—ensure LINTER_REGISTRY and createESLintParser remain
consistent.
- Around line 430-434: Replace the hardcoded empty stderr with the actual stderr
produced by the command: after obtaining result (the value returned by
$`${linter.command} ${args}`), capture its stderr (e.g., await result.stderr()
or the appropriate property/method on result) into the stderr variable and pass
that stderr along with stdout into linter.parseOutput(stdout, stderr) so parsers
that read stderr (like ESLint/Prettier/Rustfmt) can see output.
In @.opencode/plugins/xpowers-notify.ts:
- Line 41: detectBackend() currently uses the backends array
["osc777","osc99","osascript","notify-send"] together with checkBackend() which
always returns true for osc777/osc99, causing those OSC backends to be cached
even when the terminal doesn't actually support them; change detectBackend()
(and any caching logic) to verify actual runtime support before accepting an OSC
backend by updating checkBackend() to perform a real capability check for
"osc777" and "osc99" (e.g., test escape sequence support or probe the terminal)
and only return true when the probe succeeds, and ensure the cached selection is
invalidated/falls back if the probe fails so osascript/notify-send can be chosen
instead (also apply the same change where backends are inspected around the code
near the other occurrence referenced at lines 74-77).
- Around line 226-240: The event handler currently returns early when
config.onAgentIdle is falsy, preventing later branches (like the
session.compacted branch) from running; update the event function so it only
early-returns when no handlers are enabled for the current event type (e.g.,
check the specific flags before returning) — specifically, replace the blanket
if (!config.onAgentIdle) return with conditional checks that allow the
event.type === "session.compacted" path to run when config.onSessionCompact is
true, ensuring you still call notify(title, message, "info") for
"session.compacted" while keeping the existing "session.idle" behavior.
In @.opencode/plugins/xpowers-slow-mode.ts:
- Around line 90-103: matchGlob() currently converts only glob wildcards and
leaves literal regex metacharacters (e.g., .) unescaped, causing patterns like
".env.*" or "**/.beads/issues.jsonl" to overmatch; fix by first escaping all
regex-special characters in normalizedPattern (e.g., . + ^ $ () [] {} | \) into
literal form), then swap the glob tokens: temporarily replace "**" and "*" and
"?" with unique placeholders, perform the regex-escape on the rest, and finally
replace the placeholders with their intended regex fragments (".*" for "**",
"[^/]*" for "*", "." for "?") before compiling the RegExp in matchGlob so
literal dots and other metacharacters are treated literally.
- Around line 117-158: computeLineDiff currently uses Set-based comparison which
collapses duplicates and ignores reordering; replace that with an ordered
LCS-based diff over the origLines and newLines arrays so duplicates and moves
are preserved. Implement a standard LCS algorithm on original.split("\n") and
updated.split("\n") to produce a sequence of edits, compute accurate added and
removed counts from those edits, and build diffLines with "+ " and "- " prefixes
(truncate to 80 chars and cap to 20 entries). Keep the same return shape ({
added, removed, diffLines }) so callers using autoApproveThreshold, toast
severity, and review.log continue to work.
- Around line 375-400: The session idle handler is re-logging the whole
accumulated state.changes each time (and then session.deleted logs it again),
causing duplicate entries; modify the logic in the session.idle branch (the
handler that reads sessions.get(sessionId) and calls logSessionSummary and
showToast) to only flush new changes after they are logged—either by clearing
state.changes (e.g., set state.changes = [] or remove flushed entries) or by
adding a flushed marker on the session state and only passing unflushed changes
to logSessionSummary/showToast; ensure the session.deleted handler only logs
remaining unflushed changes (or skips logging if already flushed) so
logSessionSummary is called exactly once per change set.
---
Nitpick comments:
In @.opencode/plugins/xpowers-git-guard.ts:
- Around line 208-218: The current custom glob-to-regex in matchGlob produces
vulnerable patterns (e.g., replacing ** with .*), risking ReDoS from malicious
protectedPaths; replace the implementation of matchGlob in xpowers-git-guard.ts
with a tested glob library (e.g., micromatch or picomatch): add the dependency,
import the library, and use its matching API (e.g., micromatch.isMatch or
picomatch(pattern)(path)) with options to handle path separators and
case-insensitivity so protectedPaths are matched safely; keep all callers of
matchGlob unchanged.
In @.opencode/plugins/xpowers-lint-gate.ts:
- Around line 349-356: The platform check using backtick commands like `which
eslint`/`which prettier` is not portable to Windows; replace those calls in the
blocks that decide linter/formatter availability (the checks surrounding
LINTER_REGISTRY and hasPrettierConfig) with a cross-platform existence check —
e.g., use a small helper that tries the Node-friendly approach (prefer a
lightweight npm utility such as the "which" package or implement a wrapper that
runs `which` on POSIX and `where` on Windows, or use child_process to attempt
spawning the binary directly and catch ENOENT) and then use that helper instead
of `$` command strings; update the checks that currently inspect check.exitCode
to call this helper so availability detection works on Windows and POSIX alike.
In @.opencode/plugins/xpowers-task-monitor.ts:
- Around line 277-330: The extracted but unused sessionId in the event handler
(sessionId from event as any).session_id / sessionID should be removed or
actually used to guard session-scoped behavior; either delete the sessionId
line, or implement the same session-check pattern used in xpowers-git-guard.ts:
store the current session id when handling "session.created" (e.g.,
trackedSessionId = sessionId), verify incoming events match trackedSessionId
before running fetchTasks/showToast for "session.created" and "session.idle",
and use that same id when cleaning up pollTimer on "session.deleted" (e.g., if
(event.type === "session.deleted" && sessionId === trackedSessionId) {
clearInterval(pollTimer); pollTimer = null; trackedSessionId = null }). Ensure
references are to the event handler function where fetchTasks and showToast are
called and to the pollTimer cleanup block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 92f8f48e-13c8-4784-84ce-39e81389ba84
📒 Files selected for processing (12)
.opencode/context-gauge-config.json.opencode/git-guard-config.json.opencode/lint-gate-config.json.opencode/notify-config.json.opencode/plugins/xpowers-context-gauge.ts.opencode/plugins/xpowers-git-guard.ts.opencode/plugins/xpowers-lint-gate.ts.opencode/plugins/xpowers-notify.ts.opencode/plugins/xpowers-slow-mode.ts.opencode/plugins/xpowers-task-monitor.ts.opencode/slow-mode-config.json.opencode/task-monitor-config.json
- xpowers-notify.ts: Fix shell injection in osascript/notify-send via JSON.stringify() and Bun shell safe argument passing. Add OSC sequence escaping to prevent terminal injection. - xpowers-task-monitor.ts: Fix setInterval timer leak with isShuttingDown flag and centralized stopPolling(). Fix task parser regex to support multi-digit priorities (P10+). - xpowers-lint-gate.ts: Fix stderr capture from Bun shell results. Fix command construction to pass args as array for safe execution. Add linter detection caching per extension to avoid repeated 'which' calls. - xpowers-git-guard.ts: Fix git status parsing for rename entries (R status). Scope autoCommit to only files modified during session instead of 'git add -A'. Add session cleanup to prevent state leak.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4c99b70cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return { | ||
| // When the AI agent finishes responding and goes idle | ||
| event: async ({ event }) => { | ||
| if (!config.onAgentIdle) return |
There was a problem hiding this comment.
Allow compaction notifications when idle alerts are disabled
The early if (!config.onAgentIdle) return exits the entire event handler before the session.compacted branch runs, so setting onAgentIdle: false unintentionally disables compaction notifications even when onSessionCompact: true. This couples two independent settings and makes the compaction notification path unreachable in common configurations that only want to suppress idle toasts.
Useful? React with 👍 / 👎.
| 8000, | ||
| ) | ||
|
|
||
| if (!state.compactSuggested && config.suggestCompactAt <= config.dangerThreshold) { |
There was a problem hiding this comment.
Compare compact suggestion threshold against current usage
The /compact suggestion gate compares suggestCompactAt to dangerThreshold instead of comparing it to the current usage, so the runtime behavior ignores the configured threshold: if suggestCompactAt is above dangerThreshold, suggestions never fire (even at very high usage), and if it is below, suggestions still only occur after entering danger. This makes suggestCompactAt effectively nonfunctional.
Useful? React with 👍 / 👎.
| const status = await getGitStatus(ctx.$, ctx.directory) | ||
| for (const file of status.stagedFiles) { | ||
| state.filesCommitted.add(file) | ||
| state.filesModified.delete(file) | ||
| } |
There was a problem hiding this comment.
Remove committed files from modified set after git commit
Committed files are inferred from status.stagedFiles after running git commit, but post-commit staged lists are typically empty, so files that were actually committed remain in filesModified. If the session later has any real uncommitted change, stop/idle warnings will overcount and list already committed files as uncommitted, which can mislead users and incorrectly trigger stricter stop policies.
Useful? React with 👍 / 👎.
- xpowers-slow-mode.ts: Fix matchGlob regex special-char escaping. Pattern '.env' no longer matches 'aenv' (dot was unescaped regex). Add createdAt tracking and cleanupOldSessions() to prevent memory leak. - xpowers-context-gauge.ts: Fix resolveModelLimit partial matching bug where 'gpt-4' would incorrectly match 'gpt-4.1' limit (1M vs 8192). Add message ID deduplication to prevent double-counting tokens on streaming updates. Add createdAt tracking and session cleanup. - xpowers-git-guard.ts: Fix useless cleanup loop from first fix — now properly checks createdAt timestamp and removes orphaned sessions.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 759877398d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const messageId = message.id ?? (event as any).properties?.messageId | ||
| if (messageId && state.seenMessageIds.has(String(messageId))) { | ||
| return |
There was a problem hiding this comment.
Count incremental updates for repeated message IDs
The early return for previously seen messageId drops all later message.updated events for that message, which means streamed/edited assistant messages only contribute tokens from their first partial update. In sessions where responses arrive incrementally, estimatedTokens stays far below real usage, so warning/critical thresholds and /compact suggestions trigger too late or not at all.
Useful? React with 👍 / 👎.
| const newTasks = tasks.filter((t) => !seenTasks.has(t.id)) | ||
|
|
There was a problem hiding this comment.
Honor trackSeenTasks when detecting new tasks
New-task detection always filters against seenTasks regardless of configuration, so setting trackSeenTasks: false has no effect and notifications are still suppressed for previously seen task IDs. This makes the config flag nonfunctional and prevents users from choosing repeated reminders in long-lived sessions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
.opencode/plugins/xpowers-slow-mode.ts (2)
391-419:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSession summaries are logged repeatedly for the same changes.
On Lines 402-419, idle logs the full accumulated
state.changeseach time, and Line 394 logs it again on session deletion. Add a flush marker/index (or clear flushed entries) to avoid duplicate summaries.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-slow-mode.ts around lines 391 - 419, The session summaries are being logged multiple times because the idle handler and the session.deleted handler both re-log the entire state.changes; update the SessionState tracking to mark which changes have already been flushed (e.g., add a flushedIndex or flag on entries) and change both idle and session.deleted flows to only pass unflushed changes into logSessionSummary(sessionId, changes) and then advance the flushedIndex or remove/clear those flushed entries from sessions.get(sessionId). Specifically, modify the state shape and update the idle branch (where showToast and logSessionSummary are called) and the session.deleted branch to log only new/unflushed entries and then persist the flush (advance flushedIndex or slice state.changes) so duplicate summaries are not emitted; keep cleanupOldSessions() behavior unchanged.
121-161:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftSet-based diff still misrepresents real edits.
Lines 127-152 collapse duplicates and ignore ordering/replacements, so review severity, thresholds, and log stats can be wrong on common edits.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-slow-mode.ts around lines 121 - 161, computeLineDiff currently uses Set comparisons which collapses duplicates and ignores ordering/replacements; replace the set-based logic in computeLineDiff with a sequence-aware diff (e.g., LCS or Myers diff) that computes line-level additions, removals and replacements while preserving order and duplicates, produce diffLines showing up to 20 context-aware lines (use paired "- ..." and "+ ..." for replacements), and compute added/removed counts from the sequence diff results so thresholds and logs reflect real edits; update any internal variables inside computeLineDiff (origLines, newLines, added, removed, diffLines) accordingly..opencode/plugins/xpowers-git-guard.ts (2)
183-191:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle partial
git diff --statoutput.On Line 184, the regex requires files + insertions + deletions.
git diff --statcan omit insertions or deletions, which makes this returnnulland drops valid stats in idle warnings.Proposed fix
- const match = lastLine.match(/(\d+)\s+files?\s+changed.*?(\d+)\s+insertions?.*?(\d+)\s+deletions?/) - if (match) { - return { - files: parseInt(match[1], 10), - insertions: parseInt(match[2], 10), - deletions: parseInt(match[3], 10), - } - } + const filesMatch = lastLine.match(/(\d+)\s+files?\s+changed/) + if (filesMatch) { + const insertionsMatch = lastLine.match(/(\d+)\s+insertions?/) + const deletionsMatch = lastLine.match(/(\d+)\s+deletions?/) + return { + files: parseInt(filesMatch[1], 10), + insertions: insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0, + deletions: deletionsMatch ? parseInt(deletionsMatch[1], 10) : 0, + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-git-guard.ts around lines 183 - 191, The current parsing uses lastLine.match(/(\d+)\s+files?\s+changed.*?(\d+)\s+insertions?.*?(\d+)\s+deletions?/) which fails when insertions or deletions are omitted; update the parsing in xpowers-git-guard.ts (the block that builds the files/insertions/deletions object) to use a regex with optional insertion/deletion groups (or separate matches) and coerce missing captures to 0 (e.g., make insertion/deletion groups optional and use parseInt(capture || '0', 10)) so valid partial stats are returned instead of null.
314-325:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCommitted-file tracking runs after staging is cleared.
On Line 321,
getGitStatus()is called aftergit commit; staged files are usually empty by then, sofilesCommittedandfilesModifieddrift and stop/idle reminders can become inaccurate.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.opencode/plugins/xpowers-git-guard.ts around lines 314 - 325, The code calls getGitStatus() after detecting a git commit, but by then the index is cleared so stagedFiles will be empty; instead, when detecting "git commit" in command (variable command) either (1) parse the command for explicit file paths/options and add those to state.filesCommitted, or (2) run a status of the index before it’s cleared (e.g., execute git diff --cached --name-only or git status --porcelain -z --staged) to retrieve staged files prior to commit; update the logic around getGitStatus(ctx.$, ctx.directory) to use that pre-commit check and continue to update state.filesCommitted and state.filesModified accordingly (referencing getGitStatus, state.filesCommitted, state.filesModified, and the command string).
🤖 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/plugins/xpowers-git-guard.ts:
- Around line 236-245: The matchGlob function treats pattern metacharacters as
regex without escaping, causing patterns like ".env.*" to overmatch; to fix,
escape regex-special characters in normalizedPattern before performing the
wildcard-to-regex transforms in matchGlob (i.e., escape characters such as . ^ $
+ ( ) [ ] { } | \) so only * and ? are expanded; then continue to replace '**'
and '*' and '?' as currently done to produce regexPattern and build the RegExp
from that escaped-and-transformed string.
---
Duplicate comments:
In @.opencode/plugins/xpowers-git-guard.ts:
- Around line 183-191: The current parsing uses
lastLine.match(/(\d+)\s+files?\s+changed.*?(\d+)\s+insertions?.*?(\d+)\s+deletions?/)
which fails when insertions or deletions are omitted; update the parsing in
xpowers-git-guard.ts (the block that builds the files/insertions/deletions
object) to use a regex with optional insertion/deletion groups (or separate
matches) and coerce missing captures to 0 (e.g., make insertion/deletion groups
optional and use parseInt(capture || '0', 10)) so valid partial stats are
returned instead of null.
- Around line 314-325: The code calls getGitStatus() after detecting a git
commit, but by then the index is cleared so stagedFiles will be empty; instead,
when detecting "git commit" in command (variable command) either (1) parse the
command for explicit file paths/options and add those to state.filesCommitted,
or (2) run a status of the index before it’s cleared (e.g., execute git diff
--cached --name-only or git status --porcelain -z --staged) to retrieve staged
files prior to commit; update the logic around getGitStatus(ctx.$,
ctx.directory) to use that pre-commit check and continue to update
state.filesCommitted and state.filesModified accordingly (referencing
getGitStatus, state.filesCommitted, state.filesModified, and the command
string).
In @.opencode/plugins/xpowers-slow-mode.ts:
- Around line 391-419: The session summaries are being logged multiple times
because the idle handler and the session.deleted handler both re-log the entire
state.changes; update the SessionState tracking to mark which changes have
already been flushed (e.g., add a flushedIndex or flag on entries) and change
both idle and session.deleted flows to only pass unflushed changes into
logSessionSummary(sessionId, changes) and then advance the flushedIndex or
remove/clear those flushed entries from sessions.get(sessionId). Specifically,
modify the state shape and update the idle branch (where showToast and
logSessionSummary are called) and the session.deleted branch to log only
new/unflushed entries and then persist the flush (advance flushedIndex or slice
state.changes) so duplicate summaries are not emitted; keep cleanupOldSessions()
behavior unchanged.
- Around line 121-161: computeLineDiff currently uses Set comparisons which
collapses duplicates and ignores ordering/replacements; replace the set-based
logic in computeLineDiff with a sequence-aware diff (e.g., LCS or Myers diff)
that computes line-level additions, removals and replacements while preserving
order and duplicates, produce diffLines showing up to 20 context-aware lines
(use paired "- ..." and "+ ..." for replacements), and compute added/removed
counts from the sequence diff results so thresholds and logs reflect real edits;
update any internal variables inside computeLineDiff (origLines, newLines,
added, removed, diffLines) accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 71369aa3-5745-48bc-a15a-caafbf8bfa17
📒 Files selected for processing (6)
.opencode/plugins/xpowers-context-gauge.ts.opencode/plugins/xpowers-git-guard.ts.opencode/plugins/xpowers-lint-gate.ts.opencode/plugins/xpowers-notify.ts.opencode/plugins/xpowers-slow-mode.ts.opencode/plugins/xpowers-task-monitor.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- .opencode/plugins/xpowers-notify.ts
- .opencode/plugins/xpowers-task-monitor.ts
- .opencode/plugins/xpowers-lint-gate.ts
- .opencode/plugins/xpowers-context-gauge.ts
| const matchGlob = (pattern: string, path: string): boolean => { | ||
| const normalizedPath = path.replace(/\\/g, "/") | ||
| const normalizedPattern = pattern.replace(/\\/g, "/") | ||
| const regexPattern = normalizedPattern | ||
| .replace(/\*\*/g, "\u0000") | ||
| .replace(/\*/g, "[^/]*") | ||
| .replace(/\u0000/g, ".*") | ||
| .replace(/\?/g, ".") | ||
| const regex = new RegExp(`^(.*/)?${regexPattern}$`, "i") | ||
| return regex.test(normalizedPath) |
There was a problem hiding this comment.
Escape regex literals in matchGlob() before wildcard expansion.
Literal metacharacters in patterns (for example .env.*) are interpreted as regex syntax, so protected-path warnings can overmatch.
Proposed fix
+const escapeRegex = (text: string): string =>
+ text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+
const matchGlob = (pattern: string, path: string): boolean => {
const normalizedPath = path.replace(/\\/g, "/")
const normalizedPattern = pattern.replace(/\\/g, "/")
- const regexPattern = normalizedPattern
- .replace(/\*\*/g, "\u0000")
- .replace(/\*/g, "[^/]*")
- .replace(/\u0000/g, ".*")
- .replace(/\?/g, ".")
+ const regexPattern = escapeRegex(normalizedPattern)
+ .replace(/\\\*\\\*/g, "\u0000")
+ .replace(/\\\*/g, "[^/]*")
+ .replace(/\u0000/g, ".*")
+ .replace(/\\\?/g, ".")
const regex = new RegExp(`^(.*/)?${regexPattern}$`, "i")
return regex.test(normalizedPath)
}🧰 Tools
🪛 ast-grep (0.42.1)
[warning] 243-243: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^(.*/)?${regexPattern}$, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/plugins/xpowers-git-guard.ts around lines 236 - 245, The matchGlob
function treats pattern metacharacters as regex without escaping, causing
patterns like ".env.*" to overmatch; to fix, escape regex-special characters in
normalizedPattern before performing the wildcard-to-regex transforms in
matchGlob (i.e., escape characters such as . ^ $ + ( ) [ ] { } | \) so only *
and ? are expanded; then continue to replace '**' and '*' and '?' as currently
done to produce regexPattern and build the RegExp from that
escaped-and-transformed string.
xpowers-notify.ts:
- Fix early return blocking session.compacted when onAgentIdle=false
(Comments 4,19,23) — move guard inside session.idle branch
- Fix onTaskError=false not suppressing error notifications (Comment 5)
- Fix OSC backends always chosen first, never fallback (Comments 9,18)
— reorder defaults to prefer native OS notifications, add cache
invalidation on OSC failure
xpowers-task-monitor.ts:
- Fix timer never restarted after session.deleted (Comment 2) — timer
is per-plugin, not per-session; don't stop on session.deleted
- Fix parser not supporting non-priority tm formats (Comment 6) — add
fallback regex for ENG_CORE-123 style tasks without priority field
- Fix trackSeenTasks=false having no effect (Comments 7,27)
xpowers-lint-gate.ts:
- Fix auto-fix combining check+fix args breaking black/rustfmt
(Comment 3) — use fixArgs ONLY when autoFix is enabled
- Fix ESLint --format compact not matching parser (Comments 8,16) —
remove --format compact, use default stylish output
- Fix ShellCheck severity normalization (Comment 15) — map info/style
to appropriate LintIssue severity values
xpowers-context-gauge.ts:
- Fix modelLimits shallow merge losing defaults (Comment 10) — deep
merge with DEFAULT_MODEL_LIMITS
- Fix suggestCompactAt compared to dangerThreshold not usage
(Comments 12,24) — compare against live usage percentage
- Fix provider-prefixed model IDs matching wrong entry (Comment 1) —
strip provider prefix, prefer longest match
- Fix incremental token counting for streaming (Comments 11,26) —
track messageContents Map, count delta tokens only
xpowers-git-guard.ts:
- Fix git diff --stat regex on partial output (Comment 13) — make
insertions/deletions groups optional
- Fix commit tracking querying status post-commit (Comments 14,25) —
add tool.execute.before hook to capture staged files BEFORE commit
xpowers-slow-mode.ts:
- Fix set-based diff dropping duplicates/reorders (Comment 21) —
implement proper LCS-based diff algorithm
- Fix review.log duplicates on idle+delete (Comment 22) — add
summaryLogged flag to ensure summary is written once per session
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2d8780be4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (/git\s+commit/.test(command)) { | ||
| state.commitMade = true | ||
|
|
||
| // Use staged files captured BEFORE the commit (post-commit staged list is empty) | ||
| for (const file of state.pendingCommitStagedFiles) { | ||
| state.filesCommitted.add(file) | ||
| state.filesModified.delete(file) | ||
| } |
There was a problem hiding this comment.
Verify git commit success before clearing modified files
This branch marks state.commitMade = true, removes every pre-staged file from filesModified, and shows a success toast purely from matching git commit in the command string, without checking whether the commit actually succeeded. If git commit exits non-zero (e.g., pre-commit failure, empty commit, merge conflict), the plugin will treat files as committed and suppress later uncommitted-change warnings/blocking for those files, which defeats the guard in real workflows.
Useful? React with 👍 / 👎.
| if (shouldSuppress(filePath)) { | ||
| return | ||
| } |
There was a problem hiding this comment.
Run lint checks even when duplicate toasts are suppressed
The quiet-window gate returns before runLinter(...), so rapid consecutive edits skip lint execution entirely rather than only suppressing notifications. In those cases blockOnError is bypassed and autoFix never runs, even though quietDurationMs is documented as toast spam control; users making quick iterative edits can therefore commit lint-breaking changes that should have been caught.
Useful? React with 👍 / 👎.
| } else if (prev[j] > curr[j - 1]) { | ||
| i-- |
There was a problem hiding this comment.
Backtrack LCS with full state instead of two rolling rows
The LCS routine computes DP with only two rolling rows, then backtracks using prev/curr as if they still represented row i and i-1 for every step. That state is no longer valid after the forward pass, so diffs can be badly wrong (e.g., unchanged lines counted as removed), which inflates totalChanged, mislabels small edits, and produces incorrect review logs/toasts.
Useful? React with 👍 / 👎.
Summary
This PR adds 6 new OpenCode plugins that enhance the terminal development experience with notifications, safety gates, task tracking, and quality enforcement.
New Plugins
1.
xpowers-notify.ts— Desktop NotificationsSends desktop notifications when AI agents finish responding or tasks complete.
2.
xpowers-slow-mode.ts— Edit Review GateReview gate for
writeandedittool calls..opencode/cache/slow-mode/{session}/review.log3.
xpowers-task-monitor.ts— Live Task PollingLive polling of
tm readywith toast notifications.xpowers_task_statusfor AI to query task board4.
xpowers-git-guard.ts— Git Commit RemindersTracks file modifications and git commits during sessions.
5.
xpowers-context-gauge.ts— Context MonitoringMonitors estimated context window usage in real-time.
6.
xpowers-lint-gate.ts— Post-Edit LintingRuns project-appropriate linters after write/edit tool calls.
Configuration
Each plugin includes a
.opencode/*-config.jsonfile for customization:notify-config.jsonslow-mode-config.jsontask-monitor-config.jsongit-guard-config.jsoncontext-gauge-config.jsonlint-gate-config.jsonDesign Patterns
All plugins follow consistent patterns:
Testing
Plugins are loaded automatically by OpenCode from
.opencode/plugins/*.ts.No manual installation required — just checkout the branch and restart OpenCode.
Inspired By
Summary by CodeRabbit