diff --git a/.gitignore b/.gitignore index 0db5ed8..870a082 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,10 @@ # Maister task tracking (generated per-project, not part of plugin source) .maister/ /.worktrees/ + +# OpenCode build output (generated by build.sh) +platforms/opencode/output/ + +# Node modules (for OpenCode plugin) +platforms/opencode/plugin/node_modules/ +platforms/opencode/plugin/package-lock.json diff --git a/.sisyphus/evidence/task-5-no-claude-refs.txt b/.sisyphus/evidence/task-5-no-claude-refs.txt new file mode 100644 index 0000000..573541a --- /dev/null +++ b/.sisyphus/evidence/task-5-no-claude-refs.txt @@ -0,0 +1 @@ +0 diff --git a/.sisyphus/evidence/task-5-path-check.txt b/.sisyphus/evidence/task-5-path-check.txt new file mode 100644 index 0000000..bbf81fb --- /dev/null +++ b/.sisyphus/evidence/task-5-path-check.txt @@ -0,0 +1,120 @@ +platforms/opencode/output/: +agent-fragment.json +agents +commands +instructions +mcp-fragment.json +plugins + +platforms/opencode/output/agents: +bottleneck-analyzer.md +codebase-analysis-reporter.md +code-quality-pragmatist.md +code-reviewer.md +docs-operator.md +e2e-test-verifier.md +gap-analyzer.md +implementation-completeness-checker.md +implementation-planner.md +information-gatherer.md +production-readiness-checker.md +project-analyzer.md +reality-assessor.md +research-planner.md +research-synthesizer.md +solution-brainstormer.md +solution-designer.md +spec-auditor.md +specification-creator.md +task-classifier.md +task-group-implementer.md +test-suite-runner.md +ui-mockup-generator.md +user-docs-generator.md + +platforms/opencode/output/commands: +quick-dev.md +quick-plan.md +reviews-code.md +reviews-pragmatic.md +reviews-production-readiness.md +reviews-reality-check.md +reviews-spec-audit.md +work.md + +platforms/opencode/output/instructions: +maister-codebase-analyzer.md +maister-development.md +maister-docs-manager.md +maister-implementation-plan-executor.md +maister-implementation-verifier.md +maister-init.md +maister-migration.md +maister-performance.md +maister-product-design.md +maister-quick-bugfix.md +maister-research.md +maister-rules.md +maister-standards-discover.md +maister-standards-update.md +references + +platforms/opencode/output/instructions/references: +codebase-analyzer +docs-manager +init +migration +performance +product-design +research +shared +standards-discover + +platforms/opencode/output/instructions/references/codebase-analyzer: +code-analysis.md +combined.md +context-discovery.md +file-discovery.md +migration-target.md +pattern-mining.md + +platforms/opencode/output/instructions/references/docs-manager: +claude-md-template.md +index-md-template.md + +platforms/opencode/output/instructions/references/init: +architecture-template.md +roadmap-templates.md +tech-stack-template.md +vision-templates.md + +platforms/opencode/output/instructions/references/migration: +migration-strategies.md +migration-types.md + +platforms/opencode/output/instructions/references/performance: +performance-optimization-guide.md + +platforms/opencode/output/instructions/references/product-design: +characteristic-detection.md +interaction-patterns.md +visual-companion.md + +platforms/opencode/output/instructions/references/research: +brainstorming-techniques.md +design-techniques.md +research-methodologies.md + +platforms/opencode/output/instructions/references/shared: +orchestrator-creation-checklist.md +orchestrator-patterns.md + +platforms/opencode/output/instructions/references/standards-discover: +aggregation-strategy.md +code-pattern-prompt.md +config-analyzer-prompt.md +docs-extractor-prompt.md +external-analyzer-prompt.md + +platforms/opencode/output/plugins: +README.md diff --git a/Makefile b/Makefile index 2f885c1..7647304 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build validate clean watch +.PHONY: build validate clean watch opencode opencode-build opencode-validate opencode-clean build: bash platforms/copilot-cli/build.sh @@ -23,3 +23,35 @@ clean: watch: fswatch -o plugins/maister/ | xargs -n1 -I{} make build + +opencode-build: + bash platforms/opencode/build.sh + +opencode-validate: + @test -d platforms/opencode/output || (echo "FAIL: Run make opencode-build first" && exit 1) + @echo "Checking no AskUserQuestion in output..." + @! grep -r 'AskUserQuestion' platforms/opencode/output/ 2>/dev/null || (echo "FAIL: AskUserQuestion found in output" && exit 1) + @echo "Checking no TaskCreate or TaskUpdate in output..." + @! grep -r 'TaskCreate\|TaskUpdate' platforms/opencode/output/ 2>/dev/null || (echo "FAIL: TaskCreate or TaskUpdate found in output" && exit 1) + @echo "Checking no Skill(name= in output..." + @! grep -r 'Skill(name=' platforms/opencode/output/ 2>/dev/null || (echo "FAIL: Skill(name= found in output" && exit 1) + @echo "Checking no maister: prefix in output..." + @! (grep -r 'maister:' platforms/opencode/output/ --include="*.md" --include="*.json" 2>/dev/null | grep -v '\[orchestrator-name\]' | grep -v 'skill: "maister-') || (echo "FAIL: maister: prefix found in output" && exit 1) + @echo "Checking no CLAUDE.md references in output..." + @! grep -ri 'CLAUDE\.md' platforms/opencode/output/ 2>/dev/null || (echo "FAIL: CLAUDE.md reference found in output" && exit 1) + @echo "Checking agent-fragment.json is valid JSON..." + @jq . platforms/opencode/output/agent-fragment.json > /dev/null || (echo "FAIL: agent-fragment.json is not valid JSON" && exit 1) + @echo "Checking mcp-fragment.json is valid JSON..." + @jq . platforms/opencode/output/mcp-fragment.json > /dev/null || (echo "FAIL: mcp-fragment.json is not valid JSON" && exit 1) + @echo "Checking minimum 8 commands present..." + @test $$(ls platforms/opencode/output/commands/ 2>/dev/null | wc -l) -ge 8 || (echo "FAIL: Minimum 8 commands not found" && exit 1) + @echo "Checking minimum 20 agents present..." + @test $$(ls platforms/opencode/output/agents/ 2>/dev/null | wc -l) -ge 20 || (echo "FAIL: Minimum 20 agents not found" && exit 1) + @echo "Checking minimum 14 instruction files present..." + @test $$(ls platforms/opencode/output/instructions/maister-*.md 2>/dev/null | wc -l) -ge 14 || (echo "FAIL: Minimum 14 instruction files not found" && exit 1) + @echo "All checks passed" + +opencode-clean: + rm -rf platforms/opencode/output/ + +opencode: opencode-build opencode-validate diff --git a/docs/opencode-setup.md b/docs/opencode-setup.md new file mode 100644 index 0000000..6927039 --- /dev/null +++ b/docs/opencode-setup.md @@ -0,0 +1,140 @@ +# OpenCode Setup Guide + +This guide explains how to install and configure the Maister plugin for use with [OpenCode](https://github.com/anomalyco/opencode). + +## Prerequisites + +- **OpenCode installed**: See the [OpenCode repository](https://github.com/anomalyco/opencode) for installation instructions. +- **Node.js ≥ 18**: Required for running the plugin. +- **jq**: Installed for merging configuration fragments. +- **git, make, bash**: Standard development tools. + +## Quick Start + +```bash +git clone https://github.com/SkillPanel/Maister.git maister-src +cd maister-src +make opencode-build +# copy output to your project +cp -r platforms/opencode/output/commands/ /path/to/your-project/.opencode/commands/ +cp -r platforms/opencode/output/agents/ /path/to/your-project/.opencode/agents/ +cp -r platforms/opencode/output/instructions/ /path/to/your-project/.opencode/instructions/ +cp platforms/opencode/output/plugins/maister-plugin.ts /path/to/your-project/.opencode/plugins/maister-plugin.ts +``` + +## Build from Source + +The `make opencode-build` command runs the `platforms/opencode/build.sh` script, which transforms the core Maister plugin into OpenCode-compatible artifacts. It produces: + +- `platforms/opencode/output/commands/`: 8 command files (e.g., `work.md`, `quick-dev.md`). +- `platforms/opencode/output/agents/`: 24 agent definitions. +- `platforms/opencode/output/instructions/`: 14 instruction files (workflows). +- `platforms/opencode/output/agent-fragment.json`: Configuration fragment for agents. +- `platforms/opencode/output/mcp-fragment.json`: Configuration fragment for MCP servers. + +## Installation (Per-Project) + +To install Maister in your project, copy the built artifacts into your project's `.opencode/` directory: + +```bash +mkdir -p .opencode/commands .opencode/agents .opencode/instructions .opencode/plugins + +cp -r platforms/opencode/output/commands/* .opencode/commands/ +cp -r platforms/opencode/output/agents/* .opencode/agents/ +cp -r platforms/opencode/output/instructions/* .opencode/instructions/ +``` + +Note: References in `agent-fragment.json` resolve relative to the project root, so paths like `.opencode/agents/...` are used. + +## Configuration — Merging Fragments + +You need to merge the generated fragments into your `opencode.json` configuration file. + +### Merging Agent Fragment + +If `opencode.json` already exists: + +```bash +jq -s '.[0] * {"agent": (.[0].agent // {} + .[1])}' opencode.json platforms/opencode/output/agent-fragment.json > opencode.json.tmp && mv opencode.json.tmp opencode.json +``` + +If starting fresh: + +```bash +cp platforms/opencode/output/agent-fragment.json opencode.json +``` + +### Merging MCP Fragment + +Merge the MCP server configuration: + +```bash +jq -s '.[0] * {"mcpServers": (.[0].mcpServers // {} + .[1])}' opencode.json platforms/opencode/output/mcp-fragment.json > opencode.json.tmp && mv opencode.json.tmp opencode.json +``` + +### Adding Instructions + +Add the Maister instructions to the `"instructions"` array in `opencode.json`: + +```json +{ + "instructions": [ + ".opencode/instructions/maister-rules.md" + ] +} +``` + +## Plugin Setup + +OpenCode loads plugins as top-level `.opencode/plugins/*.ts` or `*.js` files. + +```bash +# In your project root: +mkdir -p .opencode/plugins +cp platforms/opencode/output/plugins/maister-plugin.ts .opencode/plugins/maister-plugin.ts + +# Install plugin dependency +cd .opencode +npm init -y 2>/dev/null || true +npm install @opencode-ai/plugin +cd .. +``` + +## Verify Installation + +Confirm the setup by checking the file structure and running the validation command: + +```bash +# From the Maister source directory: +make opencode-validate + +# In your target project: +ls .opencode/commands/ # Should show 8 .md files +ls .opencode/agents/ # Should show 24 .md files +``` + +## Available Commands + +Maister provides several entry points for different development tasks: + +| Command | Description | +|---------|-------------| +| `work` | Unified entry point — auto-classifies tasks and routes to appropriate workflow. | +| `quick-dev` | Implement task directly with standards awareness (no planning mode). | +| `quick-plan` | Enter planning mode with standards awareness. | +| `quick-bugfix` | Quick TDD-driven bug fix — write failing test, fix, verify. | +| `reviews-code` | Run automated code quality, security, and performance analysis. | +| `reviews-pragmatic` | Run pragmatic code review to detect over-engineering. | +| `reviews-production-readiness` | Verify production deployment readiness with comprehensive checks. | +| `reviews-reality-check` | Comprehensive reality assessment of completed work. | +| `reviews-spec-audit` | Independent specification audit to verify completeness and clarity. | + +## Differences from Claude Code Version + +The OpenCode version of Maister has some differences due to platform variations: + +- **Conversational Interaction**: There is no structured option selection (like `AskUserQuestion`). Instead, the AI asks questions conversationally. +- **Todo Management**: State tracking uses the `todo` tool rather than internal `TaskCreate`/`TaskUpdate` primitives. +- **Static Skills**: Skills are implemented as instruction files rather than dynamically invocable tools. +- **Step Limits**: The `steps` limit in agents may require more frequent manual intervention for very long orchestration workflows. +- **Command Syntax**: Use command names directly as slash commands (e.g., `/work` instead of `/maister:work`). diff --git a/platforms/opencode/build.sh b/platforms/opencode/build.sh new file mode 100755 index 0000000..f8e837c --- /dev/null +++ b/platforms/opencode/build.sh @@ -0,0 +1,384 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +CORE="$ROOT/plugins/maister" +OUT="$SCRIPT_DIR/output" + +# Cross-platform sed in-place (macOS needs '' arg, Linux doesn't) +sedi() { + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +echo "=== Maister → OpenCode Build ===" +echo "Source: $CORE" +echo "Output: $OUT" +echo "" + +# ───────────────────────────────────────────────────────────── +# Phase 1: Copy source +# ───────────────────────────────────────────────────────────── +echo "[Phase 1] Copying source..." +rm -rf "$OUT" +mkdir -p "$OUT" +cp -r "$CORE/." "$OUT/" + +# ───────────────────────────────────────────────────────────── +# Phase 2: Strip Claude Code artifacts +# ───────────────────────────────────────────────────────────── +echo "[Phase 2] Stripping Claude Code artifacts..." +rm -rf "$OUT/.claude-plugin" +rm -rf "$OUT/hooks" +rm -rf "$OUT/skills" +rm -f "$OUT/.mcp.json" +rm -rf "$OUT/agents" +mkdir -p "$OUT/agents" + +# ───────────────────────────────────────────────────────────── +# Phase 3: Transform commands +# ───────────────────────────────────────────────────────────── +echo "[Phase 3] Transforming commands..." + +# Mapping: command filename (no extension) → agent name +get_agent_for_command() { + local cmd="$1" + case "$cmd" in + work) echo "maister-development" ;; + development) echo "maister-development" ;; + research) echo "maister-research" ;; + performance) echo "maister-performance" ;; + migration) echo "maister-migration" ;; + product-design) echo "maister-product-design" ;; + quick-plan) echo "maister-planner" ;; + quick-dev) echo "maister-developer" ;; + quick-bugfix) echo "maister-bugfix" ;; + reviews-code) echo "maister-code-reviewer" ;; + reviews-pragmatic) echo "maister-code-quality-pragmatist" ;; + reviews-spec-audit) echo "maister-spec-auditor" ;; + reviews-reality-check) echo "maister-reality-assessor" ;; + reviews-production-readiness) echo "maister-production-readiness-checker" ;; + *) echo "maister-development" ;; + esac +} + +find "$OUT/commands" -name "*.md" | while read -r f; do + cmd_name="$(basename "$f" .md)" + agent_name="$(get_agent_for_command "$cmd_name")" + + # Strip "name:" line from frontmatter + sedi '/^name: /d' "$f" + + # Add agent: and subtask: true into frontmatter (after the opening ---) + # Only if frontmatter exists (file starts with ---) + if head -1 "$f" | grep -q '^---$'; then + sedi "0,/^---\$/{/^---\$/!b; a\\ +agent: $agent_name\\ +subtask: true +}" "$f" + fi + + # Replace Skill(name="X") invocations with OpenCode instruction references + # Pattern: Skill(name="maister:foo") → Follow the workflow in @.opencode/instructions/maister-foo.md + sedi 's|Skill(name="maister:\([^"]*\)")|Follow the workflow in @.opencode/instructions/maister-\1.md|g' "$f" + sedi "s|Skill(name='maister:\([^']*\)')|Follow the workflow in @.opencode/instructions/maister-\1.md|g" "$f" + + # Strip maister: prefix from command references in body + sedi 's|/maister:\([a-z-]*\)|/\1|g' "$f" +done + +# ───────────────────────────────────────────────────────────── +# Phase 4: Transform agents → build agent-fragment.json +# ───────────────────────────────────────────────────────────── +echo "[Phase 4] Transforming agents and building agent-fragment.json..." + +mkdir -p "$OUT/agents" + +# We'll accumulate agent JSON entries in a temp file, build with Python at end +AGENT_ENTRIES_FILE="$(mktemp)" +echo "{}" > "$AGENT_ENTRIES_FILE" + +color_to_hex() { + case "$1" in + purple) echo "#9B59B6" ;; + blue) echo "#3498DB" ;; + green) echo "#2ECC71" ;; + red) echo "#E74C3C" ;; + orange) echo "#E67E22" ;; + yellow) echo "#F1C40F" ;; + cyan) echo "#1ABC9C" ;; + pink) echo "#E91E63" ;; + gray) echo "#95A5A6" ;; + *) echo "#95A5A6" ;; + esac +} + +# Process each agent file +for src_agent in "$CORE/agents/"*.md; do + filename="$(basename "$src_agent")" + agent_slug="${filename%.md}" + agent_id="maister-${agent_slug}" + dest="$OUT/agents/maister-${filename}" + + # Parse YAML frontmatter fields + name_val="$(awk '/^---/{f++; next} f==1{print}' "$src_agent" | grep '^name:' | sed 's/^name:[[:space:]]*//')" + desc_val="$(awk '/^---/{f++; next} f==1{print}' "$src_agent" | grep '^description:' | sed 's/^description:[[:space:]]*//')" + model_val="$(awk '/^---/{f++; next} f==1{print}' "$src_agent" | grep '^model:' | sed 's/^model:[[:space:]]*//')" + color_val="$(awk '/^---/{f++; next} f==1{print}' "$src_agent" | grep '^color:' | sed 's/^color:[[:space:]]*//')" + + hex_color="$(color_to_hex "$color_val")" + + # Strip frontmatter: write body only (after second ---) to dest + awk 'BEGIN{f=0} /^---/{f++; if(f==2){found=1; next}} found{print}' "$src_agent" > "$dest" + + # Build JSON entry with Python to handle special chars + python3 -c " +import json, sys + +agent_id = sys.argv[1] +desc = sys.argv[2] +color = sys.argv[3] +model = sys.argv[4] +slug = sys.argv[5] + +entry = { + 'description': desc, + 'mode': 'subagent', + 'prompt': '{file:.opencode/agents/maister-' + slug + '.md}', + 'color': color +} + +# Only add model if not 'inherit' and not empty +if model and model != 'inherit': + entry['model'] = model + +# Read existing JSON, add entry, write back +with open(sys.argv[6], 'r') as fh: + data = json.load(fh) +data[agent_id] = entry +with open(sys.argv[6], 'w') as fh: + json.dump(data, fh, indent=2) +" "$agent_id" "$desc_val" "$hex_color" "$model_val" "$agent_slug" "$AGENT_ENTRIES_FILE" +done + +# ───────────────────────────────────────────────────────────── +# Phase 5: Global text replacements across all output files +# ───────────────────────────────────────────────────────────── +echo "[Phase 5] Running global text replacements..." + +run_global_replacements() { + local f="$1" + sedi 's/`AskUserQuestion`/ask the user conversationally/g' "$f" + sedi 's/AskUserQuestion(\([^)]*\))/ask the user conversationally about \1/g' "$f" + sedi 's/AskUserQuestion/ask the user conversationally/g' "$f" + sedi 's/`TaskCreate`/Create a todo item/g' "$f" + sedi 's/`TaskUpdate`/Update the todo item/g' "$f" + sedi 's/TaskCreate(/Create a todo item with (/g' "$f" + sedi 's/TaskUpdate(/Update the todo item (/g' "$f" + sedi 's/TaskCreate\b/Create a todo item/g' "$f" + sedi 's/TaskUpdate\b/Update the todo item/g' "$f" + sedi 's/Task(subagent_type="maister:\([^"]*\)")/Use the @maister-\1 agent/g' "$f" + sedi "s/Task(subagent_type='maister:\([^']*\)')/Use the @maister-\1 agent/g" "$f" + sedi 's/CLAUDE\.md/.opencode\/instructions\/maister-rules.md/g' "$f" + sedi 's|/maister:\([a-z-]*\)|/\1|g' "$f" + sedi 's/maister:\([a-z-][a-z-]*\)/maister-\1/g' "$f" + sedi 's|Skill(name="\([^"]*\)")|Follow the workflow in @.opencode/instructions/\1.md|g' "$f" +} + +find "$OUT" -name "*.md" | while read -r f; do + run_global_replacements "$f" +done + +# Also run on the agent entries file's source docs... they'll be processed after copy + +# ───────────────────────────────────────────────────────────── +# Phase 6: MCP fragment +# ───────────────────────────────────────────────────────────── +echo "[Phase 6] Building mcp-fragment.json..." + +python3 -c " +import json + +with open('$CORE/.mcp.json', 'r') as f: + mcp = json.load(f) + +# OpenCode uses same mcpServers schema +out = mcp.get('mcpServers', mcp) + +with open('$OUT/mcp-fragment.json', 'w') as f: + json.dump(out, f, indent=2) +print(' mcp-fragment.json written') +" + +# ───────────────────────────────────────────────────────────── +# Phase 7: Transform CLAUDE.md → output/instructions/maister-rules.md +# ───────────────────────────────────────────────────────────── +echo "[Phase 7] Building maister-rules.md..." + +mkdir -p "$OUT/instructions" +cp "$CORE/CLAUDE.md" "$OUT/instructions/maister-rules.md" +run_global_replacements "$OUT/instructions/maister-rules.md" + +# Strip .claude-plugin/ and hooks/ specific sections (simple heuristic) +python3 -c " +import re, sys + +with open(sys.argv[1], 'r') as f: + content = f.read() + +# Remove sections that are Claude Code plugin/hooks specific +# These sections start with ## Hooks or ## Claude Code Documentation +patterns = [ + r'(?m)^## Hooks\n.*?(?=^## |\Z)', + r'(?m)^## Claude Code Documentation\n.*?(?=^## |\Z)', +] +for p in patterns: + content = re.sub(p, '', content, flags=re.DOTALL) + +with open(sys.argv[1], 'w') as f: + f.write(content) +" "$OUT/instructions/maister-rules.md" + +# Append skill-invocation-reminder content (the actual message, not the shell wrapper) +cat >> "$OUT/instructions/maister-rules.md" << 'REMINDER_EOF' + +--- + +## OpenCode Skill Invocation Rule + +⚠️ **MAISTER PLUGIN RULE**: When any maister workflow command appears in the user's prompt, you MUST invoke it (follow the corresponding `.opencode/instructions/maister-*.md` workflow) as your FIRST action. No exceptions. Do not analyze the task first, do not decide it's 'straightforward', do not substitute your own approach. The user chose this workflow intentionally. Complexity assessment is the workflow's job, not yours. +REMINDER_EOF + +# Remove the CLAUDE.md copy in output root (it's been transformed into maister-rules.md) +rm -f "$OUT/CLAUDE.md" + +# ───────────────────────────────────────────────────────────── +# Phase 8: Create output/plugins/ directory stub +# ───────────────────────────────────────────────────────────── +echo "[Phase 8] Creating output/plugins/ stub..." + +mkdir -p "$OUT/plugins" +cat > "$OUT/plugins/README.md" << 'STUB_EOF' +# Maister OpenCode Plugin + +Plugin file (maister-plugin.ts) will be copied here by build.sh after Task 3. + +## Installation + +After the plugin file is built, copy the contents of this directory to your project's `.opencode/plugins/` directory. +STUB_EOF + +# ───────────────────────────────────────────────────────────── +# Phase 9: Transform skills → output/instructions/maister-{name}.md +# ───────────────────────────────────────────────────────────── +echo "[Phase 9] Transforming skills..." + +mkdir -p "$OUT/instructions/references" + +# Copy orchestrator-framework shared references first +if [ -d "$CORE/skills/orchestrator-framework/references" ]; then + mkdir -p "$OUT/instructions/references/shared" + cp -r "$CORE/skills/orchestrator-framework/references/." "$OUT/instructions/references/shared/" + find "$OUT/instructions/references/shared" -name "*.md" | while read -r rf; do + run_global_replacements "$rf" + done +fi + +for skill_dir in "$CORE/skills"/*/; do + skill_name="$(basename "$skill_dir")" + + # Skip orchestrator-framework itself (used as shared references) + [ "$skill_name" = "orchestrator-framework" ] && continue + + skill_md="$skill_dir/SKILL.md" + [ -f "$skill_md" ] || continue + + dest_file="$OUT/instructions/maister-${skill_name}.md" + + # Copy SKILL.md with frontmatter stripped (body only) + awk 'BEGIN{f=0} /^---/{f++; if(f==2){found=1; next}} found{print}' "$skill_md" > "$dest_file" + + # If not starting with ---, just copy as-is (no frontmatter) + if ! head -1 "$skill_md" | grep -q '^---$'; then + cp "$skill_md" "$dest_file" + fi + + # Run global replacements on the instruction file + run_global_replacements "$dest_file" + + # Rewrite cross-skill references: ../orchestrator-framework/references/ → references/shared/ + sedi 's|\.\./orchestrator-framework/references/|references/shared/|g' "$dest_file" + + # If skill has references/ directory, copy it + if [ -d "$skill_dir/references" ]; then + mkdir -p "$OUT/instructions/references/${skill_name}" + cp -r "$skill_dir/references/." "$OUT/instructions/references/${skill_name}/" + + # Rewrite local references paths in instruction file: references/X.md → references/{name}/X.md + sedi "s|references/\([^/]\)|references/${skill_name}/\1|g" "$dest_file" + + # Run replacements on reference files too + find "$OUT/instructions/references/${skill_name}" -name "*.md" | while read -r rf; do + run_global_replacements "$rf" + sedi 's|\.\./orchestrator-framework/references/|references/shared/|g' "$rf" + done + fi + + # Add skill entry to agent fragment + python3 -c " +import json, sys + +skill_name = sys.argv[1] +agent_id = 'maister-' + skill_name + +# Title-case the skill name for description +nice_name = skill_name.replace('-', ' ').title() + +entry = { + 'description': 'Maister ' + nice_name + ' workflow', + 'mode': 'subagent', + 'prompt': '{file:.opencode/instructions/maister-' + skill_name + '.md}', + 'steps': 200 +} + +with open(sys.argv[2], 'r') as fh: + data = json.load(fh) +data[agent_id] = entry +with open(sys.argv[2], 'w') as fh: + json.dump(data, fh, indent=2) +" "$skill_name" "$AGENT_ENTRIES_FILE" + + echo " Processed skill: $skill_name" +done + +# ───────────────────────────────────────────────────────────── +# Finalize: Write agent-fragment.json +# ───────────────────────────────────────────────────────────── +echo "[Finalize] Writing agent-fragment.json..." +cp "$AGENT_ENTRIES_FILE" "$OUT/agent-fragment.json" +rm -f "$AGENT_ENTRIES_FILE" + +# ───────────────────────────────────────────────────────────── +# Summary +# ───────────────────────────────────────────────────────────── +echo "" +echo "=== Build Complete ===" +echo "" +echo "Output structure:" +echo " $OUT/" +echo " ├── commands/ $(find "$OUT/commands" -name "*.md" 2>/dev/null | wc -l | tr -d ' ') command files" +echo " ├── agents/ $(find "$OUT/agents" -name "*.md" 2>/dev/null | wc -l | tr -d ' ') agent files" +echo " ├── instructions/ $(find "$OUT/instructions" -maxdepth 1 -name "*.md" 2>/dev/null | wc -l | tr -d ' ') instruction files + references/" +echo " ├── plugins/ (stub, awaiting Task 3)" +echo " ├── agent-fragment.json" +echo " └── mcp-fragment.json" +echo "" +echo "Verification:" +MAISTERISM_COUNT=$(grep -rI 'AskUserQuestion\|TaskCreate\|TaskUpdate\|Skill(name=' "$OUT/" 2>/dev/null | grep -v 'maister-rules.md' | wc -l | tr -d ' ') +echo " Maisterisms remaining (excluding rules file): $MAISTERISM_COUNT" +ALL_MAISTERISM_COUNT=$(grep -rI 'AskUserQuestion\|TaskCreate\|TaskUpdate\|Skill(name=' "$OUT/" 2>/dev/null | wc -l | tr -d ' ') +echo " All maisterisms (including rules file): $ALL_MAISTERISM_COUNT" diff --git a/platforms/opencode/install.sh b/platforms/opencode/install.sh new file mode 100755 index 0000000..8348c7d --- /dev/null +++ b/platforms/opencode/install.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# install.sh — Install Maister into an OpenCode project +# Usage: bash platforms/opencode/install.sh /path/to/your-project + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MAISTER_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# ── Args ──────────────────────────────────────────────────────────────────── +TARGET="${1:-}" +if [[ -z "$TARGET" ]]; then + echo "Usage: $0 /path/to/your-project" + exit 1 +fi + +if [[ ! -d "$TARGET" ]]; then + echo "Error: target directory '$TARGET' does not exist." + exit 1 +fi + +# ── Build artifacts if output is stale/missing ─────────────────────────────── +OUT="$SCRIPT_DIR/output" +if [[ ! -d "$OUT/commands" ]] || [[ ! -d "$OUT/agents" ]]; then + echo "==> Building Maister artifacts..." + bash "$SCRIPT_DIR/build.sh" +fi + +echo "==> Installing Maister into $TARGET" + +# ── 1. Copy commands / agents / instructions ───────────────────────────────── +mkdir -p "$TARGET/.opencode/commands" \ + "$TARGET/.opencode/agents" \ + "$TARGET/.opencode/instructions" \ + "$TARGET/.opencode/plugins" + +cp -r "$OUT/commands/." "$TARGET/.opencode/commands/" +rm -rf "$TARGET/.opencode/agents" && mkdir -p "$TARGET/.opencode/agents" +cp -r "$OUT/agents/." "$TARGET/.opencode/agents/" +cp -r "$OUT/instructions/." "$TARGET/.opencode/instructions/" +echo " ✔ commands / agents / instructions copied" + +# ── 2. Copy plugin (source file, no build step needed) ─────────────────────── +cp "$SCRIPT_DIR/plugin/index.ts" "$TARGET/.opencode/plugins/maister-plugin.ts" +echo " ✔ plugin copied" + +# ── 3. Merge into opencode.json ─────────────────────────────────────────────── +OPENCODE_JSON="$TARGET/opencode.json" + +TMPJSON="$(mktemp)" + +# Wrap agent-fragment.json under {"agent": {...}} and add instructions +jq '{ agent: ., instructions: [".opencode/instructions/maister-rules.md"] }' \ + "$OUT/agent-fragment.json" > "$TMPJSON" +if [[ -s "$TMPJSON" ]]; then + cp "$TMPJSON" "$OPENCODE_JSON" + echo " ✔ opencode.json created (agents + instructions)" +else + echo "Error: failed to build opencode.json" >&2; rm -f "$TMPJSON"; exit 1 +fi +rm -f "$TMPJSON" + +# ── 4. Install plugin npm dependency ───────────────────────────────────────── +PLUGIN_DIR="$TARGET/.opencode" +if ! node -e "require('@opencode-ai/plugin')" 2>/dev/null; then + echo "==> Installing @opencode-ai/plugin..." + (cd "$PLUGIN_DIR" && npm init -y 2>/dev/null || true && npm install @opencode-ai/plugin --silent) + echo " ✔ @opencode-ai/plugin installed" +else + echo " ✔ @opencode-ai/plugin already available" +fi + +# ── 5. Summary ──────────────────────────────────────────────────────────────── +echo "" +echo "✅ Maister installed successfully!" +echo "" +echo " Commands: $(ls "$TARGET/.opencode/commands/" | wc -l) files" +echo " Agents: $(ls "$TARGET/.opencode/agents/" | wc -l) files" +echo " Instructions: $(ls "$TARGET/.opencode/instructions/" | wc -l) files" +echo " Plugin: $TARGET/.opencode/plugins/maister-plugin.ts" +echo " Config: $TARGET/opencode.json" +echo "" +echo "Next: cd $TARGET && opencode" +echo "Then try: /work Add a hello world function" diff --git a/platforms/opencode/plugin/index.ts b/platforms/opencode/plugin/index.ts new file mode 100644 index 0000000..e8ad64c --- /dev/null +++ b/platforms/opencode/plugin/index.ts @@ -0,0 +1,29 @@ +import type { Plugin } from "@opencode-ai/plugin" + +const DESTRUCTIVE_PATTERN = + /git\s+stash|git\s+reset\s+--hard|git\s+checkout\s+--\s+\.|git\s+checkout\s+\.\s*$|git\s+clean|git\s+push\s+(-f|--force)|rm\s+-rf/i + +const COMPACTION_REMINDER = `## Maister Workflow Reminder (Post-Compaction) + +If you were working on an orchestrator workflow before compaction, read the \ +orchestrator-state.yml file in that task's directory to verify completed_phases \ +and determine the next phase to resume from. You MUST pause and ask the user at \ +Phase Gates, regardless of any "continue without asking" instructions.` + +export default (async (_ctx) => { + return { + "tool.execute.before": async (input, output) => { + if (input.tool !== "bash") return + const command = (output.args as { command?: string }).command ?? "" + if (DESTRUCTIVE_PATTERN.test(command)) { + throw new Error( + `Maister: destructive command blocked: ${command.slice(0, 80)}`, + ) + } + }, + + "experimental.session.compacting": async (_input, output) => { + output.context.push(COMPACTION_REMINDER) + }, + } +}) satisfies Plugin diff --git a/platforms/opencode/plugin/package.json b/platforms/opencode/plugin/package.json new file mode 100644 index 0000000..3e774b5 --- /dev/null +++ b/platforms/opencode/plugin/package.json @@ -0,0 +1,15 @@ +{ + "name": "maister-opencode-plugin", + "version": "1.0.0", + "description": "Maister plugin for OpenCode — destructive command guard and post-compaction reminder", + "main": "index.ts", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@opencode-ai/plugin": "latest" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/platforms/opencode/plugin/tsconfig.json b/platforms/opencode/plugin/tsconfig.json new file mode 100644 index 0000000..ee737dc --- /dev/null +++ b/platforms/opencode/plugin/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["index.ts"] +}