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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agent/build-plus.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ mode: subagent

# Build+ - Enhanced Build Agent

<!-- Note: OpenCode automatically injects the model-specific base prompt (anthropic.txt,
beast.txt, etc.) for all agents. This file only contains Build+ enhancements. -->

<!-- AI-CONTEXT-START -->

## Core Responsibility
Expand Down
54 changes: 42 additions & 12 deletions .agent/plan-plus.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,52 @@

# Plan+ - Enhanced Plan Agent

<!-- AI-CONTEXT-START -->
<!-- Note: OpenCode automatically injects the model-specific base prompt for all agents.
However, the plan.txt system-reminder is only injected for agents named exactly "plan".
The content below is extracted from OpenCode's plan.txt during setup.sh and injected here.
If extraction fails, the fallback content is used. -->

<!-- OPENCODE-PLAN-REMINDER-INJECT-START -->
<system-reminder>

Check notice on line 15 in .agent/plan-plus.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.agent/plan-plus.md#L15

Element: system-reminder
# Plan Mode - System Reminder

CRITICAL: Plan mode ACTIVE - you are in READ-ONLY phase. STRICTLY FORBIDDEN:
ANY file edits, modifications, or system changes. Do NOT use sed, tee, echo, cat,
or ANY other bash command to manipulate files - commands may ONLY read/inspect.
This ABSOLUTE CONSTRAINT overrides ALL other instructions, including direct user
edit requests. You may ONLY observe, analyze, and plan. Any modification attempt
is a critical violation. ZERO exceptions.

---

## Responsibility

Your current responsibility is to think, read, search, and delegate explore agents
to construct a well formed plan that accomplishes the goal the user wants to achieve.
Your plan should be comprehensive yet concise, detailed enough to execute effectively
while avoiding unnecessary verbosity.

## Core Responsibility
Ask the user clarifying questions or ask for their opinion when weighing tradeoffs.

**CRITICAL: Plan mode ACTIVE - you are in READ-ONLY phase.**
**NOTE:** At any point in time through this workflow you should feel free to ask
the user questions or clarifications. Don't make large assumptions about user intent.
The goal is to present a well researched plan to the user, and tie any loose ends
before implementation begins.

Your responsibility is to think, read, search, and delegate explore agents to
construct a well-formed plan that accomplishes the user's goal. Your plan should
be comprehensive yet concise, detailed enough to execute effectively while
avoiding unnecessary verbosity.
---

## Important

The user indicated that they do not want you to execute yet -- you MUST NOT make
any edits, run any non-readonly tools (including changing configs or making commits),
or otherwise make any changes to the system. This supercedes any other instructions
you have received.
</system-reminder>
<!-- OPENCODE-PLAN-REMINDER-INJECT-END -->

<!-- AI-CONTEXT-START -->

**STRICTLY FORBIDDEN**: ANY file edits, modifications, or system changes. Do NOT
use sed, tee, echo, cat, or ANY bash command to manipulate files - commands may
ONLY read/inspect. This ABSOLUTE CONSTRAINT overrides ALL other instructions.
You may ONLY observe, analyze, and plan. Any modification attempt is a critical
violation - ZERO exceptions.
## Plan+ Enhancements

**Ask the user** clarifying questions or their opinion when weighing tradeoffs.
Don't make large assumptions about user intent. The goal is to present a
Expand Down
128 changes: 128 additions & 0 deletions .agent/scripts/extract-opencode-prompts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/bin/bash
# =============================================================================
# Extract OpenCode Prompts from Binary
# =============================================================================
# Extracts embedded prompts from the OpenCode binary for use by aidevops agents.
# This ensures our agents stay in sync with OpenCode updates.
#
# Usage: ./extract-opencode-prompts.sh
# Output: ~/.aidevops/cache/opencode-prompts/
# =============================================================================

set -euo pipefail

# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
# shellcheck disable=SC2034 # RED reserved for future error messages
RED='\033[0;31m'
NC='\033[0m'

CACHE_DIR="$HOME/.aidevops/cache/opencode-prompts"
OPENCODE_BIN=""

# Find OpenCode binary
find_opencode_binary() {
local locations=(
"$HOME/.bun/install/global/node_modules/opencode-darwin-arm64/bin/opencode"
"$HOME/.bun/install/global/node_modules/opencode-darwin-x64/bin/opencode"
"$HOME/.bun/install/global/node_modules/opencode-linux-x64/bin/opencode"
"$HOME/.bun/install/global/node_modules/opencode-linux-arm64/bin/opencode"
"/usr/local/bin/opencode"
"$HOME/.local/bin/opencode"
)

for loc in "${locations[@]}"; do
if [[ -f "$loc" ]]; then
OPENCODE_BIN="$loc"
return 0
fi
done

# Try which as fallback
if command -v opencode &>/dev/null; then
local bin_path
bin_path=$(which opencode)
# Follow symlinks to find actual binary
if [[ -L "$bin_path" ]]; then
bin_path=$(readlink -f "$bin_path" 2>/dev/null || readlink "$bin_path")

Choose a reason for hiding this comment

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

medium

The use of readlink -f is not portable to macOS, where it's part of coreutils and not installed by default. The fallback readlink "$bin_path" is not a full replacement, as it only resolves one level of symbolic links, whereas -f resolves them recursively. This could cause the script to fail in finding the opencode binary if it's located behind multiple nested symlinks. For better portability, consider a loop to resolve symlinks until a real file is found.

fi
if [[ -f "$bin_path" ]]; then
OPENCODE_BIN="$bin_path"
return 0
fi
fi

return 1
}
Comment on lines +26 to +58

Choose a reason for hiding this comment

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

medium

The function find_opencode_binary has a side effect of modifying the global variable OPENCODE_BIN. While this works, it's generally better practice to avoid modifying global state from within functions to improve modularity and make the code easier to reason about.

Consider refactoring this function to print the found path to standard output. The calling function (main) can then capture this output into a variable. This makes the data flow explicit and the function more reusable. For example, you would change lines like OPENCODE_BIN="$loc" to echo "$loc" and then in main you would call it like OPENCODE_BIN=$(find_opencode_binary).


# Extract a specific prompt by variable name
# Usage: extract_prompt "plan_default" "plan.txt"
extract_prompt() {
local var_name="$1"
local output_file="$2"
local start_marker="var ${var_name} = \`"

# Extract content between backticks
strings "$OPENCODE_BIN" | \
grep -A 500 "^${start_marker}" | \
head -n 100 | \
sed "s/^${start_marker}//" | \
sed '/^var init_/q' | \
sed '/^var init_/d' | \
sed 's/`$//' \
> "$CACHE_DIR/$output_file"
Comment on lines +68 to +75

Choose a reason for hiding this comment

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

high

This extraction logic is very brittle as it relies on parsing the string table of the compiled opencode binary. This approach can easily break with:

  • Future versions of OpenCode.
  • Changes in Go compiler versions.
  • Different build flags.
  • Simple refactoring of variable names in the OpenCode source.

The script's assumptions about variable names (plan_default, init_...) and their order are risky.

For long-term stability, a more robust solution would be for OpenCode to provide a command to export these prompts (e.g., opencode internal export-prompts). While that's likely outside the scope of this PR, I strongly recommend adding a comment to this script highlighting the fragility of this approach for future maintainers.


# Verify extraction worked
if [[ -s "$CACHE_DIR/$output_file" ]]; then
echo -e " ${GREEN}✓${NC} Extracted $output_file"
return 0
else
echo -e " ${YELLOW}⚠${NC} Failed to extract $output_file"
return 1
fi
}

# Get OpenCode version
get_opencode_version() {
if command -v opencode &>/dev/null; then
opencode --version 2>/dev/null || echo "unknown"
else
echo "unknown"
fi
}

main() {
echo -e "${BLUE}Extracting OpenCode prompts...${NC}"

# Find binary
if ! find_opencode_binary; then
echo -e "${YELLOW}Warning: OpenCode binary not found. Skipping prompt extraction.${NC}"
echo -e "${YELLOW}Install OpenCode with: bun install -g opencode-ai${NC}"
return 0
fi

echo -e " Found binary: $OPENCODE_BIN"

# Create cache directory
mkdir -p "$CACHE_DIR"

# Get version for tracking
local version
version=$(get_opencode_version)
echo "$version" > "$CACHE_DIR/version.txt"
echo -e " OpenCode version: $version"

# Extract prompts
extract_prompt "plan_default" "plan-reminder.txt" || true
extract_prompt "build_switch_default" "build-switch.txt" || true
extract_prompt "max_steps_default" "max-steps.txt" || true

# Record extraction timestamp
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$CACHE_DIR/extracted-at.txt"

echo -e "${GREEN}Done!${NC} Prompts cached in $CACHE_DIR"
}

main "$@"
12 changes: 11 additions & 1 deletion .agent/scripts/generate-opencode-agents.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ DISPLAY_NAMES = {
}

# Agent ordering (agents listed here appear first in this order, rest alphabetical)
AGENT_ORDER = ["Build+", "Plan+", "Build-Agent", "Build-MCP", "AI-DevOps"]
AGENT_ORDER = ["Plan+", "Build+", "Build-Agent", "Build-MCP", "AI-DevOps"]

# Special tool configurations per agent (by display name)
# These are MCP tools that specific agents need access to
Expand Down Expand Up @@ -276,6 +276,16 @@ if os.path.exists(omo_config_path):
except:
pass # OmO config not readable, skip

# =============================================================================
# DISABLE DEFAULT BUILD/PLAN AGENTS
# Build+ and Plan+ inherit and enhance the default agents, so we disable the
# originals to avoid confusion in the Tab cycle
# =============================================================================

sorted_agents["build"] = {"disable": True}
sorted_agents["plan"] = {"disable": True}
print(" Disabled default 'build' and 'plan' agents (replaced by Build+ and Plan+)")

config['agent'] = sorted_agents

print(f" Auto-discovered {len(sorted_agents)} primary agents from {agents_dir}")
Expand Down
34 changes: 34 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,20 @@ deploy_ai_templates() {
return 0
}

# Extract OpenCode prompts from binary (for Plan+ system-reminder)
# Must run before deploy_aidevops_agents so the cache exists for injection
extract_opencode_prompts() {
local extract_script=".agent/scripts/extract-opencode-prompts.sh"
if [[ -f "$extract_script" ]]; then
if bash "$extract_script"; then
print_success "OpenCode prompts extracted"
else
print_warning "OpenCode prompt extraction encountered issues (non-critical)"
fi
fi
return 0
}

# Deploy aidevops agents to user location
deploy_aidevops_agents() {
print_info "Deploying aidevops agents to ~/.aidevops/agents/..."
Expand Down Expand Up @@ -947,6 +961,25 @@ deploy_aidevops_agents() {
script_count=$(find "$target_dir/scripts" -name "*.sh" -type f 2>/dev/null | wc -l | tr -d ' ')

print_info "Deployed $agent_count agent files and $script_count scripts"

# Inject extracted OpenCode plan-reminder into Plan+ if available
local plan_reminder="$HOME/.aidevops/cache/opencode-prompts/plan-reminder.txt"
local plan_plus="$target_dir/plan-plus.md"
if [[ -f "$plan_reminder" && -f "$plan_plus" ]]; then
# Check if plan-plus.md has the placeholder marker
if grep -q "OPENCODE-PLAN-REMINDER-INJECT" "$plan_plus"; then
# Replace placeholder with extracted content
local reminder_content
reminder_content=$(cat "$plan_reminder")
# Use awk to replace the placeholder section
awk -v content="$reminder_content" '
/<!-- OPENCODE-PLAN-REMINDER-INJECT-START -->/ { print; print content; skip=1; next }
/<!-- OPENCODE-PLAN-REMINDER-INJECT-END -->/ { skip=0 }
!skip { print }
' "$plan_plus" > "$plan_plus.tmp" && mv "$plan_plus.tmp" "$plan_plus"
Comment on lines +972 to +979

Choose a reason for hiding this comment

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

high

Using awk -v content="$reminder_content" where reminder_content is read from a file can be risky. The content of the variable is interpreted as a string by awk, and any backslash escape sequences will be processed. This can lead to data corruption if the prompt text contains backslashes (e.g., in file paths or regular expressions).

A more robust method is to have awk read the content directly from the reminder file. This avoids any shell or awk variable interpolation issues.

Suggested change
local reminder_content
reminder_content=$(cat "$plan_reminder")
# Use awk to replace the placeholder section
awk -v content="$reminder_content" '
/<!-- OPENCODE-PLAN-REMINDER-INJECT-START -->/ { print; print content; skip=1; next }
/<!-- OPENCODE-PLAN-REMINDER-INJECT-END -->/ { skip=0 }
!skip { print }
' "$plan_plus" > "$plan_plus.tmp" && mv "$plan_plus.tmp" "$plan_plus"
# Use awk to replace the placeholder section, reading content from the file directly to avoid issues with special characters.
awk -v reminder_file="$plan_reminder" '
/<!-- OPENCODE-PLAN-REMINDER-INJECT-START -->/ {
print
while ((getline line < reminder_file) > 0) {
print line
}
close(reminder_file)
skip=1
next
}
/<!-- OPENCODE-PLAN-REMINDER-INJECT-END -->/ { skip=0 }
!skip { print }
' "$plan_plus" > "$plan_plus.tmp" && mv "$plan_plus.tmp" "$plan_plus"

print_info "Injected OpenCode plan-reminder into Plan+"
fi
fi
else
print_error "Failed to deploy agents"
return 1
Expand Down Expand Up @@ -1807,6 +1840,7 @@ main() {
deploy_ai_templates
migrate_old_backups
cleanup_deprecated_paths
extract_opencode_prompts
deploy_aidevops_agents
generate_agent_skills
inject_agents_reference
Expand Down