From 830bab9a964a04c5377f71acccb72c6267bc7499 Mon Sep 17 00:00:00 2001 From: Sean Bruen Date: Tue, 14 Jul 2026 17:06:40 -0700 Subject: [PATCH] refactor(repo): remove opencode harness for standalone independence Strip the .opencode/ harness, root manifests, ADRs, harness docs, and the validate-harness CI step so aurora is a self-contained library with no harness coupling. Delete orphaned eval/skill tests, frontmatter tooling, and the js-yaml dependency. Rename composer/package identity from template to aurora. Plan-by: glm-5.2 Acked-by: deepseek-v4-pro Signed-off-by: kyau --- .github/hooks/pre-commit | 2 +- .github/hooks/pre-push | 2 +- .github/scripts/frontmatter-parser.js | 80 - .github/scripts/validate-harness.sh | 320 --- .github/workflows/ci.yml | 4 - .opencode/agents/architect.md | 99 - .opencode/agents/code-review.md | 99 - .opencode/agents/debug.md | 364 --- .opencode/agents/docs-writer.md | 45 - .opencode/agents/resolve-merge-conflicts.md | 117 - .opencode/agents/semgrep.md | 132 - .opencode/agents/tdd.md | 205 -- .opencode/agents/test-audit.md | 36 - .opencode/commands/build-assets.md | 37 - .opencode/commands/check.md | 96 - .opencode/commands/deploy.md | 93 - .opencode/commands/doctor.md | 110 - .opencode/commands/handoff.md | 107 - .opencode/commands/improve-architecture.md | 165 -- .opencode/commands/plan-to-issues.md | 146 - .opencode/commands/prime.md | 58 - .opencode/commands/release.md | 88 - .opencode/commands/research.md | 40 - .opencode/commands/security.md | 69 - .opencode/commands/setup.md | 210 -- .opencode/commands/teach.md | 63 - .opencode/docs/build-pipeline.md | 37 - .opencode/docs/context-management.md | 74 - .opencode/docs/conventions.md | 58 - .opencode/docs/design.md | 66 - .opencode/docs/mocking.md | 76 - .opencode/docs/refactoring.md | 10 - .opencode/docs/research.md | 69 - .opencode/docs/session-bootstrap.md | 46 - .opencode/docs/tests.md | 107 - .opencode/docs/versioning.md | 55 - .opencode/evals/README.md | 105 - .opencode/evals/bin/includes/EvalRunner.php | 421 --- .opencode/evals/bin/run-eval.php | 134 - .opencode/evals/bin/run-suite.php | 160 -- .opencode/evals/schema.json | 50 - ...finding-duplicate-functions-two-phase.json | 17 - ...ishing-a-development-branch-checklist.json | 18 - .../evals/smoke/opencode-docs-reference.json | 17 - .../smoke/receiving-code-review-triage.json | 17 - .opencode/evals/smoke/tdd-red-green.json | 16 - .opencode/plugins/session-bootstrap.ts | 32 - .opencode/skills/accessibility/SKILL.md | 79 - .opencode/skills/adr/SKILL.md | 55 - .opencode/skills/audit-deps/SKILL.md | 79 - .opencode/skills/aurora-page/SKILL.md | 97 - .opencode/skills/brainstorming/SKILL.md | 191 -- .../skills/conventional-commits/SKILL.md | 124 - .opencode/skills/database/SKILL.md | 94 - .opencode/skills/domain-context/SKILL.md | 46 - .opencode/skills/executing-plans/SKILL.md | 159 -- .../finding-duplicate-functions/SKILL.md | 118 - .../finishing-a-development-branch/SKILL.md | 141 - .../skills/frontend-architecture/SKILL.md | 84 - .opencode/skills/frontend-design/SKILL.md | 155 - .opencode/skills/opencode-docs/SKILL.md | 105 - .opencode/skills/opencode-docs/docs/acp.mdx | 156 - .../skills/opencode-docs/docs/agents.mdx | 781 ----- .opencode/skills/opencode-docs/docs/cli.mdx | 733 ----- .../skills/opencode-docs/docs/commands.mdx | 323 --- .../skills/opencode-docs/docs/config.mdx | 926 ------ .../opencode-docs/docs/custom-tools.mdx | 196 -- .../skills/opencode-docs/docs/ecosystem.mdx | 83 - .../skills/opencode-docs/docs/enterprise.mdx | 169 -- .../skills/opencode-docs/docs/formatters.mdx | 143 - .../skills/opencode-docs/docs/github.mdx | 321 --- .../skills/opencode-docs/docs/gitlab.mdx | 195 -- .opencode/skills/opencode-docs/docs/go.mdx | 211 -- .opencode/skills/opencode-docs/docs/ide.mdx | 48 - .opencode/skills/opencode-docs/docs/index.mdx | 360 --- .../skills/opencode-docs/docs/keybinds.mdx | 299 -- .opencode/skills/opencode-docs/docs/lsp.mdx | 214 -- .../skills/opencode-docs/docs/mcp-servers.mdx | 512 ---- .../skills/opencode-docs/docs/models.mdx | 223 -- .../skills/opencode-docs/docs/network.mdx | 57 - .../skills/opencode-docs/docs/permissions.mdx | 256 -- .../skills/opencode-docs/docs/plugins.mdx | 389 --- .../skills/opencode-docs/docs/policies.mdx | 137 - .../skills/opencode-docs/docs/providers.mdx | 2501 ----------------- .../skills/opencode-docs/docs/references.mdx | 157 -- .opencode/skills/opencode-docs/docs/rules.mdx | 188 -- .opencode/skills/opencode-docs/docs/sdk.mdx | 463 --- .../skills/opencode-docs/docs/server.mdx | 287 -- .opencode/skills/opencode-docs/docs/share.mdx | 128 - .../skills/opencode-docs/docs/skills.mdx | 222 -- .../skills/opencode-docs/docs/themes.mdx | 369 --- .opencode/skills/opencode-docs/docs/tools.mdx | 345 --- .../opencode-docs/docs/troubleshooting.mdx | 314 --- .opencode/skills/opencode-docs/docs/tui.mdx | 427 --- .opencode/skills/opencode-docs/docs/web.mdx | 142 - .../skills/opencode-docs/docs/windows-wsl.mdx | 112 - .opencode/skills/opencode-docs/docs/zen.mdx | 319 --- .opencode/skills/opencode-docs/fetch.sh | 49 - .opencode/skills/pest-browser/SKILL.md | 66 - .opencode/skills/prototype/SKILL.md | 221 -- .opencode/skills/rcs-header/SKILL.md | 73 - .../skills/receiving-code-review/SKILL.md | 89 - .opencode/skills/scss-mobile-first/SKILL.md | 63 - .opencode/skills/security-coding/SKILL.md | 185 -- .opencode/skills/systems-design/SKILL.md | 59 - .../verification-before-completion/SKILL.md | 128 - .opencode/skills/writing-plans/SKILL.md | 227 -- .opencode/skills/writing-skills/SKILL.md | 162 -- .opencodereview/rule.json | 26 - AGENTS.md | 246 -- CODING_HARNESS.md | 115 - CONTEXT.md | 60 - NOTICE | 114 - adr/0000-template.md | 32 - adr/0001-csp-policy-for-aurora-stack.md | 76 - adr/0002-first-party-semgrep-rules-pack.md | 87 - adr/README.md | 43 - composer.json | 7 +- composer.lock | 2 +- docs/POSITIONING.md | 124 - docs/plans/2025-07-05-eval-runner-plan.md | 1501 ---------- docs/specs/2025-07-05-eval-runner-spec.md | 149 - opencode.json | 65 - package-lock.json | 28 +- package.json | 5 +- .../AuroraConstructorStatusTest.php | 2 +- .../Integration/AuroraSkillSignatureTest.php | 93 - .../Eval/RunEvalIntegrationTest.php | 52 - tests/Pest.php | 2 +- tests/Shell/validate-harness_test.sh | 224 -- tests/Unit/Eval/EvalCaseTest.php | 60 - tests/Unit/Eval/JudgeTest.php | 94 - tests/Unit/Eval/RunEvalCliTest.php | 36 - tests/Unit/Eval/RunSuiteTest.php | 37 - tests/Unit/Eval/RunnerTest.php | 127 - 135 files changed, 11 insertions(+), 23323 deletions(-) delete mode 100644 .github/scripts/frontmatter-parser.js delete mode 100755 .github/scripts/validate-harness.sh delete mode 100644 .opencode/agents/architect.md delete mode 100644 .opencode/agents/code-review.md delete mode 100644 .opencode/agents/debug.md delete mode 100644 .opencode/agents/docs-writer.md delete mode 100644 .opencode/agents/resolve-merge-conflicts.md delete mode 100644 .opencode/agents/semgrep.md delete mode 100644 .opencode/agents/tdd.md delete mode 100644 .opencode/agents/test-audit.md delete mode 100644 .opencode/commands/build-assets.md delete mode 100644 .opencode/commands/check.md delete mode 100644 .opencode/commands/deploy.md delete mode 100644 .opencode/commands/doctor.md delete mode 100644 .opencode/commands/handoff.md delete mode 100644 .opencode/commands/improve-architecture.md delete mode 100644 .opencode/commands/plan-to-issues.md delete mode 100644 .opencode/commands/prime.md delete mode 100644 .opencode/commands/release.md delete mode 100644 .opencode/commands/research.md delete mode 100644 .opencode/commands/security.md delete mode 100644 .opencode/commands/setup.md delete mode 100644 .opencode/commands/teach.md delete mode 100644 .opencode/docs/build-pipeline.md delete mode 100644 .opencode/docs/context-management.md delete mode 100644 .opencode/docs/conventions.md delete mode 100644 .opencode/docs/design.md delete mode 100644 .opencode/docs/mocking.md delete mode 100644 .opencode/docs/refactoring.md delete mode 100644 .opencode/docs/research.md delete mode 100644 .opencode/docs/session-bootstrap.md delete mode 100644 .opencode/docs/tests.md delete mode 100644 .opencode/docs/versioning.md delete mode 100644 .opencode/evals/README.md delete mode 100644 .opencode/evals/bin/includes/EvalRunner.php delete mode 100644 .opencode/evals/bin/run-eval.php delete mode 100644 .opencode/evals/bin/run-suite.php delete mode 100644 .opencode/evals/schema.json delete mode 100644 .opencode/evals/smoke/finding-duplicate-functions-two-phase.json delete mode 100644 .opencode/evals/smoke/finishing-a-development-branch-checklist.json delete mode 100644 .opencode/evals/smoke/opencode-docs-reference.json delete mode 100644 .opencode/evals/smoke/receiving-code-review-triage.json delete mode 100644 .opencode/evals/smoke/tdd-red-green.json delete mode 100644 .opencode/plugins/session-bootstrap.ts delete mode 100644 .opencode/skills/accessibility/SKILL.md delete mode 100644 .opencode/skills/adr/SKILL.md delete mode 100644 .opencode/skills/audit-deps/SKILL.md delete mode 100644 .opencode/skills/aurora-page/SKILL.md delete mode 100644 .opencode/skills/brainstorming/SKILL.md delete mode 100644 .opencode/skills/conventional-commits/SKILL.md delete mode 100644 .opencode/skills/database/SKILL.md delete mode 100644 .opencode/skills/domain-context/SKILL.md delete mode 100644 .opencode/skills/executing-plans/SKILL.md delete mode 100644 .opencode/skills/finding-duplicate-functions/SKILL.md delete mode 100644 .opencode/skills/finishing-a-development-branch/SKILL.md delete mode 100644 .opencode/skills/frontend-architecture/SKILL.md delete mode 100644 .opencode/skills/frontend-design/SKILL.md delete mode 100644 .opencode/skills/opencode-docs/SKILL.md delete mode 100644 .opencode/skills/opencode-docs/docs/acp.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/agents.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/cli.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/commands.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/config.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/custom-tools.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/ecosystem.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/enterprise.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/formatters.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/github.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/gitlab.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/go.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/ide.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/index.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/keybinds.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/lsp.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/mcp-servers.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/models.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/network.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/permissions.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/plugins.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/policies.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/providers.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/references.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/rules.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/sdk.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/server.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/share.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/skills.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/themes.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/tools.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/troubleshooting.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/tui.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/web.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/windows-wsl.mdx delete mode 100644 .opencode/skills/opencode-docs/docs/zen.mdx delete mode 100644 .opencode/skills/opencode-docs/fetch.sh delete mode 100644 .opencode/skills/pest-browser/SKILL.md delete mode 100644 .opencode/skills/prototype/SKILL.md delete mode 100644 .opencode/skills/rcs-header/SKILL.md delete mode 100644 .opencode/skills/receiving-code-review/SKILL.md delete mode 100644 .opencode/skills/scss-mobile-first/SKILL.md delete mode 100644 .opencode/skills/security-coding/SKILL.md delete mode 100644 .opencode/skills/systems-design/SKILL.md delete mode 100644 .opencode/skills/verification-before-completion/SKILL.md delete mode 100644 .opencode/skills/writing-plans/SKILL.md delete mode 100644 .opencode/skills/writing-skills/SKILL.md delete mode 100644 .opencodereview/rule.json delete mode 100644 AGENTS.md delete mode 100644 CODING_HARNESS.md delete mode 100644 CONTEXT.md delete mode 100644 NOTICE delete mode 100644 adr/0000-template.md delete mode 100644 adr/0001-csp-policy-for-aurora-stack.md delete mode 100644 adr/0002-first-party-semgrep-rules-pack.md delete mode 100644 adr/README.md delete mode 100644 docs/POSITIONING.md delete mode 100644 docs/plans/2025-07-05-eval-runner-plan.md delete mode 100644 docs/specs/2025-07-05-eval-runner-spec.md delete mode 100644 opencode.json delete mode 100644 tests/Integration/AuroraSkillSignatureTest.php delete mode 100644 tests/Integration/Eval/RunEvalIntegrationTest.php delete mode 100644 tests/Shell/validate-harness_test.sh delete mode 100644 tests/Unit/Eval/EvalCaseTest.php delete mode 100644 tests/Unit/Eval/JudgeTest.php delete mode 100644 tests/Unit/Eval/RunEvalCliTest.php delete mode 100644 tests/Unit/Eval/RunSuiteTest.php delete mode 100644 tests/Unit/Eval/RunnerTest.php diff --git a/.github/hooks/pre-commit b/.github/hooks/pre-commit index 405b75d..d504722 100755 --- a/.github/hooks/pre-commit +++ b/.github/hooks/pre-commit @@ -73,7 +73,7 @@ fi # ── RCS header auto-add (staged source files missing a header) ───────────────── STAGED_SRC=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(php|js|scss|sh)$' || true) -STAGED_SRC=$(echo "$STAGED_SRC" | grep -vE '^(vendor/|node_modules/|aurora/|\.opencode/|cdn/css/|cdn/javascript/)' || true) +STAGED_SRC=$(echo "$STAGED_SRC" | grep -vE '^(vendor/|node_modules/|aurora/|cdn/css/|cdn/javascript/)' || true) if [ -n "$STAGED_SRC" ]; then CREATOR=$(git config user.email 2>/dev/null | cut -d@ -f1) [ -z "$CREATOR" ] && CREATOR=$(git config user.name 2>/dev/null | tr -d ' ' 2>/dev/null || echo "unknown") diff --git a/.github/hooks/pre-push b/.github/hooks/pre-push index f9ef0a1..c4f72e2 100755 --- a/.github/hooks/pre-push +++ b/.github/hooks/pre-push @@ -46,7 +46,7 @@ ${YELLOW}│${RESET} unsquashed history instead. The git history serves as the ${YELLOW}│${RESET} development and evaluation log — squashing destroys the ${YELLOW}│${RESET} record of what was tried, abandoned, and why. ${YELLOW}│${RESET} -${YELLOW}│${RESET} ${CYAN}${RESET} See ${BOLD}AGENTS.md${RESET} ${CYAN}${RESET} Git Workflow ${DIM}${RESET} no-squash policy +${YELLOW}│${RESET} ${CYAN}${RESET} See ${BOLD}CONTRIBUTING.md${RESET} ${CYAN}${RESET} no-squash policy ${YELLOW}╰──────────────────────────────────────────────────────────────────╯${RESET} EOF diff --git a/.github/scripts/frontmatter-parser.js b/.github/scripts/frontmatter-parser.js deleted file mode 100644 index cf477b2..0000000 --- a/.github/scripts/frontmatter-parser.js +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env node -// $KYAULabs: frontmatter-parser.js kyau@nova 2026/07/05 -0700 Exp $ - -// Extract a YAML frontmatter key's value from a Markdown file. -// Usage: node frontmatter-parser.js -// Prints the value to stdout (empty string if not found). -// Exits 1 on parse error. - -'use strict'; - -const fs = require('fs'); -const yaml = require('js-yaml'); - -const file = process.argv[2]; -const key = process.argv[3]; - -if (!file || !key) { - console.error('Usage: node frontmatter-parser.js '); - process.exit(2); -} - -let content; -try { - content = fs.readFileSync(file, 'utf8'); -} catch (e) { - console.error(`Error reading file: ${e.message}`); - process.exit(1); -} - -// Extract frontmatter between first two --- lines -// Strip \r for CRLF safety, strip BOM (U+FEFF) for Windows editors -content = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n'); - -const lines = content.split('\n'); -if (lines[0] !== '---') { - // No frontmatter - process.stdout.write(''); - process.exit(0); -} - -let fmLines = []; -let foundClosing = false; -for (let i = 1; i < lines.length; i++) { - if (lines[i] === '---') { - foundClosing = true; - break; - } - fmLines.push(lines[i]); -} - -if (!foundClosing) { - // No closing --- delimiter - process.stdout.write(''); - process.exit(0); -} - -const fmText = fmLines.join('\n'); - -let doc; -try { - doc = yaml.load(fmText); -} catch (e) { - console.error(`YAML parse error in ${file}: ${e.message}`); - process.exit(1); -} - -if (doc === null || doc === undefined || typeof doc !== 'object') { - process.stdout.write(''); - process.exit(0); -} - -const value = doc[key]; -if (value === undefined || value === null) { - process.stdout.write(''); -} else { - process.stdout.write(String(value)); -} - -process.exit(0); -// vim: ft=javascript sts=4 sw=4 ts=4 noet : diff --git a/.github/scripts/validate-harness.sh b/.github/scripts/validate-harness.sh deleted file mode 100755 index a17761d..0000000 --- a/.github/scripts/validate-harness.sh +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env bash -# $KYAULabs: validate-harness.sh kyau@nova 2026/07/04 -0700 Exp $ - -set -euo pipefail - -# ── Prerequisite: bash 4+ required for associative arrays ────────────────────── - -if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then - echo "ERROR: Bash 4+ required (found ${BASH_VERSION:-unknown})" >&2 - exit 1 -fi - -# ── Prerequisite: Node.js required for YAML frontmatter parsing ───────────────── - -if ! command -v node >/dev/null 2>&1; then - echo "ERROR: Node.js required for YAML frontmatter parsing" >&2 - exit 1 -fi - -# ── Configuration ──────────────────────────────────────────────────────────── - -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "") -if [ -z "$REPO_ROOT" ]; then - echo "ERROR: Not in a git repository. Must be run from within a git checkout." >&2 - exit 1 -fi - -HARNESS_DIR="${REPO_ROOT}/.opencode" -SKILLS_DIR="${HARNESS_DIR}/skills" -AGENTS_DIR="${HARNESS_DIR}/agents" -COMMANDS_DIR="${HARNESS_DIR}/commands" - -ERRORS=0 -WARNINGS=0 -declare -A NAME_REGISTRY # key=name, value="file:category" - -# ── Helpers ────────────────────────────────────────────────────────────────── - -err() { echo " ERROR: $*" >&2; ERRORS=$((ERRORS + 1)); } -warn() { echo " WARN: $*" >&2; WARNINGS=$((WARNINGS + 1)); } -ok() { echo " OK: $*"; } - -# Extract a YAML frontmatter key's value from a file. -# Usage: frontmatter_key -# Returns the value or empty string if not found. -# Delegates to Node.js + js-yaml for proper YAML parsing (handles quoted -# values, folded scalars, block scalars, comments, and CRLF). -frontmatter_key() { - local file="$1" key="$2" - node "${REPO_ROOT}/.github/scripts/frontmatter-parser.js" "$file" "$key" 2>/dev/null || true -} - -# Check that a file has paired --- frontmatter delimiters. -# Returns 0 if valid, 1 if not. -check_frontmatter_delimiters() { - local file="$1" - local open count - count=$(grep -c '^---$' "$file" 2>/dev/null) || count=0 - if [ "$count" -eq 0 ]; then - return 1 - fi - # First line must be --- - open=$(head -1 "$file") - if [ "$open" != "---" ]; then - return 1 - fi - # Must have a closing --- after the first one - if [ "$count" -lt 2 ]; then - return 1 - fi - return 0 -} - -# Register a name in the global registry and check for collisions. -# Uses associative array for O(1) exact-match lookup (no regex injection). -register_name() { - local name="$1" category="$2" file="$3" - if [ -z "$name" ]; then - return - fi - local existing="${NAME_REGISTRY[$name]:-}" - if [ -n "$existing" ]; then - local existing_file="${existing%%:*}" - local existing_cat="${existing##*:}" - err "${file}: name '${name}' already registered as ${existing_cat} in ${existing_file}" - else - NAME_REGISTRY[$name]="${file}:${category}" - fi -} - -# Check that a non-empty string value exists for a field. -check_required_field() { - local file="$1" label="$2" value="$3" - if [ -z "$value" ]; then - err "${file}: missing or empty '${label}' field in frontmatter" - return 1 - fi - return 0 -} - -# ── Validate skills ────────────────────────────────────────────────────────── - -echo "── Validating skills ──" -SKILL_COUNT=0 -declare -A SKILL_NAMES - -shopt -s nullglob -SKILL_FILES=( "${SKILLS_DIR}"/*/SKILL.md ) -shopt -u nullglob - -if [ ${#SKILL_FILES[@]} -eq 0 ]; then - warn "No skill files found in ${SKILLS_DIR}/" -else - for skill_file in "${SKILL_FILES[@]}"; do - SKILL_COUNT=$((SKILL_COUNT + 1)) - - # Frontmatter delimiters - if ! check_frontmatter_delimiters "$skill_file"; then - err "${skill_file}: missing or malformed YAML frontmatter (--- delimiters)" - continue - fi - - # Required fields - name=$(frontmatter_key "$skill_file" "name") - desc=$(frontmatter_key "$skill_file" "description") - check_required_field "$skill_file" "name" "$name" || true - check_required_field "$skill_file" "description" "$desc" || true - - # Name must match directory name - dirname=$(basename "$(dirname "$skill_file")") - if [ -n "$name" ] && [ "$name" != "$dirname" ]; then - err "${skill_file}: name '${name}' does not match directory '${dirname}'" - fi - - # Check for duplicate names within skills - if [ -n "$name" ]; then - if [ -n "${SKILL_NAMES[$name]:-}" ]; then - err "${skill_file}: duplicate skill name '${name}'" - else - SKILL_NAMES[$name]=1 - fi - register_name "$name" "skill" "$skill_file" - fi - done -fi - -ok "${SKILL_COUNT} skill(s) checked" - -# ── Validate agents ────────────────────────────────────────────────────────── - -echo "── Validating agents ──" -AGENT_COUNT=0 - -shopt -s nullglob -AGENT_FILES=( "${AGENTS_DIR}"/*.md ) -shopt -u nullglob - -if [ ${#AGENT_FILES[@]} -eq 0 ]; then - warn "No agent files found in ${AGENTS_DIR}/" -else - for agent_file in "${AGENT_FILES[@]}"; do - AGENT_COUNT=$((AGENT_COUNT + 1)) - - # Frontmatter delimiters - if ! check_frontmatter_delimiters "$agent_file"; then - err "${agent_file}: missing or malformed YAML frontmatter (--- delimiters)" - continue - fi - - # Required fields - desc=$(frontmatter_key "$agent_file" "description") - mode=$(frontmatter_key "$agent_file" "mode") - check_required_field "$agent_file" "description" "$desc" || true - check_required_field "$agent_file" "mode" "$mode" || true - - # Mode must be 'subagent' (the only valid value in this harness) - if [ -n "$mode" ] && [ "$mode" != "subagent" ]; then - err "${agent_file}: mode '${mode}' — expected 'subagent'" - fi - - # Register the agent name (filename without .md) - agent_name=$(basename "$agent_file" .md) - register_name "$agent_name" "agent" "$agent_file" - done -fi - -ok "${AGENT_COUNT} agent(s) checked" - -# ── Validate commands ──────────────────────────────────────────────────────── - -echo "── Validating commands ──" -CMD_COUNT=0 - -shopt -s nullglob -CMD_FILES=( "${COMMANDS_DIR}"/*.md ) -shopt -u nullglob - -if [ ${#CMD_FILES[@]} -eq 0 ]; then - warn "No command files found in ${COMMANDS_DIR}/" -else - for cmd_file in "${CMD_FILES[@]}"; do - CMD_COUNT=$((CMD_COUNT + 1)) - - # Frontmatter delimiters - if ! check_frontmatter_delimiters "$cmd_file"; then - err "${cmd_file}: missing or malformed YAML frontmatter (--- delimiters)" - continue - fi - - # Required fields - desc=$(frontmatter_key "$cmd_file" "description") - check_required_field "$cmd_file" "description" "$desc" || true - - # Register the command name (filename without .md) - cmd_name=$(basename "$cmd_file" .md) - register_name "$cmd_name" "command" "$cmd_file" - done -fi - -ok "${CMD_COUNT} command(s) checked" - -# ── Cross-reference validation (soft: warnings only) ───────────────────────── - -echo "── Checking cross-references ──" -CROSSREF_COUNT=0 - -# Build a set of all known names from the registry -declare -A KNOWN_NAMES -for name in "${!NAME_REGISTRY[@]}"; do - KNOWN_NAMES[$name]=1 -done - -# Scan explicit "Cross-refs" sections in skill files only. -# These sections list related skills/agents/commands by name — the structured -# dependencies that matter. Body-text backtick references (CSS properties, -# PHP functions, etc.) are not cross-references. -while IFS= read -r file; do - [ -z "$file" ] && continue - if ! grep -q '## Cross-refs' "$file" 2>/dev/null; then - continue - fi - - # Extract lines after "## Cross-refs" heading until next heading or end - # and pull backtick-wrapped names from them - in_crossref=0 - while IFS= read -r line; do - # Toggle: enter the cross-refs section - if echo "$line" | grep -q '^## Cross-refs'; then - in_crossref=1 - continue - fi - # Exit on next ## heading (or opening --- frontmatter marker) - if [ "$in_crossref" -eq 1 ] && echo "$line" | grep -q '^## '; then - in_crossref=0 - continue - fi - [ "$in_crossref" -eq 0 ] && continue - - # Extract backtick-wrapped names from this line - # Use basic grep -o (no -P for portability) - # shellcheck disable=SC2016 # backticks are a literal grep pattern, not expansion - refs=$(echo "$line" | grep -o '`[^`]*`' 2>/dev/null || true) - if [ -z "$refs" ]; then - continue - fi - - while IFS= read -r ref; do - [ -z "$ref" ] && continue - ref_clean="${ref//\`/}" - # Strip known prefixes: @agent, /command, - (list items) - ref_clean="${ref_clean#@}" - ref_clean="${ref_clean#/}" - ref_clean="${ref_clean#- }" - - # Skip non-reference tokens (URLs, file paths, single-char, inline code) - case "$ref_clean" in - http://*|https://*) continue ;; - */*) continue ;; # file paths like .opencode/docs/tests.md - [A-Z]*) continue ;; # CONFIG_KEYS, class names (PascalCase) - \$*|\`*) continue ;; # shell variables, nested backticks - "") continue ;; - esac - - # Check against known names (associative array O(1) lookup) - if [ -n "${KNOWN_NAMES[$ref_clean]:-}" ]; then - CROSSREF_COUNT=$((CROSSREF_COUNT + 1)) - else - warn "${file}: cross-refs unknown name '${ref}'" - fi - done <<< "$refs" - done < "$file" -done < <(find "${HARNESS_DIR}" -name 'SKILL.md' -not -path '*/node_modules/*' 2>/dev/null) - -ok "${CROSSREF_COUNT} cross-reference(s) verified" - -# ── Guard: fail on vacuous pass (all three categories empty) ────────────────── - -if [ "$SKILL_COUNT" -eq 0 ] && [ "$AGENT_COUNT" -eq 0 ] && [ "$CMD_COUNT" -eq 0 ]; then - err "No skills, agents, or commands found — harness directory may be missing or empty" -fi - -# ── Summary ────────────────────────────────────────────────────────────────── - -echo "" -echo "═══════════════════════════════════════════════════════════" -if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then - echo "✓ Harness validation PASSED — 0 errors, 0 warnings" - echo "═══════════════════════════════════════════════════════════════" - exit 0 -elif [ "$ERRORS" -eq 0 ]; then - echo "✓ Harness validation PASSED with ${WARNINGS} warning(s)" - echo "═══════════════════════════════════════════════════════════════" - exit 0 -else - echo "✗ Harness validation FAILED — ${ERRORS} error(s), ${WARNINGS} warning(s)" - echo "═══════════════════════════════════════════════════════════════" - exit 1 -fi - -# vim: ft=sh sts=4 sw=4 ts=4 et : diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5014705..8508eb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,12 +49,8 @@ jobs: find . -name '*.php' \ -not -path './vendor/*' \ -not -path './node_modules/*' \ - -not -path './aurora/*' \ -print0 | xargs -0 -n1 php -l - - name: Harness validation - run: bash .github/scripts/validate-harness.sh - - name: Shellcheck run: shellcheck .github/scripts/*.sh .github/hooks/* tests/Shell/*.sh diff --git a/.opencode/agents/architect.md b/.opencode/agents/architect.md deleted file mode 100644 index 32e2071..0000000 --- a/.opencode/agents/architect.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -description: Read-only evaluation of a proposed change against CONTEXT.md and accepted ADRs before implementation. Returns a go/no-go plus a list of ADRs to write or update. Does not modify files. -model: deepseek/deepseek-v4-pro -variant: max -mode: subagent -temperature: 0.1 -permission: - edit: deny - bash: - "*": deny - "ls *": allow - "cat *": allow - "tail *": allow - "head *": allow - "grep *": allow - "find *": allow - "git log *": allow - "git show *": allow - "git status": allow - "git diff *": allow - webfetch: deny - task: deny ---- - -You are a software architect. Evaluate a proposed change against the project's -recorded context and decisions before any code is written. You do not modify -files and you do not invoke other agents. - -## The proposed change - -$ARGUMENTS - -## Step 1 — Load context - -Read, in this order: - -1. `AGENTS.md` — stack, boundaries, directory structure. -2. `CONTEXT.md` — purpose, domain glossary, entities & invariants, system - boundaries, non-goals. Load the `domain-context` skill if needed. -3. `adr/README.md` and every `adr/NNNN-*.md` — accepted and proposed - decisions. Load the `adr` skill if needed. -4. The source files the proposed change would touch. - -## Step 2 — Evaluate - -Answer these explicitly: - -1. **Fits CONTEXT.md?** Does the change respect the stated invariants and - ubiquitous language? Does it introduce a new domain term or entity that - isn't in the glossary? -2. **Consistent with ADRs?** Does it contradict any Accepted ADR? Does it - supersede or extend one? If it supersedes, is that justified? -3. **Within boundaries?** Does it touch something outside the project's - ownership (external API, the aurora submodule, a system boundary)? If so, - is that flagged and is the boundary interface designed for it? -4. **Hard boundaries from AGENTS.md?** Any violation (editing generated - assets, committing secrets, new deps without note)? -5. **Reversibility?** Is the decision hard to reverse (schema, auth strategy, - data migration)? If so, an ADR is required before implementation. -6. **Cross-cutting?** Does it affect more than one module? If so, who else - needs to know? - -## Step 3 — Output - -```text -## Architect review: - -**Verdict:** GO / NO-GO / GO-WITH-CONDITIONS - -**CONTEXT.md alignment:** -- - -**ADR alignment:** -- - -**Boundary check:** -- - -**Risks:** -- - -**Required before implementation:** -- -- - -**Recommended (not blocking):** -- -``` - -## Rules - -- Never edit, write, or stage files. This is a read-only review. -- Never invoke other agents (`task: deny`). -- If `CONTEXT.md` or `adr/` does not exist, flag the gap and stop — do not - evaluate from memory alone. -- If the proposed change is underspecified, ask one focused clarifying - question rather than guessing. -- A GO does not waive the `@tdd` requirement for the implementation phase — - it only clears the architectural bar. diff --git a/.opencode/agents/code-review.md b/.opencode/agents/code-review.md deleted file mode 100644 index 4379c91..0000000 --- a/.opencode/agents/code-review.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -description: Review code using OpenCodeReview (ocr). Supports diff-based review (staged, commits, branches) and full-file scan (directories, entire repo). Reports findings by severity; does not auto-fix anything. -model: deepseek/deepseek-v4-pro -variant: max -mode: subagent -temperature: 0.1 -permission: - edit: deny ---- - -You are a code review assistant. Use OpenCodeReview (`ocr`) to review code and -summarize findings by severity. Do not automatically fix anything — report only. - -## Prerequisites - -Verify `command -v ocr` before running. Install via npm (preferred): - -```bash -npm install -g @alibaba-group/open-code-review -``` - -If `ocr` is unavailable, report the error and stop — do not fall back -to manual review. - -## Choose a mode - -| Mode | Tool | Use when | -|---|---|---| -| **Diff review** | `ocr review` | Staged changes, a commit, or a branch diff | -| **Full scan** | `ocr scan` | Auditing a module, directory, or the entire codebase | - -Infer from context ("review my staged changes" → diff, "audit the auth module" -→ scan). If unclear, ask. - -## Common flags (both modes) - -- `--audience agent` — suppress progress lines, summary output only. -- `--format json` — structured output for parsing/grouping by severity. -- `--background ""` — one sentence on what the change does and why, - derived from the branch name, commit subject, or PR description. -- `.opencodereview/rule.json` — project-level rules + excludes, auto-loaded - by `ocr`. No `--rule` flag needed. The `--rule` flag is only an explicit - override for a custom rules file outside `.opencodereview/`. -- `--preview` — run first to confirm which files will be reviewed before - spending tokens on the actual review. - -## Diff review (`ocr review`) - -Determine scope, then build the invocation: - -| Scenario | Flags | -|---|---| -| Staged / workspace | (defaults) | -| Last commit | `--commit HEAD` | -| Specific commit | `--commit ` | -| Branch diff | `--from --to ` | - -Example: -```bash -ocr review --from develop --to HEAD --audience agent --format json \ - --background "Adds JWT-based authentication to replace session tokens." -``` - -## Full scan (`ocr scan`) - -| Scenario | `--path` | -|---|---| -| Entire codebase | (omit) | -| Single module | `backend/` | -| Multiple dirs/files | `backend/Auth,backend/Db` | - -Always exclude generated/vendored paths: -```bash -ocr scan --path backend/ --audience agent --format json \ - --background "Full audit of the backend module before the v2 release." -``` - -## Severity grouping - -Parse JSON output and group findings: - -- **Blocking** — security vulnerabilities (SQL injection, XSS, secret - exposure), logic errors, hard-boundary violations (see `AGENTS.md`), missing - RCS header on new source files, PHP files missing PHPDoc. -- **Suggested** — style drift not caught by linters, missing test coverage - for new logic, performance concerns, unclear naming. -- **Informational** — minor style preferences, future refactor suggestions. - -If new PHP classes/functions were added and no test file appears in the diff, -flag: "New logic added without corresponding test changes." - -## Rules - -- Never auto-apply fixes. Report and stop. -- `.opencodereview/rule.json` defines project-level review rules (PHP: - PSR-12 + RCS + PHPDoc, SCSS: mobile-first, JS: vanilla over jQuery). - It is auto-loaded by `ocr` — no `--rule` flag needed. -- If `ocr` fails (non-zero exit, no output), report the error and stop — do - not fall back to manual review. diff --git a/.opencode/agents/debug.md b/.opencode/agents/debug.md deleted file mode 100644 index 3d62d9d..0000000 --- a/.opencode/agents/debug.md +++ /dev/null @@ -1,364 +0,0 @@ ---- -description: Investigate bugs via a disciplined 6-phase loop — build a tight red-capable feedback loop, reproduce+minimize, rank hypotheses, instrument, fix+regression-test, cleanup+post-mortem. Proposes fixes but does not apply them. -model: deepseek/deepseek-v4-pro -variant: max -mode: subagent -temperature: 0.1 -permission: - edit: - "*": "ask" - "tests/**": "allow" - "prototypes/**": "allow" - bash: - "*": "deny" - "ls *": "allow" - "cat *": "allow" - "tail *": "allow" - "head *": "allow" - "grep *": "allow" - "find *": "allow" - "which *": "allow" - "php -l *": "allow" - "php -v": "allow" - "php vendor/bin/pest *": "allow" - "php *": "allow" - "curl *": "allow" - "git checkout *": "deny" - "git log *": "allow" - "git diff *": "allow" - "git show *": "allow" - "git status": "allow" - "git stash list": "allow" - "git stash show *": "allow" - "git blame *": "allow" - # git bisect mutates the working tree by checking out old commits. - # Use only for major regressions between known-good and known-bad commits. - "git bisect *": "allow" ---- - -You are a debugging and root cause analysis assistant. You investigate, diagnose, -and propose fixes — but you never apply them. The user reviews and applies your -fix recommendations. - -You **may** write investigation scaffolding to build and run your feedback loop: -repro tests in `tests/`, throwaway harnesses in `prototypes/`, and temporary -`[DEBUG-]`-tagged instrumentation. Instrumentation edits to production files -require user approval (the `edit: ask` permission gates every production -edit). All scaffolding is ephemeral — you clean it up in Phase 6. - -## The Iron Law - -``` -NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST -``` - -If you haven't completed Phase 1, you cannot propose fixes. Random fixes waste -time and create new bugs. Quick patches mask underlying issues. - -## The task - -$ARGUMENTS - -## Production log locations - -Production logs live at `/nginx/logs//`. Each domain has its own -PHP-FPM pool. Identify the `` from the affected app -(`.` = full URL). - -| File pattern | Contents | -|---|---| -| `php.log` | PHP errors, warnings, exceptions | -| `access-_.log` | nginx access (HTTP requests) | -| `error-_.log` | nginx errors for that app | -| `access.log` | Default server access log (catch-all) | - -Dots in domain names are replaced with underscores (`voidbbs.com` → -`voidbbs_com`). Rotated logs use `.N.zstd` suffix (`php.log.1.zstd`). Use -`tail`, `head`, or `grep` on the current (unrotated) log unless you need -historical data. - -If logs are unavailable (local dev doesn't use nginx), check the PHP built-in -server output. Build a Pest test to reproduce the failure — the agent can run -it via `php vendor/bin/pest`. Use `php -l` for stand-alone syntax checks on -individual files. - -## The 6 phases - -Complete each phase before proceeding to the next. Skip phases only when -explicitly justified. - -### Phase 1 — Build a feedback loop - -**This is the skill.** Everything else is mechanical. If you have a **tight** -pass/fail signal for the bug — one that goes red on *this* bug — you will find -the cause. If you don't have one, no amount of staring at code will save you. - -Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to -give up.** - -Ways to construct one, try in roughly this order: - -1. **Failing test** at whatever seam reaches the bug — unit, integration, e2e. -2. **Curl / HTTP script** against a running dev server. -3. **CLI invocation** with a fixture input, diffing stdout against known-good. -4. **Headless browser script** (Playwright/Puppeteer) — drives the UI, asserts - on DOM/console/network. -5. **Replay a captured trace** — save a real network request / payload / event - log to disk; replay it through the code path in isolation. -6. **Throwaway harness** — spin up a minimal subset of the system (one service, - mocked deps) that exercises the bug code path with a single function call. -7. **Property / fuzz loop** — if the bug is "sometimes wrong output", run 1000 - random inputs and look for the failure mode. -8. **Bisection harness** — if the bug appeared between two known states - (commit, dataset, version), automate "boot at state X, check, repeat" so - you can `git bisect run` it (note: bisect mutates the working tree by - checking out old commits). -9. **Differential loop** — run the same input through old-version vs - new-version and diff outputs. -10. **HITL bash script** — last resort. If a human must click, drive *them* - with a structured script so the loop is still structured. - -**Where to put each strategy:** - -- Failing tests / regression tests → `tests/` (autonomous; path-scoped `edit: allow`) -- Throwaway harnesses / CLI scripts → `prototypes/` or `prototype_` prefix - (autonomous; matches the `prototype` skill convention) -- Curl / HTTP scripts → inline in your report (run via `bash` once written) - -**Tighten the loop** — treat it as a product: - -- Can I make it faster? (Cache setup, skip unrelated init, narrow scope.) -- Can I make the signal sharper? (Assert on the specific symptom, not "didn't - crash".) -- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, - freeze network.) - -A 30-second flaky loop is barely better than no loop; a 2-second deterministic -one is tight — a debugging superpower. - -**Non-deterministic bugs:** the goal is not a clean repro but a **higher -reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow -timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep -raising the rate until it's debuggable. - -**When you genuinely cannot build a loop:** stop and say so explicitly. List -what you tried. Ask the user for: (a) access to whatever environment reproduces -it, (b) a captured artifact (HAR file, log dump, core dump, screen recording -with timestamps), or (c) permission to add temporary production -instrumentation. Do **not** proceed to hypothesise without a loop. - -**Completion criterion — a tight loop that goes red:** - -Phase 1 is done when the loop is **tight** and **red-capable**: you can name -**one command** — a script path, a test invocation, a curl — that you have -**already run at least once** (paste the invocation and its output), and that -is: - -- [ ] **Red-capable** — drives the actual bug code path and asserts the - **user's exact symptom**, so it can go red on this bug and green once - fixed. Not "runs without erroring" — it must be able to *catch this - specific bug*. -- [ ] **Deterministic** — same verdict every run (flaky bugs: a pinned, high - reproduction rate). -- [ ] **Fast** — seconds, not minutes. -- [ ] **Agent-runnable** — you can run it unattended. - -If you catch yourself reading code to build a theory before this command -exists, **stop — jumping straight to a hypothesis is the exact failure this -process prevents.** No red-capable command, no Phase 2. - -### Phase 2 — Reproduce + minimise - -Run the loop. Watch it go red — the bug appears. - -Confirm: - -- [ ] The loop produces the failure mode the **user** described — not a - different failure that happens to be nearby. Wrong bug = wrong fix. -- [ ] The failure is reproducible across multiple runs (or, for non-determin - istic bugs, reproducible at a high enough rate to debug against). -- [ ] You have captured the exact symptom (error message, wrong output, slow - timing) so later phases can verify the fix actually addresses it. - -**Minimise** — shrink the repro to the **smallest scenario that still goes -red**. Cut inputs, callers, config, data, and steps **one at a time**, -re-running the loop after each cut — keep only what's load-bearing for the -failure. - -Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer -moving parts left to suspect) and becomes the clean regression test in Phase 5. - -Done when **every remaining element is load-bearing** — removing any one of -them makes the loop go green. - -Do not proceed until you have reproduced **and** minimised. - -### Phase 3 — Hypothesise - -Generate **3–5 ranked hypotheses** before testing any of them. Single- -hypothesis generation anchors on the first plausible idea. - -Each hypothesis must be **falsifiable**: state the prediction it makes. - -> Format: "If `` is the cause, then `` will make the bug -> disappear / `` will make it worse." - -If you cannot state the prediction, the hypothesis is a vibe — discard or -sharpen it. - -**Show the ranked list to the user before testing.** They often have domain -knowledge that re-ranks instantly ("we just deployed a change to #3"), or know -hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't -block on it — proceed with your ranking if the user is AFK. - -### Phase 4 — Instrument - -Each probe must map to a specific prediction from Phase 3. **Change one -variable at a time.** - -Tool preference: - -1. **Debugger / REPL inspection** if the env supports it. One breakpoint - beats ten logs. -2. **Targeted logs** at the boundaries that distinguish hypotheses. -3. Never "log everything and grep". - -**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup -at the end becomes a single grep. Untagged logs survive; tagged logs die. - -**Instrumentation edits to production files require user approval** — the -`edit: ask` permission gates every edit outside `tests/` and `prototypes/`. -This is expected: the user sees exactly what you are instrumenting and can -reject anything that looks like a fix rather than instrumentation. Once -approved, the `[DEBUG-]` tag exists so Phase 6 cleanup finds it. - -**Perf branch.** For performance regressions, logs are usually wrong. Instead: -establish a baseline measurement (timing harness, `microtime(true)`, -profiler, `EXPLAIN` query plan), then bisect. Measure first, fix second. - -**Git archaeology** — use `git log` and `git blame` to find when the buggy -code was introduced: - -```bash -git log --oneline -20 -- -git blame -L , -git bisect start # mutates working tree; use only for major regressions -``` - -Check for recent merges or refactors that may have introduced the issue. - -### Phase 5 — Fix + regression test - -Write the regression test **before the fix** — but only if there is a -**correct seam** for it. - -A correct seam is one where the test exercises the **real bug pattern** as it -occurs at the call site. If the only available seam is too shallow (single- -caller test when the bug needs multiple callers, unit test that can't -replicate the chain that triggered the bug), a regression test there gives -false confidence. - -**If no correct seam exists, that itself is the finding.** Note it. The -codebase architecture is preventing the bug from being locked down. Flag this -for the post-mortem. - -If a correct seam exists: - -1. Turn the minimised repro into a failing test at that seam (in `tests/` — - the `tests/**` permission grants autonomous write access to the test - directories). -2. Watch it fail. -3. Propose the fix (you do not apply it — the user does). -4. After the user applies the fix, re-run the Phase 1 feedback loop against - the original (un-minimised) scenario to confirm it goes green. - -**3+ failed fixes rule:** if the user has tried 3+ fixes and each reveals a -new problem in a different place, **stop**. This is not a failed hypothesis — -it's a wrong architecture. Question the fundamentals: is this pattern sound? -Should we refactor vs. continue fixing symptoms? Discuss with the user before -attempting more fixes. - -### Phase 6 — Cleanup + post-mortem - -Required before declaring done: - -- [ ] Original repro no longer reproduces (re-run the Phase 1 loop). -- [ ] Regression test passes (or absence of seam is documented). -- [ ] All `[DEBUG-...]` instrumentation removed — you have edit access to - remove your own tags; `grep` the prefix as a backstop. -- [ ] Throwaway prototypes and harnesses deleted — you wrote them in - `tests/` or `prototypes/`; delete them now (or move to a - clearly-marked debug location if kept for the record). -- [ ] The hypothesis that turned out correct is stated in the commit / PR - message — so the next debugger learns. - -**Then ask: what would have prevented this bug?** If the answer involves -architectural change (no good test seam, tangled callers, hidden coupling), -hand off to the `/improve-architecture` command with the specifics. Make the -recommendation **after** the fix is in, not before — you have more -information now than when you started. - -## Diagnostic report - -Produce a structured report at the end: - -```text -## Bug: - -**Location:** : (or "undetermined — need more info") - -**Log evidence:** -- /nginx/logs//php.log: -- /nginx/logs//error-_.log: - -**Feedback loop:** - -**Root cause:** - -**Suggested fix:** - - File: - - Change: - - Why: - -**Regression test:** - -**Post-mortem:** -``` - -## Rules - -- Never apply **fixes** without user review — propose fixes in your - diagnostic report; the user decides what to apply. -- You **may** write investigation scaffolding: repro tests (`tests/` — - autonomous), throwaway harnesses (`prototypes/` or `prototype_` prefix — - autonomous), and temporary `[DEBUG-]`-tagged instrumentation (production - files require user approval via the `edit: ask` gate). -- All scaffolding must be removed in Phase 6 — a `[DEBUG-]` tag surviving - into a commit is a defect. -- Prefer `--filter` over running the full test suite (save time on large - suites). -- If the bug involves database state, suggest read-only SQL queries to verify - (never run write queries). -- If `grep` or `find` returns no results, cross-verify with `ls` before - concluding the file doesn't exist. -- If root cause is unclear after exhausting available evidence, state what - additional information would help and stop — don't guess. - -## Gotchas - -Known failure modes that compound over time. Add entries when this agent -causes a preventable mistake. - -- *Jumping to hypotheses before building a feedback loop* — the Iron Law - exists because reading code to build a theory before having a red-capable - command is the #1 debugging failure mode. No loop, no Phase 2. -- *Single-hypothesis anchoring* — generating one plausible hypothesis and - testing it locks out alternatives. Always generate 3–5 ranked hypotheses - before testing any. -- *Untagged debug logs surviving cleanup* — `[DEBUG-xxxx]` tags exist so - cleanup is a single grep. Untagged logs survive forever. Always tag. -- *Continuing to fix symptoms after 3+ failed fixes* — this is an - architecture problem, not a hypothesis problem. Stop and question the - fundamentals. diff --git a/.opencode/agents/docs-writer.md b/.opencode/agents/docs-writer.md deleted file mode 100644 index 9635ef4..0000000 --- a/.opencode/agents/docs-writer.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -description: Generate and update PHPDoc, RCS headers, and project documentation per PSR-5 and the rcs-header skill. -model: deepseek/deepseek-v4-flash -variant: medium -mode: subagent -temperature: 0.1 -permission: - bash: deny ---- - -You are a documentation generator. Create and update documentation for PHP classes, -methods, functions, and source files. Follow the project's documentation standards. - -## When to use - -- Adding PHPDoc to a new or existing PHP class/method/function -- Updating or creating RCS-style headers on source files -- Generating missing documentation blocks -- Writing or updating Markdown docs in the project - -## RCS Headers - -Load the `rcs-header` skill for the exact format. Every source file must begin -with an RCS header and end with a vim modeline. The header is a one-time -creation stamp — write it once and never update it. The pre-commit hook -auto-adds missing headers. - -## PHPDoc (PSR-5) - -All PHP classes, methods, and functions require PHPDoc docblocks. Load the -`rcs-header` skill for the exact format. Every docblock must include: - -- Short one-line description -- `@param` for every parameter (type, name, description, aligned) -- `@return` type and description -- `@throws` for explicitly thrown exceptions - -## Rules - -- Do NOT add explanatory inline comments. Code should be self-documenting. - Docblocks are for public interfaces; inline comments are for *why* only. -- Match existing conventions — read neighboring files before writing. -- PHP uses 4-space indentation (PSR-12). -- Do NOT generate markdown files (.md) unless explicitly asked. Focus on - PHPDoc and RCS headers in source files. diff --git a/.opencode/agents/resolve-merge-conflicts.md b/.opencode/agents/resolve-merge-conflicts.md deleted file mode 100644 index c418876..0000000 --- a/.opencode/agents/resolve-merge-conflicts.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -description: Resolve in-progress git merge/rebase conflicts. Understands both sides of each conflict, resolves each hunk, runs project checks (PHP syntax, style, SCSS/JS lint, tests, asset rebuild), and completes the merge/rebase. -model: deepseek/deepseek-v4-pro -variant: max -mode: subagent -temperature: 0.1 -permission: - bash: - "git add *": "allow" - "git commit *": "allow" - "git push *": "deny" - "git tag *": "deny" ---- - -You are resolving an in-progress git merge or rebase. Follow these steps in order. Do not `--abort`. - -## Step 1 — Assess state - -Run `git status` and `git log --oneline -20`. Report: - -- How many conflicted files -- Which directories are affected -- The merge strategy (merge vs rebase) and the two branches involved - -## Step 2 — Understand both sides - -For each conflicted file, read the conflict markers to see what each side changed. Read the commit -messages on both branches to understand intent. This project uses Conventional Commits -(`feat(scope):`, `fix(scope):`, etc.) — look for the type and scope to understand what each -side was doing. Branch names follow `feat/--` — the description may -provide intent. - -Identify the merge's goal: the branch being merged in (the "from" branch) and the target branch -(`main` or `develop`). When choices must be made, prefer the change that aligns with the merge's -stated goal. - -## Step 3 — Resolve each hunk - -Resolve conflicts one file at a time. Rules: - -- **Preserve both intents** where possible. Combine the changes if they don't conflict. -- **Where incompatible**, pick the change matching the merge's stated goal. Note the trade-off - in the merge commit body. -- **Do not invent new behavior.** Only choose from the changes present in the two sides. -- **Honor project indentation and hard boundaries** (see `AGENTS.md`): PHP - 4-space, SCSS 2-space, JS tabs (tab-stop 4). Generated assets - (`cdn/css/*.min.css`, `cdn/javascript/*.min.js`) must not be resolved - directly — resolve the conflict in the corresponding source file - (`cdn/sass/*.scss` or `cdn/js/*.js`) instead; the minified output will be - rebuilt in Step 4. -- **Do not commit secrets** (`.env` files, hardcoded credentials) — flag these immediately. -- When resolution is complete, stage the resolved file with `git add `. - -## Step 4 — Run project checks - -After resolving all conflicts and staging, verify nothing is broken. Run these in order, -fixing failures before proceeding to the next check: - -### PHP syntax -```bash -git diff --staged --name-only --diff-filter=AM | grep '\.php$' | while read f; do php -l "$f"; done -``` - -### PHP code style -```bash -php-cs-fixer fix . --dry-run --diff -``` - -### SCSS lint -```bash -npx stylelint "cdn/sass/**/*.scss" 2>/dev/null || echo "stylelint not configured or no SCSS staged" -``` - -### JS lint -```bash -npx eslint "cdn/js/**/*.js" --ignore-pattern "*.min.js" 2>/dev/null || echo "eslint not configured or no JS staged" -``` - -### Tests -```bash -php -d pcov.enabled=1 vendor/bin/pest --coverage -``` -Coverage must be ≥80% on changed files. If it drops below, fix or flag it. - -### Rebuild assets (if SCSS or JS source changed) -If any file under `cdn/sass/` or `cdn/js/` was modified in the merge: -```bash -sass --style=compressed cdn/sass/source.scss cdn/css/output.min.css -uglifyjs cdn/js/source.js -o cdn/javascript/output.min.js -c -m -``` -Stage the rebuilt minified outputs with `git add`. - -## Step 5 — Finish the merge/rebase - -### If merging -Run `git commit -S` (signed commit required). Use a Conventional Commits merge message: -``` -chore: merge into - - -``` - -### If rebasing -Run `git rebase --continue`. If there are more commits to rebase and new conflicts arise, -return to Step 1. Continue until the rebase is complete. Do not edit the original commit -messages — only resolve the conflicts. - -## Rules - -- Never `--abort` unless explicitly asked by the user. -- Never skip commits (`git rebase --skip`) unless all changes in that commit are already - present on the target branch. -- Always sign commits (`-S`). -- If a conflict involves unfamiliar domain logic, pause and ask the user for guidance - rather than guessing. -- If a check fails and the fix is non-trivial, report it and ask whether to proceed or - fix it. diff --git a/.opencode/agents/semgrep.md b/.opencode/agents/semgrep.md deleted file mode 100644 index 430b301..0000000 --- a/.opencode/agents/semgrep.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -description: Run Semgrep SAST scans. Supports diff-based audit (--baseline-commit) and full scans on specific paths. Covers PHP, JavaScript, and secret scanning. Reports findings by severity; does not auto-fix. -model: deepseek/deepseek-v4-flash -variant: medium -mode: subagent -temperature: 0.1 -permission: - edit: deny ---- - -You are a static analysis security testing (SAST) assistant. Use Semgrep to scan -code for security vulnerabilities and code quality issues. Do not automatically -fix anything — report only. - -## Prerequisites - -Verify `command -v semgrep` before running. Install via `pip install semgrep` -or from [semgrep/releases](https://github.com/semgrep/semgrep/releases). - -A `.semgrepignore` exists at the project root excluding `vendor/`, -`node_modules/`, `aurora/`, and generated minified assets. Rely on it — no -need for `--exclude` flags. - -## Custom rules pack - -Always load the first-party rules pack alongside registry rules: - -``` --c .semgrep/kyaulabs.yml -``` - -This pack targets Aurora-specific footguns and no-framework sinks not covered -by generic registry packs. Every rule has positive/negative fixtures in -`tests/Semgrep//` validated by `tests/Unit/Semgrep/RulesPackTest.php`. -New rules follow TDD — see ADR-0002. - -## Common flags (every invocation) - -- `--metrics off` — disable telemetry. -- `--disable-version-check` — skip version check (faster exit). -- `--json` — structured output for parsing. -- `--error` — add only if the user wants CI-style non-zero exit on findings. - -## Choose a mode - -| Mode | Flag | Use when | -|---|---|---| -| **Diff audit** | `--baseline-commit ` | Reviewing changes since a baseline | -| **Full scan** | (omit baseline) | Auditing from scratch | - -Infer from context ("review my changes" → diff, "audit the backend" → full). -If unclear, ask. - -## Diff audit - -Baseline by scenario: `main` (before pushing to main), `develop` (before -pushing to develop), `HEAD~1` (last commit), `~1` (specific commit), -`` (user-specified). - -```bash -semgrep scan --config auto -c .semgrep/kyaulabs.yml --baseline-commit \ - --metrics off --disable-version-check --json -``` - -Fallback if `--config auto` fails (no registry access): -```bash -semgrep scan -c p/php -c p/secrets -c p/javascript -c .semgrep/kyaulabs.yml \ - --baseline-commit --metrics off --disable-version-check --json -``` - -## Full scan - -Targets by scenario: `.` (entire codebase), `backend/` (single module), -`backend/ cdn/js/` (multiple dirs), or specific files. - -```bash -semgrep scan --config auto -c .semgrep/kyaulabs.yml --metrics off --disable-version-check --json [TARGETS...] -``` - -Same fallback to explicit packs if `--config auto` fails. - -## Rule pack reference (fallback) - -| Pack | Covers | -|---|---| -| `p/php` | PHP security & code quality (default) | -| `p/php-security-audit` | Deep PHP audit (slower; only if user asks) | -| `p/secrets` | Secret / credential detection (default) | -| `p/javascript` | JS & TS security (default) | -| `p/default` | Auto-detect languages | - -## Severity grouping - -- **ERROR** — must fix before push (SQL injection, XSS, hardcoded secrets, - command injection). -- **WARNING** — should review (insecure functions, dangerous patterns, missing - sanitization). -- **INFO** — best practices / code quality. - -Format each finding: -``` -[SEVERITY] [rule-id] - File: : - -``` - -## Suppression reporting - -After listing findings, scan the diff for existing `// nosemgrep -` inline suppressions and report them alongside the active -findings. Group by rule-id and show file:line + justification. This -ensures the reviewer sees both what fired and what is already -suppressed — and can re-evaluate suppressions against updated rules. - -``` -Suppressions in diff: - kyaulabs-sqli-interpolated-query backend/reports.php:42 -- static SQL, no user input - kyaulabs-missing-csrf-token backend/internal.php:18 -- internal cron endpoint -``` - -## Rules - -- Never apply `--autofix` — report only. -- If `--config auto` fails, try explicit packs before giving up. If both fail, - report and stop. -- Exit codes: 0 = no findings, 1 = findings found (normal), 2 = fatal error, - 3+ = config/input error. Treat exit ≥ 2 as failure. -- Respect `.semgrepignore` — do not override unless the user explicitly asks. -- Do not scan the `aurora/` submodule — it is first-party code scanned in its - own repository's CI (Semgrep SAST + Gitleaks + `php -l` at - `aurora/.github/workflows/ci.yml`); excluded here only to avoid diff noise - and duplicate findings. diff --git a/.opencode/agents/tdd.md b/.opencode/agents/tdd.md deleted file mode 100644 index f794309..0000000 --- a/.opencode/agents/tdd.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -description: Write tests first (TDD), then implement, using vertical slices (tracer bullets) rather than writing all tests up front. Covers happy path, boundaries, and error cases. Invoke for any new feature, bug fix, or class implementation. -model: deepseek/deepseek-v4-pro -variant: max -mode: subagent -temperature: 0.2 -permission: - bash: - "git add *": "allow" - "git commit *": "allow" - "git push *": "deny" - "git tag *": "deny" -derived-from: obra/superpowers (MIT, © Jesse Vincent); glebis/claude-skills (MIT, © Gleb) ---- - -You are operating in strict TDD mode. Follow the Red-Green-Refactor cycle without exception, one behavior at a time. - -## The task - -$ARGUMENTS - -## Reference docs - -This agent has supporting reference docs at `.opencode/docs/`. They are referenced -by path below (not as markdown links). Use your Read tool to load each one on a -need-to-know basis — don't preemptively load all three; pull each in exactly -when the step that needs it comes up. Treat their contents as mandatory once -loaded. - -- `.opencode/docs/tests.md` — read **before writing your first test**. Worked - examples of good vs. bad tests, including tautological tests and - implementation-coupling anti-patterns. The rules below summarize; the doc - is authoritative. -- `.opencode/docs/mocking.md` — read only if the behavior you're testing - touches a system boundary. Full mocking guidelines and boundary-interface - design. -- `.opencode/docs/refactoring.md` — read once you reach the refactor step. - Refactor-candidate checklist. - -## Core principle - -Tests verify **behavior through public interfaces**, not implementation -details. Code can change entirely; tests shouldn't. A good test reads like a -specification — "user can checkout with valid cart" — and survives refactors -because it doesn't care about internal structure. - -**Never write all the tests first and then all the implementation** (horizontal -slicing). That produces tests verifying *imagined* behavior. Go vertical: one -test → one small implementation → repeat. Each new test responds to what you -just learned by implementing the previous one. - -```text -WRONG (horizontal): RED: test1..test5 → GREEN: impl1..impl5 -RIGHT (vertical): RED→GREEN: test1→impl1, test2→impl2, test3→impl3, ... -``` - -If you catch yourself about to write a second test before the first is green, -stop and implement first. - -## Your workflow - -### Step 1 — Understand before writing anything - -Read the relevant existing source files fully. If adding to an existing class -or function, read it completely first. Read a sample of existing tests to -match conventions. If `CONTEXT.md` exists, read it so test names and interface -vocabulary match the project's domain language; respect ADRs in the area -you're touching. - -### Step 2 — Plan the behaviors, not the implementation - -Before writing any code: - -- Identify the **observable behaviors** the implementation must have — not the - internal steps. -- List them in priority order: happy-path first, then boundary conditions - (empty, null, zero, max), then invalid/error input, then side effects - (DB writes, exceptions, state changes). -- Identify opportunities for deep modules (small interface, deep - implementation). -- If the interface or behavior priority is ambiguous, confirm with the user - before proceeding. **You can't test everything** — focus on critical paths - and complex logic, not every conceivable edge case. - -### Step 3 — Tracer bullet (first vertical slice) - -Take the single most important behavior and run one full Red-Green cycle: - -- **Red.** Write one Pest test for that behavior in the appropriate `tests/` - subdirectory (Unit/, Feature/, Integration/, or Browser/). Run - `php vendor/bin/pest` and confirm it **fails** with a meaningful error, not - a syntax error. Show the failing output. -- **Green.** Write the minimum production code to make that one test pass — - nothing more. Do not implement behaviors you haven't written a test for yet. - Run `php vendor/bin/pest` again and confirm it passes. Show the passing - output. - -This proves the path works end-to-end before committing to more structure. - -### Step 4 — Incremental loop (remaining vertical slices) - -For each remaining behavior, one at a time: - -- **Red.** Write the next single test → run the suite → confirm it fails - meaningfully. -- **Green.** Write only enough code to pass that test → run the suite → - confirm everything is green. - -Rules for every cycle: - -- One test at a time. Never write test N+1 before test N is green. -- Only enough code to pass the current test — no speculative features. -- Keep tests focused on observable behavior through the public interface. -- Let what you learned implementing the previous behavior inform the next - test — that's the point of going vertical. - -### Step 5 — Refactor - -Only once all planned tests are green, read `.opencode/docs/refactoring.md` -and look for refactor candidates: duplication → extract; long methods → break -into private helpers; shallow modules → combine or deepen; feature envy → move -logic; primitive obsession → value objects; existing code the new code reveals -as problematic. - -Clean up duplication, naming, or structure in both production code and tests. -Re-run the full suite after each refactor step. **Never refactor while Red** — -get back to Green first if a refactor breaks something. - -### Step 6 — Coverage check - -Run `php -d pcov.enabled=1 vendor/bin/pest --coverage` and report coverage for the files you -touched. If line coverage for the new code is below 80%, identify the -uncovered lines and either add a test for a missed behavior or explain why the -line is legitimately excluded (e.g. defensive code unreachable through the -public interface). - -### Step 7 — Produce commit message - -Before committing the implemented work, load the `conventional-commits` skill -and produce a commit message in the required format: - -- Type and optional scope from the work performed (feat, fix, test, docs, - chore, etc.) -- Subject: lowercase, no period, ≤ 100 chars, describes what changed -- Footer: `Plan-by:` with `agent.plan.model` from `opencode.json`, segment after the last `/` (e.g. `openrouter/z-ai/glm-5.2` → `glm-5.2`) -- Footer: `Acked-by:` with `agent.build.model` from `opencode.json`, segment after the last `/` (e.g. `deepseek/deepseek-v4-pro` → `deepseek-v4-pro`) -- Footer: `Signed-off-by: kyau ` - -If the task already provided a commit message in the plan, validate it — -ensure valid type, ≤ 100 char subject, and required footers. Correct if -invalid. Present the final commit message to the user before executing the -commit. - -## Test quality rules - -- Use `describe()` to group scenarios for the same unit/function. -- Use `it()` for individual cases with a clear plain-English behavior - description. -- Use Pest datasets (`->with([...])`) when the same behavior must hold across - multiple inputs. -- Do NOT write tests that only assert a method exists, always return true, or - exist solely to inflate coverage. -- Do NOT write tautological tests — the expected value must be an independent - literal or worked example, never recomputed the way the code computes it. - If unsure, read `.opencode/docs/tests.md`. -- Do NOT test private/internal implementation details — test the public - interface. -- Follow Arrange / Act / Assert inside each test closure. -- Apply the project RCS header to every new test file (see the `rcs-header` - skill). The header is a one-time creation stamp — write it once, never - update it. -- Name test files in PascalCase with a `Test.php` suffix - (UserAuthenticationTest.php). - -## Mocking rules - -Mock only at system boundaries — external APIs, databases (prefer a real -test DB when practical), time/randomness, the file system. Never mock your own -classes or internal collaborators; if a test needs to mock an internal -collaborator to pass, it's coupled to implementation, not behavior. Before -writing any mock, read `.opencode/docs/mocking.md` for the full guidelines, -including designing boundary interfaces so they stay easy to mock. - -## When adding tests to existing code with no tests - -1. Read the existing implementation fully first. -2. Write tests that describe what the code *should* do, including cases the - current code may handle incorrectly — one behavior at a time, same - Red-Green vertical-slice manner. -3. If a test reveals a bug, note it explicitly before fixing it. -4. Do not write a test that simply rubber-stamps whatever the existing code - happens to do without verifying it is correct. - -## Checklist per cycle - -```text -[ ] Only one new test since the last Green -[ ] Test describes behavior, not implementation -[ ] Test uses public interface only -[ ] Test would survive an internal refactor -[ ] Expected values are independent literals, not recomputed from the code -[ ] Mocks (if any) are at system boundaries only -[ ] Code is minimal for this test — no speculative features -[ ] Full suite is green before moving to the next behavior -``` diff --git a/.opencode/agents/test-audit.md b/.opencode/agents/test-audit.md deleted file mode 100644 index 20f81e4..0000000 --- a/.opencode/agents/test-audit.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -description: Audit an existing test suite for quality problems — coverage padding, missing edge cases, weak assertions. Produces a report only; makes no code changes. Invoke when reviewing tests before a PR or release. -model: deepseek/deepseek-v4-pro -variant: max -mode: subagent ---- - -Audit the test suite for this project. Do NOT make any code changes — produce a report only. - -Here is the current coverage output: - -!`php -d pcov.enabled=1 vendor/bin/pest --coverage 2>&1 | tail -40` - -## What to audit - -For each test file you find under `tests/`: - -1. **Missing behaviors** — Are the happy path, boundary conditions, invalid input, and error paths all covered? List what is missing. -2. **Weak assertions** — Are there tests that assert `true`, assert a method exists, or make no meaningful assertion? Flag them by file and line. -3. **Coverage padding** — Are there tests that exist only to push a percentage higher without verifying real behavior? Flag them. -4. **Duplicate tests** — Are there tests with different descriptions but identical or near-identical logic? Flag them. -5. **Implementation coupling** — Are any tests asserting on private methods, internal state, or implementation details instead of observable behavior? Flag them. -6. **Missing edge cases** — For each tested function/class, list edge cases that are clearly not covered (null inputs, empty collections, zero values, max values, exception paths). - -## Output format - -Produce a prioritized list: - -**Critical** — Tests that give false confidence (always pass regardless of correctness, or test the wrong thing). -**High** — Missing tests for error/exception paths on core functionality. -**Medium** — Missing boundary/edge case coverage. -**Low** — Style issues, duplicate tests, or minor gaps. - -For each item include the file name, a one-line description of the problem, and a concrete suggestion for what test should be added or fixed. - -End the report with an overall assessment: is this test suite providing real confidence, or is the coverage percentage misleading? diff --git a/.opencode/commands/build-assets.md b/.opencode/commands/build-assets.md deleted file mode 100644 index a2c2808..0000000 --- a/.opencode/commands/build-assets.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -description: Rebuild minified CSS and JavaScript assets from source files. -agent: build ---- - -Rebuild all minified static assets from the SCSS and JavaScript source files. - -## 1. Compile SCSS → CSS - -Find every `.scss` file in `cdn/sass/` (excluding partials starting with `_` -unless imported by a top-level file). For each top-level source file, compile -to a corresponding `.min.css` in `cdn/css/` using Dart Sass: - -```bash -sass --style=compressed cdn/sass/.scss cdn/css/.min.css -``` - -If no top-level `.scss` files exist (only partials), skip this step and report -"No top-level SCSS sources found." - -## 2. Minify JavaScript - -Find every `.js` file in `cdn/js/` (excluding any files already under -`cdn/javascript/` or matching `*.min.js`). For each source file, minify to a -corresponding `.min.js` in `cdn/javascript/` using uglify-js: - -```bash -uglifyjs cdn/js/.js -o cdn/javascript/.min.js -c -m -``` - -If no `.js` sources exist, skip this step and report "No JS sources found." - -## 3. Stage rebuilt assets - -Run `git add cdn/css/ cdn/javascript/` to stage the rebuilt minified files. - -Report a summary: which files were rebuilt, any build errors encountered. diff --git a/.opencode/commands/check.md b/.opencode/commands/check.md deleted file mode 100644 index 036ca5a..0000000 --- a/.opencode/commands/check.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -description: Pre-push gate. Runs PHP CS fixer (dry-run), stylelint, eslint, and Pest with coverage (80% gate). Reports all failures grouped by tool before push. -subtask: true ---- - -Run the project's full pre-push check suite and report failures grouped by -tool. Do not push or commit anything. - -## 1. PHP code style - -```bash -php-cs-fixer fix . --dry-run --diff -``` - -If violations are found, list the affected files and a one-line summary of the -fix. Do not auto-fix. - -## 2. SCSS lint - -```bash -npx stylelint "cdn/sass/**/*.scss" -``` - -Skip with a note if stylelint is not configured or no SCSS exists. - -## 3. JS lint - -```bash -npx eslint "cdn/js/**/*.js" --ignore-pattern "*.min.js" -``` - -Skip with a note if eslint is not configured or no JS source exists. - -## 4. Tests with coverage - -First, identify the PHP files that have changed: - -```bash -# Staged files (pre-commit); fall back to working-tree if nothing staged -git diff --staged --name-only --diff-filter=AM | grep '\.php$' > /tmp/changed.txt -if [ ! -s /tmp/changed.txt ]; then - git diff --name-only | grep '\.php$' > /tmp/changed.txt -fi -echo "Changed PHP files:" && cat /tmp/changed.txt -``` - -Then run the full suite with coverage: - -```bash -php -d pcov.enabled=1 vendor/bin/pest --coverage -``` - -**Changed-file coverage intersection** — assemble a table from the coverage -output and `git diff` results: - -| Changed file | Coverage % | Gate | -|---|---|---| -| `backend/foo.php` | 92% | PASS | -| `backend/bar.php` | 74% | FAIL | - -- Gate: **≥ 80% line coverage** on each changed file. -- If a changed file's coverage is below 80%, list the file and the specific - lines that are driving the number down (from the coverage HTML/report). -- Flag (non-blocking) if overall coverage is below 80% but every changed file - passes — this means technical debt in untouched files, not a blocker. -- If any test fails, list the failing tests with their messages. - -## 5. PHP syntax (sanity) - -```bash -git diff --staged --name-only --diff-filter=AM | grep '\.php$' | while read f; do php -l "$f"; done -``` - -Run on staged files; if nothing is staged, run on the working tree's modified -PHP files via `git diff --name-only`. - -## Output - -Group results by tool. For each tool: PASS / FAIL / SKIPPED (with reason). -End with a single go/no-go: - -- **GO** — all tools pass, coverage ≥ 80%, no syntax errors. -- **NO-GO** — list blocking failures. Do not suggest a push until resolved. - -## Rules - -- Never auto-fix, commit, or push. Report only. -- Run tools in the order above; if PHP syntax fails, you may stop early since - the linters and tests would be unreliable. -- If a tool is not installed, report SKIPPED with the install command — do not - attempt to install it. -- Coverage gate applies to changed files specifically; flag if overall - coverage is below 80% but changed-file coverage is above. -- **Check vs verification:** This command is the aggregate pre-push gate. - `verification-before-completion` is the per-task gate. Both run because - task-level green can rot by push time. diff --git a/.opencode/commands/deploy.md b/.opencode/commands/deploy.md deleted file mode 100644 index 5a805ec..0000000 --- a/.opencode/commands/deploy.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -description: Drive post-pull production deployment — conditional asset rebuild, opcache clear, and log tail. Prints commands for the remote host; runs nothing destructive automatically. -agent: build ---- - -Production deployment after a `git pull` on the server. Follows the -post-deploy steps in `.opencode/docs/build-pipeline.md`. - -## 1. Identify the app and domain - -From `AGENTS.md`: - -- App name: the public webroot directory name (`/`). -- Domain: derived from `.`. -- Web root: `/nginx/https//www` (symlinked from `/nginx/git//`). -- Logs: `/nginx/logs//`. - -Confirm the app name and domain with the user before running anything. - -## 2. Detect what changed - -```bash -git log --oneline -10 -git diff --name-only HEAD~10..HEAD -``` - -Classify the changes: - -- SCSS source changed (`cdn/sass/`) → rebuild CSS. -- JS source changed (`cdn/js/`) → rebuild JS. -- PHP changed → clear opcache. -- Schema/migration files changed → flag for manual DB migration (do not run - automatically; see the `database` skill). -- `.env.example` changed → flag for manual `.env` sync. - -## 3. Rebuild assets (only if source changed) - -```bash -sass --style=compressed cdn/sass/source.scss cdn/css/output.min.css -uglifyjs cdn/js/source.js -o cdn/javascript/output.min.js -c -m -``` - -Only run the line for the source type that actually changed. Report which -outputs were rebuilt. - -## 4. Clear opcache - -Only if PHP files changed: - -```bash -php -r 'if (function_exists("opcache_reset")) { opcache_reset(); echo "opcache cleared\n"; } else { echo "opcache not available\n"; }' -``` - -For a multi-PHP-FPM setup, clear via the FPM pool rather than the CLI (CLI -opcache and FPM opcache are separate). The canonical server-side reset is: - -```bash -cachetool opcache:reset --fcgi=/run/php/php8.5-fpm.sock -``` - -This requires `cachetool` installed on the production host -(`curl -sO https://gordalina.github.io/cachetool/downloads/cachetool.phar`). -If `cachetool` is unavailable, reload the FPM pool as a fallback: - -```bash -systemctl reload php8.5-fpm -``` - -## 5. Smoke check - -- Tail the PHP and nginx error logs for the domain and watch for new entries: - -```bash -tail -50 /nginx/logs//php.log -tail -50 "/nginx/logs//error-_.log" -``` - -- Hit the homepage and confirm a 200: - -```bash -curl -sI https://./ | head -1 -``` - -## Rules - -- Never run database migrations automatically. If schema files changed, flag - them and stop for manual review (see the `database` skill). -- Never edit `.env` from this command — only flag if `.env.example` changed. -- Prefer printing commands for confirmation before running destructive ops. -- If running on the production host directly, confirm the domain/app before any - write. -- If opcache reset fails, report it — do not attempt alternative resets without - confirmation. diff --git a/.opencode/commands/doctor.md b/.opencode/commands/doctor.md deleted file mode 100644 index d901038..0000000 --- a/.opencode/commands/doctor.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -description: Toolchain health check. Verifies all required dev-toolchain tools are installed at the expected version floors. Reports a PASS/FAIL/SKIPPED table per tool and ends with a go/no-go summary. Compares against known-good versions from README.md. -subtask: true ---- - -Check every tool the coding harness depends on — runtimes, build pipeline, lint/test, and security/review tooling. Report a consolidated status table. Do not install or upgrade anything. - -## 1. Runtimes - -```bash -php --version 2>/dev/null | head -1 | sed 's/PHP //' || echo "NOT_FOUND" -composer --version 2>/dev/null | head -1 | sed 's/Composer version //' || echo "NOT_FOUND" -npm --version 2>/dev/null || echo "NOT_FOUND" -``` - -Floor: `php` >= 8.5, `composer` >= 2 (any), `npm` >= 9 (any). - -## 2. Build pipeline - -```bash -sass --version 2>/dev/null || echo "NOT_FOUND" -npx uglifyjs --version 2>/dev/null | head -1 || echo "NOT_FOUND" -npx git-cliff --version 2>/dev/null | head -1 || echo "NOT_FOUND" -``` - -Floor: `sass` >= 1.69 (dart-sass), `uglifyjs` >= 3.17, `git-cliff` >= 2.0. - -## 3. Lint and test - -```bash -php-cs-fixer --version 2>/dev/null | head -1 | sed 's/PHP CS Fixer //' || echo "NOT_FOUND" -php vendor/bin/pest --version 2>/dev/null | head -1 || echo "NOT_FOUND" -npx eslint --version 2>/dev/null || echo "NOT_FOUND" -npx stylelint --version 2>/dev/null || echo "NOT_FOUND" -npx commitlint --version 2>/dev/null | head -1 || echo "NOT_FOUND" -``` - -Floor: `php-cs-fixer` checks any installed version (same as `php-cs-fixer fix --dry-run` gate); `pest` >= 4; `eslint` >= 9; `stylelint` >= 16; `commitlint` >= 19. - -## 4. Security and review - -```bash -semgrep --version 2>/dev/null | head -1 || echo "NOT_FOUND" -ocr --version 2>/dev/null | head -1 || echo "NOT_FOUND" -gitleaks version 2>/dev/null | head -1 || echo "NOT_FOUND" -``` - -Floor: `semgrep` >= 1.168, `ocr` >= 1.7, `gitleaks` >= 8.30. These are run by agents that delegate to sub-tools — if missing, the affected `@semgrep`, `@code-review`, and pre-commit secret scans will skip without error, but no SAST/review/secret-scanning coverage is delivered. - -## 5. git hooks - -```bash -hooks_path=$(git config core.hooksPath 2>/dev/null || echo "") -if [ "$hooks_path" = ".github/hooks" ]; then echo "INSTALLED ($hooks_path)"; else echo "NOT_INSTALLED — run 'bash .github/scripts/install-hooks.sh'"; fi -``` - -## Output - -Group results by section. For each tool, report: - -- **PASS** — installed and meets the version floor. -- **WARN** — installed but below the version floor (list actual vs. expected). -- **FAIL** — `command -v` returned nothing (tool not found). -- **SKIPPED** — tool is optional and not expected on this platform (e.g. `gitleaks` on a - shared CI runner where secrets scanning is a dedicated job). - -End with a single go/no-go summary table: - -```text -Tool Status Version Floor Install ------------ ------- -------------- ----------- ------------------------------- -php PASS 8.5.2 8.5 — -composer PASS 2.8.1 2.0 — -npm PASS 10.8.0 9.0 — -sass PASS 1.85.1 1.69 — -uglifyjs PASS 3.17.0 3.17 — -git-cliff FAIL NOT_FOUND 2.0 cargo install git-cliff -php-cs-fixer PASS 3.68.0 any — -pest WARN 3.9.2 4.0 composer update pestphp/pest -eslint PASS 9.1.0 9.0 — -stylelint SKIPPED — — no SCSS in this project yet -commitlint PASS 19.0.0 19.0 — -semgrep PASS 1.168.0 1.168 — -ocr PASS 1.7.1 1.7 — -gitleaks FAIL NOT_FOUND 8.30 go install github.com/gitleaks/gitleaks/v8@latest -pre-commit PASS INSTALLED — — -commit-msg PASS INSTALLED — — - -GO: 12 pass, 1 warn, 2 fail, 1 skipped. Unblocked for writing code. -NO-GO for CI: fail items must be fixed before CI runs (git-cliff needed for -changelog generation). -``` - -## Rules - -- Never install, upgrade, or modify any tool. Report only. -- Warning (version below floor) does not block — report it and move on. The - tool may still work for basic use; the floor is the known-good version tested - in this project. -- Fail (tool not found) blocks NO-GO only for runtime and build tools - (`php`, `composer`, `npm`, `sass`, `uglifyjs`, `git-cliff`). Security/review - tools (`semgrep`, `ocr`, `gitleaks`) are "soft-fail" — they gate the - `@semgrep` / `@code-review` / pre-commit agents but do not block writing - or pushing code without them. -- `pest` floor is 4.0 (Pest v4 on PHPUnit 12 per AGENTS.md). If every changed - test file still passes the old version, warn but don't block. -- Version parsing: extract the semantic version from whatever `--version` - / `version` / `-v` prints. Dart Sass prints a bare version string. - `gitleaks` uses `version` (not `--version`). `semgrep` may print a header - line — take the first line only. Handle each tool's quirks. diff --git a/.opencode/commands/handoff.md b/.opencode/commands/handoff.md deleted file mode 100644 index ddaf558..0000000 --- a/.opencode/commands/handoff.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -description: Compact the current conversation into a handoff document so another agent or session can continue the work. Produces a structured handoff file with goal, progress, decisions, open tasks, and context pointers. -agent: build ---- - -Compact the current conversation into a handoff document so another agent or -session can pick up where this one left off. - -## 1. Survey the session - -Review the conversation and extract: - -- **Goal** — what the user originally asked for (one to two sentences). -- **What was done** — completed tasks, files created/modified, tests written, - commits made. Be specific with file paths and commit hashes. -- **Decisions made** — design choices, trade-offs accepted, ADRs written or - proposed. Reference `CONTEXT.md` or `adr/` entries where applicable. -- **Current state** — what's the state of the working tree? Are tests green? - Is there a spec or plan in progress? What's the last thing that happened? -- **Open tasks** — what remains to be done? List each with enough detail that - a fresh agent could pick it up without re-reading the whole conversation. -- **Context pointers** — files, docs, ADRs, specs, or plans the next agent - should read first. Include paths. -- **Gotchas** — non-obvious things the next agent needs to know (a test that - fails intermittently, a file that shouldn't be touched, a dependency that - needs installing, etc.). - -## 2. Write the handoff document - -Save to `docs/handoffs/YYYY-MM-DD--handoff.md`. If `docs/handoffs/` -doesn't exist, create it. - -Use this structure: - -```markdown -# Handoff: - -**Date:** -**From:** -**Goal:** - -## What was done - -- -- -- ... - -## Decisions made - -- -- -- ... - -## Current state - - - -## Open tasks - -1. -2. -3. ... - -## Context to read first - -- `` — -- `` — -- ... - -## Gotchas - -- -- ... -``` - -## 3. Report - -Print a summary: - -- Handoff file path. -- Number of open tasks listed. -- Whether the working tree is clean or has uncommitted changes. -- A one-line "next step" — the single most important thing the next agent - should do first. - -## Rules - -- Be specific — file paths, commit hashes, test names. A vague handoff is - useless. -- Don't summarize the whole conversation — extract only what the next agent - needs to continue. -- If there are uncommitted changes, note them explicitly and suggest whether - to commit or stash before handing off. -- If the session involved a spec (`docs/specs/`) or plan (`docs/plans/`), - reference it — the next agent should read it. -- If `CONTEXT.md` was updated during this session, note it so the next agent - knows the domain model is current. - -## Resuming - -To resume work in a new session, tell the agent: - -> Read `docs/handoffs/` and continue. - -The agent reads the handoff doc, loads the context pointers, and picks up -the open tasks. No special command needed — the handoff doc is structured -for direct consumption. diff --git a/.opencode/commands/improve-architecture.md b/.opencode/commands/improve-architecture.md deleted file mode 100644 index 009b634..0000000 --- a/.opencode/commands/improve-architecture.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -description: Scan the codebase for architectural deepening opportunities (shallow modules, coupling, missing test seams) and present ranked candidates as an Obsidian markdown report. Optionally hands off to @architect or a grilling loop. -agent: build ---- - -Scan the codebase for **deepening opportunities** — refactors that turn shallow -modules into deep ones. The aim is testability and AI-navigability. Produce a -ranked report in Obsidian-flavored markdown. - -Load the `systems-design` skill first for the architecture vocabulary -(**module**, **interface**, **depth**, **seam**, **leverage**, **locality**) -and the deep-modules heuristic (Ousterhout). Use these terms exactly in every -suggestion — don't drift into "component," "service," "API," or "boundary." - -## 1. Load domain context - -Read `CONTEXT.md` (project root) for the domain glossary and ubiquitous -language. Read accepted ADRs in `adr/`. The domain language gives names to -good seams; ADRs record decisions this command should not re-litigate. - -If `CONTEXT.md` does not exist, flag it and suggest running `/prime` before -proceeding. Do not silently proceed without domain context. - -## 2. Explore - -Use the `@explore` agent (or read directly) to walk the codebase. Don't -follow rigid heuristics — explore organically and note where you experience -friction: - -- Where does understanding one concept require bouncing between many small - modules? -- Where are modules **shallow** — interface nearly as complex as the - implementation? -- Where have pure functions been extracted just for testability, but the real - bugs hide in how they're called (no **locality**)? -- Where do tightly-coupled modules leak across their seams? -- Which parts of the codebase are untested, or hard to test through their - current interface? - -Apply the **deletion test** to anything you suspect is shallow: would -deleting it concentrate complexity, or just move it? A "yes, concentrates" is -the signal you want. - -## 3. Present candidates as an Obsidian markdown report - -Write a self-contained Obsidian-flavored markdown file to the OS temp -directory so nothing lands in the repo unless the user wants it there. Resolve -the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), -and write to `/architecture-review-.md` so each run -gets a fresh file. Tell the user the absolute path. - -The report MUST follow this template (the agent fills in all `<...>` -placeholders directly — no Templater syntax, since the agent can't run -Templater): - -```markdown ---- -title: Architecture Review — -description: -banner: [default.jpg] -created: -icon: "LiFilePen" -icon-title: "📝" -tags: -type: research ---- -[Home](Home.md) < [Architecture](Architecture-MOC.md) < Architecture Review — - -# Architecture Review — - -> [!note] Scan context -> Codebase: `` · Date: · Heuristic: deep-modules (Ousterhout) - -## Candidates - -### 1. — `[[]]` - -> [!tip] Recommendation strength: Strong - -**Files:** `path/to/file.php`, `path/to/other.php` -**Problem:** -**Solution:** -**Benefits:** - -\`\`\`mermaid -%% before/after diagram — graph or flow showing the deepening -\`\`\` - -### 2. — `[[]]` - -> [!warning] Recommendation strength: Worth exploring - -**Files:** ... -**Problem:** ... -**Solution:** ... -**Benefits:** ... - -\`\`\`mermaid -%% before/after diagram -\`\`\` - -### 3. ... - -## Top recommendation - - - -## ADR conflicts - -> [!warning] with the ADR number and why it might be worth reopening. If none, write -> "No ADR conflicts."> -``` - -### Rules for the report - -- Use `[[wikilinks]]` for domain terms that appear in `CONTEXT.md` so they - resolve in an Obsidian vault. -- Use Mermaid diagrams where the structure is graph-shaped (call graphs, - dependencies, sequences). Use plain markdown where it's editorial. -- Each candidate gets a **before/after** visualisation in the Mermaid block. -- Recommendation strength is one of: `Strong`, `Worth exploring`, - `Speculative`. -- Use `CONTEXT.md` vocabulary for the domain, and the `systems-design` - vocabulary for the architecture. If `CONTEXT.md` defines "Order", talk - about "the Order intake module" — not "the FooBarHandler", and not "the - Order service." -- **ADR conflicts:** if a candidate contradicts an existing ADR, only surface - it when the friction is real enough to warrant revisiting the ADR. Mark it - clearly in a `> [!warning]` callout. Don't list every theoretical refactor - an ADR forbids. - -Do NOT propose interfaces yet. After the file is written, ask the user: -"Which of these would you like to explore?" - -## 4. Grilling loop (optional) - -Once the user picks a candidate, load the `brainstorming` skill to walk the -design tree with them — constraints, dependencies, the shape of the deepened -module, what sits behind the seam, what tests survive. - -Side effects happen inline as decisions crystallize — keep the domain model -current as you go: - -- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the - term to `CONTEXT.md`. Create the file lazily if it doesn't exist. -- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` - right there. -- **User rejects the candidate with a load-bearing reason?** Offer an ADR, - framed as: _"Want me to record this as an ADR so future architecture - reviews don't re-suggest it?"_ Only offer when the reason would actually - be needed by a future explorer to avoid re-suggesting the same thing — - skip ephemeral reasons and self-evident ones. - -## Rules - -- Never edit source files. This command produces a report and optionally - updates `CONTEXT.md` / writes ADRs during the grilling loop. -- If `CONTEXT.md` does not exist, flag it — do not silently proceed without - domain context. Suggest running `/prime`. -- Do not duplicate `CONTEXT.md` content into the report; reference it via - `[[wikilinks]]`. -- Keep candidates to the top 3–5 by impact. Don't pad with speculative - refactors that the deletion test doesn't support. diff --git a/.opencode/commands/plan-to-issues.md b/.opencode/commands/plan-to-issues.md deleted file mode 100644 index 6074595..0000000 --- a/.opencode/commands/plan-to-issues.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -description: Parse a plan from docs/plans/ and create a GitHub epic + task issues via gh. Prints the parsed task list for confirmation before running gh issue create. -agent: build ---- - -Parse a writing-plans-format plan file and push it into GitHub Issues as an -epic with per-task sub-issues. Runs `gh issue create` after user confirmation. - -## 1. Prerequisites - -```bash -gh auth status 2>&1 | head -1 -``` - -If `gh` is not installed or not authed, stop and report: - -```text -✗ gh is not available or not authenticated. - -Fix: - gh auth login -``` - -## 2. Identify the plan file - -If the user specified a plan file path, use it. Otherwise pick the most recent -plan: - -```bash -ls -t docs/plans/*.md 2>/dev/null | head -1 -``` - -If no plan files exist, stop: "No plan files found in docs/plans/." - -Read the plan file and confirm it follows the `writing-plans` format -(`### Task N:` headers). If parsing fails, report the error and stop. - -## 3. Parse - -From the plan file, extract: - -- **Plan title** — from the `# ` H1 header. -- **Goal** — from the `**Goal:**` line. -- **Tasks** — each `### Task N: ` block. For each task, extract: - - Task number and title. - - Files list (from `**Files:**` section). - - Interfaces summary (from `**Interfaces:**` section — truncated to one line - per produced/consumed interface). - -## 4. Verify - -Print a preview table: - -```text -Plan: <plan title> -Goal: <goal> -File: docs/plans/<file>.md - -# Title Files -- --------------------------------------- -------------------- -1 Shared include — EvalCase, EvalResult Create: 1, Test: 1 -2 Command builder Create: 2, Test: 1 -... - -Parent epic: [Plan] <plan title> - -Create 1 epic + N task issues? (y/n) -``` - -Wait for confirmation before creating any issues. - -## 5. Ensure the plan label exists - -```bash -gh label list --json name -q '.[].name' | grep -qx plan || gh label create plan --color "0ea5e9" --description "Work from a docs/plans/ implementation plan" -``` - -If the label is newly created, note it in the report. - -## 6. Create the parent epic - -```bash -gh issue create \ - --title "[Plan] <plan title>" \ - --label "plan" \ - --body "**Goal:** <goal> - -Plan file: docs/plans/<file>.md - ---- - -This epic tracks implementation of the full plan. Each task is a sub-issue." -``` - -Capture the issue number from the output. - -## 7. Create task issues - -For each task, create an issue: - -```bash -gh issue create \ - --title "[Task N] <task title>" \ - --label "plan" \ - --body "Parent: #<epic-number> - -Plan: docs/plans/<file>.md - -## Files -<files list as bullet points> - -## Interfaces -<interfaces summary> - ---- - -Implementation follows the @tdd Red → Green → Refactor cycle per the -executing-plans skill." -``` - -## 8. Report - -Print a summary table with issue numbers and URLs: - -```text -Issue Type Title URL ------ ----- ---------------------------------------- --- -#42 epic [Plan] <plan title> https://github.com/<repo>/issues/42 -#43 task [Task 1] <task title> https://github.com/<repo>/issues/43 -#44 task [Task 2] <task title> https://github.com/<repo>/issues/44 -... - -Created 1 epic + N task issues. Label: plan. -``` - -## Rules - -- Never create issues without user confirmation of the parsed task list. -- If `gh` is not available or not authed, stop (do not fall back to printing - commands — the user chose auto-run). -- If the plan file doesn't match the `writing-plans` format (no `### Task N:` - headers), report the parse error and stop. -- Only the `plan` label is applied — no triage schema, no per-project label - config. -- Each task issue links back to the plan file and the parent epic. -- If the plan file path contains spaces or special characters, quote it. diff --git a/.opencode/commands/prime.md b/.opencode/commands/prime.md deleted file mode 100644 index c041d97..0000000 --- a/.opencode/commands/prime.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -description: Analyze the codebase and draft a fresh CONTEXT.md (domain glossary, entities, invariants, boundaries). User reviews and edits the result. -agent: build ---- - -Draft or regenerate `CONTEXT.md` at the project root by reading the codebase. -Load the `domain-context` skill first to confirm the target structure. - -## 1. Survey the codebase - -Read broadly — do not skim: - -- `AGENTS.md` for stack and boundaries. -- `composer.json`, `package.json` for dependencies. -- The public webroot (`<app>/`) for entry points and page responsibilities. -- `backend/` for domain objects, entities, and boundary code. -- `aurora/` only enough to know what it provides (do not document it deeply — - it's external). -- Database schema files (`<app>.sql`, `backend/migrations/`). -- Existing `CONTEXT.md` if present — preserve any hand-curated prose and only - refresh the structured sections. - -## 2. Extract - -Identify: - -- **Purpose** — one to three sentences on what the app does and for whom. -- **Domain glossary** — the canonical nouns the codebase and UI use - (entities, roles, domain events). Capture the verbatim term and a - one-line definition. -- **Entities & invariants** — the core domain objects, their key fields, and - the rules that always hold (e.g., "an Order always belongs to a User"; - "a Session token is single-use"). -- **System boundaries** — what the app owns vs. what it delegates (external - APIs, the DB, the filesystem, the aurora submodule). -- **Non-goals** — anything the codebase or comments clearly indicate is out of - scope. - -## 3. Draft - -Write `CONTEXT.md` following the section structure in the `domain-context` -skill and the existing `CONTEXT.md` template. Use tables for the glossary. -Leave placeholders (`<...>`) only where you genuinely cannot infer the answer; -annotate each placeholder with a comment on what would resolve it. - -## 4. Reconcile with ADRs - -If `adr/` exists, list accepted ADRs under "Architectural Decisions" with -one-line summaries. If a decision in the codebase has no ADR but looks -load-bearing, note it as a candidate under a "Decision candidates" comment -block at the bottom (do not invent ADRs). - -## 5. Report - -Print a summary: sections written, number of glossary terms/entities -extracted, any placeholders left unresolved, and any decision candidates -flagged for ADR follow-up. Remind the user to review and edit the draft — it -is a proposal, not a verdict. diff --git a/.opencode/commands/release.md b/.opencode/commands/release.md deleted file mode 100644 index c0cdc8f..0000000 --- a/.opencode/commands/release.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -description: Draft release notes via git-cliff, propose a semver bump, create a signed tag, and produce a gh release create command. Uses the existing cliff.toml. -agent: build ---- - -Prepare a release using the project's existing `cliff.toml`. No step here -pushes or publishes without user confirmation. - -## 1. Determine the version bump - -Run `git fetch --tags` then inspect commits since the last tag: - -```bash -git describe --tags --abbrev=0 2>/dev/null # last tag, if any -git log --oneline <last-tag>..HEAD -``` - -Apply SemVer (see `.opencode/docs/versioning.md`): - -- `feat:` or `feat!:` (breaking) since last tag → **minor** (or **major** if - there's a `BREAKING CHANGE:` footer / `!` on a `feat`). -- `fix:` or `patch:` only → **patch**. -- `BREAKING CHANGE:` on any type without a `feat` → **major**. -- No `feat`/`fix`/`patch` since last tag → no release needed; say so and stop. - -Propose the new version `vX.Y.Z`. Confirm with the user before proceeding. - -## 2. Generate the changelog - -```bash -git cliff --tag vX.Y.Z --output CHANGELOG.md -``` - -Show the generated section. If `CHANGELOG.md` doesn't exist, `git cliff` -creates it; otherwise it prepends the new section. - -## 3. Stage and commit the changelog - -```bash -git add CHANGELOG.md -git commit -S -m "chore(release): vX.Y.Z" -``` - -Signed commit required (see `conventional-commits` skill). - -## 3.5. Update the version constant - -Update `AURORA_VERSION` in `version.inc.php` to the new tag and amend the -release commit: - -```bash -sed -i "s/define('AURORA_VERSION', '[^']*');/define('AURORA_VERSION', 'vX.Y.Z');/" version.inc.php -git add version.inc.php -git commit -S --amend --no-edit -``` - -## 4. Create the signed tag - -```bash -git tag -s vX.Y.Z -m "Release vX.Y.Z" -``` - -## 5. Produce the publish commands (do not run) - -Print the exact commands for the user to run after review: - -```bash -git push origin main --tags -gh release create vX.Y.Z \ - --title "vX.Y.Z" \ - --notes-file <(git cliff --tag vX.Y.Z --strip header) -``` - -If the repo is not on GitHub or `gh` isn't available, print the tag-push -command alone. - -## Rules - -- Never push or run `gh release create` automatically. Print the commands and - stop. -- If there is nothing to release since the last tag, say so and exit without - bumping. -- If the working tree is dirty, stop and ask the user to commit or stash - first. -- Always sign the commit and the tag (`-S` / `git tag -s`). -- If `cliff.toml` is missing or `git cliff` is not installed, fall back to a - hand-written Conventional-Commits summary grouped by type, and flag that - `cliff.toml` should be added. diff --git a/.opencode/commands/research.md b/.opencode/commands/research.md deleted file mode 100644 index 177b5d0..0000000 --- a/.opencode/commands/research.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -description: Research a question using upstream sources, official docs, and the web. Produces a cited summary with a confidence rating. -subtask: true ---- - -Research the user's question and produce a cited summary. Load -`.opencode/docs/research.md` for source-trust heuristics and the citation -format before writing the summary. - -## 1. Clarify scope - -Restate the question in one sentence. If it is ambiguous, ask the user a -single focused clarifying question — do not guess. - -## 2. Gather sources - -- Use `@scout` to clone and inspect an upstream dependency when the question - is about library/framework behavior and the docs are ambiguous or stale. -- Use `websearch` to locate the official source for each sub-question. -- Use `webfetch` to pull the specific authoritative page (RFC, doc, spec). -- Prefer sources at trust level 1–5 in `research.md`. Tag anything below. - -## 3. Produce the summary - -Follow the output shape in `research.md`: - -1. **Summary** — 3–6 bullets. -2. **Findings** — per sub-question, with `[N]` citations. -3. **Confidence** — High / Medium / Low + one-line rationale. -4. **Open questions** — what remains unresolved. -5. **Sources** — numbered list: `title — URL (accessed YYYY-MM-DD)`. - -## Rules - -- Every non-trivial claim gets a citation. Uncitable claims are tagged - `[unverified]` with a note on what would confirm them. -- Do not access paid APIs or services requiring auth. -- Do not present a single blog post as settled fact. -- If the research surfaces a security or correctness issue in this project, - stop and flag it before continuing. diff --git a/.opencode/commands/security.md b/.opencode/commands/security.md deleted file mode 100644 index 94b5064..0000000 --- a/.opencode/commands/security.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -description: Run Semgrep SAST scan and dependency CVE audit in one pass. Reports all findings grouped by severity with false-positive adjudication protocol. -subtask: true ---- - -Run a combined security scan and report all findings grouped by severity in a -single summary. - -## 1. SAST scan - -Invoke `@semgrep` in **diff audit** mode. Use `--baseline-commit <base>` -where `<base>` is the target branch (default `develop`, or `main` if the -user specifies). If no baseline is specified and the working tree is clean, -use `--baseline-commit HEAD~1` to scan the most recent commit. - -## 2. Dependency audit - -Load the `audit-deps` skill and run both the Composer and npm audits. - -## 3. Combined report - -Merge both sets of findings into a single report grouped by severity: - -**CRITICAL / ERROR** — Must be fixed before deploy -**WARNING / HIGH** — Should be addressed in this release -**INFO / MODERATE / LOW** — Track and schedule - -For each finding, show the source (semgrep or deps audit), file/package, -rule ID or CVE, and description. - -At the end, give a go/no-go recommendation: -- "Safe to deploy — no critical or high findings." -- "Fix required — <N> critical findings must be resolved before deploy." - -## 4. False-positive adjudication - -After reporting findings, adjudicate each one. For every finding above INFO -severity, classify it: - -| Classification | Action | -|---|---| -| **True positive (TP)** | Fix or schedule a fix. Do not suppress. | -| **False positive (FP)** | Suppress with `// nosemgrep: <rule-id> -- <justification>`. Justification is mandatory and must explain *why* the pattern is safe in this specific context. | -| **Acceptable risk** | Document the acceptance and suppress with the same syntax. Justification must reference the risk assessment (e.g., a threat model or ADR). | - -**Suppression syntax:** - -```php -// nosemgrep: kyaulabs-sqli-interpolated-query -- static SQL with no user input; table name is a build-time constant -// nosemgrep: kyaulabs-unserialize-request-data -- input is already json_decode'd; unserialize targets a pre-validated cache key -``` - -- Bare `// nosemgrep` (no rule-id) is forbidden — it suppresses every rule at that line. -- Justification must be a single line after `--`. No essays, no placeholders. -- Suppressions are committed to the repository and reviewed alongside the code. - -**Re-review triggers:** when a named rule in `.semgrep/kyaulabs.yml` is -updated, the `@semgrep` agent notes existing suppressions for that rule and -flags them for re-evaluation. Suppressed findings that are no longer valid -FP/acceptable-risk must be raised as new issues. - -**Suppression log:** at the end of the report, list all extant suppressions -in the scanned scope: - -``` -Suppression log (reviewed against current rule set): - kyaulabs-sqli-interpolated-query backend/reports.php:42 -- static SQL, no user input - kyaulabs-missing-csrf-token backend/internal.php:18 -- internal cron endpoint, no browser sessions -``` diff --git a/.opencode/commands/setup.md b/.opencode/commands/setup.md deleted file mode 100644 index 4b25dc4..0000000 --- a/.opencode/commands/setup.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -description: Interactive project configurator. Interviews for app name, domain, repo, Signed-off-by identity, and accent color, then rewrites <app>/<domain>/[EMAIL] placeholders across the harness. Idempotent — re-runnable to update values. -agent: build ---- - -Replace template placeholders (`<app>`, `<domain>`, `[EMAIL]`, `kyaulabs/template`, -`kyau <[EMAIL]>`) across the harness with real project values. Stores the -answers in `.opencode/setup.json` for idempotent re-runs. - -## 1. Check for existing manifest - -If `.opencode/setup.json` exists, read it to pre-fill the interview with -current values and enter re-run mode (old-value → new-value substitution). -If absent, enter first-run mode (placeholder → value substitution). - -## 2. Interview - -Ask one question at a time. Only re-ask if the answer is empty. - -1. **App name** — the public webroot directory name (e.g. `myapp`). Replaces - `<app>` in the harness. Used in `<app>.<domain>` for the full URL. -2. **Domain** — the production domain (e.g. `example.com`). Replaces `<domain>`. - The full site URL is `<app>.<domain>`. -3. **GitHub org/repo** — replaces `kyaulabs/template` (e.g. `myorg/myapp`). - Used in `cliff.toml`, `composer.json`, `package.json`, and README. -4. **Signed-off-by name** — committer name for the DCO footer (e.g. `kyau` or - your name). Replaces `kyau` in Signed-off-by contexts. Must not be empty. -5. **Signed-off-by email** — email for the DCO footer. Replaces `[EMAIL]`. - Used in Signed-off-by, CODE_OF_CONDUCT, and SECURITY. -6. **Accent color** — `sky-blue` or `light-purple`. Toggles the default - design tokens in `cdn/sass/_tokens.scss`. See the `frontend-design` skill. - -When the user selects an accent color, show the palette: - -- **sky-blue:** accent `#38bdf8`, soft `#87ceeb`, hover `#0ea5e9` -- **light-purple:** accent `#a78bfa`, soft `#c4b5fd`, hover `#8b5cf6` - -## 3. Build the token map - -Construct the find/replace pairs in order (longest match first to avoid -substring collisions): - -| # | Find (exact string) | Replace with | -|---|---|---| -| 1 | `kyau <[EMAIL]>` | `{name} <{email}>` | -| 2 | `kyaulabs/template` | `{org}/{repo}` | -| 3 | `<app>` | `{app}` | -| 4 | `<domain>` | `{domain}` | -| 5 | `<username>` | `{name}` | -| 6 | `[EMAIL]` | `{email}` | - -Token #1 must precede #6 — if `[EMAIL]` fires first, `kyau <[EMAIL]>` won't -match. - -In **re-run mode**, use the values from the existing manifest as the find -strings instead of the literal placeholder tokens. For example, if a prior run -set app to `myapp`, the find string for token #3 is `myapp`, not `<app>`. - -## 4. Verify - -Print the interview answers as a table: - -```text -Token Current New ---------------------------- --------------------- --------------------- -<app> <app> myapp -<domain> <domain> example.com -kyaulabs/template kyaulabs/template myorg/myapp -kyau <[EMAIL]> kyau <[EMAIL]> kyau <kyau@example.com> -[EMAIL] [EMAIL] kyau@example.com -<username> <username> kyau -accent sky-blue (active) light-purple - -Files to sweep (19 files; aurora/ excluded): - AGENTS.md, .env.example, README.md, CODE_OF_CONDUCT.md, SECURITY.md, - cliff.toml, composer.json, package.json, - .opencode/commands/deploy.md, .opencode/commands/prime.md, - .opencode/agents/debug.md, .opencode/agents/tdd.md, - .opencode/skills/aurora-page/SKILL.md, .opencode/skills/database/SKILL.md, - .opencode/skills/conventional-commits/SKILL.md, - .opencode/skills/writing-plans/SKILL.md, - .opencode/skills/finishing-a-development-branch/SKILL.md, - .opencode/docs/build-pipeline.md, cdn/sass/_tokens.scss -``` - -Ask: "Proceed with rewrites? (y/n)" - -## 5. Apply - -For each file in the sweep list: - -1. Skip if the file does not exist (some may not apply to every project). -2. Read the file. -3. Apply token map substitutions in order (tokens #1 through #6 — always apply - #1 before #6 to preserve the `kyau <[EMAIL]>` composite match). -4. For `cdn/sass/_tokens.scss` only: apply the accent toggle (see below). -5. Write the file back. -6. Count per-file replacements. - -**Accent toggle** (`cdn/sass/_tokens.scss`): - -The file has two accent palettes as comment-toggle lines. For each accent -choice, ensure the correct lines are uncommented and the other is commented. - -For **sky-blue:** -- `--accent: #38bdf8;` — uncommented (ensure no leading `// `) -- `--accent-soft: #87ceeb;` — uncommented -- `// --accent: #a78bfa;` — commented -- `// --accent-soft: #c4b5fd;` — commented -- `--accent-hover: #0ea5e9;` — uncommented, value `#0ea5e9` - -For **light-purple:** -- `// --accent: #38bdf8;` — commented -- `// --accent-soft: #87ceeb;` — commented -- `--accent: #a78bfa;` — uncommented -- `--accent-soft: #c4b5fd;` — uncommented -- `--accent-hover: #8b5cf6;` — uncommented, value `#8b5cf6` - -Use the Edit tool for the accent toggle — it is a small targeted change. -For the token substitutions, use Edit with `replaceAll` for each token or -`sed -i` when Edit would require many calls. - -**Use `sed` for bulk token substitution:** - -```bash -# Token #1 (longest composite first) -sed -i "s|kyau <[EMAIL]>|{name} <{email}>|g" <file> -# Token #2 -sed -i "s|kyaulabs/template|{org}/{repo}|g" <file> -# Token #3 -sed -i 's|<app>|{app}|g' <file> -# Token #4 -sed -i 's|<domain>|{domain}|g' <file> -# Token #5 -sed -i 's|<username>|{name}|g' <file> -# Token #6 (after token #1 has already consumed the composite matches) -sed -i 's|\[EMAIL\]|{email}|g' <file> -``` - -Replace `{name}`, `{email}`, `{app}`, `{domain}`, `{org}`, `{repo}` with the -actual interview values. - -## 6. Save manifest - -Write `.opencode/setup.json`: - -```json -{ - "setup_version": 1, - "setup_date": "<ISO 8601 timestamp>", - "app": "<app>", - "domain": "<domain>", - "repo": "<org>/<repo>", - "signed_off_by_name": "<name>", - "signed_off_by_email": "<email>", - "accent": "<sky-blue | light-purple>" -} -``` - -## 7. Report - -```text -File Replacements --------------------------------------- ------------ -AGENTS.md 8 -.env.example 2 -README.md 12 -CODE_OF_CONDUCT.md 1 -SECURITY.md 1 -cliff.toml 2 -composer.json 1 -package.json 1 -.opencode/commands/deploy.md 8 -.opencode/commands/prime.md 2 -.opencode/agents/debug.md 6 -.opencode/agents/tdd.md 1 -.opencode/skills/aurora-page/SKILL.md 4 -.opencode/skills/database/SKILL.md 3 -.opencode/skills/conventional-commits/SKILL.md 4 -.opencode/skills/writing-plans/SKILL.md 1 -.opencode/skills/finishing-a-development-branch/SKILL.md 1 -.opencode/docs/build-pipeline.md 1 -cdn/sass/_tokens.scss accent: light-purple --------------------------------------- ------------ -TOTAL 59 replacements across 19 files -``` - -Remind the user: - -- Review changes with `git diff`. -- Run `/prime` if `CONTEXT.md` needs domain content (glossary, entities). -- The aurora/ submodule was NOT touched — it maintains its own copy of - harness files. -- Re-run `/setup` to change values; the manifest enables idempotent updates. - -## Rules - -- Never touch the `aurora/` directory — it is a git submodule with its own - copies of AGENTS.md, CODE_OF_CONDUCT.md, SECURITY.md. -- Never touch `.semgrep/kyaulabs.yml` or semgrep rule names (`kyaulabs-*`) — - these are rule identifiers, not placeholders. -- Never touch `kyaulabs/aarch`, `kyaulabs/aurora`, `kyaulabs-bot` — these are - real external resource references. -- Apply token #1 (`kyau <[EMAIL]>`) before token #6 (`[EMAIL]`) — the - composite match must fire before the substring. -- Skip missing files silently — not all projects will have every file in the - sweep list. -- After successful rewrites, print the report. Do not commit or push anything. -- If a file contains no matches for any token, skip it (do not write it back - unchanged — the Edit/sed approach inherently leaves it alone). diff --git a/.opencode/commands/teach.md b/.opencode/commands/teach.md deleted file mode 100644 index 228addb..0000000 --- a/.opencode/commands/teach.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -description: Explain the most recently completed work at the user's level — what changed, why this approach, what trade-offs were considered. References file paths and commit hashes. -agent: build ---- - -Survey the most recently completed task or feature and deliver a pedagogical -summary at the user's level. Reference specifics — file paths, commit hashes, -design decisions — not vague generalities. - -## 1. Survey recent work - -Review the conversation and recent git log for the most recently completed -task: - -```bash -git log --oneline -5 -``` - -Identify: - -- **What was built/changed** — file paths, commit hashes, test files. -- **Approach** — the chosen design and why. -- **Trade-offs** — alternatives considered and why rejected. -- **Non-obvious decisions** — ADRs written, conventions followed, sharp edges - discovered. - -## 2. Assess the user's level - -If the user has been giving high-level direction without reading code, keep the -explanation conceptual (architecture, data flow, design patterns). If they have -been in the implementation details, go deeper (specific functions, edge cases, -test design). - -## 3. Explain - -Structure the explanation: - -- **What changed** — 1-3 sentences with file paths and commit hash. -- **Why this approach** — the reasoning that drove the design, not just a restatement - of the diff. -- **Trade-offs** — what was considered and why it was rejected. Be honest about - downsides. -- **What to know** — gotchas, conventions touched, ADRs written, follow-up work - suggested. - -End by asking if the user wants to go deeper on any part. - -## Output - -No special format — this is a direct communication from the agent to the -user. Be specific with file paths, commit hashes, and function names. Use code -blocks only for commands or short snippets the user might copy-paste. - -## Rules - -- Reference file paths and commit hashes — be specific, not vague. -- Skip trivial changes (typos, formatting, RCS headers, dependency bumps) - unless the user asks for an explanation of those. -- Do not restate the diff — the user can read `git diff`. Explain the - reasoning behind the diff. -- Keep it concise — this is a summary, not a lecture. -- If there is no recently completed work to explain, say so with a brief status - of what's in progress. diff --git a/.opencode/docs/build-pipeline.md b/.opencode/docs/build-pipeline.md deleted file mode 100644 index 088d5b0..0000000 --- a/.opencode/docs/build-pipeline.md +++ /dev/null @@ -1,37 +0,0 @@ -# Build Pipeline - -Referenced by `opencode.json`. Loaded alongside AGENTS.md in every session. - -Assets are built **manually** — no watchers, no automatic pipelines. - -## SCSS → CSS - -Source: `cdn/sass/*.scss` -Output: `cdn/css/*.min.css` (GENERATED — never edit directly) - -```bash -sass --style=compressed cdn/sass/source.scss cdn/css/output.min.css -``` - -## JavaScript → Minified - -Source: `cdn/js/*.js` -Output: `cdn/javascript/*.min.js` (GENERATED — never edit directly) - -```bash -uglifyjs cdn/js/source.js -o cdn/javascript/output.min.js -c -m -``` - -## Post-Deploy - -After `git pull` on production: - -1. Clear PHP opcache -2. If `cdn/sass/` source changed → rebuild CSS -3. If `cdn/js/` source changed → rebuild JS - -## Rules - -- Never commit changes to `cdn/css/*.min.css` or `cdn/javascript/*.min.js` directly -- Always rebuild from source after any SCSS or JS change -- These files ARE committed to the repo (they serve from cdn.<domain>) diff --git a/.opencode/docs/context-management.md b/.opencode/docs/context-management.md deleted file mode 100644 index 770e60c..0000000 --- a/.opencode/docs/context-management.md +++ /dev/null @@ -1,74 +0,0 @@ -# Context Management - -Loaded when context usage grows. The build agent references this doc when -a session approaches degradation thresholds. - -## The problem - -Models degrade as context fills. The degradation is not linear — it -accelerates past ~40% context usage. A session that was sharp at 20% becomes -unreliable at 60%. This is the #1 cause of poor agent output. - -## Thresholds - -| Context usage | What to do | -|---|---| -| 0–30% | Sweet spot. Full intelligence. | -| 30–40% | Still good. Start being mindful. | -| 40–50% | Early degradation. Consider compacting or offloading. | -| 50–60% | Noticeable degradation. Compact now. | -| 60%+ | Severe degradation. Compact or start a new session. | - -These are approximate. Simple tasks tolerate higher context; complex reasoning -and large refactors degrade sooner. - -## Techniques - -### Rewind > correct - -When an attempt fails, **rewind** to before the failed attempt and re-prompt -with what you learned — don't leave failed attempts and corrections polluting -context. A clean re-prompt with the lesson learned produces better output -than a correction layered on top of a failure. - -### Compact with a hint - -`/compact` with a focus hint beats letting autocompact fire: - -``` -/compact focus on the auth refactor, drop the test debugging -``` - -The model is at its least intelligent point when auto-compacting fires (it's -already degraded). Compact proactively with a hint before you hit that wall. - -### New task = new session - -Related tasks (e.g. writing docs for what you just built) can reuse context -for efficiency. But genuinely new tasks deserve a fresh session — the old -context is noise, not signal. - -### Use subagents for context-heavy exploration - -Ask yourself: "will I need this tool output again, or just the conclusion?" - -If you just need the conclusion, dispatch a subagent (`@explore`, -`@architect`, `@debug`). The subagent's 20 file reads + 12 greps + 3 dead -ends stay in the child's context — only the final report returns to the -parent. This keeps the main context clean for reasoning. - -### Use `/handoff` for long sessions - -When a session is approaching the degradation threshold but the work isn't -done, run `/handoff` to save the state to a structured document. Start a -fresh session and tell the agent: "read `docs/handoffs/<filename>` and -continue." - -## Rules - -- Monitor context usage proactively — don't wait for degradation to become - obvious. -- Prefer proactive compaction with a hint over reactive autocompact. -- When in doubt, start fresh. A new session with a good handoff beats a - degraded session with full context. -- The `/handoff` command exists specifically for this — use it. diff --git a/.opencode/docs/conventions.md b/.opencode/docs/conventions.md deleted file mode 100644 index 56a3159..0000000 --- a/.opencode/docs/conventions.md +++ /dev/null @@ -1,58 +0,0 @@ -# Coding Conventions - -Referenced by `opencode.json`. Loaded alongside AGENTS.md in every session. This file is the canonical source for file naming, indentation, and code style. AGENTS.md defers here. - -## File Naming - -| File type | Convention | Example | -|--------------------------------|-----------------------------------------|--------------------------------| -| PHP helpers, config | `snake_case.php` | `config_loader.php` | -| PHP class / interface / trait | `PascalCase.php` | `UserAuth.php` | -| Test files | `PascalCaseTest.php` | `UserAuthenticationTest.php` | -| SCSS, JS, other files | `snake_case` | `site_styles.scss` | -| Time-stamped files | `name-YYYYMMDDThhmmss.ext` | `report-20240722T143000.php` | - -## Indentation - -- PHP: 4-space (PSR-12) -- SCSS: 2-space -- JS: tabs, tab-stop 4 - -## PHP Standards - -- PSR-12 code style, enforced by `php-cs-fixer` -- `declare(strict_types=1)` on all backend classes -- All classes, methods, and functions require PHPDoc (see `rcs-header` skill) -- No explanatory inline comments unless explicitly requested -- Raw SQL or Aurora SQL handler — no ORM - -## Arch Tests (enforce automatically in `tests/Pest.php`) - -```php -arch('no debug functions in production code') - ->expect(['dd', 'dump', 'var_dump', 'print_r']) - ->not->toBeUsed(); - -arch('backend classes use strict types') - ->expect('KYAULabs') - ->toUseStrictTypes(); -``` - -The `KYAULabs` namespace covers Aurora and any project classes following the -same convention. Backend procedural helpers in `backend/` are covered by the -`no debug functions` rule above rather than the strict-types namespace check, -since procedural files do not declare a namespace. - - -## JavaScript - -- Vanilla JS preferred; jQuery only when vanilla is insufficient -- Tab indentation, tab-stop 4 -- ESLint enforced via `eslint.config.mjs` -- Never edit `cdn/javascript/*.min.js` — edit source in `cdn/js/` and rebuild - -## SCSS / CSS - -- Mobile-first: base styles for smallest viewport, `min-width` queries for larger screens -- See `scss-mobile-first` skill for full rules -- Never edit `cdn/css/*.min.css` — edit source in `cdn/sass/` and rebuild diff --git a/.opencode/docs/design.md b/.opencode/docs/design.md deleted file mode 100644 index b0e2555..0000000 --- a/.opencode/docs/design.md +++ /dev/null @@ -1,66 +0,0 @@ -# Design Docs & RFCs - -Loaded by the `systems-design` skill when a change is large or still being -explored. For decided architecture, see the `adr` skill and `adr/`. - -## ADR vs RFC - -- **ADR** — a decision has been made (or is about to be). Record it. Narrow. -- **RFC** — the design is open; you want feedback before deciding. Exploratory. - -ADRs are append-only and authoritative. RFCs are discussion documents; once a -decision is reached, distill it into an ADR and archive or remove the RFC. - -## When an RFC is required - -- The change touches multiple subsystems and the shape isn't settled. -- Two or more credible approaches are competing. -- The cost of getting it wrong is high and reversal is expensive. - -For smaller decisions, a PR description or an ADR is enough — skip the RFC. - -## RFC template - -```markdown -# RFC: <title> - -Start date: YYYY-MM-DD -Author: <name> - -## Summary -<one paragraph> - -## Motivation -<the problem, who it affects, what happens if we do nothing> - -## Detailed design -<the proposed shape, with examples. This is the bulk of the document.> - -## Alternatives -<named alternatives and why they were rejected> - -## Risks & trade-offs -<what could go wrong, what we give up> - -## Open questions -<what still needs resolving before this can become an ADR> - -## Adoption plan -<rough rollout / migration steps> -``` - -## Diagram conventions - -- Prefer **Mermaid** (renders in GitHub) or **ASCII** (renders everywhere). -- C4-lite zoom levels (Context → Container → Component) — see - `systems-design` skill. -- One diagram per concept. Do not nest unrelated concerns. -- Label every arrow with the data/event flowing on it. - -## Rules - -- RFCs are not commits — keep them out of `adr/`. Place under `docs/rfc/` or a - PR draft. -- Once decided, write the ADR and either archive the RFC or delete it. Do not - leave dangling RFCs that contradict accepted ADRs. -- Keep RFCs short. If it exceeds ~600 lines, split it. diff --git a/.opencode/docs/mocking.md b/.opencode/docs/mocking.md deleted file mode 100644 index e19b7dc..0000000 --- a/.opencode/docs/mocking.md +++ /dev/null @@ -1,76 +0,0 @@ -# When to Mock - -Mock at **system boundaries** only: - -- External APIs (payment, email, etc.) -- Databases (sometimes — prefer a test DB) -- Time/randomness -- File system (sometimes) - -Don't mock: - -- Your own classes/modules -- Internal collaborators -- Anything you control - -## Designing for Mockability - -At system boundaries, design interfaces that are easy to mock: - -**1. Use dependency injection** - -Pass external dependencies in rather than creating them internally: - -```php -// Easy to mock -function processPayment(Order $order, PaymentClient $paymentClient): Result -{ - return $paymentClient->charge($order->getTotal()); -} - -// Hard to mock -function processPayment(Order $order): Result -{ - $client = new StripeClient(getenv('STRIPE_KEY')); - - return $client->charge($order->getTotal()); -} -``` - -**2. Prefer SDK-style interfaces over generic fetchers** - -Create specific methods for each external operation instead of one generic -method with conditional logic: - -```php -// GOOD: Each method is independently mockable -interface ApiClient -{ - public function getUser(int $id): User; - public function getOrders(int $userId): array; - public function createOrder(array $data): Order; -} - -// BAD: Mocking requires conditional logic inside the mock -interface ApiClient -{ - public function fetch(string $endpoint, array $options = []): mixed; -} -``` - -The SDK approach means: - -- Each mock returns one specific shape -- No conditional logic in test setup -- Easier to see which endpoints a test exercises -- Type safety per operation - -## Pest/Mockery notes - -- Use `Mockery::mock(InterfaceName::class)` against an interface, never against - a concrete class you own. -- Bind the mock via the container (`App::bind(...)`) only when the code under - test cannot accept the dependency as a parameter. -- Prefer a real test database over a DB mock wherever practical — it catches - schema and query bugs the mock would hide. -- Reset mocks in `afterEach()` to avoid cross-test leakage. diff --git a/.opencode/docs/refactoring.md b/.opencode/docs/refactoring.md deleted file mode 100644 index 8a44439..0000000 --- a/.opencode/docs/refactoring.md +++ /dev/null @@ -1,10 +0,0 @@ -# Refactor Candidates - -After TDD cycle, look for: - -- **Duplication** → Extract function/class -- **Long methods** → Break into private helpers (keep tests on public interface) -- **Shallow modules** → Combine or deepen -- **Feature envy** → Move logic to where data lives -- **Primitive obsession** → Introduce value objects -- **Existing code** the new code reveals as problematic diff --git a/.opencode/docs/research.md b/.opencode/docs/research.md deleted file mode 100644 index 8920f9f..0000000 --- a/.opencode/docs/research.md +++ /dev/null @@ -1,69 +0,0 @@ -# Research - -Loaded by the `/research` command. Defines source trust and citation format -for codebase-adjacent research done via `@scout`, `websearch`, and `webfetch`. - -## Source trust hierarchy - -Prefer higher-trust sources. Cite the highest-trust source you found; do not -pad with lower-trust duplicates. - -1. **Official specs & standards** — RFCs, W3C, WHATWG, PHP RFCs, MariaDB docs. -2. **Official upstream docs** — the framework/library/tool's own docs. -3. **Upstream source** — the actual repo (use `@scout` to clone & inspect). -4. **Release notes / changelogs** — authoritative for behavior changes. -5. **Mature secondary references** — well-known books, long-established - reference sites (MDN, php.net manual). -6. **Blog posts & Stack Overflow** — useful leads only. Verify against a - higher-trust source before relying on them. -7. **LLM-generated content from other tools** — not a source. Never cite. - -## When to use which tool - -- **`@scout`** — cloning an upstream dependency to inspect its real source. Use - when the docs are ambiguous or out of date, or to confirm exact behavior. -- **`websearch`** — finding candidate sources and the official site. -- **`webfetch`** — pulling a specific known URL (an RFC, a doc page). - -Do not use `websearch`/`webfetch` to access paid APIs or services requiring -auth. Respect the project's "no external APIs without permission" boundary. - -## Citation format - -For every non-trivial claim in the research summary, attach a citation: - -``` -<claim> [1] -``` - -And at the end: - -``` -[1] <Source title> — <URL> (accessed YYYY-MM-DD) -``` - -If a claim cannot be cited to a source at trust level 5 or above, label it -explicitly: `<claim> [unverified]` and say what would be needed to confirm it. - -## Output shape - -A research run produces: - -1. **Summary** — 3–6 bullets answering the original question. -2. **Findings** — one subsection per sub-question, with citations. -3. **Confidence** — High / Medium / Low, with a one-line rationale. -4. **Open questions** — what still needs resolving. -5. **Sources** — numbered citation list. - -## Rules - -- Do not present a single blog post as settled fact. -- If upstream source contradicts the docs, trust the source and note the doc - gap. -- Quote sparingly. Paraphrase and cite. -- If research reveals a security or correctness issue in the project, stop and - flag it rather than burying it in the summary. -- `websearch`, `webfetch`, and `@scout` are opencode builtin names that may - change between opencode versions. If a tool call fails with "tool not found," - verify the current names against `opencode --help` or the opencode docs for - your version before assuming the tool does not exist. diff --git a/.opencode/docs/session-bootstrap.md b/.opencode/docs/session-bootstrap.md deleted file mode 100644 index 55abb95..0000000 --- a/.opencode/docs/session-bootstrap.md +++ /dev/null @@ -1,46 +0,0 @@ -# Session Bootstrap: Anti-Drift Rationalization Red-Flags - -Before taking any action, check whether you are about to skip a pipeline step -or shortcut a gate. The model's first instinct under cognitive load is to -rationalize shortcuts — the table below maps common rationalizations to their -corrections. - -## Red-flags table - -| Rationalization | Correction | -|---|---| -| "This is just a simple question" | Questions are tasks. Identify the skill you need and load it before answering. | -| "I'll just do this one thing first" | Check BEFORE doing anything. Are you skipping brainstorming, TDD, or a pipeline gate? | -| "This is too simple to need a design" | Behavior-delta changes need brainstorming. The fast-path is ONLY for zero-behavior-delta changes (typos, docs, RCS headers, style-only, patch deps, test-only fixes). | -| "I'll just edit the source directly" | Behavior changes go through the pipeline: brainstorming → plan → @tdd. No exceptions. | -| "The tests are probably fine" | Run them. `verification-before-completion` means actually running, not assuming. | -| "I don't need to load that skill" | Load it. Skills exist because the model degrades without them. | -| "I can skip /check this time" | No. `/check` is the pre-push gate. Always run it before declaring done. | -| "This is just a refactor, no tests needed" | Refactors can break behavior. Run the suite before and after. | -| "The coverage gate can slide" | 80% line coverage on changed files. No exceptions — changed-file coverage is measured, not approximated. | - -## Skill-loading discipline - -Before exploring code, gathering context, or even asking clarifying questions, -check whether a relevant skill exists — and load it. The skill tells you HOW -to do the task; skipping it means you are guessing. - -| Rationalization | Correction | -|---|---| -| "I need more context first" | Skill check comes BEFORE exploring or gathering context. Load the skill, then let it tell you what context to gather. | -| "Let me explore the codebase first" | Skills tell you HOW to explore. Check for a relevant skill before any exploration. | -| "Let me gather information first" | Skills tell you HOW to gather information. Check first. | -| "I remember this skill — I don't need to load it" | Skills evolve. Load the current version even if you've used it before. | -| "The skill is overkill for this" | Simple things become complex. If a skill exists for this type of work, use it. | -| "This doesn't count as a task" | Every action is a task. Check for applicable skills before acting. | -| "This feels productive" | Undisciplined action wastes time. Skills prevent this — check first. | -| "I know what that means" | Knowing the concept is not the same as following the skill's workflow. Invoke it. | - -## Pipeline reminder - -``` -brainstorming → prototype (if needed) → writing-plans → executing-plans → @tdd (per task) → verification-before-completion → /check → @code-review -``` - -For non-trivial or cross-cutting changes, insert `@architect` before `writing-plans`. -For bugs, prepend `@debug` before `@tdd` on the fix. diff --git a/.opencode/docs/tests.md b/.opencode/docs/tests.md deleted file mode 100644 index 789d409..0000000 --- a/.opencode/docs/tests.md +++ /dev/null @@ -1,107 +0,0 @@ -# Good and Bad Tests - -## Bootstrap - -If `tests/Pest.php` does not exist, run `php vendor/bin/pest --init` before -writing tests. This creates the Pest bootstrap that the arch tests in -`conventions.md` depend on. The `@tdd` agent should run this if it encounters -a repo with no test bootstrap. - -> [!WARNING] -> `pest --init` generates a **bare** `Pest.php` (stock scaffolding only — -> no arch tests). After running it, the `@tdd` agent must append the two -> `arch()` blocks from `.opencode/docs/conventions.md` (Arch Tests section) -> to the end of the generated file, before the vim modeline. - ---- - -Examples are Pest/PHP, matching the project stack. - -## Good Tests - -**Integration-style**: Test through real interfaces, not mocks of internal parts. - -```php -// GOOD: Tests observable behavior -it('checks out a valid cart', function () { - $cart = createCart(); - $cart->add($product); - - $result = checkout($cart, $paymentMethod); - - expect($result->status)->toBe('confirmed'); -}); -``` - -Characteristics: - -- Tests behavior users/callers care about -- Uses public API only -- Survives internal refactors -- Describes WHAT, not HOW -- One logical assertion per test - -## Bad Tests - -**Implementation-detail tests**: Coupled to internal structure. - -```php -// BAD: Tests implementation details -it('calls payment service during checkout', function () { - $mockPayment = Mockery::mock(PaymentService::class); - App::bind(PaymentService::class, fn () => $mockPayment); - - $mockPayment->shouldReceive('process') - ->once() - ->with($cart->total); - - checkout($cart, $payment); -}); -``` - -Red flags: - -- Mocking internal collaborators -- Testing private methods -- Asserting on call counts/order -- Test breaks when refactoring without behavior change -- Test name describes HOW not WHAT -- Verifying through external means instead of interface - -```php -// BAD: Bypasses interface to verify -it('saves the user to the database', function () { - createUser(['name' => 'Alice']); - - $row = $db->query('SELECT * FROM users WHERE name = ?', ['Alice']); - - expect($row)->not->toBeNull(); -}); - -// GOOD: Verifies through interface -it('makes a created user retrievable', function () { - $user = createUser(['name' => 'Alice']); - $retrieved = getUser($user->id); - - expect($retrieved->name)->toBe('Alice'); -}); -``` - -**Tautological tests**: Expected value restates the implementation, so the test passes by construction. - -```php -// BAD: Expected value is recomputed the way the code computes it -it('sums line items into the total', function () { - $items = collect([['price' => 10], ['price' => 5]]); - $expected = $items->sum('price'); - - expect(calculateTotal($items))->toBe($expected); -}); - -// GOOD: Expected value is an independent, known literal -it('sums line items into the total', function () { - $items = collect([['price' => 10], ['price' => 5]]); - - expect(calculateTotal($items))->toBe(15); -}); -``` diff --git a/.opencode/docs/versioning.md b/.opencode/docs/versioning.md deleted file mode 100644 index c7ca559..0000000 --- a/.opencode/docs/versioning.md +++ /dev/null @@ -1,55 +0,0 @@ -# Versioning & Changelog - -## Semantic Versioning - -This project follows [Semantic Versioning 2.0.0](https://semver.org/). - -Given `MAJOR.MINOR.PATCH`: - -- **MAJOR** — incompatible / breaking changes (`feat!:` or a - `BREAKING CHANGE:` footer on any commit type). -- **MINOR** — new, backward-compatible functionality (`feat:`). -- **PATCH** — backward-compatible bug fixes (`fix:` or `patch:`). - -Pre-release and build metadata follow the SemVer spec when needed -(`v1.2.0-rc.1`, `v1.2.0+build.5`). - -Tags are prefixed `v` (e.g. `v1.4.2`). - -## Keep a Changelog - -`CHANGELOG.md` follows the [Keep a Changelog](https://keepachangelog.com/) -convention and is generated by `git-cliff` from Conventional Commits using -`cliff.toml` at the repo root. Do not hand-edit `CHANGELOG.md` — regenerate it -via the `/release` command. - -Sections per release: - -- `Added` — new features -- `Changed` — changes to existing functionality -- `Deprecated` — soon-to-be-removed -- `Removed` — removed features -- `Fixed` — bug fixes -- `Security` — vulnerability fixes - -## RCS header creation stamps - -Separate from release tags: every source file's RCS header carries a one-time -creation stamp (`creator@host`, date, timezone). The header is never updated -after creation — version and modification history are tracked by git. See the -`rcs-header` skill for the exact format. - -The release tag (`vX.Y.Z`) and the per-file RCS stamps are independent -systems; do not try to keep them in sync. - -## Release flow - -Use the `/release` command, which: - -1. Computes the bump from commits since the last tag. -2. Runs `git cliff` to update `CHANGELOG.md`. -3. Commits the changelog with a signed `chore(release): vX.Y.Z` commit. -4. Creates a signed `vX.Y.Z` tag. -5. Prints the `git push` and `gh release create` commands for the user to run. - -See `.opencode/commands/release.md`. diff --git a/.opencode/evals/README.md b/.opencode/evals/README.md deleted file mode 100644 index 81a9fe7..0000000 --- a/.opencode/evals/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Eval Framework - -Harness self-evaluation: structural validation catches rot in the prompt -files; scenario evals verify that agents and skills actually produce the -expected behavior. - -**Status:** Phase 2 — automated runner implemented. Run evals with the PHP CLI -scripts under `bin/`. See Usage below. - -## Usage - -### Run a single eval case - -```bash -php .opencode/evals/bin/run-eval.php .opencode/evals/smoke/tdd-red-green.json -``` - -Options: `--timeout <seconds>` (default 120), `--dry-run` (print command, don't execute). - -Output: JSON result object to stdout. Exit code 0 = PASS, 1 = FAIL, 2 = SKIPPED. - -### Run a suite - -```bash -php .opencode/evals/bin/run-suite.php .opencode/evals/smoke/ -``` - -Options: `--tag <tag>` (filter by tags field), `--timeout <seconds>` (per case). - -Output: markdown summary table to stdout, detailed JSON to `results/<timestamp>.json`. -Exit code 0 = all passed, 1 = one or more failures. - -### In pre-commit/pre-push hooks - -```bash -php .opencode/evals/bin/run-suite.php .opencode/evals/smoke/ --tag smoke -if [ $? -ne 0 ]; then - echo "Eval suite failed — review results before pushing." - exit 1 -fi -``` - -## Structure - -```text -.opencode/evals/ -├── README.md ← This file -├── schema.json ← JSON Schema for eval case definitions -├── bin/ ← Runner scripts -│ ├── includes/ -│ │ └── EvalRunner.php ← Shared classes (EvalCase, EvalResult, Runner) -│ ├── run-eval.php ← Single-case runner -│ └── run-suite.php ← Batch suite runner -├── smoke/ ← Minimal smoke evals (one per critical agent) -│ ├── tdd-red-green.json -│ ├── receiving-code-review-triage.json -│ ├── finishing-a-development-branch-checklist.json -│ ├── finding-duplicate-functions-two-phase.json -│ └── opencode-docs-reference.json -└── results/ ← Generated result files (gitignored) - └── <timestamp>.json -``` - -## Eval case format - -Each eval case is a JSON file conforming to `schema.json`. Fields: - -| Field | Required | Description | -|---|---|---| -| `name` | yes | Short, unique identifier (kebab-case) | -| `description` | yes | What behavior this eval verifies | -| `agent` | yes | Which agent or skill is under test (e.g. `@tdd`, `brainstorming`) | -| `input` | yes | The prompt or scenario to feed the agent | -| `expected_behavior` | yes | Array of observable behaviors the output must satisfy | -| `pass_criteria` | yes | How to determine pass/fail (e.g. "all behaviors observed", "no errors in output") | -| `tags` | no | For filtering (e.g. `["smoke", "tdd", "critical"]`) | - -## Planned runner - -When an automated runner is available, evals will be driven non-interactively -against each case file — the schema and case format below are stable; only the -runner is pending. In the meantime, eval cases serve as behavioral -specifications: when you change a skill or agent, add or update its eval case -in the same commit to document the expected behavior. - -## Authoring conventions - -1. **One behavior per eval case.** Compound cases are harder to debug when - they fail. -2. **Smoke evals are minimal.** Each critical agent gets one smoke case that - exercises the happy path. Deep edge-case testing lives in dedicated eval - suites outside `smoke/`. -3. **Eval cases are versioned alongside the prompt.** When you change a - skill or agent, add or update its eval case in the same commit. This - creates a before/after record of behavior changes. -4. **Before/after comparison.** When modifying a prompt, run the eval suite - before and after the change. Diff the results. If the eval suite doesn't - have a case covering the change you're making, add one first. - -## Cross-refs - -- `AGENTS.md` § Git Workflow — no-squash policy (commit history is the eval log) -- `AGENTS.md` § Linting & Enforcement — structural validation of frontmatter - is planned via CI; until then, validate manually when adding or changing - skills, agents, or commands diff --git a/.opencode/evals/bin/includes/EvalRunner.php b/.opencode/evals/bin/includes/EvalRunner.php deleted file mode 100644 index cb6b5a1..0000000 --- a/.opencode/evals/bin/includes/EvalRunner.php +++ /dev/null @@ -1,421 +0,0 @@ -<?php - -# $KYAULabs: EvalRunner.php SEANBR~1@KYAU-DEV 2025/07/05 -0500 Exp $ - -declare(strict_types=1); - -namespace KYAULabs\Eval; - -/** - * Parsed eval case from a JSON file. - */ -class EvalCase -{ - /** @param string[] $expectedBehavior */ - public function __construct( - public readonly string $name, - public readonly string $description, - public readonly string $agent, - public readonly string $input, - public readonly array $expectedBehavior, - public readonly string $passCriteria, - public readonly array $tags = [], - ) { - } - - /** - * Parse an eval case from a JSON file. - * - * @param string $path Path to the JSON case file. - * @return self - * @throws \RuntimeException If the file cannot be read or decoded. - */ - public static function fromFile(string $path): self - { - $contents = file_get_contents($path); - if ($contents === false) { - throw new \RuntimeException("Cannot read eval case file: {$path}"); - } - - $data = json_decode($contents, true); - if (!is_array($data)) { - throw new \RuntimeException("Invalid JSON in eval case file: {$path}"); - } - - return new self( - name: $data['name'] ?? '', - description: $data['description'] ?? '', - agent: $data['agent'] ?? '', - input: $data['input'] ?? '', - expectedBehavior: $data['expected_behavior'] ?? [], - passCriteria: $data['pass_criteria'] ?? '', - tags: $data['tags'] ?? [], - ); - } - - /** - * Validate this case against the schema. Returns an array of error - * messages; empty array means valid. - * - * @return string[] - */ - public function validate(): array - { - $errors = []; - $required = ['name', 'description', 'agent', 'input', 'expected_behavior', 'pass_criteria']; - - foreach ($required as $field) { - $value = match ($field) { - 'name' => $this->name, - 'description' => $this->description, - 'agent' => $this->agent, - 'input' => $this->input, - 'expected_behavior' => $this->expectedBehavior, - 'pass_criteria' => $this->passCriteria, - }; - - if ($field === 'expected_behavior') { - if (empty($value)) { - $errors[] = "required field 'expected_behavior' is empty"; - } - } elseif ($value === '') { - $errors[] = "required field '{$field}' is empty"; - } - } - - $validCriteria = [ - 'all behaviors observed', - 'no errors in output', - 'exit code zero', - 'output contains expected string', - 'manual inspection required', - ]; - - if (!in_array($this->passCriteria, $validCriteria, true)) { - $errors[] = "pass_criteria '{$this->passCriteria}' is not a valid value"; - } - - return $errors; - } -} - -/** - * Result of running a single eval case. - */ -class EvalResult -{ - /** @param array<int, array{behavior: string, verdict: string, rationale: string}> $behaviors */ - /** @param array<string, array<string, mixed>> $deterministicChecks */ - public function __construct( - public string $name, - public string $agent, - public string $passCriteria, - public string $verdict, - public array $behaviors = [], - public array $deterministicChecks = [], - public int $durationMs = 0, - public bool $judgeUsed = false, - public ?string $error = null, - ) { - } - - /** - * @return array<string, mixed> - */ - public function toArray(): array - { - return [ - 'name' => $this->name, - 'agent' => $this->agent, - 'pass_criteria' => $this->passCriteria, - 'verdict' => $this->verdict, - 'behaviors' => $this->behaviors, - 'deterministic_checks' => $this->deterministicChecks, - 'duration_ms' => $this->durationMs, - 'judge_used' => $this->judgeUsed, - 'error' => $this->error, - ]; - } - - /** - * Return true if this result represents a pass. - * - * @return bool - */ - public function isPass(): bool - { - return $this->verdict === 'PASS'; - } - - /** - * Return true if this result represents a failure (FAIL, TIMEOUT, or INVALID). - * - * @return bool - */ - public function isFail(): bool - { - return in_array($this->verdict, ['FAIL', 'TIMEOUT', 'INVALID'], true); - } -} - -/** - * Runs a single eval case through opencode run. - */ -class Runner -{ - private string $repoRoot; - - public function __construct( - string $repoRoot, - private int $timeout = 120, - ) { - $this->repoRoot = realpath($repoRoot) ?: $repoRoot; - } - - /** - * Build the opencode run command for a case (dry-run mode). - * - * @param EvalCase $case - * @return string Shell command string. - */ - public function buildCommand(EvalCase $case): string - { - $prompt = escapeshellarg($case->input); - $path = escapeshellarg($this->repoRoot); - - return "opencode run --prompt {$prompt} --mode build --path {$path} " . - '--permissions "bash: allow, edit: allow, task: allow"'; - } - - /** - * Parse CLI arguments for run-eval.php. - * - * @param array<int, string> $argv - * @return array{caseFile: string, timeout: int, dryRun: bool} - */ - public static function parseArgs(array $argv): array - { - $caseFile = ''; - $timeout = 120; - $dryRun = false; - - for ($i = 1; $i < count($argv); $i++) { - if ($argv[$i] === '--timeout' && isset($argv[$i + 1])) { - $timeout = (int) $argv[++$i]; - } elseif ($argv[$i] === '--dry-run') { - $dryRun = true; - } elseif (!str_starts_with($argv[$i], '--')) { - $caseFile = $argv[$i]; - } - } - - return ['caseFile' => $caseFile, 'timeout' => $timeout, 'dryRun' => $dryRun]; - } - /** - * Execute a shell command and capture stdout, stderr, and exit code. - * - * @param string $cmd Shell command to execute. - * @param int $timeout Timeout in seconds. - * @return array{stdout: string, stderr: string, exitCode: int} - */ - public function executeCommand(string $cmd, int $timeout): array - { - $descriptors = [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; - - $process = proc_open($cmd, $descriptors, $pipes); - if (!is_resource($process)) { - return ['stdout' => '', 'stderr' => "Failed to start process: {$cmd}", 'exitCode' => -1]; - } - - fclose($pipes[0]); - - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - - $exitCode = proc_close($process); - - return [ - 'stdout' => $stdout !== false ? trim($stdout) : '', - 'stderr' => $stderr !== false ? trim($stderr) : '', - 'exitCode' => $exitCode, - ]; - } - - /** - * Check if the pass criteria can be resolved deterministically (no LLM judge). - * - * Returns an EvalResult if the criteria can be resolved, or null if an - * LLM judge is required. - * - * @param EvalCase $case - * @param string $stdout Captured stdout from the agent run. - * @param string $stderr Captured stderr from the agent run. - * @param int $exitCode Exit code from the agent run. - * @return EvalResult|null - */ - public function checkDeterministic( - EvalCase $case, - string $stdout, - string $stderr, - int $exitCode, - ): ?EvalResult { - $checks = []; - - $verdict = match ($case->passCriteria) { - 'exit code zero' => ($exitCode === 0) ? 'PASS' : 'FAIL', - 'no errors in output' => ($stderr === '') ? 'PASS' : 'FAIL', - 'output contains expected string' => (str_contains($stdout, $case->expectedBehavior[0] ?? '')) ? 'PASS' : 'FAIL', - 'manual inspection required' => 'UNDETERMINED', - default => null, - }; - - if ($verdict === null) { - return null; - } - - $checks['exit_code'] = ['expected' => 0, 'actual' => $exitCode]; - - return new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: $verdict, - deterministicChecks: $checks, - judgeUsed: false, - ); - } - /** - * Build the prompt for the LLM judge. - * - * @param EvalCase $case - * @param string $agentOutput Captured stdout + stderr from the agent run. - * @return string - */ - public function buildJudgePrompt(EvalCase $case, string $agentOutput): string - { - $behaviors = ''; - foreach ($case->expectedBehavior as $i => $behavior) { - $n = $i + 1; - $behaviors .= "{$n}. {$behavior}\n"; - } - - return <<<PROMPT -You are evaluating whether an AI agent's output satisfies expected behaviors. -Below is the eval case and the agent's full output. For each expected behavior, -answer YES if the output demonstrates it, NO if it does not, or UNCLEAR if -you cannot determine. Provide a one-sentence rationale per answer. - -Eval case: {$case->name} -Description: {$case->description} - -Expected behaviors: -{$behaviors} -Agent output: ---- -{$agentOutput} ---- - -Respond with ONLY a valid JSON array. No prose, no markdown fences. -[{"behavior": "<exact text>", "verdict": "YES|NO|UNCLEAR", "rationale": "<one sentence>"}, ...] -PROMPT; - } - - /** - * Parse the judge's JSON response into a behaviors array. - * - * @param string $response Raw response from the judge (may include markdown fences). - * @return array<int, array{behavior: string, verdict: string, rationale: string}> - */ - public static function parseJudgeResponse(string $response): array - { - $response = trim($response); - $response = preg_replace('/^```(?:json)?\s*\n?/', '', $response); - $response = preg_replace('/\n?```\s*$/', '', $response); - - $decoded = json_decode($response, true); - - if (!is_array($decoded)) { - return []; - } - - return array_map(function (array $item): array { - return [ - 'behavior' => $item['behavior'] ?? '', - 'verdict' => strtoupper($item['verdict'] ?? 'UNCLEAR'), - 'rationale' => $item['rationale'] ?? '', - ]; - }, $decoded); - } - - /** - * Build an EvalResult from the judge's parsed behaviors. - * - * @param EvalCase $case - * @param array<int, array{behavior: string, verdict: string, rationale: string}> $behaviors - * @param int $durationMs - * @return EvalResult - */ - public function buildJudgeResult(EvalCase $case, array $behaviors, int $durationMs): EvalResult - { - $allYes = true; - foreach ($behaviors as $b) { - if ($b['verdict'] !== 'YES') { - $allYes = false; - break; - } - } - - return new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: $allYes ? 'PASS' : 'FAIL', - behaviors: $behaviors, - deterministicChecks: [], - durationMs: $durationMs, - judgeUsed: true, - ); - } - - /** - * Run the LLM judge against the captured agent output. - * - * @param EvalCase $case - * @param string $agentOutput Captured stdout + stderr from the agent run. - * @return EvalResult - */ - public function runJudge(EvalCase $case, string $agentOutput): EvalResult - { - $prompt = $this->buildJudgePrompt($case, $agentOutput); - $judgeCmd = "opencode run --prompt " . escapeshellarg($prompt) . - " --mode build --path " . escapeshellarg($this->repoRoot); - - $start = hrtime(true); - $output = $this->executeCommand($judgeCmd, $this->timeout); - $elapsed = (int) ((hrtime(true) - $start) / 1_000_000); - - $behaviors = self::parseJudgeResponse($output['stdout']); - - return $this->buildJudgeResult($case, $behaviors, $elapsed); - } - - /** - * Check if opencode is available in PATH. - * - * @return bool - */ - public function isOpenCodeAvailable(): bool - { - $output = $this->executeCommand('command -v opencode', 5); - - return $output['exitCode'] === 0 && $output['stdout'] !== ''; - } -} - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/.opencode/evals/bin/run-eval.php b/.opencode/evals/bin/run-eval.php deleted file mode 100644 index 980c784..0000000 --- a/.opencode/evals/bin/run-eval.php +++ /dev/null @@ -1,134 +0,0 @@ -<?php - -# $KYAULabs: run-eval.php SEANBR~1@KYAU-DEV 2025/07/05 -0500 Exp $ - -declare(strict_types=1); - -/** - * run-eval.php — Execute a single eval case against opencode run. - * - * Usage: php run-eval.php <case-file> [--timeout <seconds>] [--dry-run] - * - * Reads a JSON eval case, invokes opencode run with the case's input prompt, - * applies deterministic pass criteria checks (exit code, output content, etc.), - * and optionally invokes an LLM judge for 'all behaviors observed' criteria. - * Outputs a JSON result object to stdout. - * - * Exit codes: - * 0 — PASS - * 1 — FAIL, TIMEOUT, or INVALID - * 2 — SKIPPED (opencode not available) - */ - -require_once __DIR__ . '/includes/EvalRunner.php'; - -use KYAULabs\Eval\Runner; -use KYAULabs\Eval\EvalCase; -use KYAULabs\Eval\EvalResult; - -// ── Parse arguments ────────────────────────────────────────────────────── -$args = Runner::parseArgs($argv); - -if ($args['caseFile'] === '' || !file_exists($args['caseFile'])) { - $name = $args['caseFile'] !== '' ? basename($args['caseFile']) : 'unknown'; - $result = new EvalResult( - name: $name, - agent: 'unknown', - passCriteria: '', - verdict: 'INVALID', - error: $args['caseFile'] === '' - ? 'No case file specified.' : "Case file not found: {$args['caseFile']}", - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(1); -} - -// ── Parse and validate case file ───────────────────────────────────────── -try { - $case = EvalCase::fromFile($args['caseFile']); -} catch (\RuntimeException $e) { - $result = new EvalResult( - name: basename($args['caseFile']), - agent: 'unknown', - passCriteria: '', - verdict: 'INVALID', - error: $e->getMessage(), - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(1); -} - -$errors = $case->validate(); -if (!empty($errors)) { - $result = new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: 'INVALID', - error: implode('; ', $errors), - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(1); -} - -// ── Build runner ───────────────────────────────────────────────────────── -$repoRoot = dirname(__DIR__, 3); -$runner = new Runner($repoRoot, $args['timeout']); - -// ── Dry-run mode ───────────────────────────────────────────────────────── -if ($args['dryRun']) { - $cmd = $runner->buildCommand($case); - echo "DRY RUN — would execute:\n"; - echo " {$cmd}\n"; - $judgeCmd = "opencode run --prompt '<judge prompt>' --mode build --path {$repoRoot}"; - echo "DRY RUN — judge would execute:\n"; - echo " {$judgeCmd}\n"; - exit(0); -} - -// ── Check for opencode ─────────────────────────────────────────────────── -if (!$runner->isOpenCodeAvailable()) { - $result = new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: 'SKIPPED', - error: 'opencode not found in PATH. Install opencode to run evals.', - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(2); -} - -// ── Run the agent ──────────────────────────────────────────────────────── -$start = hrtime(true); -$cmd = $runner->buildCommand($case); -$agentOutput = $runner->executeCommand($cmd, $args['timeout']); -$elapsedMs = (int) ((hrtime(true) - $start) / 1_000_000); - -// ── Deterministic gate ─────────────────────────────────────────────────── -$result = $runner->checkDeterministic( - $case, - $agentOutput['stdout'], - $agentOutput['stderr'], - $agentOutput['exitCode'], -); - -// ── LLM judge ──────────────────────────────────────────────────────────── -if ($result === null) { - $combinedOutput = $agentOutput['stdout']; - if ($agentOutput['stderr'] !== '') { - $combinedOutput .= "\n\n[stderr]\n" . $agentOutput['stderr']; - } - - $result = $runner->runJudge($case, $combinedOutput); - $result->durationMs = $elapsedMs; -} else { - $result->durationMs = $elapsedMs; -} - -// ── Output ─────────────────────────────────────────────────────────────── -echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - -exit($result->isPass() ? 0 : 1); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/.opencode/evals/bin/run-suite.php b/.opencode/evals/bin/run-suite.php deleted file mode 100644 index 3584310..0000000 --- a/.opencode/evals/bin/run-suite.php +++ /dev/null @@ -1,160 +0,0 @@ -<?php - -# $KYAULabs: run-suite.php SEANBR~1@KYAU-DEV 2025/07/05 -0500 Exp $ - -declare(strict_types=1); - -/** - * run-suite.php — Batch eval suite runner. - * - * Usage: php run-suite.php <directory> [--tag <tag>] [--timeout <seconds>] [--dry-run] - * - * Discovers all .json eval case files in the given directory, optionally - * filtered by --tag. Runs each through run-eval.php, aggregates results, - * prints a markdown summary table to stdout, and writes a detailed JSON - * results file. - * - * Exit codes: - * 0 — all cases PASS - * 1 — one or more cases FAIL, TIMEOUT, or INVALID - */ - -$repoRoot = realpath(dirname(__DIR__, 3)); -$runEvalScript = __DIR__ . '/run-eval.php'; - -// ── Parse arguments ────────────────────────────────────────────────────── -$directory = ''; -$tag = null; -$timeout = 120; -$dryRun = false; - -for ($i = 1; $i < count($argv); $i++) { - if ($argv[$i] === '--tag' && isset($argv[$i + 1])) { - $tag = $argv[++$i]; - } elseif ($argv[$i] === '--timeout' && isset($argv[$i + 1])) { - $timeout = (int) $argv[++$i]; - } elseif ($argv[$i] === '--dry-run') { - $dryRun = true; - } elseif (!str_starts_with($argv[$i], '--')) { - $directory = $argv[$i]; - } -} - -if ($directory === '' || !is_dir($directory)) { - fwrite(STDERR, "Error: directory not found: {$directory}\n"); - fwrite(STDERR, "Usage: php run-suite.php <directory> [--tag <tag>] [--timeout <seconds>] [--dry-run]\n"); - exit(1); -} - -// ── Discover case files ────────────────────────────────────────────────── -$files = glob($directory . '/*.json'); -$cases = []; - -foreach ($files as $file) { - $contents = file_get_contents($file); - if ($contents === false) { - continue; - } - - $data = json_decode($contents, true); - if (!is_array($data)) { - continue; - } - - if ($tag !== null && !in_array($tag, $data['tags'] ?? [], true)) { - continue; - } - - $cases[] = ['file' => $file, 'name' => $data['name'] ?? basename($file)]; -} - -if (empty($cases)) { - echo "No eval cases found in {$directory}" . - ($tag !== null ? " with tag '{$tag}'" : '') . ".\n"; - exit(0); -} - -// ── Run each case ───────────────────────────────────────────────────────── -$results = []; -$dryFlag = $dryRun ? ' --dry-run' : ''; - -foreach ($cases as $i => $caseInfo) { - $num = $i + 1; - $total = count($cases); - echo "Running [{$num}/{$total}] {$caseInfo['name']}...\n"; - - $cmd = "php {$runEvalScript} " . escapeshellarg($caseInfo['file']) . - " --timeout {$timeout}{$dryFlag} 2>&1"; - $output = []; - $exitCode = 0; - exec($cmd, $output, $exitCode); - - $joined = implode("\n", $output); - $decoded = json_decode($joined, true); - - if (is_array($decoded)) { - $results[] = $decoded; - } else { - $results[] = [ - 'name' => $caseInfo['name'], - 'verdict' => 'INVALID', - 'error' => 'Failed to parse run-eval output', - ]; - } -} - -// ── Markdown summary ───────────────────────────────────────────────────── -echo "\n"; -echo str_repeat('-', 60) . "\n"; -echo "\n| # | Eval Case | Verdict | Behaviors | Duration | Judge |\n"; -echo "|---|---|---|---|---|---|\n"; - -$passCount = 0; -$failCount = 0; -$skipCount = 0; -$timeoutCount = 0; -$invalidCount = 0; - -foreach ($results as $i => $r) { - $num = $i + 1; - $name = $r['name'] ?? 'unknown'; - $verdict = $r['verdict'] ?? 'UNKNOWN'; - $behaviors = count($r['behaviors'] ?? []); - $yesBehaviors = count(array_filter($r['behaviors'] ?? [], fn ($b) => ($b['verdict'] ?? '') === 'YES')); - $duration = isset($r['duration_ms']) ? sprintf('%.1fs', $r['duration_ms'] / 1000) : '-'; - $judge = ($r['judge_used'] ?? false) ? 'yes' : 'no'; - - echo "| {$num} | {$name} | {$verdict} | {$yesBehaviors}/{$behaviors} | {$duration} | {$judge} |\n"; - - match ($verdict) { - 'PASS' => $passCount++, - 'FAIL' => $failCount++, - 'TIMEOUT' => $timeoutCount++, - 'SKIPPED' => $skipCount++, - default => $invalidCount++, - }; -} - -$total = count($results); -echo "\n**Suite: {$passCount}/{$total} passed ({$failCount} failed, {$timeoutCount} timeout, {$skipCount} skipped, {$invalidCount} invalid)**\n"; -echo "\n" . str_repeat('-', 60) . "\n"; - -// ── Write JSON results ─────────────────────────────────────────────────── -$resultsDir = dirname(__DIR__) . '/results'; -if (!is_dir($resultsDir)) { - mkdir($resultsDir, 0755, true); -} - -$timestamp = date('Y-m-d\THis'); -$resultsFile = $resultsDir . "/{$timestamp}.json"; -file_put_contents( - $resultsFile, - json_encode(['timestamp' => $timestamp, 'results' => $results], JSON_PRETTY_PRINT), -); - -echo "\nDetailed results: {$resultsFile}\n"; - -$anyNonPass = $failCount > 0 || $timeoutCount > 0 || $invalidCount > 0; -exit($anyNonPass ? 1 : 0); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/.opencode/evals/schema.json b/.opencode/evals/schema.json deleted file mode 100644 index bb84b83..0000000 --- a/.opencode/evals/schema.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://kyaulabs.com/evals/schema.json", - "title": "Harness Eval Case", - "description": "A single eval case for an OpenCode agent or skill.", - "type": "object", - "required": ["name", "description", "agent", "input", "expected_behavior", "pass_criteria"], - "properties": { - "name": { - "type": "string", - "description": "Short, unique identifier in kebab-case.", - "pattern": "^[a-z][a-z0-9-]*$" - }, - "description": { - "type": "string", - "description": "What observable behavior this eval verifies." - }, - "agent": { - "type": "string", - "description": "The agent or skill under test (e.g. @tdd, brainstorming).", - "pattern": "^@?[a-z][a-z0-9_-]*$" - }, - "input": { - "type": "string", - "description": "The prompt or scenario to feed the agent." - }, - "expected_behavior": { - "type": "array", - "description": "Observable behaviors the output must satisfy.", - "minItems": 1, - "items": { "type": "string" } - }, - "pass_criteria": { - "type": "string", - "description": "How to determine pass/fail.", - "enum": [ - "all behaviors observed", - "no errors in output", - "exit code zero", - "output contains expected string", - "manual inspection required" - ] - }, - "tags": { - "type": "array", - "description": "Optional tags for filtering (e.g. smoke, tdd, critical).", - "items": { "type": "string" } - } - } -} diff --git a/.opencode/evals/smoke/finding-duplicate-functions-two-phase.json b/.opencode/evals/smoke/finding-duplicate-functions-two-phase.json deleted file mode 100644 index 5e81708..0000000 --- a/.opencode/evals/smoke/finding-duplicate-functions-two-phase.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "finding-duplicate-functions-two-phase", - "description": "Verify finding-duplicate-functions runs both phases — structural scan then intent clustering — and applies the deletion-test gate before proposing extraction.", - "agent": "finding-duplicate-functions", - "input": "Scan backend/helpers.php and backend/utils.php for semantic duplicates. Both files contain functions that format dates but with different parameter names and ordering.", - "expected_behavior": [ - "Agent announces using finding-duplicate-functions skill", - "Agent runs Phase 1 structural scan (signature similarity, body structure)", - "Agent runs Phase 2 intent clustering (same problem or different problems?)", - "Agent applies the deletion test before any extraction proposal", - "Agent outputs the three-section format: high-confidence, candidates, false positives", - "Agent does NOT propose extraction for pairs under 5 lines", - "Agent does NOT propose extraction where deletion test shows 'moves complexity sideways'" - ], - "pass_criteria": "all behaviors observed", - "tags": ["smoke", "process", "architecture"] -} diff --git a/.opencode/evals/smoke/finishing-a-development-branch-checklist.json b/.opencode/evals/smoke/finishing-a-development-branch-checklist.json deleted file mode 100644 index 8824a22..0000000 --- a/.opencode/evals/smoke/finishing-a-development-branch-checklist.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "finishing-a-development-branch-checklist", - "description": "Verify finishing-a-development-branch skill runs the pre-completion checklist and presents disposal options without auto-merging.", - "agent": "finishing-a-development-branch", - "input": "I've finished all tasks on branch feat/kyau-abc123-add-login. All plan tasks are checked off. Verify the branch is ready and tell me my options.", - "expected_behavior": [ - "Agent announces using finishing-a-development-branch skill", - "Agent runs each checklist item — working tree, rebase state, /check, @code-review", - "Agent does not skip any checklist item", - "If any item fails, agent stops and reports the failure without presenting options", - "If all items pass, agent presents the summary block with 4 numbered options", - "Agent does NOT propose git push", - "Agent does NOT auto-merge — waits for user response", - "Agent reminds about no-squash policy" - ], - "pass_criteria": "all behaviors observed", - "tags": ["smoke", "process", "git"] -} diff --git a/.opencode/evals/smoke/opencode-docs-reference.json b/.opencode/evals/smoke/opencode-docs-reference.json deleted file mode 100644 index 73bdd7c..0000000 --- a/.opencode/evals/smoke/opencode-docs-reference.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "opencode-docs-reference", - "description": "Verify opencode-docs skill loads and references vendored docs instead of guessing OpenCode config semantics.", - "agent": "opencode-docs", - "input": "What keys are valid in opencode.json for the 'agent' section? How do I set a custom command?", - "expected_behavior": [ - "Agent announces using opencode-docs skill", - "Agent identifies the relevant doc files from the quick-reference table", - "Agent reads config.mdx for opencode.json agent section keys", - "Agent reads commands.mdx for custom command configuration", - "Agent cites the doc file names in its answer (e.g. 'per config.mdx')", - "Agent does NOT make up config keys that aren't in the vendored docs", - "Agent does NOT call /research for questions the vendored docs cover" - ], - "pass_criteria": "all behaviors observed", - "tags": ["smoke", "docs", "config"] -} diff --git a/.opencode/evals/smoke/receiving-code-review-triage.json b/.opencode/evals/smoke/receiving-code-review-triage.json deleted file mode 100644 index 42b7064..0000000 --- a/.opencode/evals/smoke/receiving-code-review-triage.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "receiving-code-review-triage", - "description": "Verify receiving-code-review skill triages findings by severity — fixes Blocking, defers Suggested with reason, acknowledges Informational.", - "agent": "receiving-code-review", - "input": "You received the following findings from @code-review:\n\nBlocking: Missing RCS header on backend/NewModule.php\nSuggested: Consider extracting the validation logic from processForm() into a helper\nInformational: The variable $x could be renamed to $count for clarity\n\nTriage and respond.", - "expected_behavior": [ - "Agent announces using receiving-code-review skill", - "Agent reads ALL findings before acting on any", - "Blocking finding (missing RCS header) is fixed without pushback", - "Suggested finding is evaluated — either fixed cleanly or deferred with a one-line reason", - "Informational finding is acknowledged, not implemented", - "Agent presents a summary of what was fixed, deferred, and why", - "Agent does not argue with the Blocking finding" - ], - "pass_criteria": "all behaviors observed", - "tags": ["smoke", "process", "review"] -} diff --git a/.opencode/evals/smoke/tdd-red-green.json b/.opencode/evals/smoke/tdd-red-green.json deleted file mode 100644 index 5bf4ad4..0000000 --- a/.opencode/evals/smoke/tdd-red-green.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "tdd-red-green", - "description": "Verify @tdd agent loads, enters Red phase (writes a failing test), then Green phase (writes minimal implementation to pass).", - "agent": "@tdd", - "input": "Write a function `add(a, b)` that returns the sum of two integers. Use TDD.", - "expected_behavior": [ - "Agent reads existing code and test conventions before writing", - "Agent writes exactly one failing test before implementation (Red phase)", - "Agent confirms the test fails with a meaningful error, not a syntax error", - "Agent writes minimal production code to pass the one test (Green phase)", - "Agent confirms the full suite is green before proceeding", - "Agent does not write speculative features or untested code" - ], - "pass_criteria": "all behaviors observed", - "tags": ["smoke", "tdd", "critical"] -} diff --git a/.opencode/plugins/session-bootstrap.ts b/.opencode/plugins/session-bootstrap.ts deleted file mode 100644 index 40923bb..0000000 --- a/.opencode/plugins/session-bootstrap.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import type { Plugin } from "@opencode-ai/plugin"; - -/** - * Injects the rationalization red-flags bootstrap into the system prompt on - * every LLM call and into the compaction context. This is structural - * enforcement — the model cannot "forget" to load a skill because the - * anti-drift text is pushed by the plugin, not chosen by the model. - * - * The bootstrap text lives in `.opencode/docs/session-bootstrap.md` so it - * can be edited without touching plugin code. - */ - -export const SessionBootstrap: Plugin = async ({ directory }) => { - const bootstrapPath = join( - directory, - ".opencode", - "docs", - "session-bootstrap.md", - ); - const bootstrap = readFileSync(bootstrapPath, "utf-8"); - - return { - "experimental.chat.system.transform": async (_input, output) => { - output.system.push(bootstrap); - }, - "experimental.session.compacting": async (_input, output) => { - output.context.push(bootstrap); - }, - }; -}; diff --git a/.opencode/skills/accessibility/SKILL.md b/.opencode/skills/accessibility/SKILL.md deleted file mode 100644 index 8ee5914..0000000 --- a/.opencode/skills/accessibility/SKILL.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: accessibility -description: Use when writing or reviewing frontend markup, SCSS, or JS that produces UI. Covers WCAG 2.2 AA, semantic HTML over ARIA, focus management, motion safety, and contrast floors for neumorphic surfaces. Complements scss-mobile-first and frontend-design. ---- - -## WCAG 2.2 AA — the floor - -Every public-facing UI must meet WCAG 2.2 Level AA. The quick checks below are -not exhaustive; when in doubt, consult the spec. - -## Prefer semantic HTML over ARIA - -Use the native element first. ARIA is a patch, not a replacement. - -- `<button>` not `<div role="button">` -- `<a href>` not `<span role="link">` -- `<nav>`, `<main>`, `<header>`, `<footer>`, `<article>`, `<section>` for - landmarks — do not sprinkle `role="navigation"` on a `<div>`. -- Only add `aria-*` when the native semantics are insufficient (tabs, - accordions, live regions, custom widgets). - -Rule of thumb: **if a native element exists, use it.** - -## Focus management - -- Every interactive element must be reachable and operable by keyboard. -- Never remove focus outlines without providing a visible alternative - (`:focus-visible` is the modern choice). -- Focus order follows DOM order. Do not fight it with `tabindex` gymnastics. -- Modals and drawers must trap focus while open and restore it on close. -- Skip-to-content link as the first focusable element on each page. - -## Touch targets - -- Minimum **44 × 44 CSS px** for primary interactive targets on mobile. -- Adjacent targets need a gap (≥ 8px) to avoid mis-taps. - -## Motion safety - -- Honor `prefers-reduced-motion: reduce`. -- When reduced motion is requested, disable non-essential animations and - transitions, including neumorphic shadow transitions. Snap to final state. -- Do not put essential content in motion that can't be paused or skipped. - -## Contrast (neumorphism-specific) - -Neumorphism relies on soft shadows on a mid-tone surface, which can fail -contrast. The floor: - -- **Text on surface:** ≥ **4.5:1** (AA for normal text), ≥ 3:1 for large text. -- **UI component boundaries** (button edges, input borders): ≥ **3:1** against - adjacent colors. -- If a neumorphic surface cannot meet the text contrast floor, add a - visible border or darken the text — do not weaken the shadow. - -When the `frontend-design` skill's token values produce a failing contrast -ratio, the accessibility floor wins. Adjust the token, not the component. - -## Color & state - -- Never rely on color alone to convey state (errors, success, disabled). - Pair color with text or an icon. -- Form errors: announce with `aria-live="polite"` and associate via - `aria-describedby` pointing at the error message. -- Disabled controls: use `disabled`/`aria-disabled` and a non-color cue. - -## Images & media - -- Decorative images: `alt=""` (empty). Do not omit the attribute. -- Informative images: describe the meaning, not the pixels. -- `loading="lazy"` on below-the-fold images; do not lazy-load the LCP image. -- Provide captions/transcripts for audio/video. - -## Cross-refs - -- `scss-mobile-first` — breakpoints, units, touch target sizes. -- `frontend-design` — neumorphic surface tokens and shadow recipes (contrast - floor enforced here). -- `frontend-architecture` — JS module pattern for keyboard handlers. diff --git a/.opencode/skills/adr/SKILL.md b/.opencode/skills/adr/SKILL.md deleted file mode 100644 index 3617d31..0000000 --- a/.opencode/skills/adr/SKILL.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: adr -description: Use when writing, reviewing, or superseding an Architecture Decision Record. Covers the Nygard-format template, numbering, and status transitions. ADRs live in adr/. ---- - -## ADR Format - -Records live in `adr/`. Filename: `adr/NNNN-kebab-case-title.md` (zero-padded -sequence starting at `0001`). Copy `adr/0000-template.md` to start. - -Each ADR has these sections: - -1. **Title** — `# NNNN. <title>` -2. **Date** — `YYYY-MM-DD` -3. **Status** — one of: `Proposed`, `Accepted`, `Deprecated`, `Superseded` -4. **Context** — the problem and forces -5. **Decision** — the choice, in active present tense -6. **Consequences** — positive, negative, neutral -7. **Alternatives Considered** — named and rejected - -## Status Transitions - -- `Proposed` → `Accepted` (ratified) -- `Accepted` → `Superseded` (replaced by a new ADR; leave the file unchanged, - add `Superseded by ADR-NNNN` under Status, create the successor) -- `Accepted` → `Deprecated` (no longer in effect, no replacement) - -Never edit the body of an Accepted ADR. Supersede it. - -## When to write - -- A hard-to-reverse or expensive-to-change decision (data model, auth - strategy, deployment topology, framework adoption). -- A choice that forecloses other options. -- A cross-cutting decision affecting more than one module. - -## When not to - -- Routine implementation choices. -- Decisions already covered by an existing ADR or `AGENTS.md`. -- Anything that fits in a commit message or PR description. - -## On acceptance - -1. Set status `Accepted`. -2. Add a one-liner to `CONTEXT.md` under "Architectural Decisions": - `- adr/0001-<title>.md — <one-line summary>` - -## Rules - -- One decision per ADR. -- Sequence numbers never repeat or gap. -- Keep `adr/README.md` and `adr/0000-template.md` in sync with this skill if - the format changes. -- Do not delete ADRs; supersede them. diff --git a/.opencode/skills/audit-deps/SKILL.md b/.opencode/skills/audit-deps/SKILL.md deleted file mode 100644 index 455f267..0000000 --- a/.opencode/skills/audit-deps/SKILL.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: audit-deps -description: Use when auditing PHP (Composer) and JavaScript (npm) dependencies for known CVEs. Parses structured output into severity tables with fix commands. Skips gracefully if manifests are absent. ---- - -## Composer Audit - -If `composer.json` exists in the project root, run: - -```bash -composer audit --format json -``` - -A `composer.lock` must also be present (audit checks locked versions). If only -`composer.json` exists without a lock file, note that and skip. - -Parse the JSON output. Report findings grouped by severity: - -| Severity | Meaning | -|---|---| -| critical | Remote code execution, critical data exposure | -| high | SQL injection, authentication bypass | -| medium | XSS, information disclosure, DoS | -| low | Minor issues, configuration weaknesses | - -For each finding, show: -- Affected package and version -- CVE ID or advisory identifier -- Brief description -- Suggested fix: `composer update <package>` - -If no vulnerabilities are found, report: "No known vulnerabilities in PHP dependencies." - ---- - -## npm Audit - -If `package.json` exists (check the project root first, then subdirectories like -`cdn/`, `.opencode/`), run: - -```bash -npm audit --json -``` - -A `package-lock.json` must also be present. If not, note it and skip. - -Parse the JSON output. npm audit nests advisories under affected packages. -Flatten them and group by severity: - -| Severity | Meaning | -|---|---| -| critical | RCE, arbitrary code execution | -| high | Significant security impact | -| moderate | XSS, information disclosure | -| low | Minor issues | - -For each finding, show: -- Affected package, installed version, and vulnerable version range -- CVE ID or GitHub Advisory ID -- Suggested fix: `npm update <package>` or `npm install <package>@latest` - -If no vulnerabilities are found, report: "No known vulnerabilities in JavaScript dependencies." - ---- - -## Rules - -- Both tools are read-only — they never modify `composer.lock` or `package-lock.json`. -- Skip either check gracefully if the corresponding manifest files don't exist. -- If a lockfile is missing but the manifest exists, report a warning: - lockfiles are required for auditing; a fresh clone cannot be audited until - dependencies are installed against potentially unvetted versions. Committing - lockfiles eliminates this gap (see the lockfile policy in AGENTS.md). -- If `composer audit --format json` returns a non-zero exit due to vulnerabilities, - that's expected — parse the JSON output regardless. -- If `npm audit --json` times out (large dependency trees can be slow), note it and - report the partial results. -- If either tool is not installed (e.g., `composer` or `npm` not in PATH), report - the error and skip that check — do not attempt to install them. diff --git a/.opencode/skills/aurora-page/SKILL.md b/.opencode/skills/aurora-page/SKILL.md deleted file mode 100644 index 051d0cf..0000000 --- a/.opencode/skills/aurora-page/SKILL.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -name: aurora-page -description: Use when creating a new PHP page. Provides the standard Aurora Framework page template with RCS header, htmlHeader/htmlFooter pattern, DNS prefetch, CSS/JS registration, and required includes. ---- - -## Before Generating - -Verify the Aurora submodule is present. If `aurora/aurora.inc.php` does not exist, output -this error and **stop** — do not generate any code: - -```text -✗ Aurora submodule is missing. - -Fix: - git submodule add https://github.com/kyaulabs/aurora aurora - git submodule update --init -``` - -## Aurora Standard Page Template - -New PHP pages follow this exact structure. Replace `<app>`, `<domain>`, and page-specific -values: - -```php -<?php -# $KYAULabs: index.php kyau@host YYYY/MM/DD -0700 Exp $ - -$rus = getrusage(); -require_once(__DIR__ . "/../aurora/aurora.inc.php"); - -$site = new KYAULabs\Aurora(template: "index.html", cdn: "/cdn", status: (bool)($_ENV['APP_DEBUG'] ?? false), html: true); -$site->title = "Page Title"; -$site->description = "Page description for search engines."; -$site->dns = ["cdn.<domain>"]; -$site->css = [ 'css/site.min.css' => '//cdn.<domain>/css/site.min.css' ]; -$site->js = [ 'javascript/site.min.js' => '//cdn.<domain>/javascript/site.min.js' ]; -$site->preload = [ - '/css/site.min.css' => 'style', - '/javascript/site.min.js' => 'script', -]; -$site->htmlHeader(); -// page content here -$site->htmlFooter(); -echo $site->comment($rus, basename(__FILE__)); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -## Aurora Key Facts - -- Repository: `kyaulabs/aurora` (git submodule at `aurora/`) -- Entry point: `require_once(__DIR__ . "/../aurora/aurora.inc.php")` -- Main class: `KYAULabs\Aurora` -- Provides: HTML header/footer templating, unconditional SRI hashes, resource preloading, SQL handler, performance statistics via `comment()` -- `$rus = getrusage()` must be called before the Aurora include — it captures start-of-request resource usage for the `comment()` performance footer - -## Aurora Constructor Parameters - -```php -new KYAULabs\Aurora(?string $template = null, ?string $cdn = '/cdn', bool $status = false, bool $html = false, ?string $templateDir = null) -``` - -- `$template` — base HTML template name (e.g. `"index.html"`) -- `$cdn` — CDN directory path (e.g. `"/cdn"`) -- `$status` — **debug mode**: enables `display_errors`, `display_startup_errors`, `E_ALL` reporting, and `html_errors`. Must be `false` in production. Wire to `(bool)($_ENV['APP_DEBUG'] ?? false)`. -- `$html` — **HTML output mode**: sets `mb_http_output('UTF-8')` and sends `Content-Type: text/html; charset=UTF-8` header -- `$templateDir` — optional custom template overlay directory (checked first; falls back to Aurora's default `html/` directory) - -**Always use named arguments** at the call site. The constructor has positional -bool parameters (`$status`, `$html`) that are a documented sharp edge — a call -using bare `true, true` is ambiguous and has caused production incidents where -error display was accidentally enabled. Named arguments make the dangerous -`$status` parameter explicit and self-documenting. PHP 8.0+ is required; no -API change to Aurora is needed. - -## Gotchas - -- *Using `$status = true` in production* — enables full error display with - stack traces, absolute paths, and SQL fragments rendered to any visitor on - an unhandled error. Always use `(bool)($_ENV['APP_DEBUG'] ?? false)` for - the `$status` parameter. `APP_DEBUG` must be `false` in `.env` for - production deployments. -- *Constructor unconditionally enables error display before the `$status` - gate* — Aurora's constructor calls `ini_set('display_errors','1')` at - lines 88-91 of `aurora.inc.php` regardless of the `$status` value. If an - `AuroraException` is thrown during template/initialization validation - (missing template, bad CDN directory), the error renders verbosely even - when `$status=false`. This is an upstream issue in `kyaulabs/aurora`. - Until fixed, ensure the template file and CDN directory always exist before - deploying. -- *SRI is unconditional in Aurora* — the constructor has no SRI toggle. - `htmlStyles()`, `htmlScripts()`, and `htmlPreload()` always emit - `integrity="sha512-..."` attributes regardless of constructor arguments. -- *`$rus` without `comment()`* — `$rus = getrusage()` captures - start-of-request resource usage but only produces visible output when - paired with `$site->comment($rus, basename(__FILE__))`. Both must be - present in the page template for the performance footer to render. diff --git a/.opencode/skills/brainstorming/SKILL.md b/.opencode/skills/brainstorming/SKILL.md deleted file mode 100644 index 8533fe9..0000000 --- a/.opencode/skills/brainstorming/SKILL.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -name: brainstorming -description: "Use before any creative work — creating features, building components, adding functionality, or modifying behavior. Refines rough ideas into a validated design through one-question-at-a-time grilling, then writes a spec. Hard-gate: no implementation until the design is approved and a spec is written (fast-path for zero-behavior-delta changes)." -derived-from: mattpocock/skills (MIT, © Matt Pocock) ---- - -# Brainstorming Ideas Into Designs - -Turn ideas into fully formed designs through natural collaborative dialogue. -Start by understanding the current project context, then ask questions one at a -time to refine the idea. Once you understand what you're building, present the -design and get user approval before any implementation. - -<HARD-GATE> -Do NOT invoke any implementation skill, write any code, scaffold any project, -or take any implementation action until you have presented a design and the -user has approved it. This applies to every change that has a behavior delta -or introduces new functionality. - -**Fast-path exception — skip brainstorming for changes with zero behavior delta:** -- Typo fixes (comments, strings, docs, log messages) -- Documentation-only changes (README, comments, PHPDoc blocks) -- RCS header / vim modeline additions or corrections -- Style/formatting-only changes with no logic impact (php-cs-fixer, lint fixes) -- Patch-level dependency bumps (no API breakage, no new features pulled in) -- Test-only changes (fixing flaky assertions, adding tests for existing - behavior — no production code touched) - -The fast-path skip means: classify the change (e.g. "typo fix → fast-path"), -announce it, implement directly, then run verification-before-completion and -/check. If any doubt about triviality, default to the full brainstorming flow. -</HARD-GATE> - -## Anti-pattern: "This is too simple to need a design" - -Behavior-changing work goes through this process, even when it seems simple. -"Simple" changes are where unexamined assumptions cause the most wasted work. -The design can be short (a few sentences for truly simple changes), but you -MUST present it and get approval. For zero-behavior-delta changes (typos, -docs, RCS headers, style-only, patch deps, test-only fixes), use the fast-path -exception defined in the HARD-GATE above. - -## Checklist - -Complete these in order: - -1. **Explore project context** — check files, docs, recent commits, `CONTEXT.md`. -2. **Ask clarifying questions** — one at a time, understand purpose/constraints/ - success criteria. -3. **Propose 2–3 approaches** — with trade-offs and your recommendation. -4. **Present design** — in sections scaled to their complexity, get user - approval after each section. -5. **Write spec** — save to `docs/specs/YYYY-MM-DD-<topic>-spec.md` and commit. -6. **Spec self-review** — quick inline check for placeholders, contradictions, - ambiguity, scope. -7. **User reviews written spec** — ask the user to review before proceeding. -8. **Transition to implementation** — invoke the `writing-plans` skill to create - an implementation plan. - -## The process - -**Understanding the idea:** - -- Check the current project state first (files, docs, recent commits). -- Before asking detailed questions, assess scope: if the request describes - multiple independent subsystems, flag it immediately. Don't spend questions - refining details of a project that needs to be decomposed first. -- If the project is too large for a single spec, help the user decompose into - sub-projects: what are the independent pieces, how do they relate, what - order should they be built? Then brainstorm the first sub-project through - the normal design flow. Each sub-project gets its own spec → plan → - implementation cycle. -- For appropriately-scoped changes, ask questions **one at a time**. Prefer - multiple choice when possible; open-ended is fine too. Only one question - per message — if a topic needs more exploration, break it into multiple - questions. -- Focus on understanding: purpose, constraints, success criteria. - -**Exploring approaches:** - -- Propose 2–3 different approaches with trade-offs. -- Present options conversationally with your recommendation and reasoning. -- Lead with your recommended option and explain why. - -**Presenting the design:** - -- Once you believe you understand what you're building, present the design. -- Scale each section to its complexity: a few sentences if straightforward, - up to 200–300 words if nuanced. -- Ask after each section whether it looks right so far. -- Cover: architecture, components, data flow, error handling, testing. -- Be ready to go back and clarify if something doesn't make sense. - -**Design for isolation and clarity:** - -- Break the system into smaller units that each have one clear purpose, - communicate through well-defined interfaces, and can be understood and - tested independently. -- For each unit, answer: what does it do, how do you use it, and what does - it depend on? -- Apply the deep-modules heuristic (see `systems-design` skill): the interface - should be materially smaller than the implementation. - -**Working in existing codebases:** - -- Explore the current structure before proposing changes. Follow existing - patterns. -- Where existing code has problems that affect the work (a file that's grown - too large, unclear boundaries, tangled responsibilities), include targeted - improvements as part of the design — the way a good developer improves code - they're working in. -- Don't propose unrelated refactoring. Stay focused on what serves the - current goal. - -## Domain language - -If `CONTEXT.md` exists, use its domain glossary verbatim in the design. If -the design introduces a new domain term, note it — it should be added to -`CONTEXT.md` before or during implementation (see `domain-context` skill). -If a decision is hard to reverse, note it as an ADR candidate. - -## After the design - -**Write the spec:** - -- Save the validated design to `docs/specs/YYYY-MM-DD-<topic>-spec.md`. -- Commit the design document to git. -- If `docs/specs/` doesn't exist, create it. - -**Spec self-review** — look at it with fresh eyes: - -1. **Placeholder scan:** any "TBD", "TODO", incomplete sections, or vague - requirements? Fix them. -2. **Internal consistency:** do any sections contradict each other? -3. **Scope check:** is this focused enough for a single implementation plan, - or does it need decomposition? -4. **Ambiguity check:** could any requirement be interpreted two different - ways? If so, pick one and make it explicit. - -Fix any issues inline. No need to re-review — just fix and move on. - -**User review gate:** - -After the spec review passes, ask the user to review the written spec: - -> "Spec written and committed to `<path>`. Please review it and let me know -> if you want to make any changes before we start writing out the -> implementation plan." - -Wait for the user's response. If they request changes, make them and re-run -the spec review. Only proceed once the user approves. - -**Implementation:** - -- Invoke the `writing-plans` skill to create a detailed implementation plan. -- Do NOT invoke any other skill. `writing-plans` is the next step. - -## Key principles - -- **One question at a time** — don't overwhelm with multiple questions. -- **Multiple choice preferred** — easier to answer than open-ended when - possible. -- **YAGNI ruthlessly** — remove unnecessary features from all designs. -- **Explore alternatives** — always propose 2–3 approaches before settling. -- **Incremental validation** — present design, get approval before moving on. -- **Be flexible** — go back and clarify when something doesn't make sense. - -## Cross-refs - -- `writing-plans` skill — the next step after design approval. -- `domain-context` skill — read `CONTEXT.md` before designing; update it with - new terms. -- `systems-design` skill — ADR vs RFC decision, deep-modules heuristic, - interface-design checklist. -- `@architect` agent — for non-trivial or cross-cutting changes, suggest an - architect review before implementation. - -## Gotchas - -Known failure modes that compound over time. Add entries when this skill -causes a preventable mistake. - -- *Skipping brainstorming for behavior-delta changes* — simple-looking behavior - changes are where unexamined assumptions cause the most wasted work. The - design can be short, but it must be presented and approved. Trivial - zero-behavior-delta work (typos, docs, headers, style, patch deps, test-only - fixes) follows the fast-path — that's the explicit exception, not a loophole. -- *Asking multiple questions in one message* — overwhelms the user and - produces lower-quality answers. One question at a time, always. -- *Jumping to implementation before spec is written* — the hard-gate exists - for a reason. No code until the design is approved and the spec is saved. diff --git a/.opencode/skills/conventional-commits/SKILL.md b/.opencode/skills/conventional-commits/SKILL.md deleted file mode 100644 index 57e7eaa..0000000 --- a/.opencode/skills/conventional-commits/SKILL.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -name: conventional-commits -description: Use when writing or reviewing commit messages. Covers the required Conventional Commits format, valid types, scope rules, and examples. Enforced by commitlint in the commit-msg hook. ---- - -## Conventional Commits Format - -``` -<type>[optional scope]: <subject> - -[optional body] - -[optional footer(s)] -``` - -- Subject line: lowercase, no period at end, max 100 characters -- Body: wrap at 72 characters, explain *why* not *what* -- Signed commits required (`git commit -S`) -- Every commit must include `Plan-by:`, `Acked-by:`, and `Signed-off-by:` footers - -## Required Footers - -Every commit message must end with three footers: - -- **`Plan-by:`** — the planning model, in kebab-case. Sourced from - `agent.plan.model` in `opencode.json` (the segment after the last `/`). - Example: `openrouter/z-ai/glm-5.2` → `glm-5.2`. -- **`Acked-by:`** — the build model, in kebab-case. Sourced from - `agent.build.model` in `opencode.json` (the segment after the last `/`). - Example: `deepseek/deepseek-v4-pro` → `deepseek-v4-pro`. - -> [!CAUTION] -> Do NOT use role names (`build-agent`, `code-review`, `tdd`, etc.) — only the -> model ID. The Plan-by and Acked-by footers track which configured models -> planned and built the change, not which agent role orchestrated it. -- **`Signed-off-by:`** — the human user approving the change, formatted as - `username <email>`. Default when no user is specified: - `kyau <git@kyaulabs.com>`. - -These are mandatory for traceability. The agent writes them automatically by -reading `agent.plan.model` and `agent.build.model` from `opencode.json` and -taking the segment after the last `/`. - -## Valid Types - -| Type | When to use | SemVer impact | -|------------|-----------------------------------------------------------|---------------| -| `feat` | A new feature | MINOR | -| `fix` | A bug fix | PATCH | -| `patch` | A bug fix (alias of `fix`, project convention) | PATCH | -| `docs` | Documentation only changes | — | -| `style` | Formatting, whitespace — no logic change | — | -| `refactor` | Code change that neither fixes a bug nor adds a feature | — | -| `perf` | Performance improvement | PATCH | -| `test` | Adding or correcting tests | — | -| `build` | Build system or asset pipeline changes | — | -| `ci` | CI/CD configuration changes | — | -| `chore` | Maintenance (deps, tooling) — no production code change | — | -| `revert` | Reverts a previous commit | — | -| `ignore` | Excluded from the changelog (initial commit only) | none (ignored)| - -The `patch` and `ignore` types are project-specific extensions defined in -`commitlint.config.js`. `ignore` exists for the initial repository commit and -is otherwise unused — do not adopt it for normal commits. - -## Breaking Changes - -Add `!` after the type/scope, and add a `BREAKING CHANGE:` footer: - -``` -feat(auth)!: replace session tokens with JWTs - -BREAKING CHANGE: existing session tokens are invalidated on deploy. -``` - -## Scope - -Scope is optional but recommended for larger projects. Use the affected module, -directory, or feature area: `feat(aurora)`, `fix(db)`, `test(auth)`. - -## Examples - -``` -feat(auth): add remember-me cookie to login flow - -Plan-by: glm-5.2 -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com> -``` - -``` -fix(db): prevent SQL injection in user search query - -Plan-by: glm-5.2 -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com> -Fixes: #42 -``` - -``` -test(auth): add boundary cases for empty credentials - -Plan-by: glm-5.2 -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com> -``` - -``` -refactor(backend): extract DB retry logic into helper -chore: update composer dependencies -docs: add browser test setup instructions to AGENTS.md -``` - -Examples above show the required footers on all non-trivial commits. -`chore` and `docs` commits that are purely mechanical may omit footers at the -user's discretion. - -## Enforcement - -Commitlint validates every commit message via `.github/hooks/commit-msg`. -The hook blocks the commit if the format is invalid. -Config: `commitlint.config.js` extends `@commitlint/config-conventional`, -with a custom `type-enum` that adds `build`, `patch`, and `ignore` to the -standard set. diff --git a/.opencode/skills/database/SKILL.md b/.opencode/skills/database/SKILL.md deleted file mode 100644 index 472216b..0000000 --- a/.opencode/skills/database/SKILL.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -name: database -description: Use when designing or modifying the MariaDB schema, writing SQL, or changing the <app>.sql convention. Covers schema design, migrations (no ORM), indexing/EXPLAIN, and SQL style. Injection prevention lives in security-coding. ---- - -This project uses **raw SQL and Aurora's SQL handler — no ORM**. Schema -discipline is manual. - -## Schema file convention - -- Project root: `<app>.sql` — the canonical schema (CREATE TABLE statements). -- Treat it as source of truth for the current schema shape. -- Apply schema changes via a migration file under `backend/migrations/` named - `YYYYMMDDThhmmss_<short_description>.sql` (ISO-8601 timestamp prefix). See - "Migrations" below. - -## Schema design - -- `InnoDB` engine, `utf8mb4` charset, `utf8mb4_unicode_ci` collation — on every - table. Do not rely on server defaults. -- Surrogate primary key: `id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY`. -- Foreign keys explicit, with `ON DELETE` and `ON UPDATE` specified (default - `RESTRICT`; use `CASCADE` only when you mean it). -- `NOT NULL` by default. Allow NULL only when the absence of a value is - meaningful and distinct from the default. -- Timestamps: `created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP` and - `updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE - CURRENT_TIMESTAMP`. -- Money: store as integer minor units (cents), never FLOAT/DECIMAL for - currency unless you have a specific reason. -- Enums: prefer a lookup table with a FK over `ENUM` when the set of values - may grow. - -```sql -CREATE TABLE `orders` ( - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `user_id` BIGINT UNSIGNED NOT NULL, - `total_cents` BIGINT NOT NULL, - `status` VARCHAR(32) NOT NULL DEFAULT 'pending', - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - KEY `idx_orders_user` (`user_id`), - CONSTRAINT `fk_orders_user` - FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) - ON DELETE RESTRICT ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -## Migrations (no ORM) - -- One migration per change, timestamp-prefixed: `20260702T214200_add_orders_table.sql`. -- Migrations are **forward-only**. Do not write down-migrations; revert by - writing a new forward migration that undoes the prior one. -- Each migration must be idempotent where practical (use `IF NOT EXISTS` on - creates, guard alters). -- Apply migrations in timestamp order; record applied migrations in a - `schema_migrations` table. -- After applying, regenerate or update `<app>.sql` to reflect the new shape. - -## Indexing - -- Index foreign keys unless the table is tiny. -- Index columns used in `WHERE`, `JOIN`, and `ORDER BY` for hot queries. -- Prefer composite indexes ordered by selectivity (most-selective first), but - only when the query actually filters on the leading column(s). -- Do not index low-cardinality columns in isolation (a boolean alone is - useless). -- Verify with `EXPLAIN` before merging — look for `type: ALL` (full scan) on - large tables. - -```sql -EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC; -``` - -## SQL style - -- Keywords UPPERCASE, identifiers `backtick_quoted`. -- One column per line in multi-line statements. -- End every statement with `;`. -- `SELECT` explicit columns, never `SELECT *` in production code (schema - changes silently break it). -- `INSERT` always lists columns: `INSERT INTO t (a, b) VALUES (?, ?)`. -- Use `... IN (...)` for small sets; for large sets, a join against a values - table or a temp table. - -## Rules - -- Never store secrets in the DB schema (no password column holds plaintext — - see `security-coding`). -- Never run write queries from a `@debug` session — suggest read-only - verification only. -- Back up before destructive migrations in production. -- Keep `<app>.sql` and the migration files in sync — both are committed. diff --git a/.opencode/skills/domain-context/SKILL.md b/.opencode/skills/domain-context/SKILL.md deleted file mode 100644 index 008e336..0000000 --- a/.opencode/skills/domain-context/SKILL.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: domain-context -description: Use before any work that touches domain logic, entities, or ubiquitous language. Read CONTEXT.md first and update it when domain terms, entities, or boundaries change. ---- - -## Before domain-coupled work - -Read `CONTEXT.md` (project root) before: - -- Adding or modifying an entity or domain object. -- Introducing a new domain term in code, tests, or UI. -- Touching a system boundary (new external API, new DB table, new service). -- Writing an ADR (the ADR's "Context" section should align with this file). - -Use the terms defined in CONTEXT.md's **Domain Glossary** verbatim in test -names, function names, and UI copy. This is the project's ubiquitous language. - -## When to update CONTEXT.md - -Update the file when: - -- A new domain term is introduced — add it to the glossary **before** using - it in code. -- An entity is added, removed, or its invariants change. -- A new system boundary or external dependency is introduced. -- An ADR is accepted — add it to the "Architectural Decisions" list. - -## What does NOT belong in CONTEXT.md - -- Implementation details and file paths (those live in `AGENTS.md`). -- Stack choices and build commands (those live in `AGENTS.md` / build docs). -- Test mechanics (those live in `.opencode/docs/tests.md`). -- Refactor checklists (those live in `.opencode/docs/refactoring.md`). - -CONTEXT.md is the *what* and *why* of the domain, never the *how*. - -## Rules - -- If `CONTEXT.md` does not exist, flag it — do not silently proceed without - domain context. Suggest running `/prime` to generate a draft. -- Do not duplicate CONTEXT.md content into ADRs or skills; reference it. -- Keep glossary entries to one definition each; expand in prose only if a term - is genuinely ambiguous. -- For non-trivial or cross-cutting changes, `@architect` includes a - domain-context read as Step 1 of its workflow; the `domain-context` skill - alone suffices for small in-domain changes. diff --git a/.opencode/skills/executing-plans/SKILL.md b/.opencode/skills/executing-plans/SKILL.md deleted file mode 100644 index 3179e58..0000000 --- a/.opencode/skills/executing-plans/SKILL.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -name: executing-plans -description: Use when executing a multi-task implementation plan from docs/plans/. Defines two execution modes (inline batch-with-checkpoints, @tdd-dispatch with two-stage review), per-task review gates, halt/re-plan policy, and context management across long plans. -derived-from: obra/superpowers (MIT, © Jesse Vincent) ---- - -# Executing Plans - -Execute an implementation plan task by task, with structured review gates and -clear halt thresholds. This skill sits between `writing-plans` (which produces -the plan) and `@tdd` (which implements each task). - -**Announce at start:** "I'm using the executing-plans skill to execute the -plan at `docs/plans/<filename>.md`." - -## Choose an execution mode - -### Inline mode — parent executes tasks directly - -Use for small plans (≤5 tasks) or when the parent already has full context. - -- The parent reads the plan, picks up task 1, and implements it directly - (Red → Green → Refactor cycle, same as `@tdd` but in-session). -- After each task: run `verification-before-completion`, checkpoint with the - user (ask if they want to review before continuing). -- Commit after each task (or logical group) with the commit message from the - plan task's step. - -### @tdd-dispatch mode — parent dispatches @tdd per task - -Use for larger plans or when context budget matters (each @tdd dispatch is a -fresh subagent context — the parent's context grows only with reviews, not -implementation details). - -- Dispatch `@tdd` with the full task text from the plan as the prompt. -- The subagent implements the task, returns its output (including test results - and commit hash). -- The parent reviews the output (see "Per-task review gate" below). -- Only proceed to the next task after the review gate passes. - -## Per-task review gate - -After each task completes (whether inline or via `@tdd`), run a two-stage -review before moving to the next task: - -### Stage 1 — Spec-compliance - -Does the task output match what the plan specified? - -- [ ] Files created/modified match the plan's "Files" list exactly. -- [ ] Interfaces match: the "Produces" signatures match what neighboring - tasks expect in their "Consumes" blocks. -- [ ] Test names and locations match the plan. -- [ ] Commit message matches the plan task's prescribed message (or follows - conventional-commits format if the plan didn't prescribe one). -- [ ] No scope creep — the task implemented only what was specified, not - neighbouring tasks or speculative features. - -If spec-compliance fails, note the discrepancy and decide: fix inline (if -minor, e.g. wrong file path) or re-plan the task with the user (if the plan's -assumptions were wrong). - -### Stage 2 — Code-quality - -Does the implementation follow harness conventions? - -- [ ] Every new or modified source file has an RCS header and vim modeline - (see `rcs-header` skill). -- [ ] PHP classes/methods/functions have PHPDoc (PSR-5). -- [ ] No debug artifacts (`dd()`, `dump()`, `var_dump()`, `print_r()`, - `console.log()`, `[DEBUG-...]` tags). -- [ ] No generated files edited directly (`cdn/css/*.min.css`, - `cdn/javascript/*.min.js`). -- [ ] Tests use Pest conventions: `describe()` / `it()` with behavior - descriptions, Arrange/Act/Assert, no tautological tests. - -If code-quality fails, fix the issues inline (e.g. add missing RCS header) -and re-run `verification-before-completion` before proceeding. Do not -delegate these fixes — they are the parent's responsibility. - -### After both stages pass - -Run `verification-before-completion` on the task's output. Only proceed to -the next task once all checks pass. Update the plan's checkbox status -(`- [x]`) for the completed task. - -## Halt / re-plan policy - -Stop and re-evaluate rather than pushing through when the plan is going -wrong: - -| Trigger | Action | -|---|---| -| A task's tests won't go green after **3 attempts** | Halt that task. Re-plan it with the user — the plan's approach may be wrong. | -| **2 consecutive tasks** need re-planning | Halt the entire plan. The plan's assumptions are wrong. Re-plan with `writing-plans`. | -| A task's findings **invalidate the plan's assumptions** (architectural blocker, dependency conflict, discovered constraint) | Halt immediately. Suggest `@architect` review or re-plan with `writing-plans`. | -| The user changes requirements mid-plan | Halt. Update the spec (see `brainstorming` skill), then re-plan with `writing-plans`. | - -**Never silently deviate from the plan.** If a task can't be done as written, -halt and surface the discrepancy — don't improvise. - -## Context management across long plans - -Long plans (10+ tasks) will exhaust context. Manage it proactively: - -- After every **5 tasks**, check context usage against the thresholds in - `.opencode/docs/context-management.md`. -- When context exceeds **40%**, compact with a hint: - `/compact focus on executing plan <filename>, drop completed tasks 1-N`. -- When context exceeds **60%** or the session shows degradation, run - `/handoff` and start a fresh session. Tell the new session: - "Read `docs/handoffs/<filename>` and continue executing the plan." -- In @tdd-dispatch mode, each dispatch is a fresh subagent context — the - parent's context grows only with reviews and orchestration decisions, not - implementation details. This naturally extends the parent's range. -- Update the plan's checkbox status (`- [x]`) after each task completes so - a resumed session knows exactly where to pick up. - -## Rules - -- After each task completes, run both stages of the review gate BEFORE - dispatching the next task. -- Commit after each task (or logical group) with the plan's prescribed - commit message. Validate the message format before committing (see - `conventional-commits` skill). -- Update the plan's checkbox status after each task completes. -- Never continue past a halt trigger without user intervention. -- The parent handles code-quality fixes (missing RCS headers, debug artifact - cleanup) — don't bounce those back to @tdd. - -## Cross-refs - -- `writing-plans` skill — the step before this one (produces the plan). -- `verification-before-completion` skill — run after each task is green. -- `@tdd` agent — executes each task in Red → Green → Refactor cycles. -- `@architect` agent — insert before re-planning if a halt was architectural. -- `brainstorming` skill — re-plan from scratch if requirements change. -- `conventional-commits` skill — validate commit messages. -- `rcs-header` skill — fix missing RCS headers during code-quality review. -- `.opencode/docs/context-management.md` — context thresholds and compaction. -- `/handoff` command — save state when context degrades. - -## Gotchas - -Known failure modes that compound over time. Add entries when this skill -causes a preventable mistake. - -- *Skipping the per-task review gate* — the plan assumes each task's interfaces - connect correctly. If task N produces `clearLayers()` but task N+1 consumes - `clearFullLayers()`, the plan breaks silently. Review gate catches this. -- *Pushing through when a task is stuck* — three attempts without green is a - signal the approach is wrong, not that you need a fourth attempt. Halt and - re-plan. -- *Context exhaustion on long plans* — the parent can hold ~15 task reviews - before context degrades. Compact proactively or use @tdd-dispatch mode to - keep the parent context lean. -- *Code-quality fixes delegated to @tdd* — RCS header misses, debug artifacts, - and convention violations are the parent's responsibility to catch and fix. - Don't bounce a completed task back to the subagent for a style fix. diff --git a/.opencode/skills/finding-duplicate-functions/SKILL.md b/.opencode/skills/finding-duplicate-functions/SKILL.md deleted file mode 100644 index 0854484..0000000 --- a/.opencode/skills/finding-duplicate-functions/SKILL.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -name: finding-duplicate-functions -description: "Use when seeking semantic duplication in a codebase — functions that solve the same problem for different callers. Complements /improve-architecture's deletion test. Two-phase: classical extraction then LLM intent-clustering." -derived-from: obra/superpowers-lab (MIT, © Jesse Vincent) ---- - -# Finding Duplicate Functions - -Detect semantic duplication — functions that solve the same problem but look -different enough to escape a simple token-diff. Duplicate intent is the inverse -of a shallow module: redundant depth, not insufficient depth. This skill -pairs with `/improve-architecture`'s deletion test to identify candidates for -extraction. - -**Announce at start:** "I'm using the finding-duplicate-functions skill to scan -for semantic duplication." - -## When to use - -- During an `/improve-architecture` pass — duplicates are a deepening signal. -- When refactoring a module and noticing similar shape across functions. -- As a periodic hygiene pass on a module that has grown organically. - -## Phase 1 — Classical extraction - -Scan for functions with structurally similar bodies and similar signatures. -Use tools available to the agent — ripgrep for signature patterns, read files -for the implementations. - -For each candidate pair, calculate: -- Same number of parameters, or a clear subset/superset relationship? -- Parameters of the same types (if typed)? -- Body structure — same sequence of operations but with different constants - or different local variable names? -- Same return type or return shape? - -**Output of Phase 1:** a list of candidate pairs with file:line references -and a similarity note (high/medium/low). - -## Phase 2 — LLM intent-clustering - -For each Phase 1 candidate pair rated medium or high, read both functions in -full and ask: **"Do these two functions solve the same problem for different -callers, or different problems that happen to look similar?"** - -- **Same problem** → they share intent. One should be extracted into a shared - helper; the callers differ only in the parameters they pass. -- **Different problems** → they share tokens but not intent. Leave them - separate — extraction would couple unrelated things. -- **One is a superset** → the larger function duplicates the smaller one's - logic plus extras. Extract the shared core; the larger function calls it. - -## Deletion-test gate - -Before proposing extraction, apply the deletion test from -`/improve-architecture`: if you deleted both functions and replaced them with -a shared helper, would complexity concentrate (good — the extraction is -deepening) or just move sideways (bad — you're rearranging tokens)? - -- **Concentrates complexity** → the shared helper has fewer paths than the - two originals combined. Propose extraction. -- **Moves complexity sideways** → the shared helper mirrors one of the - originals with a flag parameter. Do NOT propose — this is false deduplication. - -## Output format - -```markdown -## Duplicate Detection — <module or directory> - -### High-confidence duplicates - -#### <function-a> / <function-b> -**Files:** `path/a.php:NN`, `path/b.php:MM` -**Shared intent:** [one-line description of the problem they both solve] -**Extraction proposal:** [signature of the shared helper] -**Deletion test:** concentrates complexity / moves sideways (skip) -**Callers:** N in file A, M in file B - -### Candidates (medium — needs deeper reading) - -- [list with file:line references] - -### False positives (different intent, similar tokens) - -- [list with one-line explanation of why they differ] -``` - -## Rules - -- Only propose extraction when **both** phases agree — Phase 1 finds structural - similarity AND Phase 2 confirms shared intent. -- The deletion test is a hard gate. If extraction does not concentrate - complexity, do not propose it. -- Do not propose extraction for functions under 5 lines — the overhead of a - helper exceeds the savings. -- Respect the existing architecture. If the codebase already has a helper - collection pattern (e.g. a `helpers/` directory or a trait), use it. -- Read function bodies — do not rely on signatures alone. - -## Cross-refs - -- `/improve-architecture` command — the deletion test lives here; this skill - is a companion scanner. -- `systems-design` skill — deep-modules heuristic and architecture vocabulary. -- `@explore` agent — use for the Phase 1 structural scan across many files. -- `brainstorming` skill — if an extraction candidate is large, brainstorm the - extraction design before implementing. - -## Gotchas - -- *False deduplication* — two functions that look alike but solve different - problems. Extraction couples unrelated things and creates a shallow module - with a flag parameter. Phase 2 intent-clustering prevents this. -- *Skipping the deletion test* — without it, every similar-looking pair looks - like a win. The deletion test separates real deepening from token shuffling. -- *Proposing extraction for trivial functions* — a 3-line wrapper around a - standard-library call is not duplication. Floor: 5 lines of project-specific - logic. diff --git a/.opencode/skills/finishing-a-development-branch/SKILL.md b/.opencode/skills/finishing-a-development-branch/SKILL.md deleted file mode 100644 index 74c9c8a..0000000 --- a/.opencode/skills/finishing-a-development-branch/SKILL.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -name: finishing-a-development-branch -description: Use when a feature branch's work is complete — all tasks green, /check passed, @code-review clean — and you need to verify readiness and present disposal options (merge, PR, keep, discard). -derived-from: obra/superpowers (MIT, © Jesse Vincent) ---- - -# Finishing a Development Branch - -Complete a feature branch with a disciplined verification checklist and -structured disposal options. This skill runs after all plan tasks are done -and the branch is ready for integration. - -**Announce at start:** "I'm using the finishing-a-development-branch skill to -verify and wrap up this feature branch." - -## Pre-completion checklist - -Run every item; do not skip: - -- [ ] All plan tasks checked off (`- [x]` in `docs/plans/`). -- [ ] `verification-before-completion` run on the final state — all checks - passed. -- [ ] `/check` green — lint + coverage 80% minimum. -- [ ] `@code-review` clean — only Informational findings remain (see - `receiving-code-review` skill if not). -- [ ] Working tree clean — `git status` shows no uncommitted changes. -- [ ] Branch is rebased on `develop` (or `main` if no develop branch): - `git fetch origin && git rebase origin/develop`. -- [ ] No merge conflicts after rebase. If conflicts exist, suggest - `@resolve-merge-conflicts` or manual resolution. -- [ ] All commits follow Conventional Commits format and include - `Plan-by:` + `Acked-by:` + `Signed-off-by:` footers (see `conventional-commits` skill). - -After every item passes, present the summary below. If any item fails, stop -and fix it — do not proceed with a failing item. - -## Summary after checklist - -Present this block to the user: - -``` -## Branch completion — ready for integration - -Branch: <branch-name> -Target: develop -Commits: N (all conventional-commits, all signed) -Check: /check green -Review: @code-review clean -Coverage: >= 80% on changed files - -What would you like to do? -1. Merge to develop (I will prepare the merge command) -2. Open a pull request (I will prepare the gh pr command) -3. Keep branch for further work (no action) -4. Discard branch (I will warn; you confirm) -``` - -Wait for the user's response. Do not auto-merge or auto-push. - -## Option responses - -### Merge - -Present the merge commands. Both require user confirmation: - -```bash -git checkout develop -git merge --no-ff <branch-name> -git push origin develop # human executes this -``` - -Note: `git push` is denied to agents — the user pushes. Do not attempt it -and do not offer the `git push` command in the approval dialog. - -After the user confirms the merge is done, offer to delete the local branch: - -```bash -git branch -d <branch-name> -``` - -### Pull request - -Prepare the `gh pr create` command: - -```bash -gh pr create --base develop --head <branch-name> --title "<subject>" --body "<body>" -``` - -Use the branch description and commit subjects to construct the title and body. -Present the exact command for user approval. - -### Keep / Discard - -If "keep" — done. If "discard" — warn the user that unmerged commits will be -lost, then present: - -```bash -git checkout develop -git branch -D <branch-name> -``` - -## No-squash reminder - -Each logical change is its own atomic commit — the git history serves as the -development and evaluation log. Do not squash. Do not suggest squashing. The -`--no-ff` merge flag preserves the branch commit history. - -## Rules - -- Every checklist item must pass before presenting the summary. No partial - passes. -- Rebase on the target branch before presenting. Do not suggest merging into - an outdated base. -- Never auto-merge. Never auto-push. The user drives integration. -- Do not delete branches the user hasn't confirmed merging. -- Respect the `feat/<username>-<hash>-<description>` convention and the - no-squash policy from `AGENTS.md`. - -## Cross-refs - -- `executing-plans` skill — produces the plan whose tasks you check off. -- `verification-before-completion` skill — the verification step in the - checklist. -- `/check` command — the lint + coverage gate. -- `@code-review` agent — generates findings; use `receiving-code-review` skill - to triage them. -- `conventional-commits` skill — validates commit message format. -- `@resolve-merge-conflicts` agent — if rebase produces conflicts. -- `receiving-code-review` skill — triage any `@code-review` findings that - aren't Informational. -- `AGENTS.md` § Git Workflow — branch naming convention and no-squash policy. - -## Gotchas - -- *Presenting the summary before the checklist is complete* — a red checklist - item means the branch is NOT ready. Fix it first. -- *Skipping the rebase* — stale branches produce merge conflicts later. - Rebase before presenting. -- *Offering git push* — agents cannot push. Do not offer or attempt it. -- *Suggesting squashing* — the no-squash policy is load-bearing for the - evaluation log. Never suggest or offer a squash merge. diff --git a/.opencode/skills/frontend-architecture/SKILL.md b/.opencode/skills/frontend-architecture/SKILL.md deleted file mode 100644 index 8d2ca62..0000000 --- a/.opencode/skills/frontend-architecture/SKILL.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -name: frontend-architecture -description: Use when structuring frontend JS or wiring up page behavior. Covers progressive enhancement, vanilla-JS module pattern, the jQuery-only-when-insufficient policy, design-token consumption from CSS custom properties, and CSP-friendly inline-script rules. Does NOT cover visual design — see frontend-design. ---- - -## Progressive enhancement - -Content and core function work without JS. JS layers on behavior, not -existence. - -- A `<form>` posts and works server-side before JS enhances it. -- Links are `<a href>` to real URLs before AJAX is layered on. -- The page is readable with JS disabled. Never hide content behind a - `js-enabled` class gate. - -Failure mode: if JS fails to load, the user still gets the content and a -working (if less polished) experience. - -## Vanilla-JS module pattern - -Each page's behavior lives in one IIFE module per source file in `cdn/js/`, -rebuilt to `cdn/javascript/*.min.js` (see build pipeline). - -```javascript -// cdn/js/site.js -(function () { - "use strict"; - - function init() { - // wire up behavior on DOM ready - } - - if (document.readyState !== "loading") { - init(); - } else { - document.addEventListener("DOMContentLoaded", init); - } -})(); -``` - -- One module per concern. Do not build a god-module. -- No bundler, no import maps, no build step beyond uglifyjs. Plain script tags. -- Expose at most one global namespace object if cross-module sharing is needed; - prefer keeping modules self-contained. - -## jQuery policy - -Vanilla JS is the default. Reach for jQuery only when: - -- A dependency already ships jQuery and the cost of removing it is high. -- A specific feature is materially shorter in jQuery and the vanilla - equivalent is genuinely insufficient (not just unfamiliar). - -If jQuery is introduced, note the reason in the PR. Modern vanilla APIs -(`querySelector`, `classList`, `fetch`, `IntersectionObserver`, -`CustomElements`) cover the vast majority of cases. - -## Design tokens - -Visual tokens (colors, shadows, surfaces) are defined canonically by the -`frontend-design` skill as `:root` CSS custom properties. This skill -**consumes** them; it does not redefine them. - -- Read tokens via `var(--token)` in SCSS, not hardcoded values. -- Component-level overrides belong in the component's SCSS, scoped to the - component's class — never on `:root` (that is the design layer's domain). -- If a token is missing, add it in the design layer first, then consume it. - -## CSP-friendly scripts - -- Avoid inline event handlers (`onclick=`, `onload=`). -- Inline `<script>` tags are forbidden. Aurora emits only external - `<script src>` tags with SRI hashes (`integrity`, `crossorigin`); script - tags emitted through Aurora's `$site->js` array (see `aurora-page` skill) - carry SRI automatically. Page authors must not emit inline scripts in the - body. -- `'unsafe-inline'` is forbidden in the CSP. See ADR-0001. - -## Cross-refs - -- `frontend-design` — visual language, neumorphism, token definitions. -- `accessibility` — keyboard handlers, focus management, motion safety. -- `scss-mobile-first` — where styles live and how they're built. -- `aurora-page` — how scripts and styles are registered on a page. diff --git a/.opencode/skills/frontend-design/SKILL.md b/.opencode/skills/frontend-design/SKILL.md deleted file mode 100644 index c527ec3..0000000 --- a/.opencode/skills/frontend-design/SKILL.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -name: frontend-design -description: Use when writing or reviewing SCSS/CSS that defines visual language. Sets the mandatory baseline — responsive always, mobile-first, CSS transitions for ease, CSS-driven load/scroll flow, neumorphic design language, and the default light/dark theme with sky-blue or light-purple highlights. Defines the canonical :root tokens consumed by frontend-architecture. ---- - -## Mandatory baseline - -The following are non-negotiable defaults for every project. Deviating -requires a documented exception (an ADR or a page-level note in `CONTEXT.md`). - -### 1. Responsive — always - -Every project must be responsive. Breakpoint mechanics, fluid units, and -`min-width` rules live in the `scss-mobile-first` skill. This skill does not -redefine them. - -### 2. Mobile-first mindset - -Mobile-first is mandatory. See `scss-mobile-first` for base-viewport size, -breakpoint values, and `min-width` mechanics. - -### 3. Transitions for ease - -- Default to `transition: <property> 200–300ms ease` on interactive states - (hover, focus, active). -- Only transition transform, opacity, color, background, border, box-shadow. -- Never transition layout-affecting properties (`width`, `height`, `top`, - `left`, `margin`, `padding`) — they cause reflow and jank. -- Honor `prefers-reduced-motion: reduce` — see the `accessibility` skill. - -### 4. CSS-driven load & scroll flow - -The site should appear to flow as it loads and scrolls, achieved primarily -with CSS. - -- **Entrance animations** via `@keyframes` + `animation`. -- **Scroll reveals** via `IntersectionObserver` adding an `.is-visible` class - (the JS module lives in `frontend-architecture`). Progressive enhancement - rules apply: default the element to **visible** if JS fails — only hide via - a `.js .will-reveal` selector that requires JS to have run. Do not gate - content visibility behind JS. -- **Stagger siblings** with `animation-delay` / `transition-delay` using - `calc(var(--i) * 80ms)` and inline `--i` per element. -- Keep total entrance duration ≤ **600ms**. Faster is better. -- Respect `prefers-reduced-motion` — snap to final state, no animation. - -### 5. Neumorphism — default design language - -Soft-UI: dual shadows (a light one top-left, a dark one bottom-right) on a -monochrome mid-tone surface. Inset shadows for pressed/active states. - -**Shadow recipes:** - -```scss -// Raised (default interactive surface) -box-shadow: 6px 6px 12px var(--shadow-dark), - -6px -6px 12px var(--shadow-light); - -// Pressed / active / inset -box-shadow: inset 6px 6px 12px var(--shadow-dark), - inset -6px -6px 12px var(--shadow-light); - -// Hover lift -box-shadow: 8px 8px 16px var(--shadow-dark), - -8px -8px 16px var(--shadow-light); -``` - -- Surface must be a **mid-tone**. Neumorphism fails on pure white or pure - black — there's no range for the dual shadow to play in. -- Pressed/inset state replaces the raised shadow; do not stack both. -- Keep shadow radii consistent across components on a page. - -**Contrast floor:** neumorphic surfaces can fail text/UI contrast. The -`accessibility` skill enforces ≥ 4.5:1 for text and ≥ 3:1 for component -boundaries. If the default tokens fail, add a visible border or darken the -text — never weaken the shadow to fake contrast. - -**Exceptions:** neumorphism is the default. Documented exceptions are allowed -where readability or density wins (article body text, dense data tables). Note -the exception in an ADR or a page-level note in `CONTEXT.md`. - -### 6. Color scheme - -- **If the user or page specifies a palette, use it verbatim.** Do not impose - the default on an explicit request. -- **Otherwise** use the default below: - -| Mode | Surface | Text | Accent options | -| --- | --- | --- | --- | -| Light | neutral grey (`#e0e5ec`-ish) | dark | sky blue **or** light purple | -| Dark | dark neutral | light | sky blue **or** light purple | - -- Sky blue: `#38bdf8` (accent), `#87ceeb` (soft). -- Light purple: `#a78bfa` (accent), `#c4b5fd` (soft). -- Respect `prefers-color-scheme` unless the user overrides it explicitly. -- Accents are for highlights, active states, focus rings — not large fills. - -## Canonical tokens (`:root`) - -These are the source of truth. `frontend-architecture` and components consume -them via `var(--token)`. Define in the base SCSS file (e.g. -`cdn/sass/_tokens.scss`), never in component files. - -```scss -:root { - // Surface - --surface: #e0e5ec; - - // Shadows (neumorphism) - --shadow-light: #ffffff; - --shadow-dark: #b8bcc4; - - // Text - --text: #2d3748; - --text-muted: #5a6472; - - // Accents (pick one of the two; both provided as options) - --accent: #38bdf8; // sky blue - --accent-soft: #87ceeb; - // --accent: #a78bfa; // light purple (uncomment to switch) - // --accent-soft: #c4b5fd; - --accent-hover: #0ea5e9; - - // Focus ring (accessibility — must meet 3:1) - --focus-ring: #2563eb; -} - -@media (prefers-color-scheme: dark) { - :root { - --surface: #2d3340; - --shadow-light: #353d4d; - --shadow-dark: #1a1f2a; - --text: #e2e8f0; - --text-muted: #a0aec0; - } -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} -``` - -## Cross-refs - -- `scss-mobile-first` — breakpoints, units, build mechanics. -- `accessibility` — contrast floors, motion safety, focus management. -- `frontend-architecture` — how to consume tokens in JS and component SCSS. -- `aurora-page` — where the base CSS file is registered. diff --git a/.opencode/skills/opencode-docs/SKILL.md b/.opencode/skills/opencode-docs/SKILL.md deleted file mode 100644 index 41feb92..0000000 --- a/.opencode/skills/opencode-docs/SKILL.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -name: opencode-docs -description: Use when writing or editing opencode.json, agent/skill/command frontmatter, plugin hooks, or permission rules — before guessing or calling /research. Vendors opencode.ai/docs locally for fast, cited reference. -derived-from: obra/superpowers-developing-for-claude-code (MIT, © Jesse Vincent) ---- - -# OpenCode Docs — Local Reference - -This skill vendors opencode.ai/docs content locally so agents can cite -authoritative config schemas, hook signatures, and permission rules without -web-fetching every question. The vendored docs live in `docs/` alongside -this SKILL.md. - -**Announce at start:** "I'm using the opencode-docs skill to reference the -OpenCode documentation." - -## Quick-reference table - -Common questions → which doc file to read: - -| Question | Doc file | -|---|---| -| What keys are valid in opencode.json? | `config.mdx` | -| How do I define a custom agent? | `agents.mdx` | -| How do I define a custom command? | `commands.mdx` | -| How do I write a skill? | `skills.mdx` | -| How do I write a plugin? | `plugins.mdx` | -| What permission rules are available? | `permissions.mdx` | -| How do rules files work (.opencode/rules/)? | `rules.mdx` | -| What tool configurations can I set? | `tools.mdx` | -| How does the reference system work? | `references.mdx` | -| How do I use the SDK (@opencode-ai/plugin)? | `sdk.mdx` | -| How does the server mode work? | `server.mdx` | -| How does `opencode run` work (non-interactive CLI)? | `cli.mdx` | -| How do I configure LLM providers? | `providers.mdx` | -| How do I configure a model? | `models.mdx` | -| How do I configure themes/colors? | `themes.mdx` | -| How do I configure code formatters? | `formatters.mdx` | -| How do network settings work? | `network.mdx` | -| How do LSP servers work? | `lsp.mdx` | -| How do MCP servers work? | `mcp-servers.mdx` | -| How do policies (approvals) work? | `policies.mdx` | -| How do custom tools work? | `custom-tools.mdx` | -| What platform-specific docs exist? | `windows-wsl.mdx`, `tui.mdx`, `web.mdx` | - -For anything not in this table, read `docs/` directory listing — each `.mdx` -file corresponds to a page on opencode.ai/docs. - -## How to use this skill - -1. **Identify the question** — which config area, hook, or schema? -2. **Find the doc file** — use the quick-reference table above, or list - `docs/` to see all available files. -3. **Read the doc file** — use the Read tool. The files are MDX (Markdown + - JSX components); focus on the prose and code blocks, ignore JSX tags. -4. **Cite the source** — when answering, reference the doc file name - (e.g. "per `config.mdx`") so the answer is traceable. - -Do not guess OpenCode semantics. If the question is not covered by the -vendored docs, fall back to `/research` and note that the docs may be stale -(suggest running `fetch.sh` to refresh). - -## Updating the docs - -Run `fetch.sh` from within this skill's directory to refresh the vendored -docs from the latest `anomalyco/opencode` dev branch: - -```bash -bash .opencode/skills/opencode-docs/fetch.sh -``` - -The script shallow-clones the repo with sparse checkout, extracts only the -English top-level `.mdx` files from `packages/web/src/content/docs/`, and -cleans up the temp clone. It excludes translation directories (`ar/`, `de/`, -etc.). Manual refresh — no watchers (per AGENTS.md build policy). - -## Rules - -- Always load this skill when writing or editing `opencode.json`, agent - frontmatter, command frontmatter, skill frontmatter, plugin code, or - permission rules. -- When the answer comes from a vendored doc, cite it by filename. -- If the answer is NOT covered, say so — do not fill the gap by guessing. -- If the vendored docs are stale or the question references a feature newer - than the docs, suggest running `fetch.sh` to refresh. - -## Cross-refs - -- `writing-skills` skill — how to write skills, agents, and commands per - OpenCode conventions. -- `customize-opencode` skill — editing opencode's own configuration (may - overlap; this skill provides the doc reference; customize-opencode provides - the editing workflow). -- `/research` command — fall back when the vendored docs don't cover the - question. - -## Gotchas - -- *Guessing config semantics instead of reading the vendored doc* — the docs - are right there in `docs/`. Read them before answering. -- *Treating vendored docs as frozen* — they are a snapshot. If a feature - doesn't work as documented, the docs may be stale. Run `fetch.sh`. -- *Confusing MDX components for prose* — the vendored files contain JSX - (`<Tab>`, `<Code>`, etc.). Focus on the markdown prose; the JSX is - presentational scaffolding from the opencode.ai website build. diff --git a/.opencode/skills/opencode-docs/docs/acp.mdx b/.opencode/skills/opencode-docs/docs/acp.mdx deleted file mode 100644 index 43d89ea..0000000 --- a/.opencode/skills/opencode-docs/docs/acp.mdx +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: ACP Support -description: Use OpenCode in any ACP-compatible editor. ---- - -OpenCode supports the [Agent Client Protocol](https://agentclientprotocol.com) or (ACP), allowing you to use it directly in compatible editors and IDEs. - -:::tip -For a list of editors and tools that support ACP, check out the [ACP progress report](https://zed.dev/blog/acp-progress-report#available-now). -::: - -ACP is an open protocol that standardizes communication between code editors and AI coding agents. - ---- - -## Configure - -To use OpenCode via ACP, configure your editor to run the `opencode acp` command. - -The command starts OpenCode as an ACP-compatible subprocess that communicates with your editor over JSON-RPC via stdio. - -Below are examples for popular editors that support ACP. - ---- - -### Zed - -Add to your [Zed](https://zed.dev) configuration (`~/.config/zed/settings.json`): - -```json title="~/.config/zed/settings.json" -{ - "agent_servers": { - "OpenCode": { - "command": "opencode", - "args": ["acp"] - } - } -} -``` - -To open it, use the `agent: new thread` action in the **Command Palette**. - -You can also bind a keyboard shortcut by editing your `keymap.json`: - -```json title="keymap.json" -[ - { - "bindings": { - "cmd-alt-o": [ - "agent::NewExternalAgentThread", - { - "agent": { - "custom": { - "name": "OpenCode", - "command": { - "command": "opencode", - "args": ["acp"] - } - } - } - } - ] - } - } -] -``` - ---- - -### JetBrains IDEs - -Add to your [JetBrains IDE](https://www.jetbrains.com/) acp.json according to the [documentation](https://www.jetbrains.com/help/ai-assistant/acp.html): - -```json title="acp.json" -{ - "agent_servers": { - "OpenCode": { - "command": "/absolute/path/bin/opencode", - "args": ["acp"] - } - } -} -``` - -To open it, use the new 'OpenCode' agent in the AI Chat agent selector. - ---- - -### Avante.nvim - -Add to your [Avante.nvim](https://github.com/yetone/avante.nvim) configuration: - -```lua -{ - acp_providers = { - ["opencode"] = { - command = "opencode", - args = { "acp" } - } - } -} -``` - -If you need to pass environment variables: - -```lua {6-8} -{ - acp_providers = { - ["opencode"] = { - command = "opencode", - args = { "acp" }, - env = { - OPENCODE_API_KEY = os.getenv("OPENCODE_API_KEY") - } - } - } -} -``` - ---- - -### CodeCompanion.nvim - -To use OpenCode as an ACP agent in [CodeCompanion.nvim](https://github.com/olimorris/codecompanion.nvim), add the following to your Neovim config: - -```lua -require("codecompanion").setup({ - interactions = { - chat = { - adapter = { - name = "opencode", - model = "claude-sonnet-4", - }, - }, - }, -}) -``` - -This config sets up CodeCompanion to use OpenCode as the ACP agent for chat. - -If you need to pass environment variables (like `OPENCODE_API_KEY`), refer to [Configuring Adapters: Environment Variables](https://codecompanion.olimorris.dev/getting-started#setting-an-api-key) in the CodeCompanion.nvim documentation for full details. - -## Support - -OpenCode works the same via ACP as it does in the terminal. All features are supported: - -:::note -Some built-in slash commands like `/undo` and `/redo` are currently unsupported. -::: - -- Built-in tools (file operations, terminal commands, etc.) -- Custom tools and slash commands -- MCP servers configured in your OpenCode config -- Project-specific rules from `AGENTS.md` -- Custom formatters and linters -- Agents and permissions system diff --git a/.opencode/skills/opencode-docs/docs/agents.mdx b/.opencode/skills/opencode-docs/docs/agents.mdx deleted file mode 100644 index 53048b7..0000000 --- a/.opencode/skills/opencode-docs/docs/agents.mdx +++ /dev/null @@ -1,781 +0,0 @@ ---- -title: Agents -description: Configure and use specialized agents. ---- - -Agents are specialized AI assistants that can be configured for specific tasks and workflows. They allow you to create focused tools with custom prompts, models, and tool access. - -:::tip -Use the plan agent to analyze code and review suggestions without making any code changes. -::: - -You can switch between agents during a session or invoke them with the `@` mention. - ---- - -## Types - -There are two types of agents in OpenCode; primary agents and subagents. - ---- - -### Primary agents - -Primary agents are the main assistants you interact with directly. You can cycle through them using the **Tab** key, or your configured `switch_agent` keybind. These agents handle your main conversation. Tool access is configured via permissions — for example, Build has all tools enabled while Plan is restricted. - -:::tip -You can use the **Tab** key to switch between primary agents during a session. -::: - -OpenCode comes with two built-in primary agents, **Build** and **Plan**. We'll -look at these below. - ---- - -### Subagents - -Subagents are specialized assistants that primary agents can invoke for specific tasks. You can also manually invoke them by **@ mentioning** them in your messages. - -OpenCode comes with three built-in subagents, **General**, **Explore**, and **Scout**. We'll look at this below. - ---- - -## Built-in - -OpenCode comes with two built-in primary agents and three built-in subagents. - ---- - -### Use build - -_Mode_: `primary` - -Build is the **default** primary agent with all tools enabled. This is the standard agent for development work where you need full access to file operations and system commands. - ---- - -### Use plan - -_Mode_: `primary` - -A restricted agent designed for planning and analysis. We use a permission system to give you more control and prevent unintended changes. -By default, all of the following are set to `ask`: - -- `file edits`: All writes, patches, and edits -- `bash`: All bash commands - -This agent is useful when you want the LLM to analyze code, suggest changes, or create plans without making any actual modifications to your codebase. - ---- - -### Use general - -_Mode_: `subagent` - -A general-purpose agent for researching complex questions and executing multi-step tasks. Has full tool access (except todo), so it can make file changes when needed. Use this to run multiple units of work in parallel. - ---- - -### Use explore - -_Mode_: `subagent` - -A fast, read-only agent for exploring codebases. Cannot modify files. Use this when you need to quickly find files by patterns, search code for keywords, or answer questions about the codebase. - ---- - -### Use scout - -_Mode_: `subagent` - -A read-only agent for external docs and dependency research. Use this when you need to clone a dependency repository into OpenCode's managed cache, inspect library source, or cross-reference local code against upstream implementations without modifying your workspace. - ---- - -### Use compaction - -_Mode_: `primary` - -Hidden system agent that compacts long context into a smaller summary. It runs automatically when needed and is not selectable in the UI. - ---- - -### Use title - -_Mode_: `primary` - -Hidden system agent that generates short session titles. It runs automatically and is not selectable in the UI. - ---- - -### Use summary - -_Mode_: `primary` - -Hidden system agent that creates session summaries. It runs automatically and is not selectable in the UI. - ---- - -## Usage - -1. For primary agents, use the **Tab** key to cycle through them during a session. You can also use your configured `switch_agent` keybind. - -2. Subagents can be invoked: - - **Automatically** by primary agents for specialized tasks based on their descriptions. - - Manually by **@ mentioning** a subagent in your message. For example. - - ```txt frame="none" - @general help me search for this function - ``` - -3. **Navigation between sessions**: When subagents create child sessions, use `session_child_first` (default: **\<Leader>+Down**) to enter the first child session from the parent. - -4. Once you are in a child session, use: - - `session_child_cycle` (default: **Right**) to cycle to the next child session - - `session_child_cycle_reverse` (default: **Left**) to cycle to the previous child session - - `session_parent` (default: **Up**) to return to the parent session - - This lets you switch between the main conversation and specialized subagent work. - ---- - -## Configure - -You can customize the built-in agents or create your own through configuration. Agents can be configured in two ways: - ---- - -### JSON - -Configure agents in your `opencode.json` config file: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "mode": "primary", - "model": "anthropic/claude-sonnet-4-20250514", - "prompt": "{file:./prompts/build.txt}", - "permission": { - "edit": "allow", - "bash": "allow" - } - }, - "plan": { - "mode": "primary", - "model": "anthropic/claude-haiku-4-20250514", - "permission": { - "edit": "deny", - "bash": "deny" - } - }, - "code-reviewer": { - "description": "Reviews code for best practices and potential issues", - "mode": "subagent", - "model": "anthropic/claude-sonnet-4-20250514", - "prompt": "You are a code reviewer. Focus on security, performance, and maintainability.", - "permission": { - "edit": "deny" - } - } - } -} -``` - ---- - -### Markdown - -You can also define agents using markdown files. Place them in: - -- Global: `~/.config/opencode/agents/` -- Per-project: `.opencode/agents/` - -```markdown title="~/.config/opencode/agents/review.md" ---- -description: Reviews code for quality and best practices -mode: subagent -model: anthropic/claude-sonnet-4-20250514 -temperature: 0.1 -permission: - edit: deny - bash: deny ---- - -You are in code review mode. Focus on: - -- Code quality and best practices -- Potential bugs and edge cases -- Performance implications -- Security considerations - -Provide constructive feedback without making direct changes. -``` - -The markdown file name becomes the agent name. For example, `review.md` creates a `review` agent. - ---- - -## Options - -Let's look at these configuration options in detail. - ---- - -### Description - -Use the `description` option to provide a brief description of what the agent does and when to use it. - -```json title="opencode.json" -{ - "agent": { - "review": { - "description": "Reviews code for best practices and potential issues" - } - } -} -``` - -This is a **required** config option. - ---- - -### Temperature - -Control the randomness and creativity of the LLM's responses with the `temperature` config. - -Lower values make responses more focused and deterministic, while higher values increase creativity and variability. - -```json title="opencode.json" -{ - "agent": { - "plan": { - "temperature": 0.1 - }, - "creative": { - "temperature": 0.8 - } - } -} -``` - -Temperature values typically range from 0.0 to 1.0: - -- **0.0-0.2**: Very focused and deterministic responses, ideal for code analysis and planning -- **0.3-0.5**: Balanced responses with some creativity, good for general development tasks -- **0.6-1.0**: More creative and varied responses, useful for brainstorming and exploration - -```json title="opencode.json" -{ - "agent": { - "analyze": { - "temperature": 0.1, - "prompt": "{file:./prompts/analysis.txt}" - }, - "build": { - "temperature": 0.3 - }, - "brainstorm": { - "temperature": 0.7, - "prompt": "{file:./prompts/creative.txt}" - } - } -} -``` - -If no temperature is specified, OpenCode uses model-specific defaults; typically 0 for most models, 0.55 for Qwen models. - ---- - -### Max steps - -Control the maximum number of agentic iterations an agent can perform before being forced to respond with text only. This allows users who wish to control costs to set a limit on agentic actions. - -If this is not set, the agent will continue to iterate until the model chooses to stop or the user interrupts the session. - -```json title="opencode.json" -{ - "agent": { - "quick-thinker": { - "description": "Fast reasoning with limited iterations", - "prompt": "You are a quick thinker. Solve problems with minimal steps.", - "steps": 5 - } - } -} -``` - -When the limit is reached, the agent receives a special system prompt instructing it to respond with a summarization of its work and recommended remaining tasks. - -:::caution -The legacy `maxSteps` field is deprecated. Use `steps` instead. -::: - ---- - -### Disable - -Set to `true` to disable the agent. - -```json title="opencode.json" -{ - "agent": { - "review": { - "disable": true - } - } -} -``` - ---- - -### Prompt - -Specify a custom system prompt file for this agent with the `prompt` config. The prompt file should contain instructions specific to the agent's purpose. - -```json title="opencode.json" -{ - "agent": { - "review": { - "prompt": "{file:./prompts/code-review.txt}" - } - } -} -``` - -This path is relative to where the config file is located. So this works for both the global OpenCode config and the project specific config. - ---- - -### Model - -Use the `model` config to override the model for this agent. Useful for using different models optimized for different tasks. For example, a faster model for planning, a more capable model for implementation. - -:::tip -If you don’t specify a model, primary agents use the [model globally configured](/docs/config#models) while subagents will use the model of the primary agent that invoked the subagent. -::: - -```json title="opencode.json" -{ - "agent": { - "plan": { - "model": "anthropic/claude-haiku-4-20250514" - } - } -} -``` - -The model ID in your OpenCode config uses the format `provider/model-id`. For example, if you're using [OpenCode Zen](/docs/zen), you would use `opencode/gpt-5.1-codex` for GPT 5.1 Codex. - ---- - -### Tools (deprecated) - -`tools` is **deprecated**. Prefer the agent's [`permission`](#permissions) field for new configs, updates and more fine-grained control. - -Allows you to control which tools are available in this agent. You can enable or disable specific tools by setting them to `true` or `false`. In an agent's `tools` config, `true` is equivalent to `{"*": "allow"}` permission and `false` is equivalent to `{"*": "deny"}` permission. - -```json title="opencode.json" {3-6,9-12} -{ - "$schema": "https://opencode.ai/config.json", - "tools": { - "write": true, - "bash": true - }, - "agent": { - "plan": { - "tools": { - "write": false, - "bash": false - } - } - } -} -``` - -:::note -The agent-specific config overrides the global config. -::: - -You can also use wildcards in legacy `tools` entries to control multiple tools at once. For example, to disable all tools from an MCP server: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "readonly": { - "tools": { - "mymcp_*": false, - "write": false, - "edit": false - } - } - } -} -``` - -[Learn more about tools](/docs/tools). - ---- - -### Permissions - -You can configure permissions to manage what actions an agent can take. Each permission key can be set to: - -- `"ask"` — Prompt for approval before running the tool -- `"allow"` — Allow all operations without approval -- `"deny"` — Disable the tool - -The available permission keys are: - -| Key | Tools it gates | -| -------------------- | ---------------------------------------------------------------- | -| `read` | `read` | -| `edit` | `write`, `edit`, `apply_patch` | -| `glob` | `glob` | -| `grep` | `grep` | -| `list` | `list` | -| `bash` | `bash` | -| `task` | `task` | -| `external_directory` | Any tool that reads or writes files outside the project worktree | -| `todowrite` | `todowrite`, `todoread` | -| `webfetch` | `webfetch` | -| `websearch` | `websearch` | -| `lsp` | `lsp` | -| `skill` | `skill` | -| `question` | `question` | -| `doom_loop` | Recovery prompts when an agent appears stuck | - -`read`, `edit`, `glob`, `grep`, `list`, `bash`, `task`, `external_directory`, `lsp`, and `skill` accept either a shorthand action (`"allow" | "ask" | "deny"`) or an object of glob/pattern → action for fine-grained control. The remaining keys accept the shorthand action only. - -:::note -Permission keys are matched as wildcard patterns against the underlying tool name, so the same syntax works for built-ins, custom tools, and MCP tools — for example `"mymcp_*": "deny"` denies every tool from an MCP server, and `"mymcp_search": "ask"` targets a single one. -::: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "deny" - } -} -``` - -You can override these permissions per agent. - -```json title="opencode.json" {3-5,8-10} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "deny" - }, - "agent": { - "build": { - "permission": { - "edit": "ask" - } - } - } -} -``` - -You can also set permissions in Markdown agents. - -```markdown title="~/.config/opencode/agents/review.md" ---- -description: Code review without edits -mode: subagent -permission: - edit: deny - bash: - "*": ask - "git diff": allow - "git log*": allow - "grep *": allow - webfetch: deny ---- - -Only analyze code and suggest changes. -``` - -You can set permissions for specific bash commands. - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "permission": { - "bash": { - "git push": "ask", - "grep *": "allow" - } - } - } - } -} -``` - -This can take a glob pattern. - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "permission": { - "bash": { - "git *": "ask" - } - } - } - } -} -``` - -And you can also use the `*` wildcard to manage permissions for all commands. -Since the last matching rule takes precedence, put the `*` wildcard first and specific rules after. - -```json title="opencode.json" {8} -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "build": { - "permission": { - "bash": { - "*": "ask", - "git status *": "allow" - } - } - } - } -} -``` - -[Learn more about permissions](/docs/permissions). - ---- - -### Mode - -Control the agent's mode with the `mode` config. The `mode` option is used to determine how the agent can be used. - -```json title="opencode.json" -{ - "agent": { - "review": { - "mode": "subagent" - } - } -} -``` - -The `mode` option can be set to `primary`, `subagent`, or `all`. If no `mode` is specified, it defaults to `all`. - ---- - -### Hidden - -Hide a subagent from the `@` autocomplete menu with `hidden: true`. Useful for internal subagents that should only be invoked programmatically by other agents via the Task tool. - -```json title="opencode.json" -{ - "agent": { - "internal-helper": { - "mode": "subagent", - "hidden": true - } - } -} -``` - -This only affects user visibility in the autocomplete menu. Hidden agents can still be invoked by the model via the Task tool if permissions allow. - -:::note -Only applies to `mode: subagent` agents. -::: - ---- - -### Task permissions - -Control which subagents an agent can invoke via the Task tool with `permission.task`. Uses glob patterns for flexible matching. - -```json title="opencode.json" -{ - "agent": { - "orchestrator": { - "mode": "primary", - "permission": { - "task": { - "*": "deny", - "orchestrator-*": "allow", - "code-reviewer": "ask" - } - } - } - } -} -``` - -When set to `deny`, the subagent is removed from the Task tool description entirely, so the model won't attempt to invoke it. - -:::tip -Rules are evaluated in order, and the **last matching rule wins**. In the example above, `orchestrator-planner` matches both `*` (deny) and `orchestrator-*` (allow), but since `orchestrator-*` comes after `*`, the result is `allow`. -::: - -:::tip -Users can always invoke any subagent directly via the `@` autocomplete menu, even if the agent's task permissions would deny it. -::: - ---- - -### Color - -Customize the agent's visual appearance in the UI with the `color` option. This affects how the agent appears in the interface. - -Use a valid hex color (e.g., `#FF5733`) or theme color: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`. - -```json title="opencode.json" -{ - "agent": { - "creative": { - "color": "#ff6b6b" - }, - "code-reviewer": { - "color": "accent" - } - } -} -``` - ---- - -### Top P - -Control response diversity with the `top_p` option. Alternative to temperature for controlling randomness. - -```json title="opencode.json" -{ - "agent": { - "brainstorm": { - "top_p": 0.9 - } - } -} -``` - -Values range from 0.0 to 1.0. Lower values are more focused, higher values more diverse. - ---- - -### Additional - -Any other options you specify in your agent configuration will be **passed through directly** to the provider as model options. This allows you to use provider-specific features and parameters. - -For example, with OpenAI's reasoning models, you can control the reasoning effort: - -```json title="opencode.json" {6,7} -{ - "agent": { - "deep-thinker": { - "description": "Agent that uses high reasoning effort for complex problems", - "model": "openai/gpt-5", - "reasoningEffort": "high", - "textVerbosity": "low" - } - } -} -``` - -These additional options are model and provider-specific. Check your provider's documentation for available parameters. - -:::tip -Run `opencode models` to see a list of the available models. -::: - ---- - -## Create agents - -You can create new agents using the following command: - -```bash -opencode agent create -``` - -This interactive command will: - -1. Ask where to save the agent; global or project-specific. -2. Description of what the agent should do. -3. Generate an appropriate system prompt and identifier. -4. Let you select which permissions the agent should be allowed (anything you don't select is denied). -5. Finally, create a markdown file with the agent configuration. - ---- - -## Use cases - -Here are some common use cases for different agents. - -- **Build agent**: Full development work with all tools enabled -- **Plan agent**: Analysis and planning without making changes -- **Review agent**: Code review with read-only access plus documentation tools -- **Debug agent**: Focused on investigation with bash and read tools enabled -- **Docs agent**: Documentation writing with file operations but no system commands - ---- - -## Examples - -Here are some example agents you might find useful. - -:::tip -Do you have an agent you'd like to share? [Submit a PR](https://github.com/anomalyco/opencode). -::: - ---- - -### Documentation agent - -```markdown title="~/.config/opencode/agents/docs-writer.md" ---- -description: Writes and maintains project documentation -mode: subagent -permission: - bash: deny ---- - -You are a technical writer. Create clear, comprehensive documentation. - -Focus on: - -- Clear explanations -- Proper structure -- Code examples -- User-friendly language -``` - ---- - -### Security auditor - -```markdown title="~/.config/opencode/agents/security-auditor.md" ---- -description: Performs security audits and identifies vulnerabilities -mode: subagent -permission: - edit: deny ---- - -You are a security expert. Focus on identifying potential security issues. - -Look for: - -- Input validation vulnerabilities -- Authentication and authorization flaws -- Data exposure risks -- Dependency vulnerabilities -- Configuration security issues -``` diff --git a/.opencode/skills/opencode-docs/docs/cli.mdx b/.opencode/skills/opencode-docs/docs/cli.mdx deleted file mode 100644 index 94d9ba3..0000000 --- a/.opencode/skills/opencode-docs/docs/cli.mdx +++ /dev/null @@ -1,733 +0,0 @@ ---- -title: CLI -description: OpenCode CLI options and commands. ---- - -import { Tabs, TabItem } from "@astrojs/starlight/components" - -The OpenCode CLI by default starts the [TUI](/docs/tui) when run without any arguments. - -```bash -opencode -``` - -But it also accepts commands as documented on this page. This allows you to interact with OpenCode programmatically. - -```bash -opencode run "Explain how closures work in JavaScript" -``` - ---- - -### tui - -Start the OpenCode terminal user interface. - -```bash -opencode [project] -``` - -#### Flags - -| Flag | Short | Description | -| ------------------------------------------- | ----- | ----------------------------------------------------------------------- | -| <nobr><code>{"--continue"}</code></nobr> | `-c` | Continue the last session | -| <nobr><code>{"--session"}</code></nobr> | `-s` | Session ID to continue | -| <nobr><code>{"--fork"}</code></nobr> | | Fork the session when continuing (use with `--continue` or `--session`) | -| <nobr><code>{"--prompt"}</code></nobr> | | Prompt to use | -| <nobr><code>{"--model"}</code></nobr> | `-m` | Model to use in the form of provider/model | -| <nobr><code>{"--agent"}</code></nobr> | | Agent to use | -| <nobr><code>{"--auto"}</code></nobr> | | Auto-approve permissions that are not explicitly denied | -| <nobr><code>{"--port"}</code></nobr> | | Port to listen on | -| <nobr><code>{"--hostname"}</code></nobr> | | Hostname to listen on | -| <nobr><code>{"--mdns"}</code></nobr> | | Enable mDNS discovery | -| <nobr><code>{"--mdns-domain"}</code></nobr> | | Custom mDNS domain name | -| <nobr><code>{"--cors"}</code></nobr> | | Additional browser origin(s) to allow CORS | - ---- - -## Commands - -The OpenCode CLI also has the following commands. - ---- - -### agent - -Manage agents for OpenCode. - -```bash -opencode agent [command] -``` - ---- - -#### create - -Create a new agent with custom configuration. - -```bash -opencode agent create -``` - -This command will guide you through creating a new agent with a custom system prompt and permission configuration. Anything you don't allow is denied in the generated agent's frontmatter. - -#### Flags - -| Flag | Short | Description | -| ------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| <nobr><code>{"--path"}</code></nobr> | | Directory to write the agent file to (defaults to global or `.opencode/agent` based on the prompt) | -| <nobr><code>{"--description"}</code></nobr> | | What the agent should do | -| <nobr><code>{"--mode"}</code></nobr> | | Agent mode: `all`, `primary`, or `subagent` | -| <nobr><code>{"--permissions"}</code></nobr> | | Comma-separated list of permissions to allow (default: all). Available: `bash`, `read`, `edit`, `glob`, `grep`, `webfetch`, `task`, `todowrite`, `websearch`, `lsp`, `skill`. Anything omitted is denied. Alias: `--tools` | -| <nobr><code>{"--model"}</code></nobr> | `-m` | Model to use, in `provider/model` format | - -Passing all of `--path`, `--description`, `--mode`, and `--permissions` runs the command non-interactively. - ---- - -#### list - -List all available agents. - -```bash -opencode agent list -``` - ---- - -### attach - -Attach a terminal to an already running OpenCode backend server started via `serve` or `web` commands. - -```bash -opencode attach [url] -``` - -This allows using the TUI with a remote OpenCode backend. For example: - -```bash -# Start the backend server for web/mobile access -opencode web --port 4096 --hostname 0.0.0.0 - -# In another terminal, attach the TUI to the running backend -opencode attach http://10.20.30.40:4096 -``` - -#### Flags - -| Flag | Short | Description | -| ---------------------------------------- | ----- | -------------------------------------------------------------------------- | -| <nobr><code>{"--dir"}</code></nobr> | | Working directory to start TUI in | -| <nobr><code>{"--continue"}</code></nobr> | `-c` | Continue the last session | -| <nobr><code>{"--session"}</code></nobr> | `-s` | Session ID to continue | -| <nobr><code>{"--fork"}</code></nobr> | | Fork the session when continuing (use with `--continue` or `--session`) | -| <nobr><code>{"--password"}</code></nobr> | `-p` | Basic auth password (defaults to `OPENCODE_SERVER_PASSWORD`) | -| <nobr><code>{"--username"}</code></nobr> | `-u` | Basic auth username (defaults to `OPENCODE_SERVER_USERNAME` or `opencode`) | - ---- - -### auth - -Command to manage credentials and login for providers. - -```bash -opencode auth [command] -``` - ---- - -#### login - -OpenCode is powered by the provider list at [Models.dev](https://models.dev), so you can use `opencode auth login` to configure API keys for any provider you'd like to use. This is stored in `~/.local/share/opencode/auth.json`. - -```bash -opencode auth login -``` - -When OpenCode starts up it loads the providers from the credentials file. And if there are any keys defined in your environments or a `.env` file in your project. - -##### Flags - -| Flag | Short | Description | -| ---------------------------------------- | ----- | ---------------------------------------------------- | -| <nobr><code>{"--provider"}</code></nobr> | `-p` | Provider ID or name to log in to | -| <nobr><code>{"--method"}</code></nobr> | `-m` | Login method label to use, skipping method selection | - ---- - -#### list - -Lists all the authenticated providers as stored in the credentials file. - -```bash -opencode auth list -``` - -Or the short version. - -```bash -opencode auth ls -``` - ---- - -#### logout - -Logs you out of a provider by clearing it from the credentials file. - -```bash -opencode auth logout -``` - ---- - -### github - -Manage the GitHub agent for repository automation. - -```bash -opencode github [command] -``` - ---- - -#### install - -Install the GitHub agent in your repository. - -```bash -opencode github install -``` - -This sets up the necessary GitHub Actions workflow and guides you through the configuration process. [Learn more](/docs/github). - ---- - -#### run - -Run the GitHub agent. This is typically used in GitHub Actions. - -```bash -opencode github run -``` - -##### Flags - -| Flag | Description | -| ------------------------------------- | -------------------------------------- | -| <nobr><code>{"--event"}</code></nobr> | GitHub mock event to run the agent for | -| <nobr><code>{"--token"}</code></nobr> | GitHub personal access token | - ---- - -### mcp - -Manage Model Context Protocol servers. - -```bash -opencode mcp [command] -``` - ---- - -#### add - -Add an MCP server to your configuration. - -```bash -opencode mcp add -``` - -This command will guide you through adding either a local or remote MCP server. - ---- - -#### list - -List all configured MCP servers and their connection status. - -```bash -opencode mcp list -``` - -Or use the short version. - -```bash -opencode mcp ls -``` - ---- - -#### auth - -Authenticate with an OAuth-enabled MCP server. - -```bash -opencode mcp auth [name] -``` - -If you don't provide a server name, you'll be prompted to select from available OAuth-capable servers. - -You can also list OAuth-capable servers and their authentication status. - -```bash -opencode mcp auth list -``` - -Or use the short version. - -```bash -opencode mcp auth ls -``` - ---- - -#### logout - -Remove OAuth credentials for an MCP server. - -```bash -opencode mcp logout [name] -``` - ---- - -#### debug - -Debug OAuth connection issues for an MCP server. - -```bash -opencode mcp debug <name> -``` - ---- - -### models - -List all available models from configured providers. - -```bash -opencode models [provider] -``` - -This command displays all models available across your configured providers in the format `provider/model`. - -This is useful for figuring out the exact model name to use in [your config](/docs/config/). - -You can optionally pass a provider ID to filter models by that provider. - -```bash -opencode models anthropic -``` - -#### Flags - -| Flag | Description | -| --------------------------------------- | ------------------------------------------------------------ | -| <nobr><code>{"--refresh"}</code></nobr> | Refresh the models cache from models.dev | -| <nobr><code>{"--verbose"}</code></nobr> | Use more verbose model output (includes metadata like costs) | - -Use the `--refresh` flag to update the cached model list. This is useful when new models have been added to a provider and you want to see them in OpenCode. - -```bash -opencode models --refresh -``` - ---- - -### run - -Run opencode in non-interactive mode by passing a prompt directly. - -```bash -opencode run [message..] -``` - -This is useful for scripting, automation, or when you want a quick answer without launching the full TUI. For example. - -```bash "opencode run" -opencode run Explain the use of context in Go -``` - -You can also attach to a running `opencode serve` instance to avoid MCP server cold boot times on every run: - -```bash -# Start a headless server in one terminal -opencode serve - -# In another terminal, run commands that attach to it -opencode run --attach http://localhost:4096 "Explain async/await in JavaScript" -``` - -#### Flags - -| Flag | Short | Description | -| ---------------------------------------- | ----- | -------------------------------------------------------------------------- | -| <nobr><code>{"--command"}</code></nobr> | | The command to run, use message for args | -| <nobr><code>{"--continue"}</code></nobr> | `-c` | Continue the last session | -| <nobr><code>{"--session"}</code></nobr> | `-s` | Session ID to continue | -| <nobr><code>{"--fork"}</code></nobr> | | Fork the session when continuing (use with `--continue` or `--session`) | -| <nobr><code>{"--share"}</code></nobr> | | Share the session | -| <nobr><code>{"--model"}</code></nobr> | `-m` | Model to use in the form of provider/model | -| <nobr><code>{"--agent"}</code></nobr> | | Agent to use | -| <nobr><code>{"--file"}</code></nobr> | `-f` | File(s) to attach to message | -| <nobr><code>{"--format"}</code></nobr> | | Format: default (formatted) or json (raw JSON events) | -| <nobr><code>{"--title"}</code></nobr> | | Title for the session (uses truncated prompt if no value provided) | -| <nobr><code>{"--attach"}</code></nobr> | | Attach to a running opencode server (e.g., http://localhost:4096) | -| <nobr><code>{"--password"}</code></nobr> | `-p` | Basic auth password (defaults to `OPENCODE_SERVER_PASSWORD`) | -| <nobr><code>{"--username"}</code></nobr> | `-u` | Basic auth username (defaults to `OPENCODE_SERVER_USERNAME` or `opencode`) | -| <nobr><code>{"--dir"}</code></nobr> | | Directory to run in, or path on the remote server when attaching | -| <nobr><code>{"--port"}</code></nobr> | | Port for the local server (defaults to random port) | -| <nobr><code>{"--variant"}</code></nobr> | | Model variant (provider-specific reasoning effort) | -| <nobr><code>{"--thinking"}</code></nobr> | | Show thinking blocks | -| <nobr><code>{"--auto"}</code></nobr> | | Auto-approve permissions that are not explicitly denied | - ---- - -### serve - -Start a headless OpenCode server for API access. Check out the [server docs](/docs/server) for the full HTTP interface. - -```bash -opencode serve -``` - -This starts an HTTP server that provides API access to opencode functionality without the TUI interface. Set `OPENCODE_SERVER_PASSWORD` to enable HTTP basic auth (username defaults to `opencode`). - -#### Flags - -| Flag | Description | -| ------------------------------------------- | ------------------------------------------ | -| <nobr><code>{"--port"}</code></nobr> | Port to listen on | -| <nobr><code>{"--hostname"}</code></nobr> | Hostname to listen on | -| <nobr><code>{"--mdns"}</code></nobr> | Enable mDNS discovery | -| <nobr><code>{"--mdns-domain"}</code></nobr> | Custom mDNS domain name | -| <nobr><code>{"--cors"}</code></nobr> | Additional browser origin(s) to allow CORS | - ---- - -### session - -Manage OpenCode sessions. - -```bash -opencode session [command] -``` - ---- - -#### list - -List all OpenCode sessions. - -```bash -opencode session list -``` - -##### Flags - -| Flag | Short | Description | -| ----------------------------------------- | ----- | ------------------------------------ | -| <nobr><code>{"--max-count"}</code></nobr> | `-n` | Limit to N most recent sessions | -| <nobr><code>{"--format"}</code></nobr> | | Output format: table or json (table) | - ---- - -#### delete - -Delete an OpenCode session. - -```bash -opencode session delete <sessionID> -``` - ---- - -### stats - -Show token usage and cost statistics for your OpenCode sessions. - -```bash -opencode stats -``` - -#### Flags - -| Flag | Description | -| --------------------------------------- | --------------------------------------------------------------------------- | -| <nobr><code>{"--days"}</code></nobr> | Show stats for the last N days (all time) | -| <nobr><code>{"--tools"}</code></nobr> | Number of tools to show (all) | -| <nobr><code>{"--models"}</code></nobr> | Show model usage breakdown (hidden by default). Pass a number to show top N | -| <nobr><code>{"--project"}</code></nobr> | Filter by project (all projects, empty string: current project) | - ---- - -### export - -Export session data as JSON. - -```bash -opencode export [sessionID] -``` - -If you don't provide a session ID, you'll be prompted to select from available sessions. - -#### Flags - -| Flag | Description | -| ---------------------------------------- | ------------------------------------- | -| <nobr><code>{"--sanitize"}</code></nobr> | Redact sensitive transcript/file data | - ---- - -### import - -Import session data from a JSON file or OpenCode share URL. - -```bash -opencode import <file> -``` - -You can import from a local file or an OpenCode share URL. - -```bash -opencode import session.json -opencode import https://opncd.ai/s/abc123 -``` - ---- - -### web - -Start a headless OpenCode server with a web interface. - -```bash -opencode web -``` - -This starts an HTTP server and opens a web browser to access OpenCode through a web interface. Set `OPENCODE_SERVER_PASSWORD` to enable HTTP basic auth (username defaults to `opencode`). - -#### Flags - -| Flag | Description | -| ------------------------------------------- | ------------------------------------------ | -| <nobr><code>{"--port"}</code></nobr> | Port to listen on | -| <nobr><code>{"--hostname"}</code></nobr> | Hostname to listen on | -| <nobr><code>{"--mdns"}</code></nobr> | Enable mDNS discovery | -| <nobr><code>{"--mdns-domain"}</code></nobr> | Custom mDNS domain name | -| <nobr><code>{"--cors"}</code></nobr> | Additional browser origin(s) to allow CORS | - ---- - -### acp - -Start an ACP (Agent Client Protocol) server. - -```bash -opencode acp -``` - -This command starts an ACP server that communicates via stdin/stdout using nd-JSON. - -#### Flags - -| Flag | Description | -| ------------------------------------------- | ------------------------------------------ | -| <nobr><code>{"--cwd"}</code></nobr> | Working directory | -| <nobr><code>{"--port"}</code></nobr> | Port to listen on | -| <nobr><code>{"--hostname"}</code></nobr> | Hostname to listen on | -| <nobr><code>{"--mdns"}</code></nobr> | Enable mDNS discovery | -| <nobr><code>{"--mdns-domain"}</code></nobr> | Custom mDNS domain name | -| <nobr><code>{"--cors"}</code></nobr> | Additional browser origin(s) to allow CORS | - ---- - -### plugin - -Install a plugin and update your config. - -```bash -opencode plugin <module> -``` - -Or use the alias. - -```bash -opencode plug <module> -``` - -#### Flags - -| Flag | Short | Description | -| -------------------------------------- | ----- | ------------------------------- | -| <nobr><code>{"--global"}</code></nobr> | `-g` | Install in global config | -| <nobr><code>{"--force"}</code></nobr> | `-f` | Replace existing plugin version | - ---- - -### pr - -Fetch and checkout a GitHub PR branch, then run OpenCode. - -```bash -opencode pr <number> -``` - ---- - -### db - -Database tools. - -```bash -opencode db [query] -``` - -#### Flags - -| Flag | Description | -| -------------------------------------- | ------------------------------ | -| <nobr><code>{"--format"}</code></nobr> | Output format: `json` or `tsv` | - ---- - -#### path - -Print the database path. - -```bash -opencode db path -``` - ---- - -### debug - -Debugging and troubleshooting tools. - -```bash -opencode debug [command] -``` - ---- - -### uninstall - -Uninstall OpenCode and remove all related files. - -```bash -opencode uninstall -``` - -#### Flags - -| Flag | Short | Description | -| ------------------------------------------- | ----- | ------------------------------------------- | -| <nobr><code>{"--keep-config"}</code></nobr> | `-c` | Keep configuration files | -| <nobr><code>{"--keep-data"}</code></nobr> | `-d` | Keep session data and snapshots | -| <nobr><code>{"--dry-run"}</code></nobr> | | Show what would be removed without removing | -| <nobr><code>{"--force"}</code></nobr> | `-f` | Skip confirmation prompts | - ---- - -### upgrade - -Updates opencode to the latest version or a specific version. - -```bash -opencode upgrade [target] -``` - -To upgrade to the latest version. - -```bash -opencode upgrade -``` - -To upgrade to a specific version. - -```bash -opencode upgrade v0.1.48 -``` - -#### Flags - -| Flag | Short | Description | -| -------------------------------------- | ----- | ----------------------------------------------------------------- | -| <nobr><code>{"--method"}</code></nobr> | `-m` | The installation method that was used; curl, npm, pnpm, bun, brew | - ---- - -## Global Flags - -The opencode CLI takes the following global flags. - -| Flag | Short | Description | -| ------------------------------------------ | ----- | ------------------------------------ | -| <nobr><code>{"--help"}</code></nobr> | `-h` | Display help | -| <nobr><code>{"--version"}</code></nobr> | `-v` | Print version number | -| <nobr><code>{"--print-logs"}</code></nobr> | | Print logs to stderr | -| <nobr><code>{"--log-level"}</code></nobr> | | Log level (DEBUG, INFO, WARN, ERROR) | -| <nobr><code>{"--pure"}</code></nobr> | | Run without external plugins | - ---- - -## Environment variables - -OpenCode can be configured using environment variables. - -| Variable | Type | Description | -| ------------------------------------- | ------- | ------------------------------------------------- | -| `OPENCODE_AUTO_SHARE` | boolean | Automatically share sessions | -| `OPENCODE_GIT_BASH_PATH` | string | Path to Git Bash executable on Windows | -| `OPENCODE_CONFIG` | string | Path to config file | -| `OPENCODE_TUI_CONFIG` | string | Path to TUI config file | -| `OPENCODE_CONFIG_DIR` | string | Path to config directory | -| `OPENCODE_CONFIG_CONTENT` | string | Inline json config content | -| `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disable automatic update checks | -| `OPENCODE_DISABLE_PRUNE` | boolean | Disable pruning of old data | -| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disable automatic terminal title updates | -| `OPENCODE_PERMISSION` | string | Inlined json permissions config | -| `OPENCODE_DISABLE_DEFAULT_PLUGINS` | boolean | Disable default plugins | -| `OPENCODE_DISABLE_LSP_DOWNLOAD` | boolean | Disable automatic LSP server downloads | -| `OPENCODE_ENABLE_EXPERIMENTAL_MODELS` | boolean | Enable experimental models | -| `OPENCODE_DISABLE_AUTOCOMPACT` | boolean | Disable automatic context compaction | -| `OPENCODE_DISABLE_CLAUDE_CODE` | boolean | Disable reading from `.claude` (prompt + skills) | -| `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT` | boolean | Disable reading `~/.claude/CLAUDE.md` | -| `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | boolean | Disable loading `.claude/skills` | -| `OPENCODE_DISABLE_MODELS_FETCH` | boolean | Disable fetching models from remote sources | -| `OPENCODE_DISABLE_MOUSE` | boolean | Disable mouse capture in the TUI | -| `OPENCODE_FAKE_VCS` | string | Fake VCS provider for testing purposes | -| `OPENCODE_CLIENT` | string | Client identifier (defaults to `cli`) | -| `OPENCODE_ENABLE_EXA` | boolean | Enable Exa web search tools | -| `OPENCODE_SERVER_PASSWORD` | string | Enable basic auth for `serve`/`web` | -| `OPENCODE_SERVER_USERNAME` | string | Override basic auth username (default `opencode`) | -| `OPENCODE_MODELS_URL` | string | Custom URL for fetching models configuration | - ---- - -### Experimental - -These environment variables enable experimental features that may change or be removed. - -| Variable | Type | Description | -| ----------------------------------------------- | ------- | --------------------------------------- | -| `OPENCODE_EXPERIMENTAL` | boolean | Enable the experimental umbrella flag | -| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Enable icon discovery | -| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Disable copy on select in TUI | -| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Default timeout for bash commands in ms | -| `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` | number | Max output tokens for LLM responses | -| `OPENCODE_EXPERIMENTAL_FILEWATCHER` | boolean | Enable file watcher for entire dir | -| `OPENCODE_EXPERIMENTAL_OXFMT` | boolean | Enable oxfmt formatter | -| `OPENCODE_EXPERIMENTAL_LSP_TOOL` | boolean | Enable experimental LSP tool | -| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER` | boolean | Disable file watcher | -| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features | -| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable TY LSP for python files | -| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode | -| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Enable background subagent tasks | -| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Enable experimental event system | -| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Enable native LLM request path | -| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Enable parallel web search execution | -| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Enable Scout subagent | -| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Enable workspace support | diff --git a/.opencode/skills/opencode-docs/docs/commands.mdx b/.opencode/skills/opencode-docs/docs/commands.mdx deleted file mode 100644 index 1d7e4f1..0000000 --- a/.opencode/skills/opencode-docs/docs/commands.mdx +++ /dev/null @@ -1,323 +0,0 @@ ---- -title: Commands -description: Create custom commands for repetitive tasks. ---- - -Custom commands let you specify a prompt you want to run when that command is executed in the TUI. - -```bash frame="none" -/my-command -``` - -Custom commands are in addition to the built-in commands like `/init`, `/undo`, `/redo`, `/share`, `/help`. [Learn more](/docs/tui#commands). - ---- - -## Create command files - -Create markdown files in the `commands/` directory to define custom commands. - -Create `.opencode/commands/test.md`: - -```md title=".opencode/commands/test.md" ---- -description: Run tests with coverage -agent: build -model: anthropic/claude-3-5-sonnet-20241022 ---- - -Run the full test suite with coverage report and show any failures. -Focus on the failing tests and suggest fixes. -``` - -The frontmatter defines command properties. The content becomes the template. - -Use the command by typing `/` followed by the command name. - -```bash frame="none" -"/test" -``` - ---- - -## Configure - -You can add custom commands through the OpenCode config or by creating markdown files in the `commands/` directory. - ---- - -### JSON - -Use the `command` option in your OpenCode [config](/docs/config): - -```json title="opencode.jsonc" {4-12} -{ - "$schema": "https://opencode.ai/config.json", - "command": { - // This becomes the name of the command - "test": { - // This is the prompt that will be sent to the LLM - "template": "Run the full test suite with coverage report and show any failures.\nFocus on the failing tests and suggest fixes.", - // This is shown as the description in the TUI - "description": "Run tests with coverage", - "agent": "build", - "model": "anthropic/claude-3-5-sonnet-20241022" - } - } -} -``` - -Now you can run this command in the TUI: - -```bash frame="none" -/test -``` - ---- - -### Markdown - -You can also define commands using markdown files. Place them in: - -- Global: `~/.config/opencode/commands/` -- Per-project: `.opencode/commands/` - -```markdown title="~/.config/opencode/commands/test.md" ---- -description: Run tests with coverage -agent: build -model: anthropic/claude-3-5-sonnet-20241022 ---- - -Run the full test suite with coverage report and show any failures. -Focus on the failing tests and suggest fixes. -``` - -The markdown file name becomes the command name. For example, `test.md` lets -you run: - -```bash frame="none" -/test -``` - ---- - -## Prompt config - -The prompts for the custom commands support several special placeholders and syntax. - ---- - -### Arguments - -Pass arguments to commands using the `$ARGUMENTS` placeholder. - -```md title=".opencode/commands/component.md" ---- -description: Create a new component ---- - -Create a new React component named $ARGUMENTS with TypeScript support. -Include proper typing and basic structure. -``` - -Run the command with arguments: - -```bash frame="none" -/component Button -``` - -And `$ARGUMENTS` will be replaced with `Button`. - -You can also access individual arguments using positional parameters: - -- `$1` - First argument -- `$2` - Second argument -- `$3` - Third argument -- And so on... - -For example: - -```md title=".opencode/commands/create-file.md" ---- -description: Create a new file with content ---- - -Create a file named $1 in the directory $2 -with the following content: $3 -``` - -Run the command: - -```bash frame="none" -/create-file config.json src "{ \"key\": \"value\" }" -``` - -This replaces: - -- `$1` with `config.json` -- `$2` with `src` -- `$3` with `{ "key": "value" }` - ---- - -### Shell output - -Use _!`command`_ to inject [bash command](/docs/tui#bash-commands) output into your prompt. - -For example, to create a custom command that analyzes test coverage: - -```md title=".opencode/commands/analyze-coverage.md" ---- -description: Analyze test coverage ---- - -Here are the current test results: -!`npm test` - -Based on these results, suggest improvements to increase coverage. -``` - -Or to review recent changes: - -```md title=".opencode/commands/review-changes.md" ---- -description: Review recent changes ---- - -Recent git commits: -!`git log --oneline -10` - -Review these changes and suggest any improvements. -``` - -Commands run in your project's root directory and their output becomes part of the prompt. - ---- - -### File references - -Include files in your command using `@` followed by the filename. - -```md title=".opencode/commands/review-component.md" ---- -description: Review component ---- - -Review the component in @src/components/Button.tsx. -Check for performance issues and suggest improvements. -``` - -The file content gets included in the prompt automatically. - ---- - -## Options - -Let's look at the configuration options in detail. - ---- - -### Template - -The `template` option defines the prompt that will be sent to the LLM when the command is executed. - -```json title="opencode.json" -{ - "command": { - "test": { - "template": "Run the full test suite with coverage report and show any failures.\nFocus on the failing tests and suggest fixes." - } - } -} -``` - -This is a **required** config option. - ---- - -### Description - -Use the `description` option to provide a brief description of what the command does. - -```json title="opencode.json" -{ - "command": { - "test": { - "description": "Run tests with coverage" - } - } -} -``` - -This is shown as the description in the TUI when you type in the command. - ---- - -### Agent - -Use the `agent` config to optionally specify which [agent](/docs/agents) should execute this command. -If this is a [subagent](/docs/agents/#subagents) the command will trigger a subagent invocation by default. -To disable this behavior, set `subtask` to `false`. - -```json title="opencode.json" -{ - "command": { - "review": { - "agent": "plan" - } - } -} -``` - -This is an **optional** config option. If not specified, defaults to your current agent. - ---- - -### Subtask - -Use the `subtask` boolean to force the command to trigger a [subagent](/docs/agents/#subagents) invocation. -This is useful if you want the command to not pollute your primary context and will **force** the agent to act as a subagent, -even if `mode` is set to `primary` on the [agent](/docs/agents) configuration. - -```json title="opencode.json" -{ - "command": { - "analyze": { - "subtask": true - } - } -} -``` - -This is an **optional** config option. - ---- - -### Model - -Use the `model` config to override the default model for this command. - -```json title="opencode.json" -{ - "command": { - "analyze": { - "model": "anthropic/claude-3-5-sonnet-20241022" - } - } -} -``` - -This is an **optional** config option. - ---- - -## Built-in - -opencode includes several built-in commands like `/init`, `/undo`, `/redo`, `/share`, `/help`; [learn more](/docs/tui#commands). - -:::note -Custom commands can override built-in commands. -::: - -If you define a custom command with the same name, it will override the built-in command. diff --git a/.opencode/skills/opencode-docs/docs/config.mdx b/.opencode/skills/opencode-docs/docs/config.mdx deleted file mode 100644 index c1a69f5..0000000 --- a/.opencode/skills/opencode-docs/docs/config.mdx +++ /dev/null @@ -1,926 +0,0 @@ ---- -title: Config -description: Using the OpenCode JSON config. ---- - -You can configure OpenCode using a JSON config file. - ---- - -## Format - -OpenCode supports both **JSON** and **JSONC** (JSON with Comments) formats. - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "model": "anthropic/claude-sonnet-4-5", - "autoupdate": true, - "server": { - "port": 4096, - }, -} -``` - ---- - -## Locations - -You can place your config in a couple of different locations and they have a -different order of precedence. - -:::note -Configuration files are **merged together**, not replaced. -::: - -Configuration files are merged together, not replaced. Settings from the following config locations are combined. Later configs override earlier ones only for conflicting keys. Non-conflicting settings from all configs are preserved. - -For example, if your global config sets `autoupdate: true` and your project config sets `model: "anthropic/claude-sonnet-4-5"`, the final configuration will include both settings. - ---- - -### Precedence order - -Config sources are loaded in this order (later sources override earlier ones): - -1. **Remote config** (from `.well-known/opencode`) - organizational defaults -2. **Global config** (`~/.config/opencode/opencode.json`) - user preferences -3. **Custom config** (`OPENCODE_CONFIG` env var) - custom overrides -4. **Project config** (`opencode.json` in project) - project-specific settings -5. **`.opencode` directories** - agents, commands, plugins -6. **Inline config** (`OPENCODE_CONFIG_CONTENT` env var) - runtime overrides -7. **Managed config files** (`/Library/Application Support/opencode/` on macOS) - admin-controlled -8. **macOS managed preferences** (`.mobileconfig` via MDM) - highest priority, not user-overridable - -This means project configs can override global defaults, and global configs can override remote organizational defaults. Managed settings override everything. - -:::note -The `.opencode` and `~/.config/opencode` directories use **plural names** for subdirectories: `agents/`, `commands/`, `modes/`, `plugins/`, `skills/`, `tools/`, and `themes/`. Singular names (e.g., `agent/`) are also supported for backwards compatibility. -::: - ---- - -### Remote - -Organizations can provide default configuration via the `.well-known/opencode` endpoint. This is fetched automatically when you authenticate with a provider that supports it. - -Remote config is loaded first, serving as the base layer. All other config sources (global, project) can override these defaults. - -For example, if your organization provides MCP servers that are disabled by default: - -```json title="Remote config from .well-known/opencode" -{ - "mcp": { - "jira": { - "type": "remote", - "url": "https://jira.example.com/mcp", - "enabled": false - } - } -} -``` - -You can enable specific servers in your local config: - -```json title="opencode.json" -{ - "mcp": { - "jira": { - "type": "remote", - "url": "https://jira.example.com/mcp", - "enabled": true - } - } -} -``` - ---- - -### Global - -Place your global OpenCode config in `~/.config/opencode/opencode.json`. Use global config for user-wide server/runtime preferences like providers, models, and permissions. - -For TUI-specific settings, use `~/.config/opencode/tui.json`. - -Global config overrides remote organizational defaults. - ---- - -### Per project - -Add `opencode.json` in your project root. Project config has the highest precedence among standard config files - it overrides both global and remote configs. - -For project-specific TUI settings, add `tui.json` alongside it. - -:::tip -Place project specific config in the root of your project. -::: - -When OpenCode starts up, it first looks for a config file in the current directory, then traverses up to the nearest Git directory. - -This is also safe to be checked into Git and uses the same schema as the global one. - ---- - -### Custom path - -Specify a custom config file path using the `OPENCODE_CONFIG` environment variable. - -```bash -export OPENCODE_CONFIG=/path/to/my/custom-config.json -opencode run "Hello world" -``` - -Custom config is loaded between global and project configs in the precedence order. - ---- - -### Custom directory - -Specify a custom config directory using the `OPENCODE_CONFIG_DIR` -environment variable. This directory will be searched for agents, commands, -modes, and plugins just like the standard `.opencode` directory, and should -follow the same structure. - -```bash -export OPENCODE_CONFIG_DIR=/path/to/my/config-directory -opencode run "Hello world" -``` - -The custom directory is loaded after the global config and `.opencode` directories, so it **can override** their settings. - ---- - -### Managed settings - -Organizations can enforce configuration that users cannot override. Managed settings are loaded at the highest priority tier. - -#### File-based - -Drop an `opencode.json` or `opencode.jsonc` file in the system managed config directory: - -| Platform | Path | -| -------- | ---------------------------------------- | -| macOS | `/Library/Application Support/opencode/` | -| Linux | `/etc/opencode/` | -| Windows | `%ProgramData%\opencode` | - -These directories require admin/root access to write, so users cannot modify them. - -#### macOS managed preferences - -On macOS, OpenCode reads managed preferences from the `ai.opencode.managed` preference domain. Deploy a `.mobileconfig` via MDM (Jamf, Kandji, FleetDM) and the settings are enforced automatically. - -OpenCode checks these paths: - -1. `/Library/Managed Preferences/<user>/ai.opencode.managed.plist` -2. `/Library/Managed Preferences/ai.opencode.managed.plist` - -The plist keys map directly to `opencode.json` fields. MDM metadata keys (`PayloadUUID`, `PayloadType`, etc.) are stripped automatically. - -**Creating a `.mobileconfig`** - -Use the `ai.opencode.managed` PayloadType. The OpenCode config keys go directly in the payload dict: - -```xml -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" - "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>PayloadContent</key> - <array> - <dict> - <key>PayloadType</key> - <string>ai.opencode.managed</string> - <key>PayloadIdentifier</key> - <string>com.example.opencode.config</string> - <key>PayloadUUID</key> - <string>GENERATE-YOUR-OWN-UUID</string> - <key>PayloadVersion</key> - <integer>1</integer> - <key>share</key> - <string>disabled</string> - <key>server</key> - <dict> - <key>hostname</key> - <string>127.0.0.1</string> - </dict> - <key>permission</key> - <dict> - <key>*</key> - <string>ask</string> - <key>bash</key> - <dict> - <key>*</key> - <string>ask</string> - <key>rm -rf *</key> - <string>deny</string> - </dict> - </dict> - </dict> - </array> - <key>PayloadType</key> - <string>Configuration</string> - <key>PayloadIdentifier</key> - <string>com.example.opencode</string> - <key>PayloadUUID</key> - <string>GENERATE-YOUR-OWN-UUID</string> - <key>PayloadVersion</key> - <integer>1</integer> -</dict> -</plist> -``` - -Generate unique UUIDs with `uuidgen`. Customize the settings to match your organization's requirements. - -**Deploying via MDM** - -- **Jamf Pro:** Computers > Configuration Profiles > Upload > scope to target devices or smart groups -- **FleetDM:** Add the `.mobileconfig` to your gitops repo under `mdm.macos_settings.custom_settings` and run `fleetctl apply` - -**Verifying on a device** - -Double-click the `.mobileconfig` to install locally for testing (shows in System Settings > Privacy & Security > Profiles), then run: - -```bash -opencode debug config -``` - -All managed preference keys appear in the resolved config and cannot be overridden by user or project configuration. - ---- - -## Schema - -The server/runtime config schema is defined in [**`opencode.ai/config.json`**](https://opencode.ai/config.json). - -TUI config uses [**`opencode.ai/tui.json`**](https://opencode.ai/tui.json). - -Your editor should be able to validate and autocomplete based on the schema. - ---- - -### TUI - -Use a dedicated `tui.json` (or `tui.jsonc`) file for TUI-specific settings. - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto", - "mouse": true, - "attention": { - "enabled": true, - "notifications": true, - "sound": true, - "volume": 0.4 - } -} -``` - -Use `OPENCODE_TUI_CONFIG` to point to a custom TUI config file. - -Set `attention.enabled` to turn on TUI desktop notifications and sounds. See [TUI attention](/docs/tui#attention). - -Legacy `theme`, `keybinds`, and `tui` keys in `opencode.json` are deprecated and automatically migrated when possible. - ---- - -### Server - -You can configure server settings for the `opencode serve` and `opencode web` commands through the `server` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "server": { - "port": 4096, - "hostname": "0.0.0.0", - "mdns": true, - "mdnsDomain": "myproject.local", - "cors": ["http://localhost:5173"] - } -} -``` - -Available options: - -- `port` - Port to listen on. -- `hostname` - Hostname to listen on. When `mdns` is enabled and no hostname is set, defaults to `0.0.0.0`. -- `mdns` - Enable mDNS service discovery. This allows other devices on the network to discover your OpenCode server. -- `mdnsDomain` - Custom domain name for mDNS service. Defaults to `opencode.local`. Useful for running multiple instances on the same network. -- `cors` - Additional origins to allow for CORS when using the HTTP server from a browser-based client. Values must be full origins (scheme + host + optional port), eg `https://app.example.com`. - -[Learn more about the server here](/docs/server). - ---- - -### Shell - -You can configure the shell used for the interactive terminal using the `shell` option. Compatible shells are also used for agent tool calls. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "shell": "pwsh" -} -``` - -If not specified, OpenCode will automatically discover and use a sensible default based on your operating system (e.g. `pwsh` or `cmd.exe` on Windows, `/bin/zsh` or `/bin/bash` on macOS/Linux). You can provide an absolute path or a short name. - ---- - -### Tools - -You can manage the tools an LLM can use through the `tools` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "tools": { - "write": false, - "bash": false - } -} -``` - -[Learn more about tools here](/docs/tools). - ---- - -### Models - -You can configure the providers and models you want to use in your OpenCode config through the `provider`, `model` and `small_model` options. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": {}, - "model": "anthropic/claude-sonnet-4-5", - "small_model": "anthropic/claude-haiku-4-5" -} -``` - -The `small_model` option configures a separate model for lightweight tasks like title generation. By default, OpenCode tries to use a cheaper model if one is available from your provider, otherwise it falls back to your main model. - -Provider options can include `timeout`, `chunkTimeout`, and `setCacheKey`: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "anthropic": { - "options": { - "timeout": 600000, - "chunkTimeout": 30000, - "setCacheKey": true - } - } - } -} -``` - -- `timeout` - Request timeout in milliseconds (default: 300000). Set to `false` to disable. -- `chunkTimeout` - Timeout in milliseconds between streamed response chunks. If no chunk arrives in time, the request is aborted. -- `setCacheKey` - Ensure a cache key is always set for designated provider. - -You can also configure [local models](/docs/models#local). [Learn more](/docs/models). - ---- - -### Policies - -Use the `experimental.policies` option to allow or deny OpenCode actions on configured resources. Currently, policies can control which providers OpenCode may use. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "experimental": { - "policies": [ - { - "effect": "deny", - "action": "provider.use", - "resource": "openai" - } - ] - } -} -``` - -[Learn more about policies here](/docs/policies). - ---- - -### Image attachments - -OpenCode normalizes image attachments before sending them to the model. By default, images are resized when they exceed `2000x2000` pixels or `5242880` base64 bytes. - -Configure image attachment limits with the `attachment.image` option: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "attachment": { - "image": { - "auto_resize": true, - "max_width": 2000, - "max_height": 2000, - "max_base64_bytes": 5242880 - } - } -} -``` - -- `auto_resize` - Resize images that exceed the configured limits before provider requests. Set to `false` to reject oversized images instead. -- `max_width` - Maximum image width in pixels before resizing or rejection. -- `max_height` - Maximum image height in pixels before resizing or rejection. -- `max_base64_bytes` - Maximum encoded image payload size. This is the base64 payload size, not the original file size. - -If an image still cannot fit after resizing, OpenCode omits oversized tool-result images or fails oversized user-provided images with an image size error. - ---- - -#### Provider-Specific Options - -Some providers support additional configuration options beyond the generic `timeout` and `apiKey` settings. - -##### Amazon Bedrock - -Amazon Bedrock supports AWS-specific configuration: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "my-aws-profile", - "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" - } - } - } -} -``` - -- `region` - AWS region for Bedrock (defaults to `AWS_REGION` env var or `us-east-1`) -- `profile` - AWS named profile from `~/.aws/credentials` (defaults to `AWS_PROFILE` env var) -- `endpoint` - Custom endpoint URL for VPC endpoints. This is an alias for the generic `baseURL` option using AWS-specific terminology. If both are specified, `endpoint` takes precedence. - -:::note -Bearer tokens (`AWS_BEARER_TOKEN_BEDROCK` or `/connect`) take precedence over profile-based authentication. See [authentication precedence](/docs/providers#authentication-precedence) for details. -::: - -[Learn more about Amazon Bedrock configuration](/docs/providers#amazon-bedrock). - ---- - -### Themes - -Set your UI theme in `tui.json`. - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "theme": "tokyonight" -} -``` - -[Learn more here](/docs/themes). - ---- - -### Agents - -You can configure specialized agents for specific tasks through the `agent` option. - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "agent": { - "code-reviewer": { - "description": "Reviews code for best practices and potential issues", - "model": "anthropic/claude-sonnet-4-5", - "prompt": "You are a code reviewer. Focus on security, performance, and maintainability.", - "tools": { - // Disable file modification tools for review-only agent - "write": false, - "edit": false, - }, - }, - }, -} -``` - -You can also define agents using markdown files in `~/.config/opencode/agents/` or `.opencode/agents/`. [Learn more here](/docs/agents). - ---- - -### Default agent - -You can set the default agent using the `default_agent` option. This determines which agent is used when none is explicitly specified. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "default_agent": "plan" -} -``` - -The default agent must be a primary agent (not a subagent). This can be a built-in agent like `"build"` or `"plan"`, or a [custom agent](/docs/agents) you've defined. If the specified agent doesn't exist or is a subagent, OpenCode will fall back to `"build"` with a warning. - -This setting applies across all interfaces: TUI, CLI (`opencode run`), desktop app, and GitHub Action. - ---- - -### Sharing - -You can configure the [share](/docs/share) feature through the `share` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "share": "manual" -} -``` - -This takes: - -- `"manual"` - Allow manual sharing via commands (default) -- `"auto"` - Automatically share new conversations -- `"disabled"` - Disable sharing entirely - -By default, sharing is set to manual mode where you need to explicitly share conversations using the `/share` command. - ---- - -### Commands - -You can configure custom commands for repetitive tasks through the `command` option. - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "command": { - "test": { - "template": "Run the full test suite with coverage report and show any failures.\nFocus on the failing tests and suggest fixes.", - "description": "Run tests with coverage", - "agent": "build", - "model": "anthropic/claude-haiku-4-5", - }, - "component": { - "template": "Create a new React component named $ARGUMENTS with TypeScript support.\nInclude proper typing and basic structure.", - "description": "Create a new component", - }, - }, -} -``` - -You can also define commands using markdown files in `~/.config/opencode/commands/` or `.opencode/commands/`. [Learn more here](/docs/commands). - ---- - -### Keybinds - -Customize TUI keyboard shortcuts in `tui.json` with `keybinds`. - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "keybinds": { - "command_list": "ctrl+p" - } -} -``` - -`keybinds` is merged with built-in defaults, so you only need to configure the shortcuts you want to change. - -[Learn more here](/docs/keybinds). - ---- - -### Snapshot - -OpenCode uses snapshots to track file changes during agent operations, enabling you to undo and revert changes within a session. Snapshots are enabled by default. - -For large repositories or projects with many submodules, the snapshot system can cause slow indexing and significant disk usage as it tracks all changes using an internal git repository. You can disable snapshots using the `snapshot` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "snapshot": false -} -``` - -Note that disabling snapshots means changes made by the agent cannot be rolled back through the UI. - ---- - -### Autoupdate - -OpenCode will automatically download any new updates when it starts up. You can disable this with the `autoupdate` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "autoupdate": false -} -``` - -If you don't want updates but want to be notified when a new version is available, set `autoupdate` to `"notify"`. -Notice that this only works if it was not installed using a package manager such as Homebrew. - ---- - -### Formatters - -You can enable and configure code formatters through the `formatter` option. Omit it to keep formatters disabled. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "formatter": true -} -``` - -Use an object to keep built-ins enabled while configuring overrides or custom formatters. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "formatter": { - "prettier": { - "disabled": true - }, - "custom-prettier": { - "command": ["npx", "prettier", "--write", "$FILE"], - "environment": { - "NODE_ENV": "development" - }, - "extensions": [".js", ".ts", ".jsx", ".tsx"] - } - } -} -``` - -[Learn more about formatters here](/docs/formatters). - ---- - -### LSP Servers - -You can enable and configure LSP servers through the `lsp` option. Omit it to keep LSP disabled. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "lsp": true -} -``` - -Use an object to keep built-ins enabled while configuring overrides or custom LSP servers. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "lsp": { - "typescript": { - "disabled": true - } - } -} -``` - -[Learn more about LSP servers here](/docs/lsp). - ---- - -### Permissions - -By default, opencode **allows all operations** without requiring explicit approval. You can change this using the `permission` option. - -For example, to ensure that the `edit` and `bash` tools require user approval: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "ask", - "bash": "ask" - } -} -``` - -[Learn more about permissions here](/docs/permissions). - ---- - -### Compaction - -You can control context compaction behavior through the `compaction` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "compaction": { - "auto": true, - "prune": false, - "reserved": 10000 - } -} -``` - -- `auto` - Automatically compact the session when context is full (default: `true`). -- `prune` - Remove old tool outputs to save tokens (default: `false`). Set to `true` to enable pruning. -- `reserved` - Token buffer for compaction. Leaves enough window to avoid overflow during compaction. - ---- - -### Watcher - -You can configure file watcher ignore patterns through the `watcher` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "watcher": { - "ignore": ["node_modules/**", "dist/**", ".git/**"] - } -} -``` - -Patterns follow glob syntax. Use this to exclude noisy directories from file watching. - ---- - -### MCP servers - -You can configure MCP servers you want to use through the `mcp` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": {} -} -``` - -[Learn more here](/docs/mcp-servers). - ---- - -### Plugins - -[Plugins](/docs/plugins) extend OpenCode with custom tools, hooks, and integrations. - -Place plugin files in `.opencode/plugins/` or `~/.config/opencode/plugins/`. You can also load plugins from npm through the `plugin` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-helicone-session", "@my-org/custom-plugin"] -} -``` - -[Learn more here](/docs/plugins). - ---- - -### Instructions - -You can configure the instructions for the model you're using through the `instructions` option. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "instructions": ["CONTRIBUTING.md", "docs/guidelines.md", ".cursor/rules/*.md"] -} -``` - -This takes an array of paths and glob patterns to instruction files. [Learn more -about rules here](/docs/rules). - ---- - -### Disabled providers - -You can disable providers that are loaded automatically through the `disabled_providers` option. This is useful when you want to prevent certain providers from being loaded even if their credentials are available. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "disabled_providers": ["openai", "gemini"] -} -``` - -:::note -The `disabled_providers` takes priority over `enabled_providers`. -::: - -The `disabled_providers` option accepts an array of provider IDs. When a provider is disabled: - -- It won't be loaded even if environment variables are set. -- It won't be loaded even if API keys are configured through the `/connect` command. -- The provider's models won't appear in the model selection list. - ---- - -### Enabled providers - -You can specify an allowlist of providers through the `enabled_providers` option. When set, only the specified providers will be enabled and all others will be ignored. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "enabled_providers": ["anthropic", "openai"] -} -``` - -This is useful when you want to restrict OpenCode to only use specific providers rather than disabling them one by one. - -:::note -The `disabled_providers` takes priority over `enabled_providers`. -::: - -If a provider appears in both `enabled_providers` and `disabled_providers`, the `disabled_providers` takes priority for backwards compatibility. - ---- - -### Experimental - -The `experimental` key contains options that are under active development. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "experimental": {} -} -``` - -:::caution -Experimental options are not stable. They may change or be removed without notice. -::: - ---- - -## Variables - -You can use variable substitution in your config files to reference environment variables and file contents. - ---- - -### Env vars - -Use `{env:VARIABLE_NAME}` to substitute environment variables: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "model": "{env:OPENCODE_MODEL}", - "provider": { - "anthropic": { - "models": {}, - "options": { - "apiKey": "{env:ANTHROPIC_API_KEY}" - } - } - } -} -``` - -If the environment variable is not set, it will be replaced with an empty string. - ---- - -### Files - -Use `{file:path/to/file}` to substitute the contents of a file: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "instructions": ["./custom-instructions.md"], - "provider": { - "openai": { - "options": { - "apiKey": "{file:~/.secrets/openai-key}" - } - } - } -} -``` - -File paths can be: - -- Relative to the config file directory -- Or absolute paths starting with `/` or `~` - -These are useful for: - -- Keeping sensitive data like API keys in separate files. -- Including large instruction files without cluttering your config. -- Sharing common configuration snippets across multiple config files. diff --git a/.opencode/skills/opencode-docs/docs/custom-tools.mdx b/.opencode/skills/opencode-docs/docs/custom-tools.mdx deleted file mode 100644 index d89922b..0000000 --- a/.opencode/skills/opencode-docs/docs/custom-tools.mdx +++ /dev/null @@ -1,196 +0,0 @@ ---- -title: Custom Tools -description: Create tools the LLM can call in opencode. ---- - -Custom tools are functions you create that the LLM can call during conversations. They work alongside opencode's [built-in tools](/docs/tools) like `read`, `write`, and `bash`. - ---- - -## Creating a tool - -Tools are defined as **TypeScript** or **JavaScript** files. However, the tool definition can invoke scripts written in **any language** — TypeScript or JavaScript is only used for the tool definition itself. - ---- - -### Location - -They can be defined: - -- Locally by placing them in the `.opencode/tools/` directory of your project. -- Or globally, by placing them in `~/.config/opencode/tools/`. - ---- - -### Structure - -The easiest way to create tools is using the `tool()` helper which provides type-safety and validation. - -```ts title=".opencode/tools/database.ts" {1} -import { tool } from "@opencode-ai/plugin" - -export default tool({ - description: "Query the project database", - args: { - query: tool.schema.string().describe("SQL query to execute"), - }, - async execute(args) { - // Your database logic here - return `Executed query: ${args.query}` - }, -}) -``` - -The **filename** becomes the **tool name**. The above creates a `database` tool. - ---- - -#### Multiple tools per file - -You can also export multiple tools from a single file. Each export becomes **a separate tool** with the name **`<filename>_<exportname>`**: - -```ts title=".opencode/tools/math.ts" -import { tool } from "@opencode-ai/plugin" - -export const add = tool({ - description: "Add two numbers", - args: { - a: tool.schema.number().describe("First number"), - b: tool.schema.number().describe("Second number"), - }, - async execute(args) { - return (args.a + args.b).toString() - }, -}) - -export const multiply = tool({ - description: "Multiply two numbers", - args: { - a: tool.schema.number().describe("First number"), - b: tool.schema.number().describe("Second number"), - }, - async execute(args) { - return (args.a * args.b).toString() - }, -}) -``` - -This creates two tools: `math_add` and `math_multiply`. - ---- - -#### Name collisions with built-in tools - -Custom tools are keyed by tool name. If a custom tool uses the same name as a built-in tool, the custom tool takes precedence. - -For example, this file replaces the built-in `bash` tool: - -```ts title=".opencode/tools/bash.ts" -import { tool } from "@opencode-ai/plugin" - -export default tool({ - description: "Restricted bash wrapper", - args: { - command: tool.schema.string(), - }, - async execute(args) { - return `blocked: ${args.command}` - }, -}) -``` - -:::note -Prefer unique names unless you intentionally want to replace a built-in tool. If you want to disable a built in tool but not override it, use [permissions](/docs/permissions). -::: - ---- - -### Arguments - -You can use `tool.schema`, which is just [Zod](https://zod.dev), to define argument types. - -```ts "tool.schema" -args: { - query: tool.schema.string().describe("SQL query to execute") -} -``` - -You can also import [Zod](https://zod.dev) directly and return a plain object: - -```ts {6} -import { z } from "zod" - -export default { - description: "Tool description", - args: { - param: z.string().describe("Parameter description"), - }, - async execute(args, context) { - // Tool implementation - return "result" - }, -} -``` - ---- - -### Context - -Tools receive context about the current session: - -```ts title=".opencode/tools/project.ts" {8} -import { tool } from "@opencode-ai/plugin" - -export default tool({ - description: "Get project information", - args: {}, - async execute(args, context) { - // Access context information - const { agent, sessionID, messageID, directory, worktree } = context - return `Agent: ${agent}, Session: ${sessionID}, Message: ${messageID}, Directory: ${directory}, Worktree: ${worktree}` - }, -}) -``` - -Use `context.directory` for the session working directory. -Use `context.worktree` for the git worktree root. - ---- - -## Examples - -### Write a tool in Python - -You can write your tools in any language you want. Here's an example that adds two numbers using Python. - -First, create the tool as a Python script: - -```python title=".opencode/tools/add.py" -import sys - -a = int(sys.argv[1]) -b = int(sys.argv[2]) -print(a + b) -``` - -Then create the tool definition that invokes it: - -```ts title=".opencode/tools/python-add.ts" {10} -import { tool } from "@opencode-ai/plugin" -import path from "path" - -export default tool({ - description: "Add two numbers using Python", - args: { - a: tool.schema.number().describe("First number"), - b: tool.schema.number().describe("Second number"), - }, - async execute(args, context) { - const script = path.join(context.worktree, ".opencode/tools/add.py") - const result = await Bun.$`python3 ${script} ${args.a} ${args.b}`.text() - return result.trim() - }, -}) -``` - -Here we are using the [`Bun.$`](https://bun.com/docs/runtime/shell) utility to run the Python script. diff --git a/.opencode/skills/opencode-docs/docs/ecosystem.mdx b/.opencode/skills/opencode-docs/docs/ecosystem.mdx deleted file mode 100644 index 2fc3b65..0000000 --- a/.opencode/skills/opencode-docs/docs/ecosystem.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Ecosystem -description: Projects and integrations built with OpenCode. ---- - -A collection of community projects built on OpenCode. - -:::note -Want to add your OpenCode related project to this list? Submit a PR. -::: - -You can also check out [awesome-opencode](https://github.com/awesome-opencode/awesome-opencode) and [opencode.cafe](https://opencode.cafe), a community that aggregates the ecosystem and community. - ---- - -## Plugins - -| Name | Description | -| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -| [opencode-daytona](https://github.com/daytonaio/daytona/tree/main/libs/opencode-plugin) | Automatically run OpenCode sessions in isolated Daytona sandboxes with git sync and live previews | -| [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) | Automatically inject Helicone session headers for request grouping | -| [opencode-type-inject](https://github.com/nick-vi/opencode-type-inject) | Auto-inject TypeScript/Svelte types into file reads with lookup tools | -| [opencode-openai-codex-auth](https://github.com/numman-ali/opencode-openai-codex-auth) | Use your ChatGPT Plus/Pro subscription instead of API credits | -| [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) | Use your existing Gemini plan instead of API billing | -| [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Use Antigravity's free models instead of API billing | -| [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) | Multi-branch devcontainer isolation with shallow clones and auto-assigned ports | -| [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth) | Google Antigravity OAuth Plugin, with support for Google Search, and more robust API handling | -| [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) | Optimize token usage by pruning obsolete tool outputs | -| [opencode-vibeguard](https://github.com/inkdust2021/opencode-vibeguard) | Redact secrets/PII into VibeGuard-style placeholders before LLM calls; restore locally | -| [opencode-websearch-cited](https://github.com/ghoulr/opencode-websearch-cited.git) | Add native websearch support for supported providers with Google grounded style | -| [opencode-pty](https://github.com/shekohex/opencode-pty.git) | Enables AI agents to run background processes in a PTY, send interactive input to them. | -| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations | -| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | -| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | -| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply editing, WarpGrep codebase search, and context compaction via Morph | -| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | -| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events | -| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context | -| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Allow OpenCode agents to lazy load prompts on demand with skill discovery and injection | -| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistent memory across sessions using Supermemory | -| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing | -| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control | -| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax | -| [opencode-conductor](https://github.com/derekbar90/opencode-conductor) | Protocol-Driven Workflow: Automation of the Context -> Spec -> Plan -> Implement lifecycle. | -| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity | -| [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms | -| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence | -| [opencode-notify](https://github.com/kdcokenny/opencode-notify) | Native OS notifications for OpenCode – know when tasks complete | -| [opencode-workspace](https://github.com/kdcokenny/opencode-workspace) | Bundled multi-agent orchestration harness – 16 components, one install | -| [opencode-worktree](https://github.com/kdcokenny/opencode-worktree) | Zero-friction git worktrees for OpenCode | -| [opencode-sentry-monitor](https://github.com/stolinski/opencode-sentry-monitor) | Trace and debug your AI agents with Sentry AI Monitoring | -| [opencode-firecrawl](https://github.com/firecrawl/opencode-firecrawl) | Web scraping, crawling, and search via the Firecrawl CLI | -| [opencode-jfrog-plugin](https://github.com/jfrog/opencode-jfrog-plugin) | JFrog Plugin for seamless integration of Opencode users to JFrog platform | -| [opencode-goal-plugin](https://github.com/willytop8/OpenCode-goal-plugin) | Session-scoped `/goal` workflow that keeps objectives in context and auto-continues until complete | - ---- - -## Projects - -| Name | Description | -| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | -| [kimaki](https://github.com/remorses/kimaki) | Discord bot to control OpenCode sessions, built on the SDK | -| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Neovim plugin for editor-aware prompts, built on the API | -| [portal](https://github.com/hosenur/portal) | Mobile-first web UI for OpenCode over Tailscale/VPN | -| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Template for building OpenCode plugins | -| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Neovim frontend for opencode - a terminal-based AI coding agent | -| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Vercel AI SDK provider for using OpenCode via @opencode-ai/sdk | -| [OpenChamber](https://github.com/btriapitsyn/openchamber) | Web / Desktop App and VS Code Extension for OpenCode | -| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Obsidian plugin that embeds OpenCode in Obsidian's UI | -| [OpenWork](https://github.com/different-ai/openwork) | An open-source alternative to Claude Cowork, powered by OpenCode | -| [ocx](https://github.com/kdcokenny/ocx) | OpenCode extension manager with portable, isolated profiles. | -| [CodeNomad](https://github.com/NeuralNomadsAI/CodeNomad) | Desktop, Web, Mobile and Remote Client App for OpenCode | - ---- - -## Agents - -| Name | Description | -| ----------------------------------------------------------------- | ------------------------------------------------------------ | -| [Agentic](https://github.com/Cluster444/agentic) | Modular AI agents and commands for structured development | -| [opencode-agents](https://github.com/darrenhinde/opencode-agents) | Configs, prompts, agents, and plugins for enhanced workflows | diff --git a/.opencode/skills/opencode-docs/docs/enterprise.mdx b/.opencode/skills/opencode-docs/docs/enterprise.mdx deleted file mode 100644 index a93134d..0000000 --- a/.opencode/skills/opencode-docs/docs/enterprise.mdx +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: Enterprise -description: Using OpenCode securely in your organization. ---- - -export const enterprise = "https://opencode.ai/enterprise" - -OpenCode Enterprise is for organizations that want to ensure that their code and data never leaves their infrastructure. It can do this by using a centralized config that integrates with your SSO and internal AI gateway. - -:::note -OpenCode does not store any of your code or context data. -::: - -To get started with OpenCode Enterprise: - -1. Do a trial internally with your team. -2. **<a href={enterprise}>Contact us</a>** to discuss pricing and implementation options. - ---- - -## Trial - -OpenCode is open source and does not store any of your code or context data, so your developers can simply [get started](/docs/) and carry out a trial. - ---- - -### Data handling - -**OpenCode does not store your code or context data.** All processing happens locally or through direct API calls to your AI provider. - -This means that as long as you are using a provider you trust, or an internal -AI gateway, you can use OpenCode securely. - -The only caveat here is the optional `/share` feature. - ---- - -#### Sharing conversations - -If a user enables the `/share` feature, the conversation and the data associated with it are sent to the service we use to host these share pages at opencode.ai. - -The data is currently served through our CDN's edge network, and is cached on the edge near your users. - -We recommend you disable this for your trial. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "share": "disabled" -} -``` - -[Learn more about sharing](/docs/share). - ---- - -### Code ownership - -**You own all code produced by OpenCode.** There are no licensing restrictions or ownership claims. - ---- - -## Pricing - -We use a per-seat model for OpenCode Enterprise. If you have your own LLM gateway, we do not charge for tokens used. For further details about pricing and implementation options, **<a href={enterprise}>contact us</a>**. - ---- - -## Deployment - -Once you have completed your trial and you are ready to use OpenCode at -your organization, you can **<a href={enterprise}>contact us</a>** to discuss -pricing and implementation options. - ---- - -### Central Config - -We can set up OpenCode to use a single central config for your entire organization. - -This centralized config can integrate with your SSO provider and ensures all users access only your internal AI gateway. - ---- - -### SSO integration - -Through the central config, OpenCode can integrate with your organization's SSO provider for authentication. - -This allows OpenCode to obtain credentials for your internal AI gateway through your existing identity management system. - ---- - -### Internal AI gateway - -With the central config, OpenCode can also be configured to use only your internal AI gateway. - -You can also disable all other AI providers, ensuring all requests go through your organization's approved infrastructure. - ---- - -### Self-hosting - -While we recommend disabling the share pages to ensure your data never leaves -your organization, we can also help you self-host them on your infrastructure. - -This is currently on our roadmap. If you're interested, **<a href={enterprise}>let us know</a>**. - ---- - -## FAQ - -<details> -<summary>What is OpenCode Enterprise?</summary> - -OpenCode Enterprise is for organizations that want to ensure that their code and data never leaves their infrastructure. It can do this by using a centralized config that integrates with your SSO and internal AI gateway. - -</details> - -<details> -<summary>How do I get started with OpenCode Enterprise?</summary> - -Simply start with an internal trial with your team. OpenCode by default does not store your code or context data, making it easy to get started. - -Then **<a href={enterprise}>contact us</a>** to discuss pricing and implementation options. - -</details> - -<details> -<summary>How does enterprise pricing work?</summary> - -We offer per-seat enterprise pricing. If you have your own LLM gateway, we do not charge for tokens used. For further details, **<a href={enterprise}>contact us</a>** for a custom quote based on your organization's needs. - -</details> - -<details> -<summary>Is my data secure with OpenCode Enterprise?</summary> - -Yes. OpenCode does not store your code or context data. All processing happens locally or through direct API calls to your AI provider. With central config and SSO integration, your data remains secure within your organization's infrastructure. - -</details> - -<details> -<summary>Can we use our own private NPM registry?</summary> - -OpenCode supports private npm registries through Bun's native `.npmrc` file support. If your organization uses a private registry, such as JFrog Artifactory, Nexus, or similar, ensure developers are authenticated before running OpenCode. - -To set up authentication with your private registry: - -```bash -npm login --registry=https://your-company.jfrog.io/api/npm/npm-virtual/ -``` - -This creates `~/.npmrc` with authentication details. OpenCode will automatically -pick this up. - -:::caution -You must be logged into the private registry before running OpenCode. -::: - -Alternatively, you can manually configure a `.npmrc` file: - -```bash title="~/.npmrc" -registry=https://your-company.jfrog.io/api/npm/npm-virtual/ -//your-company.jfrog.io/api/npm/npm-virtual/:_authToken=${NPM_AUTH_TOKEN} -``` - -Developers must be logged into the private registry before running OpenCode to ensure packages can be installed from your enterprise registry. - -</details> diff --git a/.opencode/skills/opencode-docs/docs/formatters.mdx b/.opencode/skills/opencode-docs/docs/formatters.mdx deleted file mode 100644 index 58b63fa..0000000 --- a/.opencode/skills/opencode-docs/docs/formatters.mdx +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: Formatters -description: OpenCode uses language specific formatters. ---- - -OpenCode can format files after they are written or edited using language-specific formatters. Formatters are disabled by default; enable them in your config before OpenCode will run them. - ---- - -## Built-in - -OpenCode comes with several built-in formatters for popular languages and frameworks. Below is a list of the formatters, supported file extensions, and commands or config options it needs. - -| Formatter | Extensions | Requirements | -| -------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| air | .R | `air` command available | -| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file | -| cargofmt | .rs | `cargo fmt` command available | -| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file | -| cljfmt | .clj, .cljs, .cljc, .edn | `cljfmt` command available | -| dart | .dart | `dart` command available | -| dfmt | .d | `dfmt` command available | -| gleam | .gleam | `gleam` command available | -| gofmt | .go | `gofmt` command available | -| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | -| ktlint | .kt, .kts | `ktlint` command available | -| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available | -| nixfmt | .nix | `nixfmt` command available | -| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file | -| ormolu | .hs | `ormolu` command available | -| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) | -| pint | .php | `laravel/pint` dependency in `composer.json` | -| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` | -| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | -| ruff | .py, .pyi | `ruff` command available with config | -| rustfmt | .rs | `rustfmt` command available | -| shfmt | .sh, .bash | `shfmt` command available | -| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | -| terraform | .tf, .tfvars | `terraform` command available | -| uv | .py, .pyi | `uv` command available | -| zig | .zig, .zon | `zig` command available | - -When formatters are enabled, OpenCode will use `prettier` for matching files if your project has `prettier` in `package.json`. - ---- - -## How it works - -When OpenCode writes or edits a file and formatters are enabled, it: - -1. Checks the file extension against all enabled formatters. -2. Runs the appropriate formatter command on the file. -3. Applies the formatting changes. - -This process happens in the background for enabled formatters. - ---- - -## Configure - -You can enable and customize formatters through the `formatter` section in your OpenCode config. - -To enable all built-in formatters, set `formatter` to `true`. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "formatter": true -} -``` - -Use an object to keep built-ins enabled while configuring overrides or custom formatters. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "formatter": {} -} -``` - -Each formatter configuration supports the following: - -| Property | Type | Description | -| ------------- | -------- | ------------------------------------------------------------------------------------------ | -| `disabled` | boolean | Set this to `true` to disable the formatter | -| `command` | string[] | The command to run for formatting. Required for custom formatters; optional for built-ins. | -| `environment` | object | Environment variables to set when running the formatter | -| `extensions` | string[] | File extensions this formatter should handle | - -Let's look at some examples. - ---- - -### Disabling formatters - -If `formatter` is omitted, all formatters are disabled. To disable all formatters after another config enabled them, set `formatter` to `false`: - -```json title="opencode.json" {3} -{ - "$schema": "https://opencode.ai/config.json", - "formatter": false -} -``` - -To disable a **specific** formatter, set `disabled` to `true`: - -```json title="opencode.json" {5} -{ - "$schema": "https://opencode.ai/config.json", - "formatter": { - "prettier": { - "disabled": true - } - } -} -``` - ---- - -### Custom formatters - -You can configure built-in formatters with options like `environment` or `extensions`. To add a custom formatter, specify a `command` and `extensions`: - -```json title="opencode.json" {4-14} -{ - "$schema": "https://opencode.ai/config.json", - "formatter": { - "prettier": { - "command": ["npx", "prettier", "--write", "$FILE"], - "environment": { - "NODE_ENV": "development" - }, - "extensions": [".js", ".ts", ".jsx", ".tsx"] - }, - "custom-markdown-formatter": { - "command": ["deno", "fmt", "$FILE"], - "extensions": [".md"] - } - } -} -``` - -The **`$FILE` placeholder** in the command will be replaced with the path to the file being formatted. diff --git a/.opencode/skills/opencode-docs/docs/github.mdx b/.opencode/skills/opencode-docs/docs/github.mdx deleted file mode 100644 index a31fe1e..0000000 --- a/.opencode/skills/opencode-docs/docs/github.mdx +++ /dev/null @@ -1,321 +0,0 @@ ---- -title: GitHub -description: Use OpenCode in GitHub issues and pull-requests. ---- - -OpenCode integrates with your GitHub workflow. Mention `/opencode` or `/oc` in your comment, and OpenCode will execute tasks within your GitHub Actions runner. - ---- - -## Features - -- **Triage issues**: Ask OpenCode to look into an issue and explain it to you. -- **Fix and implement**: Ask OpenCode to fix an issue or implement a feature. And it will work in a new branch and submits a PR with all the changes. -- **Secure**: OpenCode runs inside your GitHub's runners. - ---- - -## Installation - -Run the following command in a project that is in a GitHub repo: - -```bash -opencode github install -``` - -This will walk you through installing the GitHub app, creating the workflow, and setting up secrets. - ---- - -### Manual Setup - -Or you can set it up manually. - -1. **Install the GitHub app** - - Head over to [**github.com/apps/opencode-agent**](https://github.com/apps/opencode-agent). Make sure it's installed on the target repository. - -2. **Add the workflow** - - Add the following workflow file to `.github/workflows/opencode.yml` in your repo. Make sure to set the appropriate `model` and required API keys in `env`. - - ```yml title=".github/workflows/opencode.yml" {24,26} - name: opencode - - on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - - jobs: - opencode: - if: | - contains(github.event.comment.body, '/oc') || - contains(github.event.comment.body, '/opencode') - runs-on: ubuntu-latest - permissions: - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Run OpenCode - uses: anomalyco/opencode/github@latest - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - with: - model: anthropic/claude-sonnet-4-20250514 - # share: true - # github_token: xxxx - ``` - -3. **Store the API keys in secrets** - - In your organization or project **settings**, expand **Secrets and variables** on the left and select **Actions**. And add the required API keys. - ---- - -## Configuration - -- `model`: The model to use with OpenCode. Takes the format of `provider/model`. This is **required**. -- `agent`: The agent to use. Must be a primary agent. Falls back to `default_agent` from config or `"build"` if not found. -- `share`: Whether to share the OpenCode session. Defaults to **true** for public repositories. -- `prompt`: Optional custom prompt to override the default behavior. Use this to customize how OpenCode processes requests. -- `token`: Optional GitHub access token for performing operations such as creating comments, committing changes, and opening pull requests. By default, OpenCode uses the installation access token from the OpenCode GitHub App, so commits, comments, and pull requests appear as coming from the app. - - Alternatively, you can use the GitHub Action runner's [built-in `GITHUB_TOKEN`](https://docs.github.com/en/actions/tutorials/authenticate-with-github_token) without installing the OpenCode GitHub App. Just make sure to grant the required permissions in your workflow: - - ```yaml - permissions: - id-token: write - contents: write - pull-requests: write - issues: write - ``` - - You can also use a [personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)(PAT) if preferred. - ---- - -## Supported Events - -OpenCode can be triggered by the following GitHub events: - -| Event Type | Triggered By | Details | -| ----------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `issue_comment` | Comment on an issue or PR | Mention `/opencode` or `/oc` in your comment. OpenCode reads context and can create branches, open PRs, or reply. | -| `pull_request_review_comment` | Comment on specific code lines in a PR | Mention `/opencode` or `/oc` while reviewing code. OpenCode receives file path, line numbers, and diff context. | -| `issues` | Issue opened or edited | Automatically trigger OpenCode when issues are created or modified. Requires `prompt` input. | -| `pull_request` | PR opened or updated | Automatically trigger OpenCode when PRs are opened, synchronized, or reopened. Useful for automated reviews. | -| `schedule` | Cron-based schedule | Run OpenCode on a schedule. Requires `prompt` input. Output goes to logs and PRs (no issue to comment on). | -| `workflow_dispatch` | Manual trigger from GitHub UI | Trigger OpenCode on demand via Actions tab. Requires `prompt` input. Output goes to logs and PRs. | - -### Schedule Example - -Run OpenCode on a schedule to perform automated tasks: - -```yaml title=".github/workflows/opencode-scheduled.yml" -name: Scheduled OpenCode Task - -on: - schedule: - - cron: "0 9 * * 1" # Every Monday at 9am UTC - -jobs: - opencode: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - pull-requests: write - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - persist-credentials: false - - - name: Run OpenCode - uses: anomalyco/opencode/github@latest - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - with: - model: anthropic/claude-sonnet-4-20250514 - prompt: | - Review the codebase for any TODO comments and create a summary. - If you find issues worth addressing, open an issue to track them. -``` - -For scheduled events, the `prompt` input is **required** since there's no comment to extract instructions from. Scheduled workflows run without a user context to permission-check, so the workflow must grant `contents: write` and `pull-requests: write` if you expect OpenCode to create branches or PRs. - ---- - -### Pull Request Example - -Automatically review PRs when they are opened or updated: - -```yaml title=".github/workflows/opencode-review.yml" -name: opencode-review - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - -jobs: - review: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - pull-requests: read - issues: read - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - uses: anomalyco/opencode/github@latest - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - model: anthropic/claude-sonnet-4-20250514 - use_github_token: true - prompt: | - Review this pull request: - - Check for code quality issues - - Look for potential bugs - - Suggest improvements -``` - -For `pull_request` events, if no `prompt` is provided, OpenCode defaults to reviewing the pull request. - ---- - -### Issues Triage Example - -Automatically triage new issues. This example filters to accounts older than 30 days to reduce spam: - -```yaml title=".github/workflows/opencode-triage.yml" -name: Issue Triage - -on: - issues: - types: [opened] - -jobs: - triage: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - pull-requests: write - issues: write - steps: - - name: Check account age - id: check - uses: actions/github-script@v7 - with: - script: | - const user = await github.rest.users.getByUsername({ - username: context.payload.issue.user.login - }); - const created = new Date(user.data.created_at); - const days = (Date.now() - created) / (1000 * 60 * 60 * 24); - return days >= 30; - result-encoding: string - - - uses: actions/checkout@v6 - if: steps.check.outputs.result == 'true' - with: - persist-credentials: false - - - uses: anomalyco/opencode/github@latest - if: steps.check.outputs.result == 'true' - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - with: - model: anthropic/claude-sonnet-4-20250514 - prompt: | - Review this issue. If there's a clear fix or relevant docs: - - Provide documentation links - - Add error handling guidance for code examples - Otherwise, do not comment. -``` - -For `issues` events, the `prompt` input is **required** since there's no comment to extract instructions from. - ---- - -## Custom prompts - -Override the default prompt to customize OpenCode's behavior for your workflow. - -```yaml title=".github/workflows/opencode.yml" -- uses: anomalyco/opencode/github@latest - with: - model: anthropic/claude-sonnet-4-5 - prompt: | - Review this pull request: - - Check for code quality issues - - Look for potential bugs - - Suggest improvements -``` - -This is useful for enforcing specific review criteria, coding standards, or focus areas relevant to your project. - ---- - -## Examples - -Here are some examples of how you can use OpenCode in GitHub. - -- **Explain an issue** - - Add this comment in a GitHub issue. - - ``` - /opencode explain this issue - ``` - - OpenCode will read the entire thread, including all comments, and reply with a clear explanation. - -- **Fix an issue** - - In a GitHub issue, say: - - ``` - /opencode fix this - ``` - - And OpenCode will create a new branch, implement the changes, and open a PR with the changes. - -- **Review PRs and make changes** - - Leave the following comment on a GitHub PR. - - ``` - Delete the attachment from S3 when the note is removed /oc - ``` - - OpenCode will implement the requested change and commit it to the same PR. - -- **Review specific code lines** - - Leave a comment directly on code lines in the PR's "Files" tab. OpenCode automatically detects the file, line numbers, and diff context to provide precise responses. - - ``` - [Comment on specific lines in Files tab] - /oc add error handling here - ``` - - When commenting on specific lines, OpenCode receives: - - The exact file being reviewed - - The specific lines of code - - The surrounding diff context - - Line number information - - This allows for more targeted requests without needing to specify file paths or line numbers manually. diff --git a/.opencode/skills/opencode-docs/docs/gitlab.mdx b/.opencode/skills/opencode-docs/docs/gitlab.mdx deleted file mode 100644 index 5ba0b90..0000000 --- a/.opencode/skills/opencode-docs/docs/gitlab.mdx +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: GitLab -description: Use OpenCode in GitLab issues and merge requests. ---- - -OpenCode integrates with your GitLab workflow through your GitLab CI/CD pipeline or with GitLab Duo. - -In both cases, OpenCode will run on your GitLab runners. - ---- - -## GitLab CI - -OpenCode works in a regular GitLab pipeline. You can build it into a pipeline as a [CI component](https://docs.gitlab.com/ee/ci/components/) - -Here we are using a community-created CI/CD component for OpenCode — [nagyv/gitlab-opencode](https://gitlab.com/nagyv/gitlab-opencode). - ---- - -### Features - -- **Use custom configuration per job**: Configure OpenCode with a custom configuration directory, for example `./config/#custom-directory` to enable or disable functionality per OpenCode invocation. -- **Minimal setup**: The CI component sets up OpenCode in the background, you only need to create the OpenCode configuration and the initial prompt. -- **Flexible**: The CI component supports several inputs for customizing its behavior - ---- - -### Setup - -1. Store your OpenCode authentication JSON as a File type CI environment variables under **Settings** > **CI/CD** > **Variables**. Make sure to mark them as "Masked and hidden". -2. Add the following to your `.gitlab-ci.yml` file. - - ```yaml title=".gitlab-ci.yml" - include: - - component: $CI_SERVER_FQDN/nagyv/gitlab-opencode/opencode@2 - inputs: - config_dir: ${CI_PROJECT_DIR}/opencode-config - auth_json: $OPENCODE_AUTH_JSON # The variable name for your OpenCode authentication JSON - command: optional-custom-command - message: "Your prompt here" - ``` - -For more inputs and use cases [check out the docs](https://gitlab.com/explore/catalog/nagyv/gitlab-opencode) for this component. - ---- - -## GitLab Duo - -OpenCode integrates with your GitLab workflow. -Mention `@opencode` in a comment, and OpenCode will execute tasks within your GitLab CI pipeline. - ---- - -### Features - -- **Triage issues**: Ask OpenCode to look into an issue and explain it to you. -- **Fix and implement**: Ask OpenCode to fix an issue or implement a feature. - It will create a new branch and raise a merge request with the changes. -- **Secure**: OpenCode runs on your GitLab runners. - ---- - -### Setup - -OpenCode runs in your GitLab CI/CD pipeline, here's what you'll need to set it up: - -:::tip -Check out the [**GitLab docs**](https://docs.gitlab.com/user/duo_agent_platform/agent_assistant/) for up to date instructions. -::: - -1. Configure your GitLab environment -2. Set up CI/CD -3. Get an AI model provider API key -4. Create a service account -5. Configure CI/CD variables -6. Create a flow config file, here's an example: - - <details> - - <summary>Flow configuration</summary> - - ```yaml - image: node:22-slim - commands: - - echo "Installing opencode" - - npm install --global opencode-ai - - echo "Installing glab" - - export GITLAB_TOKEN=$GITLAB_TOKEN_OPENCODE - - apt-get update --quiet && apt-get install --yes curl wget gpg git && rm --recursive --force /var/lib/apt/lists/* - - curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash - - apt-get install --yes glab - - echo "Configuring glab" - - echo $GITLAB_HOST - - echo "Creating OpenCode auth configuration" - - mkdir --parents ~/.local/share/opencode - - | - cat > ~/.local/share/opencode/auth.json << EOF - { - "anthropic": { - "type": "api", - "key": "$ANTHROPIC_API_KEY" - } - } - EOF - - echo "Configuring git" - - git config --global user.email "opencode@gitlab.com" - - git config --global user.name "OpenCode" - - echo "Testing glab" - - glab issue list - - echo "Running OpenCode" - - | - opencode run " - You are an AI assistant helping with GitLab operations. - - Context: $AI_FLOW_CONTEXT - Task: $AI_FLOW_INPUT - Event: $AI_FLOW_EVENT - - Please execute the requested task using the available GitLab tools. - Be thorough in your analysis and provide clear explanations. - - <important> - Please use the glab CLI to access data from GitLab. The glab CLI has already been authenticated. You can run the corresponding commands. - - If you are asked to summarize an MR or issue or asked to provide more information then please post back a note to the MR/Issue so that the user can see it. - You don't need to commit or push up changes, those will be done automatically based on the file changes you make. - </important> - " - - git checkout --branch $CI_WORKLOAD_REF origin/$CI_WORKLOAD_REF - - echo "Checking for git changes and pushing if any exist" - - | - if ! git diff --quiet || ! git diff --cached --quiet || [ --not --zero "$(git ls-files --others --exclude-standard)" ]; then - echo "Git changes detected, adding and pushing..." - git add . - if git diff --cached --quiet; then - echo "No staged changes to commit" - else - echo "Committing changes to branch: $CI_WORKLOAD_REF" - git commit --message "Codex changes" - echo "Pushing changes up to $CI_WORKLOAD_REF" - git push https://gitlab-ci-token:$GITLAB_TOKEN@$GITLAB_HOST/gl-demo-ultimate-dev-ai-epic-17570/test-java-project.git $CI_WORKLOAD_REF - echo "Changes successfully pushed" - fi - else - echo "No git changes detected, skipping push" - fi - variables: - - ANTHROPIC_API_KEY - - GITLAB_TOKEN_OPENCODE - - GITLAB_HOST - ``` - - </details> - -You can refer to the [GitLab CLI agents docs](https://docs.gitlab.com/user/duo_agent_platform/agent_assistant/) for detailed instructions. - ---- - -### Examples - -Here are some examples of how you can use OpenCode in GitLab. - -:::tip -You can configure to use a different trigger phrase than `@opencode`. -::: - -- **Explain an issue** - - Add this comment in a GitLab issue. - - ``` - @opencode explain this issue - ``` - - OpenCode will read the issue and reply with a clear explanation. - -- **Fix an issue** - - In a GitLab issue, say: - - ``` - @opencode fix this - ``` - - OpenCode will create a new branch, implement the changes, and open a merge request with the changes. - -- **Review merge requests** - - Leave the following comment on a GitLab merge request. - - ``` - @opencode review this merge request - ``` - - OpenCode will review the merge request and provide feedback. diff --git a/.opencode/skills/opencode-docs/docs/go.mdx b/.opencode/skills/opencode-docs/docs/go.mdx deleted file mode 100644 index a55f9f4..0000000 --- a/.opencode/skills/opencode-docs/docs/go.mdx +++ /dev/null @@ -1,211 +0,0 @@ ---- -title: Go -description: Low cost subscription for open coding models. ---- - -import config from "../../../config.mjs" -export const console = config.console -export const email = `mailto:${config.email}` - -OpenCode Go is a low cost subscription — **$5 for your first month**, then **$10/month** — that gives you reliable access to popular open coding models. - -Go works like any other provider in OpenCode. You subscribe to OpenCode Go and -get your API key. It's **completely optional** and you don't need to use it to -use OpenCode. - -It is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. - ---- - -## Background - -Open models have gotten really good. They now reach performance close to -proprietary models for coding tasks. And because many providers can serve them -competitively, they are usually far cheaper. - -However, getting reliable, low latency access to them can be difficult. Providers -vary in quality and availability. - -:::tip -We tested a select group of models and providers that work well with OpenCode. -::: - -To fix this, we did a couple of things: - -1. We tested a select group of open models and talked to their teams about how to - best run them. -2. We then worked with a few providers to make sure these were being served - correctly. -3. Finally, we benchmarked the combination of the model/provider and came up - with a list that we feel good recommending. - -OpenCode Go gives you access to these models for **$5 for your first month**, then **$10/month**. - ---- - -## How it works - -OpenCode Go works like any other provider in OpenCode. - -1. You sign in to **<a href={console}>OpenCode Zen</a>**, subscribe to Go, and - copy your API key. -2. You run the `/connect` command in the TUI, select `OpenCode Go`, and paste - your API key. -3. Run `/models` in the TUI to see the list of models available through Go. - -:::note -Only one member per workspace can subscribe to OpenCode Go. -::: - -The current list of models includes: - -- **GLM-5.2** -- **GLM-5.1** -- **Kimi K2.7 Code** -- **Kimi K2.6** -- **MiMo-V2.5** -- **MiMo-V2.5-Pro** -- **MiniMax M3** -- **MiniMax M2.7** -- **Qwen3.7 Max** -- **Qwen3.7 Plus** -- **Qwen3.6 Plus** -- **DeepSeek V4 Pro** -- **DeepSeek V4 Flash** - -The list of models may change as we test and add new ones. - ---- - -## Usage limits - -OpenCode Go includes the following limits: - -- **5 hour limit** — $12 of usage -- **Weekly limit** — $30 of usage -- **Monthly limit** — $60 of usage - -Limits are defined in dollar value. This means your actual request count depends on the model you use. Cheaper models like DeepSeek V4 Flash allow for more requests, while higher-cost models like GLM-5.2 allow for fewer. - -The table below provides an estimated request count based on typical Go usage patterns: - -| Model | requests per 5 hour | requests per week | requests per month | -| ----------------- | ------------------- | ----------------- | ------------------ | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| Kimi K2.7 Code | 1,350 | 4,630 | 9,250 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Qwen3.7 Max | 950 | 2,390 | 4,770 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 3,450 | 8,550 | 17,150 | -| DeepSeek V4 Flash | 31,650 | 79,050 | 158,150 | - -The estimates are based on observed average request patterns: - -- GLM-5.2/5.1 — 700 input, 52,000 cached, 150 output tokens per request -- Kimi K2.7 Code/K2.6 — 870 input, 55,000 cached, 200 output tokens per request -- DeepSeek V4 Pro — 750 input, 82,000 cached, 290 output tokens per request -- DeepSeek V4 Flash — 790 input, 68,000 cached, 280 output tokens per request -- MiniMax M3 — 510 input, 56,000 cached, 190 output tokens per request -- MiniMax M2.7 — 300 input, 55,000 cached, 125 output tokens per request -- MiMo-V2.5 — 830 input, 71,500 cached, 295 output tokens per request -- MiMo-V2.5-Pro — 790 input, 86,000 cached, 305 output tokens per request -- Qwen3.7 Max — 420 input, 66,000 cached, 200 output tokens per request -- Qwen3.7 Plus — 500 input, 57,000 cached, 190 output tokens per request -- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - -The estimates are also based on the following prices per 1M tokens: - -| Model | Input | Output | Cached Read | Cached Write | -| ---------------------------- | ----- | ------ | ----------- | ------------ | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | -| MiMo V2.5 Pro | $1.74 | $3.48 | $0.0145 | - | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | -| DeepSeek V4 Pro | $1.74 | $3.48 | $0.0145 | - | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.0028 | - | - -You can track your current usage in the **<a href={console}>console</a>**. - -:::tip -If you reach the usage limit, you can continue using the free models. -::: - -Usage limits may change as we learn from early usage and feedback. - ---- - -### Usage beyond limits - -If you also have credits on your Zen balance, you can enable the **Use balance** -option in the console. When enabled, Go will fall back to your Zen balance -after you've reached your usage limits instead of blocking requests. - ---- - -## Endpoints - -You can also access Go models through the following API endpoints. - -| Model | Model ID | Endpoint | AI SDK Package | -| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.2 | glm-5.2 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.7 Code | kimi-k2.7-code | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M3 | minimax-m3 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | - -The [model id](/docs/config/#models) in your OpenCode config -uses the format `opencode-go/<model-id>`. For example, for Kimi K2.7 Code, you would -use `opencode-go/kimi-k2.7-code` in your config. - ---- - -### Models - -You can fetch the full list of available models and their metadata from: - -``` -https://opencode.ai/zen/go/v1/models -``` - ---- - -## Privacy - -The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Our providers follow a zero-retention policy and do not use your data for model training. - ---- - -## Goals - -We created OpenCode Go to: - -1. Make AI coding **accessible** to more people with a low cost subscription. -2. Provide **reliable** access to the best open coding models. -3. Curate models that are **tested and benchmarked** for coding agent use. -4. Have **no lock-in** by allowing you to use any other provider with OpenCode as well. diff --git a/.opencode/skills/opencode-docs/docs/ide.mdx b/.opencode/skills/opencode-docs/docs/ide.mdx deleted file mode 100644 index 0f708d9..0000000 --- a/.opencode/skills/opencode-docs/docs/ide.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: IDE -description: The OpenCode extension for VS Code, Cursor, and other IDEs ---- - -OpenCode integrates with VS Code, Cursor, or any IDE that supports a terminal. Just run `opencode` in the terminal to get started. - ---- - -## Usage - -- **Quick Launch**: Use `Cmd+Esc` (Mac) or `Ctrl+Esc` (Windows/Linux) to open OpenCode in a split terminal view, or focus an existing terminal session if one is already running. -- **New Session**: Use `Cmd+Shift+Esc` (Mac) or `Ctrl+Shift+Esc` (Windows/Linux) to start a new OpenCode terminal session, even if one is already open. You can also click the OpenCode button in the UI. -- **Context Awareness**: Automatically share your current selection or tab with OpenCode. -- **File Reference Shortcuts**: Use `Cmd+Option+K` (Mac) or `Alt+Ctrl+K` (Linux/Windows) to insert file references. For example, `@File#L37-42`. - ---- - -## Installation - -To install OpenCode on VS Code and popular forks like Cursor, Windsurf, VSCodium: - -1. Open VS Code -2. Open the integrated terminal -3. Run `opencode` - the extension installs automatically - -If on the other hand you want to use your own IDE when you run `/editor` or `/export` from the TUI, you'll need to set `export EDITOR="code --wait"`. [Learn more](/docs/tui/#editor-setup). - ---- - -### Manual Install - -Search for **OpenCode** in the Extension Marketplace and click **Install**. - ---- - -### Troubleshooting - -If the extension fails to install automatically: - -- Ensure you’re running `opencode` in the integrated terminal. -- Confirm the CLI for your IDE is installed: - - For VS Code: `code` command - - For Cursor: `cursor` command - - For Windsurf: `windsurf` command - - For VSCodium: `codium` command - - If not, run `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux) and search for "Shell Command: Install 'code' command in PATH" (or the equivalent for your IDE) -- Ensure VS Code has permission to install extensions diff --git a/.opencode/skills/opencode-docs/docs/index.mdx b/.opencode/skills/opencode-docs/docs/index.mdx deleted file mode 100644 index 90e7eaf..0000000 --- a/.opencode/skills/opencode-docs/docs/index.mdx +++ /dev/null @@ -1,360 +0,0 @@ ---- -title: Intro -description: Get started with OpenCode. ---- - -import { Tabs, TabItem } from "@astrojs/starlight/components" -import config from "../../../config.mjs" -export const console = config.console - -[**OpenCode**](/) is an open source AI coding agent. It's available as a terminal-based interface, desktop app, or IDE extension. - -![OpenCode TUI with the opencode theme](../../assets/lander/screenshot.png) - -Let's get started. - ---- - -#### Prerequisites - -To use OpenCode in your terminal, you'll need: - -1. A modern terminal emulator like: - - [WezTerm](https://wezterm.org), cross-platform - - [Alacritty](https://alacritty.org), cross-platform - - [Ghostty](https://ghostty.org), Linux and macOS - - [Kitty](https://sw.kovidgoyal.net/kitty/), Linux and macOS - -2. API keys for the LLM providers you want to use. - ---- - -## Install - -The easiest way to install OpenCode is through the install script. - -```bash -curl -fsSL https://opencode.ai/install | bash -``` - -You can also install it with the following commands: - -- **Using Node.js** - - <Tabs> - - <TabItem label="npm"> - ```bash - npm install -g opencode-ai - ``` - - </TabItem> - - <TabItem label="Bun"> - ```bash - bun install -g opencode-ai - ``` - - </TabItem> - - <TabItem label="pnpm"> - ```bash - pnpm install -g opencode-ai - ``` - - </TabItem> - - <TabItem label="Yarn"> - ```bash - yarn global add opencode-ai - ``` - - </TabItem> - - </Tabs> - -- **Using Homebrew on macOS and Linux** - - ```bash - brew install anomalyco/tap/opencode - ``` - - > We recommend using the OpenCode tap for the most up to date releases. The official `brew install opencode` formula is maintained by the Homebrew team and is updated less frequently. - -- **Installing on Arch Linux** - - ```bash - sudo pacman -S opencode # Arch Linux (Stable) - paru -S opencode-bin # Arch Linux (Latest from AUR) - ``` - -#### Windows - -:::tip[Recommended: Use WSL] -For the best experience on Windows, we recommend using [Windows Subsystem for Linux (WSL)](/docs/windows-wsl). It provides better performance and full compatibility with OpenCode's features. -::: - -- **Using Chocolatey** - - ```bash - choco install opencode - ``` - -- **Using Scoop** - - ```bash - scoop install opencode - ``` - -- **Using NPM** - - ```bash - npm install -g opencode-ai - ``` - -- **Using Mise** - - ```bash - mise use -g github:anomalyco/opencode - ``` - -- **Using Docker** - - ```bash - docker run -it --rm ghcr.io/anomalyco/opencode - ``` - -Support for installing OpenCode on Windows using Bun is currently in progress. - -You can also grab the binary from the [Releases](https://github.com/anomalyco/opencode/releases). - ---- - -## Configure - -With OpenCode you can use any LLM provider by configuring their API keys. - -If you are new to using LLM providers, we recommend using [OpenCode Zen](/docs/zen). -It's a curated list of models that have been tested and verified by the OpenCode -team. - -1. Run the `/connect` command in the TUI, select opencode, and head to [opencode.ai/auth](https://opencode.ai/auth). - - ```txt - /connect - ``` - -2. Sign in, add your billing details, and copy your API key. - -3. Paste your API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -Alternatively, you can select one of the other providers. [Learn more](/docs/providers#directory). - ---- - -## Initialize - -Now that you've configured a provider, you can navigate to a project that -you want to work on. - -```bash -cd /path/to/project -``` - -And run OpenCode. - -```bash -opencode -``` - -Next, initialize OpenCode for the project by running the following command. - -```bash frame="none" -/init -``` - -This will get OpenCode to analyze your project and create an `AGENTS.md` file in -the project root. - -:::tip -You should commit your project's `AGENTS.md` file to Git. -::: - -This helps OpenCode understand the project structure and the coding patterns -used. - ---- - -## Usage - -You are now ready to use OpenCode to work on your project. Feel free to ask it -anything! - -If you are new to using an AI coding agent, here are some examples that might -help. - ---- - -### Ask questions - -You can ask OpenCode to explain the codebase to you. - -:::tip -Use the `@` key to fuzzy search for files in the project. -::: - -```txt frame="none" "@packages/functions/src/api/index.ts" -How is authentication handled in @packages/functions/src/api/index.ts -``` - -This is helpful if there's a part of the codebase that you didn't work on. - ---- - -### Add features - -You can ask OpenCode to add new features to your project. Though we first recommend asking it to create a plan. - -1. **Create a plan** - - OpenCode has a _Plan mode_ that disables its ability to make changes and - instead suggest _how_ it'll implement the feature. - - Switch to it using the **Tab** key. You'll see an indicator for this in the lower right corner. - - ```bash frame="none" title="Switch to Plan mode" - <TAB> - ``` - - Now let's describe what we want it to do. - - ```txt frame="none" - When a user deletes a note, we'd like to flag it as deleted in the database. - Then create a screen that shows all the recently deleted notes. - From this screen, the user can undelete a note or permanently delete it. - ``` - - You want to give OpenCode enough details to understand what you want. It helps - to talk to it like you are talking to a junior developer on your team. - - :::tip - Give OpenCode plenty of context and examples to help it understand what you - want. - ::: - -2. **Iterate on the plan** - - Once it gives you a plan, you can give it feedback or add more details. - - ```txt frame="none" - We'd like to design this new screen using a design I've used before. - [Image #1] Take a look at this image and use it as a reference. - ``` - - :::tip - Drag and drop images into the terminal to add them to the prompt. - ::: - - OpenCode can scan any images you give it and add them to the prompt. You can - do this by dragging and dropping an image into the terminal. - -3. **Build the feature** - - Once you feel comfortable with the plan, switch back to _Build mode_ by - hitting the **Tab** key again. - - ```bash frame="none" - <TAB> - ``` - - And asking it to make the changes. - - ```bash frame="none" - Sounds good! Go ahead and make the changes. - ``` - ---- - -### Make changes - -For more straightforward changes, you can ask OpenCode to directly build it -without having to review the plan first. - -```txt frame="none" "@packages/functions/src/settings.ts" "@packages/functions/src/notes.ts" -We need to add authentication to the /settings route. Take a look at how this is -handled in the /notes route in @packages/functions/src/notes.ts and implement -the same logic in @packages/functions/src/settings.ts -``` - -You want to make sure you provide a good amount of detail so OpenCode makes the right -changes. - ---- - -### Undo changes - -Let's say you ask OpenCode to make some changes. - -```txt frame="none" "@packages/functions/src/api/index.ts" -Can you refactor the function in @packages/functions/src/api/index.ts? -``` - -But you realize that it is not what you wanted. You **can undo** the changes -using the `/undo` command. - -```bash frame="none" -/undo -``` - -OpenCode will now revert the changes you made and show your original message -again. - -```txt frame="none" "@packages/functions/src/api/index.ts" -Can you refactor the function in @packages/functions/src/api/index.ts? -``` - -From here you can tweak the prompt and ask OpenCode to try again. - -:::tip -You can run `/undo` multiple times to undo multiple changes. -::: - -Or you **can redo** the changes using the `/redo` command. - -```bash frame="none" -/redo -``` - ---- - -## Share - -The conversations that you have with OpenCode can be [shared with your -team](/docs/share). - -```bash frame="none" -/share -``` - -This will create a link to the current conversation and copy it to your clipboard. - -:::note -Conversations are not shared by default. -::: - -Here's an [example conversation](https://opencode.ai/s/4XP1fce5) with OpenCode. - ---- - -## Customize - -And that's it! You are now a pro at using OpenCode. - -To make it your own, we recommend [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), [configuring code formatters](/docs/formatters), [creating custom commands](/docs/commands), or playing around with the [OpenCode config](/docs/config). diff --git a/.opencode/skills/opencode-docs/docs/keybinds.mdx b/.opencode/skills/opencode-docs/docs/keybinds.mdx deleted file mode 100644 index 86f67df..0000000 --- a/.opencode/skills/opencode-docs/docs/keybinds.mdx +++ /dev/null @@ -1,299 +0,0 @@ ---- -title: Keybinds -description: Customize your keybinds. ---- - -OpenCode has a list of keybinds that you can customize through `tui.json`. - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "leader_timeout": 2000, - "keybinds": { - "leader": "ctrl+x", - "app_exit": "ctrl+c,ctrl+d,<leader>q", - "app_debug": "none", - "app_console": "none", - "app_heap_snapshot": "none", - "app_toggle_animations": "none", - "app_toggle_file_context": "none", - "app_toggle_diffwrap": "none", - "app_toggle_paste_summary": "none", - "app_toggle_session_directory_filter": "none", - "command_list": "ctrl+p", - "help_show": "none", - "docs_open": "none", - - "editor_open": "<leader>e", - "theme_list": "<leader>t", - "theme_switch_mode": "none", - "theme_mode_lock": "none", - "sidebar_toggle": "<leader>b", - "scrollbar_toggle": "none", - "status_view": "<leader>s", - - "session_export": "<leader>x", - "session_copy": "none", - "session_move": "none", - "session_new": "<leader>n", - "session_list": "<leader>l", - "session_timeline": "<leader>g", - "session_fork": "none", - "session_rename": "ctrl+r", - "session_delete": "ctrl+d", - "session_share": "none", - "session_unshare": "none", - "session_interrupt": "escape", - "session_compact": "<leader>c", - "session_toggle_timestamps": "none", - "session_toggle_generic_tool_output": "none", - "session_child_first": "<leader>down", - "session_child_cycle": "right", - "session_child_cycle_reverse": "left", - "session_parent": "up", - - "stash_delete": "ctrl+d", - "model_provider_list": "ctrl+a", - "model_favorite_toggle": "ctrl+f", - "model_list": "<leader>m", - "model_cycle_recent": "f2", - "model_cycle_recent_reverse": "shift+f2", - "model_cycle_favorite": "none", - "model_cycle_favorite_reverse": "none", - "mcp_list": "none", - "provider_connect": "none", - "console_org_switch": "none", - "agent_list": "<leader>a", - "agent_cycle": "tab", - "agent_cycle_reverse": "shift+tab", - "variant_cycle": "ctrl+t", - "variant_list": "none", - - "messages_page_up": "pageup,ctrl+alt+b", - "messages_page_down": "pagedown,ctrl+alt+f", - "messages_line_up": "ctrl+alt+y", - "messages_line_down": "ctrl+alt+e", - "messages_half_page_up": "ctrl+alt+u", - "messages_half_page_down": "ctrl+alt+d", - "messages_first": "ctrl+g,home", - "messages_last": "ctrl+alt+g,end", - "messages_next": "none", - "messages_previous": "none", - "messages_last_user": "none", - "messages_copy": "<leader>y", - "messages_undo": "<leader>u", - "messages_redo": "<leader>r", - "messages_toggle_conceal": "<leader>h", - "tool_details": "none", - "display_thinking": "none", - - "prompt_submit": "none", - "prompt_editor_context_clear": "none", - "prompt_skills": "none", - "prompt_stash": "none", - "prompt_stash_pop": "none", - "prompt_stash_list": "none", - "workspace_set": "none", - - "input_clear": "ctrl+c", - "input_paste": { - "key": "ctrl+v", - "preventDefault": false - }, - "input_submit": "return", - "input_newline": "shift+return,ctrl+return,alt+return,ctrl+j", - "input_move_left": "left,ctrl+b", - "input_move_right": "right,ctrl+f", - "input_move_up": "up", - "input_move_down": "down", - "input_select_left": "shift+left", - "input_select_right": "shift+right", - "input_select_up": "shift+up", - "input_select_down": "shift+down", - "input_line_home": "ctrl+a", - "input_line_end": "ctrl+e", - "input_select_line_home": "ctrl+shift+a", - "input_select_line_end": "ctrl+shift+e", - "input_visual_line_home": "alt+a", - "input_visual_line_end": "alt+e", - "input_select_visual_line_home": "alt+shift+a", - "input_select_visual_line_end": "alt+shift+e", - "input_buffer_home": "home", - "input_buffer_end": "end", - "input_select_buffer_home": "shift+home", - "input_select_buffer_end": "shift+end", - "input_delete_line": "ctrl+shift+d", - "input_delete_to_line_end": "ctrl+k", - "input_delete_to_line_start": "ctrl+u", - "input_backspace": "backspace,shift+backspace", - "input_delete": "ctrl+d,delete,shift+delete", - "input_undo": "ctrl+-,super+z", - "input_redo": "ctrl+.,super+shift+z", - "input_word_forward": "alt+f,alt+right,ctrl+right", - "input_word_backward": "alt+b,alt+left,ctrl+left", - "input_select_word_forward": "alt+shift+f,alt+shift+right", - "input_select_word_backward": "alt+shift+b,alt+shift+left", - "input_delete_word_forward": "alt+d,alt+delete,ctrl+delete", - "input_delete_word_backward": "ctrl+w,ctrl+backspace,alt+backspace", - "input_select_all": "super+a", - "history_previous": "up", - "history_next": "down", - - "dialog.select.prev": "up,ctrl+p", - "dialog.select.next": "down,ctrl+n", - "dialog.select.page_up": "pageup", - "dialog.select.page_down": "pagedown", - "dialog.select.home": "home", - "dialog.select.end": "end", - "dialog.select.submit": "return", - "dialog.prompt.submit": "return", - "dialog.mcp.toggle": "space", - "prompt.autocomplete.prev": "up,ctrl+p", - "prompt.autocomplete.next": "down,ctrl+n", - "prompt.autocomplete.hide": "escape", - "prompt.autocomplete.select": "return", - "prompt.autocomplete.complete": "tab", - "permission.prompt.fullscreen": "ctrl+f", - "plugins.toggle": "space", - "dialog.plugins.install": "shift+i", - - "terminal_suspend": "ctrl+z", - "terminal_title_toggle": "none", - "tips_toggle": "<leader>h", - "plugin_manager": "none", - "plugin_install": "none", - - "which_key_toggle": "ctrl+alt+k", - "which_key_layout_toggle": "ctrl+alt+shift+k", - "which_key_pending_toggle": "ctrl+alt+shift+p", - "which_key_group_previous": "ctrl+alt+left,ctrl+alt+[", - "which_key_group_next": "ctrl+alt+right,ctrl+alt+]", - "which_key_scroll_up": "ctrl+alt+up,ctrl+alt+p", - "which_key_scroll_down": "ctrl+alt+down,ctrl+alt+n", - "which_key_page_up": "ctrl+alt+pageup", - "which_key_page_down": "ctrl+alt+pagedown", - "which_key_home": "ctrl+alt+home", - "which_key_end": "ctrl+alt+end" - } -} -``` - -:::note -On Windows, the defaults for `input_undo` and `terminal_suspend` are different: - -- `input_undo` defaults to `ctrl+z,ctrl+-,super+z` when it is not explicitly configured. The `ctrl+z` binding is added because Windows terminals do not support POSIX suspend. -- `terminal_suspend` is forced to `none` because native Windows terminals do not support POSIX suspend. - ::: - ---- - -## Leader Key - -OpenCode uses a `leader` key for many keybinds. This avoids conflicts in your terminal. - -By default, `ctrl+x` is the leader key and many actions require you to first press the leader key and then the shortcut. For example, to start a new session you first press `ctrl+x` and then press `n`. - -You don't need to use a leader key for your keybinds but we recommend doing so. - -Some navigation keybinds intentionally do not use the leader key by default. For subagent sessions, the defaults are `session_child_first` = `<leader>down`, `session_child_cycle` = `right`, `session_child_cycle_reverse` = `left`, and `session_parent` = `up`. - -`leader_timeout` controls how long OpenCode waits for the next key after the leader key. It defaults to `2000` milliseconds. - ---- - -## Binding Values - -A string can contain one shortcut or multiple comma-separated shortcuts. You can also use an array for multiple shortcuts. - -For advanced cases, use an object with `key`, `event`, `preventDefault`, or `fallthrough`. - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "keybinds": { - "messages_copy": ["<leader>y", "ctrl+shift+c"], - "input_paste": { - "key": "ctrl+v", - "preventDefault": false - } - } -} -``` - ---- - -## Disable Keybind - -You can disable a keybind by adding the key to `tui.json` with a value of `"none"` or `false`. - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "keybinds": { - "session_compact": "none" - } -} -``` - ---- - -## Desktop Prompt Shortcuts - -The OpenCode desktop app prompt input supports common Readline/Emacs-style shortcuts for editing text. These are built-in and currently not configurable via `opencode.json`. - -| Shortcut | Action | -| -------- | ---------------------------------------- | -| `ctrl+a` | Move to start of current line | -| `ctrl+e` | Move to end of current line | -| `ctrl+b` | Move cursor back one character | -| `ctrl+f` | Move cursor forward one character | -| `alt+b` | Move cursor back one word | -| `alt+f` | Move cursor forward one word | -| `ctrl+d` | Delete character under cursor | -| `ctrl+k` | Kill to end of line | -| `ctrl+u` | Kill to start of line | -| `ctrl+w` | Kill previous word | -| `alt+d` | Kill next word | -| `ctrl+t` | Transpose characters | -| `ctrl+g` | Cancel popovers / abort running response | - ---- - -## Shift+Enter - -Some terminals don't send modifier keys with Enter by default. You may need to configure your terminal to send `Shift+Enter` as an escape sequence. - -### Windows Terminal - -Open your `settings.json` at: - -``` -%LOCALAPPDATA%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json -``` - -Add this to the root-level `actions` array: - -```json -"actions": [ - { - "command": { - "action": "sendInput", - "input": "\u001b[13;2u" - }, - "id": "User.sendInput.ShiftEnterCustom" - } -] -``` - -Add this to the root-level `keybindings` array: - -```json -"keybindings": [ - { - "keys": "shift+enter", - "id": "User.sendInput.ShiftEnterCustom" - } -] -``` - -Save the file and restart Windows Terminal or open a new tab. diff --git a/.opencode/skills/opencode-docs/docs/lsp.mdx b/.opencode/skills/opencode-docs/docs/lsp.mdx deleted file mode 100644 index b9428e5..0000000 --- a/.opencode/skills/opencode-docs/docs/lsp.mdx +++ /dev/null @@ -1,214 +0,0 @@ ---- -title: LSP Servers -description: OpenCode integrates with your LSP servers. ---- - -OpenCode can integrate with Language Server Protocol (LSP) servers to use diagnostics as feedback for the agent. - ---- - -## Built-in - -OpenCode comes with several built-in LSP servers for popular languages: - -| LSP Server | Extensions | Requirements | -| ------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------ | -| astro | .astro | Auto-installs for Astro projects | -| bash | .sh, .bash, .zsh, .ksh | Auto-installs bash-language-server | -| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects | -| csharp | .cs, .csx | `.NET SDK` installed | -| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` command available | -| dart | .dart | `dart` command available | -| deno | .ts, .tsx, .js, .jsx, .mjs | `deno` command available (auto-detects deno.json/deno.jsonc) | -| elixir-ls | .ex, .exs | `elixir` command available | -| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` dependency in project | -| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` installed | -| gleam | .gleam | `gleam` command available | -| gopls | .go | `go` command available | -| hls | .hs, .lhs | `haskell-language-server-wrapper` command available | -| jdtls | .java | `Java SDK (version 21+)` installed | -| julials | .jl | `julia` and `LanguageServer.jl` installed | -| kotlin-ls | .kt, .kts | Auto-installs for Kotlin projects | -| lua-ls | .lua | Auto-installs for Lua projects | -| nixd | .nix | `nixd` command available | -| ocaml-lsp | .ml, .mli | `ocamllsp` command available | -| oxlint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | `oxlint` dependency in project | -| php intelephense | .php | Auto-installs for PHP projects | -| prisma | .prisma | `prisma` command available | -| pyright | .py, .pyi | `pyright` dependency installed | -| razor | .razor, .cshtml | `.NET SDK` and VS Code C# extension installed | -| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | `ruby` and `gem` commands available | -| rust | .rs | `rust-analyzer` command available | -| sourcekit-lsp | .swift, .objc, .objcpp | `swift` installed (`xcode` on macOS) | -| svelte | .svelte | Auto-installs for Svelte projects | -| terraform | .tf, .tfvars | Auto-installs from GitHub releases | -| tinymist | .typ, .typc | Auto-installs from GitHub releases | -| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependency in project | -| vue | .vue | Auto-installs for Vue projects | -| yaml-ls | .yaml, .yml | Auto-installs Red Hat yaml-language-server | -| zls | .zig, .zon | `zig` command available | - -LSP is disabled by default. When enabled, servers start when one of the above file extensions is detected and the requirements are met. - -:::note -You can disable automatic LSP server downloads by setting the `OPENCODE_DISABLE_LSP_DOWNLOAD` environment variable to `true`. -::: - ---- - -## How It Works - -When LSP is enabled and opencode opens a file, it: - -1. Checks the file extension against all enabled LSP servers. -2. Starts the appropriate LSP server if not already running. - ---- - -## Best Practices - -LSP can help the agent find and fix issues by providing diagnostics from language servers. This is useful in some projects, but it is not always a net positive. - -Language servers can get out of sync, use significant memory, vary by version or project, and slow down agent workflows. In many projects it is better to have the agent run lint, typecheck, or other diagnostic CLI tools directly, so errors are fed back into the agent loop without those tradeoffs. Document those commands in instruction files such as `AGENTS.md` or skills so the agent knows what to run. Enable LSP when your project benefits from additional language-server feedback. - ---- - -## Configure - -You can enable and customize LSP servers through the `lsp` section in your opencode config. - -To enable all built-in LSP servers, set `lsp` to `true`. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "lsp": true -} -``` - -Use an object to keep built-ins enabled while configuring overrides or custom servers. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "lsp": {} -} -``` - -Each configured LSP server entry supports the following: - -Server entries need `command` unless they only disable a server. - -| Property | Type | Description | -| ---------------- | -------- | ------------------------------------------------- | -| `disabled` | boolean | Set this to `true` to disable the LSP server | -| `command` | string[] | The command to start the LSP server | -| `extensions` | string[] | File extensions this LSP server should handle | -| `env` | object | Environment variables to set when starting server | -| `initialization` | object | Initialization options to send to the LSP server | - -Let's look at some examples. - ---- - -### Environment variables - -Use the `env` property to set environment variables when starting the LSP server: - -```json title="opencode.json" {5-8} -{ - "$schema": "https://opencode.ai/config.json", - "lsp": { - "rust": { - "command": ["rust-analyzer"], - "env": { - "RUST_LOG": "debug" - } - } - } -} -``` - ---- - -### Initialization options - -Use the `initialization` property to pass initialization options to the LSP server. These are server-specific settings sent during the LSP `initialize` request: - -```json title="opencode.json" {5-13} -{ - "$schema": "https://opencode.ai/config.json", - "lsp": { - "custom-lsp": { - "command": ["custom-lsp-server", "--stdio"], - "extensions": [".custom"], - "initialization": { - "preferences": { - "importModuleSpecifierPreference": "relative" - } - } - } - } -} -``` - -:::note -Initialization options vary by LSP server. Check your LSP server's documentation for available options. -::: - ---- - -### Disabling LSP servers - -If `lsp` is omitted, all LSP servers are disabled. To disable all LSP servers after another config enabled them, set `lsp` to `false`: - -```json title="opencode.json" {3} -{ - "$schema": "https://opencode.ai/config.json", - "lsp": false -} -``` - -To disable a **specific** LSP server, set `disabled` to `true`: - -```json title="opencode.json" {5} -{ - "$schema": "https://opencode.ai/config.json", - "lsp": { - "typescript": { - "disabled": true - } - } -} -``` - ---- - -### Custom LSP servers - -You can add custom LSP servers by specifying the command and file extensions: - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "lsp": { - "custom-lsp": { - "command": ["custom-lsp-server", "--stdio"], - "extensions": [".custom"] - } - } -} -``` - ---- - -## Additional Information - -### PHP Intelephense - -PHP Intelephense offers premium features through a license key. You can provide a license key by placing (only) the key in a text file at: - -- On macOS/Linux: `$HOME/intelephense/license.txt` -- On Windows: `%USERPROFILE%/intelephense/license.txt` - -The file should contain only the license key with no additional content. diff --git a/.opencode/skills/opencode-docs/docs/mcp-servers.mdx b/.opencode/skills/opencode-docs/docs/mcp-servers.mdx deleted file mode 100644 index 215938e..0000000 --- a/.opencode/skills/opencode-docs/docs/mcp-servers.mdx +++ /dev/null @@ -1,512 +0,0 @@ ---- -title: MCP servers -description: Add local and remote MCP tools. ---- - -You can add external tools to OpenCode using the _Model Context Protocol_, or MCP. OpenCode supports both local and remote servers. - -Once added, MCP tools are automatically available to the LLM alongside built-in tools. - ---- - -#### Caveats - -When you use an MCP server, it adds to the context. This can quickly add up if you have a lot of tools. So we recommend being careful with which MCP servers you use. - -:::tip -MCP servers add to your context, so you want to be careful with which ones you enable. -::: - -Certain MCP servers, like the GitHub MCP server, tend to add a lot of tokens and can easily exceed the context limit. - ---- - -## Enable - -You can define MCP servers in your [OpenCode Config](https://opencode.ai/docs/config/) under `mcp`. Add each MCP with a unique name. You can refer to that MCP by name when prompting the LLM. - -```jsonc title="opencode.jsonc" {6} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "name-of-mcp-server": { - // ... - "enabled": true, - }, - "name-of-other-mcp-server": { - // ... - }, - }, -} -``` - -You can also disable a server by setting `enabled` to `false`. This is useful if you want to temporarily disable a server without removing it from your config. - ---- - -### Overriding remote defaults - -Organizations can provide default MCP servers via their `.well-known/opencode` endpoint. These servers may be disabled by default, allowing users to opt-in to the ones they need. - -To enable a specific server from your organization's remote config, add it to your local config with `enabled: true`: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "jira": { - "type": "remote", - "url": "https://jira.example.com/mcp", - "enabled": true - } - } -} -``` - -Your local config values override the remote defaults. See [config precedence](/docs/config#precedence-order) for more details. - ---- - -## Local - -Add local MCP servers using `type` to `"local"` within the MCP object. - -```jsonc title="opencode.jsonc" {15} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-local-mcp-server": { - "type": "local", - // Or ["bun", "x", "my-mcp-command"] - "command": ["npx", "-y", "my-mcp-command"], - "enabled": true, - "environment": { - "MY_ENV_VAR": "my_env_var_value", - }, - }, - }, -} -``` - -The command is how the local MCP server is started. You can also pass in a list of environment variables as well. - -For example, here's how you can add the test [`@modelcontextprotocol/server-everything`](https://www.npmjs.com/package/@modelcontextprotocol/server-everything) MCP server. - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "mcp_everything": { - "type": "local", - "command": ["npx", "-y", "@modelcontextprotocol/server-everything"], - }, - }, -} -``` - -And to use it I can add `use the mcp_everything tool` to my prompts. - -```txt "mcp_everything" -use the mcp_everything tool to add the number 3 and 4 -``` - ---- - -#### Options - -Here are all the options for configuring a local MCP server. - -| Option | Type | Required | Description | -| ------------- | ------- | -------- | ---------------------------------------------------------------------------------------- | -| `type` | String | Y | Type of MCP server connection, must be `"local"`. | -| `command` | Array | Y | Command and arguments to run the MCP server. | -| `cwd` | String | | Working directory for the MCP server process. Relative paths resolve from the workspace. | -| `environment` | Object | | Environment variables to set when running the server. | -| `enabled` | Boolean | | Enable or disable the MCP server on startup. | -| `timeout` | Number | | Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds). | - ---- - -## Remote - -Add remote MCP servers by setting `type` to `"remote"`. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-remote-mcp": { - "type": "remote", - "url": "https://my-mcp-server.com", - "enabled": true, - "headers": { - "Authorization": "Bearer MY_API_KEY" - } - } - } -} -``` - -The `url` is the URL of the remote MCP server and with the `headers` option you can pass in a list of headers. - ---- - -#### Options - -| Option | Type | Required | Description | -| --------- | ------- | -------- | ----------------------------------------------------------------------------------- | -| `type` | String | Y | Type of MCP server connection, must be `"remote"`. | -| `url` | String | Y | URL of the remote MCP server. | -| `enabled` | Boolean | | Enable or disable the MCP server on startup. | -| `headers` | Object | | Headers to send with the request. | -| `oauth` | Object | | OAuth authentication configuration. See [OAuth](#oauth) section below. | -| `timeout` | Number | | Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds). | - ---- - -## OAuth - -OpenCode automatically handles OAuth authentication for remote MCP servers. When a server requires authentication, OpenCode will: - -1. Detect the 401 response and initiate the OAuth flow -2. Use **Dynamic Client Registration (RFC 7591)** if supported by the server -3. Store tokens securely for future requests - ---- - -### Automatic - -For most OAuth-enabled MCP servers, no special configuration is needed. Just configure the remote server: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-oauth-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp" - } - } -} -``` - -If the server requires authentication, OpenCode will prompt you to authenticate when you first try to use it. If not, you can [manually trigger the flow](#authenticating) with `opencode mcp auth <server-name>`. - ---- - -### Pre-registered - -If you have client credentials from the MCP server provider, you can configure them: - -```json title="opencode.json" {7-11} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-oauth-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp", - "oauth": { - "clientId": "{env:MY_MCP_CLIENT_ID}", - "clientSecret": "{env:MY_MCP_CLIENT_SECRET}", - "scope": "tools:read tools:execute" - } - } - } -} -``` - ---- - -### Authenticating - -You can manually trigger authentication or manage credentials. - -Authenticate with a specific MCP server: - -```bash -opencode mcp auth my-oauth-server -``` - -List all MCP servers and their auth status: - -```bash -opencode mcp list -``` - -Remove stored credentials: - -```bash -opencode mcp logout my-oauth-server -``` - -The `mcp auth` command will open your browser for authorization. After you authorize, OpenCode will store the tokens securely in `~/.local/share/opencode/mcp-auth.json`. - ---- - -#### Disabling OAuth - -If you want to disable automatic OAuth for a server (e.g., for servers that use API keys instead), set `oauth` to `false`: - -```json title="opencode.json" {7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-api-key-server": { - "type": "remote", - "url": "https://mcp.example.com/mcp", - "oauth": false, - "headers": { - "Authorization": "Bearer {env:MY_API_KEY}" - } - } - } -} -``` - ---- - -#### OAuth Options - -| Option | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------------- | -| `oauth` | Object \| false | OAuth config object, or `false` to disable OAuth auto-detection. | -| `clientId` | String | OAuth client ID. If not provided, dynamic client registration will be attempted. | -| `clientSecret` | String | OAuth client secret, if required by the authorization server. | -| `scope` | String | OAuth scopes to request during authorization. | - -#### Debugging - -If a remote MCP server is failing to authenticate, you can diagnose issues with: - -```bash -# View auth status for all OAuth-capable servers -opencode mcp auth list - -# Debug connection and OAuth flow for a specific server -opencode mcp debug my-oauth-server -``` - -The `mcp debug` command shows the current auth status, tests HTTP connectivity, and attempts the OAuth discovery flow. - ---- - -## Manage - -Your MCPs are available as tools in OpenCode, alongside built-in tools. So you can manage them through the OpenCode config like any other tool. - ---- - -### Global - -This means that you can enable or disable them globally. - -```json title="opencode.json" {14} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp-foo": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-foo"] - }, - "my-mcp-bar": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-bar"] - } - }, - "tools": { - "my-mcp-foo": false - } -} -``` - -We can also use a glob pattern to disable all matching MCPs. - -```json title="opencode.json" {14} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp-foo": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-foo"] - }, - "my-mcp-bar": { - "type": "local", - "command": ["bun", "x", "my-mcp-command-bar"] - } - }, - "tools": { - "my-mcp*": false - } -} -``` - -Here we are using the glob pattern `my-mcp*` to disable all MCPs. - ---- - -### Per agent - -If you have a large number of MCP servers you may want to only enable them per agent and disable them globally. To do this: - -1. Disable it as a tool globally. -2. In your [agent config](/docs/agents#tools), enable the MCP server as a tool. - -```json title="opencode.json" {11, 14-18} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "my-mcp": { - "type": "local", - "command": ["bun", "x", "my-mcp-command"], - "enabled": true - } - }, - "tools": { - "my-mcp*": false - }, - "agent": { - "my-agent": { - "tools": { - "my-mcp*": true - } - } - } -} -``` - ---- - -#### Glob patterns - -The glob pattern uses simple regex globbing patterns: - -- `*` matches zero or more of any character (e.g., `"my-mcp*"` matches `my-mcp_search`, `my-mcp_list`, etc.) -- `?` matches exactly one character -- All other characters match literally - -:::note -MCP server tools are registered with server name as prefix, so to disable all tools for a server simply use: - -``` -"mymcpservername_*": false -``` - -::: - ---- - -## Examples - -Below are examples of some common MCP servers. You can submit a PR if you want to document other servers. - ---- - -### Sentry - -Add the [Sentry MCP server](https://mcp.sentry.dev) to interact with your Sentry projects and issues. - -```json title="opencode.json" {4-8} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "sentry": { - "type": "remote", - "url": "https://mcp.sentry.dev/mcp", - "oauth": {} - } - } -} -``` - -After adding the configuration, authenticate with Sentry: - -```bash -opencode mcp auth sentry -``` - -This will open a browser window to complete the OAuth flow and connect OpenCode to your Sentry account. - -Once authenticated, you can use Sentry tools in your prompts to query issues, projects, and error data. - -```txt "use sentry" -Show me the latest unresolved issues in my project. use sentry -``` - ---- - -### Context7 - -Add the [Context7 MCP server](https://github.com/upstash/context7) to search through docs. - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp" - } - } -} -``` - -If you have signed up for a free account, you can use your API key and get higher rate-limits. - -```json title="opencode.json" {7-9} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - "headers": { - "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" - } - } - } -} -``` - -Here we are assuming that you have the `CONTEXT7_API_KEY` environment variable set. - -Add `use context7` to your prompts to use Context7 MCP server. - -```txt "use context7" -Configure a Cloudflare Worker script to cache JSON API responses for five minutes. use context7 -``` - -Alternatively, you can add something like this to your [AGENTS.md](/docs/rules/). - -```md title="AGENTS.md" -When you need to search docs, use `context7` tools. -``` - ---- - -### Grep by Vercel - -Add the [Grep by Vercel](https://grep.app) MCP server to search through code snippets on GitHub. - -```json title="opencode.json" {4-7} -{ - "$schema": "https://opencode.ai/config.json", - "mcp": { - "gh_grep": { - "type": "remote", - "url": "https://mcp.grep.app" - } - } -} -``` - -Since we named our MCP server `gh_grep`, you can add `use the gh_grep tool` to your prompts to get the agent to use it. - -```txt "use the gh_grep tool" -What's the right way to set a custom domain in an SST Astro component? use the gh_grep tool -``` - -Alternatively, you can add something like this to your [AGENTS.md](/docs/rules/). - -```md title="AGENTS.md" -If you are unsure how to do something, use `gh_grep` to search code examples from GitHub. -``` diff --git a/.opencode/skills/opencode-docs/docs/models.mdx b/.opencode/skills/opencode-docs/docs/models.mdx deleted file mode 100644 index b135e75..0000000 --- a/.opencode/skills/opencode-docs/docs/models.mdx +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: Models -description: Configuring an LLM provider and model. ---- - -OpenCode uses the [AI SDK](https://ai-sdk.dev/) and [Models.dev](https://models.dev) to support **75+ LLM providers** and it supports running local models. - ---- - -## Providers - -Most popular providers are preloaded by default. If you've added the credentials for a provider through the `/connect` command, they'll be available when you start OpenCode. - -Learn more about [providers](/docs/providers). - ---- - -## Select a model - -Once you've configured your provider you can select the model you want by typing in: - -```bash frame="none" -/models -``` - ---- - -## Recommended models - -There are a lot of models out there, with new models coming out every week. - -:::tip -Consider using one of the models we recommend. -::: - -However, there are only a few of them that are good at both generating code and tool calling. - -Here are several models that work well with OpenCode, in no particular order. (This is not an exhaustive list nor is it necessarily up to date): - -- GPT 5.2 -- GPT 5.1 Codex -- Claude Opus 4.5 -- Claude Sonnet 4.5 -- Minimax M2.1 -- Gemini 3 Pro - ---- - -## Set a default - -To set one of these as the default model, you can set the `model` key in your -OpenCode config. - -```json title="opencode.json" {3} -{ - "$schema": "https://opencode.ai/config.json", - "model": "lmstudio/google/gemma-3n-e4b" -} -``` - -Here the full ID is `provider_id/model_id`. For example, if you're using [OpenCode Zen](/docs/zen), you would use `opencode/gpt-5.1-codex` for GPT 5.1 Codex. - -If you've configured a [custom provider](/docs/providers#custom), the `provider_id` is key from the `provider` part of your config, and the `model_id` is the key from `provider.models`. - ---- - -## Configure models - -You can globally configure a model's options through the config. - -```jsonc title="opencode.jsonc" {7-12,19-24} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "openai": { - "models": { - "gpt-5": { - "options": { - "reasoningEffort": "high", - "textVerbosity": "low", - "reasoningSummary": "auto", - "include": ["reasoning.encrypted_content"], - }, - }, - }, - }, - "anthropic": { - "models": { - "claude-sonnet-4-5-20250929": { - "options": { - "thinking": { - "type": "enabled", - "budgetTokens": 16000, - }, - }, - }, - }, - }, - }, -} -``` - -Here we're configuring global settings for two built-in models: `gpt-5` when accessed via the `openai` provider, and `claude-sonnet-4-20250514` when accessed via the `anthropic` provider. -The built-in provider and model names can be found on [Models.dev](https://models.dev). - -You can also configure these options for any agents that you are using. The agent config overrides any global options here. [Learn more](/docs/agents/#additional). - -You can also define custom variants that extend built-in ones. Variants let you configure different settings for the same model without creating duplicate entries: - -```jsonc title="opencode.jsonc" {6-21} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "opencode": { - "models": { - "gpt-5": { - "variants": { - "high": { - "reasoningEffort": "high", - "textVerbosity": "low", - "reasoningSummary": "auto", - }, - "low": { - "reasoningEffort": "low", - "textVerbosity": "low", - "reasoningSummary": "auto", - }, - }, - }, - }, - }, - }, -} -``` - ---- - -## Variants - -Many models support multiple variants with different configurations. OpenCode ships with built-in default variants for popular providers. - -### Built-in variants - -OpenCode ships with default variants for many providers: - -**Anthropic**: - -- `high` - High thinking budget (default) -- `max` - Maximum thinking budget - -**OpenAI**: - -Varies by model but roughly: - -- `none` - No reasoning -- `minimal` - Minimal reasoning effort -- `low` - Low reasoning effort -- `medium` - Medium reasoning effort -- `high` - High reasoning effort -- `xhigh` - Extra high reasoning effort - -**Google**: - -- `low` - Lower effort/token budget -- `high` - Higher effort/token budget - -:::tip -This list is not comprehensive. Many other providers have built-in defaults too. -::: - -### Custom variants - -You can override existing variants or add your own: - -```jsonc title="opencode.jsonc" {7-18} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "openai": { - "models": { - "gpt-5": { - "variants": { - "thinking": { - "reasoningEffort": "high", - "textVerbosity": "low", - }, - "fast": { - "disabled": true, - }, - }, - }, - }, - }, - }, -} -``` - -### Cycle variants - -Use the keybind `variant_cycle` to quickly switch between variants. [Learn more](/docs/keybinds). - ---- - -## Loading models - -When OpenCode starts up, it checks for models in the following priority order: - -1. The `--model` or `-m` command line flag. The format is the same as in the config file: `provider_id/model_id`. - -2. The model list in the OpenCode config. - - ```json title="opencode.json" - { - "$schema": "https://opencode.ai/config.json", - "model": "anthropic/claude-sonnet-4-20250514" - } - ``` - - The format here is `provider/model`. - -3. The last used model. - -4. The first model using an internal priority. diff --git a/.opencode/skills/opencode-docs/docs/network.mdx b/.opencode/skills/opencode-docs/docs/network.mdx deleted file mode 100644 index 2fcfd9f..0000000 --- a/.opencode/skills/opencode-docs/docs/network.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Network -description: Configure proxies and custom certificates. ---- - -OpenCode supports standard proxy environment variables and custom certificates for enterprise network environments. - ---- - -## Proxy - -OpenCode respects standard proxy environment variables. - -```bash -# HTTPS proxy (recommended) -export HTTPS_PROXY=https://proxy.example.com:8080 - -# HTTP proxy (if HTTPS not available) -export HTTP_PROXY=http://proxy.example.com:8080 - -# Bypass proxy for local server (required) -export NO_PROXY=localhost,127.0.0.1 -``` - -:::caution -The TUI communicates with a local HTTP server. You must bypass the proxy for this connection to prevent routing loops. -::: - -You can configure the server's port and hostname using [CLI flags](/docs/cli#run). - ---- - -### Authenticate - -If your proxy requires basic authentication, include credentials in the URL. - -```bash -export HTTPS_PROXY=http://username:password@proxy.example.com:8080 -``` - -:::caution -Avoid hardcoding passwords. Use environment variables or secure credential storage. -::: - -For proxies requiring advanced authentication like NTLM or Kerberos, consider using an LLM Gateway that supports your authentication method. - ---- - -## Custom certificates - -If your enterprise uses custom CAs for HTTPS connections, configure OpenCode to trust them. - -```bash -export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem -``` - -This works for both proxy connections and direct API access. diff --git a/.opencode/skills/opencode-docs/docs/permissions.mdx b/.opencode/skills/opencode-docs/docs/permissions.mdx deleted file mode 100644 index ccc4034..0000000 --- a/.opencode/skills/opencode-docs/docs/permissions.mdx +++ /dev/null @@ -1,256 +0,0 @@ ---- -title: Permissions -description: Control which actions require approval to run. ---- - -OpenCode uses the `permission` config to decide whether a given action should run automatically, prompt you, or be blocked. - -As of `v1.1.1`, the legacy `tools` boolean config is deprecated and has been merged into `permission`. The old `tools` config is still supported for backwards compatibility. - ---- - -## Actions - -Each permission rule resolves to one of: - -- `"allow"` — run without approval -- `"ask"` — prompt for approval -- `"deny"` — block the action - ---- - -## Auto mode - -Start OpenCode with `--auto` to automatically approve permission requests that are not explicitly denied. - -```bash -opencode --auto -``` - -You can also use auto mode with [`opencode run`](/docs/cli#run). - -```bash -opencode run --auto "Refactor this module" -``` - -Explicit `"deny"` rules are still enforced. Auto mode only changes requests that would otherwise ask for approval. - -In the TUI, open the command palette and select **Enable auto-approve permissions** or **Disable auto-approve permissions** to change modes. When auto mode is active, the prompt displays a muted `auto` indicator next to the current agent. - ---- - -## Configuration - -You can set permissions globally (with `*`), and override specific tools. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "*": "ask", - "bash": "allow", - "edit": "deny" - } -} -``` - -You can also set all permissions at once: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": "allow" -} -``` - ---- - -## Granular Rules (Object Syntax) - -For most permissions, you can use an object to apply different actions based on the tool input. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "bash": { - "*": "ask", - "git *": "allow", - "npm *": "allow", - "rm *": "deny", - "grep *": "allow" - }, - "edit": { - "*": "deny", - "packages/web/src/content/docs/*.mdx": "allow" - } - } -} -``` - -Rules are evaluated by pattern match, with the **last matching rule winning**. A common pattern is to put the catch-all `"*"` rule first, and more specific rules after it. - -### Wildcards - -Permission patterns use simple wildcard matching: - -- `*` matches zero or more of any character -- `?` matches exactly one character -- All other characters match literally - -### Home Directory Expansion - -You can use `~` or `$HOME` at the start of a pattern to reference your home directory. This is particularly useful for [`external_directory`](#external-directories) rules. - -- `~/projects/*` -> `/Users/username/projects/*` -- `$HOME/projects/*` -> `/Users/username/projects/*` -- `~` -> `/Users/username` - -### External Directories - -Use `external_directory` to allow tool calls that touch paths outside the working directory where OpenCode was started. This applies to any tool that takes a path as input (for example `read`, `edit`, `glob`, `grep`, and many `bash` commands). - -Home expansion (like `~/...`) only affects how a pattern is written. It does not make an external path part of the current workspace, so paths outside the working directory must still be allowed via `external_directory`. - -For example, this allows access to everything under `~/projects/personal/`: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "external_directory": { - "~/projects/personal/**": "allow" - } - } -} -``` - -Any directory allowed here inherits the same defaults as the current workspace. Since [`read` defaults to `allow`](#defaults), reads are also allowed for entries under `external_directory` unless overridden. Add explicit rules when a tool should be restricted in these paths, such as blocking edits while keeping reads: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "external_directory": { - "~/projects/personal/**": "allow" - }, - "edit": { - "~/projects/personal/**": "deny" - } - } -} -``` - -Keep the list focused on trusted paths, and layer extra allow or deny rules as needed for other tools (for example `bash`). - ---- - -## Available Permissions - -OpenCode permissions are keyed by tool name, plus a couple of safety guards: - -- `read` — reading a file (matches the file path) -- `edit` — all file modifications (covers `edit`, `write`, `patch`) -- `glob` — file globbing (matches the glob pattern) -- `grep` — content search (matches the regex pattern) -- `bash` — running shell commands (matches parsed commands like `git status --porcelain`) -- `task` — launching subagents (matches the subagent type) -- `skill` — loading a skill (matches the skill name) -- `lsp` — running LSP queries (currently non-granular) -- `question` — asking the user questions during execution -- `webfetch` — fetching a URL (matches the URL) -- `websearch` — web search (matches the query) -- `external_directory` — triggered when a tool touches paths outside the project working directory -- `doom_loop` — triggered when the same tool call repeats 3 times with identical input - ---- - -## Defaults - -If you don’t specify anything, OpenCode starts from permissive defaults: - -- Most permissions default to `"allow"`. -- `doom_loop` and `external_directory` default to `"ask"`. -- `read` is `"allow"`, but `.env` files are denied by default: - -```json title="opencode.json" -{ - "permission": { - "read": { - "*": "allow", - "*.env": "deny", - "*.env.*": "deny", - "*.env.example": "allow" - } - } -} -``` - ---- - -## What “Ask” Does - -When OpenCode prompts for approval, the UI offers three outcomes: - -- `once` — approve just this request -- `always` — approve future requests matching the suggested patterns (for the rest of the current OpenCode session) -- `reject` — deny the request - -The set of patterns that `always` would approve is provided by the tool (for example, bash approvals typically whitelist a safe command prefix like `git status*`). - ---- - -## Agents - -You can override permissions per agent. Agent permissions are merged with the global config, and agent rules take precedence. [Learn more](/docs/agents#permissions) about agent permissions. - -:::note -Refer to the [Granular Rules (Object Syntax)](#granular-rules-object-syntax) section above for more detailed pattern matching examples. -::: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "bash": { - "*": "ask", - "git *": "allow", - "git commit *": "deny", - "git push *": "deny", - "grep *": "allow" - } - }, - "agent": { - "build": { - "permission": { - "bash": { - "*": "ask", - "git *": "allow", - "git commit *": "ask", - "git push *": "deny", - "grep *": "allow" - } - } - } - } -} -``` - -You can also configure agent permissions in Markdown: - -```markdown title="~/.config/opencode/agents/review.md" ---- -description: Code review without edits -mode: subagent -permission: - edit: deny - bash: ask - webfetch: deny ---- - -Only analyze code and suggest changes. -``` - -:::tip -Use pattern matching for commands with arguments. `"grep *"` allows `grep pattern file.txt`, while `"grep"` alone would block it. Commands like `git status` work for default behavior but require explicit permission (like `"git status *"`) when arguments are passed. -::: diff --git a/.opencode/skills/opencode-docs/docs/plugins.mdx b/.opencode/skills/opencode-docs/docs/plugins.mdx deleted file mode 100644 index a8be798..0000000 --- a/.opencode/skills/opencode-docs/docs/plugins.mdx +++ /dev/null @@ -1,389 +0,0 @@ ---- -title: Plugins -description: Write your own plugins to extend OpenCode. ---- - -Plugins allow you to extend OpenCode by hooking into various events and customizing behavior. You can create plugins to add new features, integrate with external services, or modify OpenCode's default behavior. - -For examples, check out the [plugins](/docs/ecosystem#plugins) created by the community. - ---- - -## Use a plugin - -There are two ways to load plugins. - ---- - -### From local files - -Place JavaScript or TypeScript files in the plugin directory. - -- `.opencode/plugins/` - Project-level plugins -- `~/.config/opencode/plugins/` - Global plugins - -Files in these directories are automatically loaded at startup. - ---- - -### From npm - -Specify npm packages in your config file. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-helicone-session", "opencode-wakatime", "@my-org/custom-plugin"] -} -``` - -Both regular and scoped npm packages are supported. - -Browse available plugins in the [ecosystem](/docs/ecosystem#plugins). - ---- - -### How plugins are installed - -**npm plugins** are installed automatically using Bun at startup. Packages and their dependencies are cached in `~/.cache/opencode/node_modules/`. - -**Local plugins** are loaded directly from the plugin directory. To use external packages, you must create a `package.json` within your config directory (see [Dependencies](#dependencies)), or publish the plugin to npm and [add it to your config](/docs/config#plugins). - ---- - -### Load order - -Plugins are loaded from all sources and all hooks run in sequence. The load order is: - -1. Global config (`~/.config/opencode/opencode.json`) -2. Project config (`opencode.json`) -3. Global plugin directory (`~/.config/opencode/plugins/`) -4. Project plugin directory (`.opencode/plugins/`) - -Duplicate npm packages with the same name and version are loaded once. However, a local plugin and an npm plugin with similar names are both loaded separately. - ---- - -## Create a plugin - -A plugin is a **JavaScript/TypeScript module** that exports one or more plugin -functions. Each function receives a context object and returns a hooks object. - ---- - -### Dependencies - -Local plugins and custom tools can use external npm packages. Add a `package.json` to your config directory with the dependencies you need. - -```json title=".opencode/package.json" -{ - "dependencies": { - "shescape": "^2.1.0" - } -} -``` - -OpenCode runs `bun install` at startup to install these. Your plugins and tools can then import them. - -```ts title=".opencode/plugins/my-plugin.ts" -import { escape } from "shescape" - -export const MyPlugin = async (ctx) => { - return { - "tool.execute.before": async (input, output) => { - if (input.tool === "bash") { - output.args.command = escape(output.args.command) - } - }, - } -} -``` - ---- - -### Basic structure - -```js title=".opencode/plugins/example.js" -export const MyPlugin = async ({ project, client, $, directory, worktree }) => { - console.log("Plugin initialized!") - - return { - // Hook implementations go here - } -} -``` - -The plugin function receives: - -- `project`: The current project information. -- `directory`: The current working directory. -- `worktree`: The git worktree path. -- `client`: An opencode SDK client for interacting with the AI. -- `$`: Bun's [shell API](https://bun.com/docs/runtime/shell) for executing commands. - ---- - -### TypeScript support - -For TypeScript plugins, you can import types from the plugin package: - -```ts title="my-plugin.ts" {1} -import type { Plugin } from "@opencode-ai/plugin" - -export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree }) => { - return { - // Type-safe hook implementations - } -} -``` - ---- - -### Events - -Plugins can subscribe to events as seen below in the Examples section. Here is a list of the different events available. - -#### Command Events - -- `command.executed` - -#### File Events - -- `file.edited` -- `file.watcher.updated` - -#### Installation Events - -- `installation.updated` - -#### LSP Events - -- `lsp.client.diagnostics` -- `lsp.updated` - -#### Message Events - -- `message.part.removed` -- `message.part.updated` -- `message.removed` -- `message.updated` - -#### Permission Events - -- `permission.asked` -- `permission.replied` - -#### Server Events - -- `server.connected` - -#### Session Events - -- `session.created` -- `session.compacted` -- `session.deleted` -- `session.diff` -- `session.error` -- `session.idle` -- `session.status` -- `session.updated` - -#### Todo Events - -- `todo.updated` - -#### Shell Events - -- `shell.env` - -#### Tool Events - -- `tool.execute.after` -- `tool.execute.before` - -#### TUI Events - -- `tui.prompt.append` -- `tui.command.execute` -- `tui.toast.show` - ---- - -## Examples - -Here are some examples of plugins you can use to extend opencode. - ---- - -### Send notifications - -Send notifications when certain events occur: - -```js title=".opencode/plugins/notification.js" -export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => { - return { - event: async ({ event }) => { - // Send notification on session completion - if (event.type === "session.idle") { - await $`osascript -e 'display notification "Session completed!" with title "opencode"'` - } - }, - } -} -``` - -We are using `osascript` to run AppleScript on macOS. Here we are using it to send notifications. - -:::note -If you’re using the OpenCode desktop app, it can send system notifications automatically when a response is ready or when a session errors. -::: - ---- - -### .env protection - -Prevent opencode from reading `.env` files: - -```javascript title=".opencode/plugins/env-protection.js" -export const EnvProtection = async ({ project, client, $, directory, worktree }) => { - return { - "tool.execute.before": async (input, output) => { - if (input.tool === "read" && output.args.filePath.includes(".env")) { - throw new Error("Do not read .env files") - } - }, - } -} -``` - ---- - -### Inject environment variables - -Inject environment variables into all shell execution (AI tools and user terminals): - -```javascript title=".opencode/plugins/inject-env.js" -export const InjectEnvPlugin = async () => { - return { - "shell.env": async (input, output) => { - output.env.MY_API_KEY = "secret" - output.env.PROJECT_ROOT = input.cwd - }, - } -} -``` - ---- - -### Custom tools - -Plugins can also add custom tools to opencode: - -```ts title=".opencode/plugins/custom-tools.ts" -import { type Plugin, tool } from "@opencode-ai/plugin" - -export const CustomToolsPlugin: Plugin = async (ctx) => { - return { - tool: { - mytool: tool({ - description: "This is a custom tool", - args: { - foo: tool.schema.string(), - }, - async execute(args, context) { - const { directory, worktree } = context - return `Hello ${args.foo} from ${directory} (worktree: ${worktree})` - }, - }), - }, - } -} -``` - -The `tool` helper creates a custom tool that opencode can call. It takes a Zod schema function and returns a tool definition with: - -- `description`: What the tool does -- `args`: Zod schema for the tool's arguments -- `execute`: Function that runs when the tool is called - -Your custom tools will be available to opencode alongside built-in tools. - -:::note -If a plugin tool uses the same name as a built-in tool, the plugin tool takes precedence. -::: - ---- - -### Logging - -Use `client.app.log()` instead of `console.log` for structured logging: - -```ts title=".opencode/plugins/my-plugin.ts" -export const MyPlugin = async ({ client }) => { - await client.app.log({ - body: { - service: "my-plugin", - level: "info", - message: "Plugin initialized", - extra: { foo: "bar" }, - }, - }) -} -``` - -Levels: `debug`, `info`, `warn`, `error`. See [SDK documentation](https://opencode.ai/docs/sdk) for details. - ---- - -### Compaction hooks - -Customize the context included when a session is compacted: - -```ts title=".opencode/plugins/compaction.ts" -import type { Plugin } from "@opencode-ai/plugin" - -export const CompactionPlugin: Plugin = async (ctx) => { - return { - "experimental.session.compacting": async (input, output) => { - // Inject additional context into the compaction prompt - output.context.push(` -## Custom Context - -Include any state that should persist across compaction: -- Current task status -- Important decisions made -- Files being actively worked on -`) - }, - } -} -``` - -The `experimental.session.compacting` hook fires before the LLM generates a continuation summary. Use it to inject domain-specific context that the default compaction prompt would miss. - -You can also replace the compaction prompt entirely by setting `output.prompt`: - -```ts title=".opencode/plugins/custom-compaction.ts" -import type { Plugin } from "@opencode-ai/plugin" - -export const CustomCompactionPlugin: Plugin = async (ctx) => { - return { - "experimental.session.compacting": async (input, output) => { - // Replace the entire compaction prompt - output.prompt = ` -You are generating a continuation prompt for a multi-agent swarm session. - -Summarize: -1. The current task and its status -2. Which files are being modified and by whom -3. Any blockers or dependencies between agents -4. The next steps to complete the work - -Format as a structured prompt that a new agent can use to resume work. -` - }, - } -} -``` - -When `output.prompt` is set, it completely replaces the default compaction prompt. The `output.context` array is ignored in this case. diff --git a/.opencode/skills/opencode-docs/docs/policies.mdx b/.opencode/skills/opencode-docs/docs/policies.mdx deleted file mode 100644 index c2ada5d..0000000 --- a/.opencode/skills/opencode-docs/docs/policies.mdx +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: Policies -description: Control which configured resources OpenCode may use. ---- - -Policies control whether OpenCode may perform an action on a named resource. This feature is experimental and is configured with the `experimental.policies` array in `opencode.json`. - -Policies are separate from [permissions](/docs/permissions). Permissions control what tools can do during a session, while policies control whether OpenCode may use a resource such as an LLM provider. - ---- - -## Configuration - -Each policy statement has three fields: - -- `effect` - Either `"allow"` or `"deny"`. -- `action` - The operation being controlled. -- `resource` - The resource ID or wildcard pattern the statement applies to. - -For example, deny use of the `openai` provider: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "experimental": { - "policies": [ - { - "effect": "deny", - "action": "provider.use", - "resource": "openai" - } - ] - } -} -``` - -A provider denied by policy is not available for model selection or model use, even if it has credentials or is otherwise configured correctly. - ---- - -## Available Policies - -OpenCode currently supports one policy action: - -| Action | Resource | Description | -| -------------- | ----------------------------- | ------------------------------------- | -| `provider.use` | Provider ID, such as `openai` | Allow or deny use of an LLM provider. | - -More policy actions may be added in the future. - ---- - -## Matching - -The `resource` field supports wildcard matching. Use `*` to match zero or more characters and `?` to match one character. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "experimental": { - "policies": [ - { - "effect": "deny", - "action": "provider.use", - "resource": "company-*" - } - ] - } -} -``` - -This denies providers such as `company-us` and `company-eu`. - ---- - -## Rule Order - -When multiple statements match, the last matching statement wins. Put broad rules first, then more specific exceptions after them. - -For example, allow only Anthropic: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "experimental": { - "policies": [ - { - "effect": "deny", - "action": "provider.use", - "resource": "*" - }, - { - "effect": "allow", - "action": "provider.use", - "resource": "anthropic" - } - ] - } -} -``` - -If no policy matches a provider, provider use is allowed by default. - -Policies may be set in both your global config and project config. If policies from both locations match the same provider, your global policy takes priority over the project policy. This prevents a repository from re-enabling a provider that you deny globally. - ---- - -## Provider Lists - -Use policies instead of the older `disabled_providers` and `enabled_providers` settings when controlling provider access. - -To replace `disabled_providers`: - -```json title="opencode.json" -{ - "experimental": { - "policies": [ - { "effect": "deny", "action": "provider.use", "resource": "openai" }, - { "effect": "deny", "action": "provider.use", "resource": "google" } - ] - } -} -``` - -To replace `enabled_providers`, deny all providers first and allow the selected providers after it: - -```json title="opencode.json" -{ - "experimental": { - "policies": [ - { "effect": "deny", "action": "provider.use", "resource": "*" }, - { "effect": "allow", "action": "provider.use", "resource": "anthropic" }, - { "effect": "allow", "action": "provider.use", "resource": "openai" } - ] - } -} -``` diff --git a/.opencode/skills/opencode-docs/docs/providers.mdx b/.opencode/skills/opencode-docs/docs/providers.mdx deleted file mode 100644 index d3c5413..0000000 --- a/.opencode/skills/opencode-docs/docs/providers.mdx +++ /dev/null @@ -1,2501 +0,0 @@ ---- -title: Providers -description: Using any LLM provider in OpenCode. ---- - -import config from "../../../config.mjs" -export const console = config.console - -OpenCode uses the [AI SDK](https://ai-sdk.dev/) and [Models.dev](https://models.dev) to support **75+ LLM providers** and it supports running local models. - -To add a provider you need to: - -1. Add the API keys for the provider using the `/connect` command. -2. Configure the provider in your OpenCode config. - ---- - -### Credentials - -When you add a provider's API keys with the `/connect` command, they are stored -in `~/.local/share/opencode/auth.json`. - ---- - -### Config - -You can customize the providers through the `provider` section in your OpenCode -config. - ---- - -#### Base URL - -You can customize the base URL for any provider by setting the `baseURL` option. This is useful when using proxy services or custom endpoints. - -```json title="opencode.json" {6} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "anthropic": { - "options": { - "baseURL": "https://api.anthropic.com/v1" - } - } - } -} -``` - ---- - -#### Hiding models - -You can hide specific models from the `/models` picker for a provider using the `blacklist` option. This is useful when a provider exposes models you don't want to use or select. - -```json title="opencode.json" {6} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "anthropic": { - "blacklist": ["claude-opus-4-20250514"] - } - } -} -``` - -The inverse `whitelist` option hides every model except the ones listed. - -```json title="opencode.json" {6} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "anthropic": { - "whitelist": ["claude-sonnet-4-20250514"] - } - } -} -``` - -Both options take an array of model IDs — the same IDs shown in the `/models` picker. - -- `blacklist` removes the listed models from the picker. -- `whitelist` keeps only the listed models and hides the rest. -- You can combine them: `whitelist` narrows the set, then `blacklist` removes entries from it. - ---- - -## OpenCode Zen - -OpenCode Zen is a list of models provided by the OpenCode team that have been -tested and verified to work well with OpenCode. [Learn more](/docs/zen). - -:::tip -If you are new, we recommend starting with OpenCode Zen. -::: - -1. Run the `/connect` command in the TUI, select `OpenCode Zen`, and head to [opencode.ai/auth](https://opencode.ai/zen). - - ```txt - /connect - ``` - -2. Sign in, add your billing details, and copy your API key. - -3. Paste your API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run `/models` in the TUI to see the list of models we recommend. - - ```txt - /models - ``` - -It works like any other provider in OpenCode and is completely optional to use. - ---- - -## OpenCode Go - -OpenCode Go is a low cost subscription plan that provides reliable access to popular open coding models provided by the OpenCode team that have been -tested and verified to work well with OpenCode. - -1. Run the `/connect` command in the TUI, select `OpenCode Go`, and head to [opencode.ai/auth](https://opencode.ai/zen). - - ```txt - /connect - ``` - -2. Sign in, add your billing details, and copy your API key. - -3. Paste your API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run `/models` in the TUI to see the list of models we recommend. - - ```txt - /models - ``` - -It works like any other provider in OpenCode and is completely optional to use. - ---- - -## Directory - -Let's look at some of the providers in detail. If you'd like to add a provider to the -list, feel free to open a PR. - -:::note -Don't see a provider here? Submit a PR. -::: - ---- - -### 302.AI - -1. Head over to the [302.AI console](https://302.ai/), create an account, and generate an API key. - -2. Run the `/connect` command and search for **302.AI**. - - ```txt - /connect - ``` - -3. Enter your 302.AI API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - ---- - -### Amazon Bedrock - -To use Amazon Bedrock with OpenCode: - -1. Head over to the **Model catalog** in the Amazon Bedrock console and request - access to the models you want. - - :::tip - You need to have access to the model you want in Amazon Bedrock. - ::: - -2. **Configure authentication** using one of the following methods: - - *** - - #### Environment Variables (Quick Start) - - Set one of these environment variables while running opencode: - - ```bash - # Option 1: Using AWS access keys - AWS_ACCESS_KEY_ID=XXX AWS_SECRET_ACCESS_KEY=YYY opencode - - # Option 2: Using named AWS profile - AWS_PROFILE=my-profile opencode - - # Option 3: Using Bedrock bearer token - AWS_BEARER_TOKEN_BEDROCK=XXX opencode - ``` - - Or add them to your bash profile: - - ```bash title="~/.bash_profile" - export AWS_PROFILE=my-dev-profile - export AWS_REGION=us-east-1 - ``` - - *** - - #### Configuration File (Recommended) - - For project-specific or persistent configuration, use `opencode.json`: - - ```json title="opencode.json" - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "my-aws-profile" - } - } - } - } - ``` - - **Available options:** - - `region` - AWS region (e.g., `us-east-1`, `eu-west-1`) - - `profile` - AWS named profile from `~/.aws/credentials` - - `endpoint` - Custom endpoint URL for VPC endpoints (alias for generic `baseURL` option) - - :::tip - Configuration file options take precedence over environment variables. - ::: - - *** - - #### Advanced: VPC Endpoints - - If you're using VPC endpoints for Bedrock: - - ```json title="opencode.json" - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - "options": { - "region": "us-east-1", - "profile": "production", - "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" - } - } - } - } - ``` - - :::note - The `endpoint` option is an alias for the generic `baseURL` option, using AWS-specific terminology. If both `endpoint` and `baseURL` are specified, `endpoint` takes precedence. - ::: - - *** - - #### Authentication Methods - - **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: Create an IAM user and generate access keys in the AWS Console - - **`AWS_PROFILE`**: Use named profiles from `~/.aws/credentials`. First configure with `aws configure --profile my-profile` or `aws sso login` - - **`AWS_BEARER_TOKEN_BEDROCK`**: Generate long-term API keys from the Amazon Bedrock console - - **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**: For EKS IRSA (IAM Roles for Service Accounts) or other Kubernetes environments with OIDC federation. These environment variables are automatically injected by Kubernetes when using service account annotations. - - *** - - #### Authentication Precedence - - Amazon Bedrock uses the following authentication priority: - 1. **Bearer Token** - `AWS_BEARER_TOKEN_BEDROCK` environment variable or token from `/connect` command - 2. **AWS Credential Chain** - Profile, access keys, shared credentials, IAM roles, Web Identity Tokens (EKS IRSA), instance metadata - - :::note - When a bearer token is set (via `/connect` or `AWS_BEARER_TOKEN_BEDROCK`), it takes precedence over all AWS credential methods including configured profiles. - ::: - -3. Run the `/models` command to select the model you want. - - ```txt - /models - ``` - -:::note -For custom inference profiles, use the model and provider name in the key and set the `id` property to the arn. This ensures correct caching. -::: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "amazon-bedrock": { - // ... - "models": { - "anthropic-claude-sonnet-4.5": { - "id": "arn:aws:bedrock:us-east-1:xxx:application-inference-profile/yyy" - } - } - } - } -} -``` - ---- - -### Anthropic - -1. Once you've signed up, run the `/connect` command and select Anthropic. - - ```txt - /connect - ``` - -2. Here you can select the **Claude Pro/Max** option and it'll open your browser - and ask you to authenticate. - - ```txt - ┌ Select auth method - │ - │ Manually enter API Key - └ - ``` - -3. Now all the Anthropic models should be available when you use the `/models` command. - - ```txt - /models - ``` - -:::info -There are plugins that allow you to use your Claude Pro/Max models with -OpenCode. Anthropic explicitly prohibits this. - -Previous versions of OpenCode came bundled with these plugins but that is no -longer the case as of 1.3.0 - -Other companies support freedom of choice with developer tooling - you can use -the following subscriptions in OpenCode with zero setup: - -- ChatGPT Plus -- Github Copilot -- Gitlab Duo - ::: - ---- - -### Atomic Chat - -You can configure opencode to use local models through [Atomic Chat](https://atomic.chat), a desktop application that runs local LLMs behind an OpenAI-compatible API server (default endpoint `http://127.0.0.1:1337/v1`). - -```json title="opencode.json" "atomic-chat" {5, 6, 8, 10-14} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "atomic-chat": { - "npm": "@ai-sdk/openai-compatible", - "name": "Atomic Chat (local)", - "options": { - "baseURL": "http://127.0.0.1:1337/v1" - }, - "models": { - "<your-model-id>": { - "name": "<your-model-name>" - } - } - } - } -} -``` - -In this example: - -- `atomic-chat` is the custom provider ID. This can be any string you want. -- `npm` specifies the package to use for this provider. Here, `@ai-sdk/openai-compatible` is used for any OpenAI-compatible API. -- `name` is the display name for the provider in the UI. -- `options.baseURL` is the endpoint for the local server. Change the host and port to match your Atomic Chat setup. -- `models` is a map of model IDs to their display names. Each ID must match the `id` returned by `GET /v1/models` — run `curl http://127.0.0.1:1337/v1/models` to list the ids currently loaded in Atomic Chat. - -:::tip -If tool calls aren't working well, pick a loaded model with strong tool-calling support (for example, a Qwen-Coder or DeepSeek-Coder variant). -::: - ---- - -### Azure OpenAI - -:::note -If you encounter "I'm sorry, but I cannot assist with that request" errors, try changing the content filter from **DefaultV2** to **Default** in your Azure resource. -::: - -1. Head over to the [Azure portal](https://portal.azure.com/) and create an **Azure OpenAI** resource. You'll need: - - **Resource name**: This becomes part of your API endpoint (`https://RESOURCE_NAME.openai.azure.com/`) - - **API key**: Either `KEY 1` or `KEY 2` from your resource - -2. Go to [Azure AI Foundry](https://ai.azure.com/) and deploy a model. - - :::note - The deployment name must match the model name for opencode to work properly. - ::: - -3. Run the `/connect` command and search for **Azure**. - - ```txt - /connect - ``` - -4. Enter your API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -5. Set your resource name as an environment variable: - - ```bash - AZURE_RESOURCE_NAME=XXX opencode - ``` - - Or add it to your bash profile: - - ```bash title="~/.bash_profile" - export AZURE_RESOURCE_NAME=XXX - ``` - -6. Run the `/models` command to select your deployed model. - - ```txt - /models - ``` - ---- - -### Azure Cognitive Services - -1. Head over to the [Azure portal](https://portal.azure.com/) and create an **Azure OpenAI** resource. You'll need: - - **Resource name**: This becomes part of your API endpoint (`https://AZURE_COGNITIVE_SERVICES_RESOURCE_NAME.cognitiveservices.azure.com/`) - - **API key**: Either `KEY 1` or `KEY 2` from your resource - -2. Go to [Azure AI Foundry](https://ai.azure.com/) and deploy a model. - - :::note - The deployment name must match the model name for opencode to work properly. - ::: - -3. Run the `/connect` command and search for **Azure Cognitive Services**. - - ```txt - /connect - ``` - -4. Enter your API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -5. Set your resource name as an environment variable: - - ```bash - AZURE_COGNITIVE_SERVICES_RESOURCE_NAME=XXX opencode - ``` - - Or add it to your bash profile: - - ```bash title="~/.bash_profile" - export AZURE_COGNITIVE_SERVICES_RESOURCE_NAME=XXX - ``` - -6. Run the `/models` command to select your deployed model. - - ```txt - /models - ``` - ---- - -### Baseten - -1. Head over to the [Baseten](https://app.baseten.co/), create an account, and generate an API key. - -2. Run the `/connect` command and search for **Baseten**. - - ```txt - /connect - ``` - -3. Enter your Baseten API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - ---- - -### Cerebras - -1. Head over to the [Cerebras console](https://inference.cerebras.ai/), create an account, and generate an API key. - -2. Run the `/connect` command and search for **Cerebras**. - - ```txt - /connect - ``` - -3. Enter your Cerebras API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Qwen 3 Coder 480B_. - - ```txt - /models - ``` - ---- - -### Cloudflare AI Gateway - -Cloudflare AI Gateway lets you access models from OpenAI, Anthropic, Workers AI, and more through a unified endpoint. With [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/) you don't need separate API keys for each provider. - -1. Head over to the [Cloudflare dashboard](https://dash.cloudflare.com/), navigate to **AI** > **AI Gateway**, and create a new gateway. Note your **Account ID** and **Gateway ID**. - -2. Run the `/connect` command and search for **Cloudflare AI Gateway**. - - ```txt - /connect - ``` - -3. Enter your **Account ID** when prompted. - - ```txt - ┌ Enter your Cloudflare Account ID - │ - │ - └ enter - ``` - -4. Enter your **Gateway ID** when prompted. - - ```txt - ┌ Enter your Cloudflare AI Gateway ID - │ - │ - └ enter - ``` - -5. Enter your **Cloudflare API token**. - - ```txt - ┌ Gateway API token - │ - │ - └ enter - ``` - -6. Run the `/models` command to select a model. - - ```txt - /models - ``` - - You can also add models through your opencode config. - - ```json title="opencode.json" - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "cloudflare-ai-gateway": { - "models": { - "openai/gpt-4o": {}, - "anthropic/claude-sonnet-4": {} - } - } - } - } - ``` - - Alternatively, you can set environment variables instead of using `/connect`. - - ```bash title="~/.bash_profile" - export CLOUDFLARE_ACCOUNT_ID=your-32-character-account-id - export CLOUDFLARE_GATEWAY_ID=your-gateway-id - export CLOUDFLARE_API_TOKEN=your-api-token - ``` - ---- - -### Cloudflare Workers AI - -Cloudflare Workers AI lets you run AI models on Cloudflare's global network directly via REST API, with no separate provider accounts needed for supported models. - -1. Head over to the [Cloudflare dashboard](https://dash.cloudflare.com/), navigate to **Workers AI**, and select **Use REST API** to get your **Account ID** and create an API token. - -2. Run the `/connect` command and search for **Cloudflare Workers AI**. - - ```txt - /connect - ``` - -3. Enter your **Account ID** when prompted. - - ```txt - ┌ Enter your Cloudflare Account ID - │ - │ - └ enter - ``` - -4. Enter your **Cloudflare API key**. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -5. Run the `/models` command to select a model. - - ```txt - /models - ``` - - Alternatively, you can set environment variables instead of using `/connect`. - - ```bash title="~/.bash_profile" - export CLOUDFLARE_ACCOUNT_ID=your-32-character-account-id - export CLOUDFLARE_API_KEY=your-api-token - ``` - ---- - -### Cortecs - -1. Head over to the [Cortecs console](https://cortecs.ai/), create an account, and generate an API key. - -2. Run the `/connect` command and search for **Cortecs**. - - ```txt - /connect - ``` - -3. Enter your Cortecs API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Kimi K2 Instruct_. - - ```txt - /models - ``` - ---- - -### DeepSeek - -1. Head over to the [DeepSeek console](https://platform.deepseek.com/), create an account, and click **Create new API key**. - -2. Run the `/connect` command and search for **DeepSeek**. - - ```txt - /connect - ``` - -3. Enter your DeepSeek API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a DeepSeek model like _DeepSeek V4 Pro_. - - ```txt - /models - ``` - ---- - -### Deep Infra - -1. Head over to the [Deep Infra dashboard](https://deepinfra.com/dash), create an account, and generate an API key. - -2. Run the `/connect` command and search for **Deep Infra**. - - ```txt - /connect - ``` - -3. Enter your Deep Infra API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - ---- - -### DigitalOcean - -DigitalOcean's [Inference Engine](https://docs.digitalocean.com/products/inference/) provides access to open models like GPT-OSS, Llama, Qwen, and DeepSeek, plus custom [Inference Routers](https://docs.digitalocean.com/products/genai-platform/concepts/inference-routers/) that route each request to the cheapest, fastest, or best-fit model for a task. - -OpenCode supports two authentication methods: - -- **OAuth (Recommended)** — Sign in to your DigitalOcean account; OpenCode uses your DigitalOcean API token directly for inference and discovers your Inference Routers. -- **Model Access Key** — Paste an existing key from the DigitalOcean console. - -#### OAuth (Recommended) - -1. Run the `/connect` command and search for **DigitalOcean**. - - ```txt - /connect - ``` - -2. Select **Login with DigitalOcean**. - - ```txt - ┌ Select auth method - │ - │ Login with DigitalOcean - │ Paste Model Access Key - └ - ``` - -3. Your browser opens to authorize OpenCode. Sign in and approve. - - :::note - OpenCode requests `genai:read` and `inference:query` OAuth scopes. Your DigitalOcean API token is used directly for inference — no separate Model Access Key is created. - ::: - - :::note - Inference Routers only appear in the model picker after OAuth. Pasting a Model Access Key manually does not discover routers. - ::: - -4. Run the `/models` command. Your Inference Routers appear as the format `router:` in the model selection. - - ```txt - /models - ``` - -5. To pick up newly created Inference Routers, re-run `/connect` and select **DigitalOcean** again. - -#### Using a Model Access Key - -If you'd rather paste a key directly: - -1. Head over to the **Manage** page in the Inference section of the [DigitalOcean console](https://cloud.digitalocean.com/) and create a new key. - -2. Run the `/connect` command and select **DigitalOcean**, then **Paste Model Access Key**. - - ```txt - ┌ Enter your DigitalOcean Model Access Key - │ - │ - └ enter - ``` - - :::note - Inference Routers are not auto-discovered with this method. To surface them in the model picker, sign in via OAuth instead. - ::: - -3. Run the `/models` command to select a model. - - ```txt - /models - ``` - -#### Environment Variable - -Alternatively, set your Model Access Key as an environment variable. - -```bash frame="none" -export DIGITALOCEAN_ACCESS_TOKEN=your-model-access-key -``` - -#### Inference Routers - -Inference Routers let you define a routing policy across multiple models — picking the cheapest, fastest, or most appropriate model per request based on the task. After OAuth, OpenCode surfaces each router as `router:<router-name>` in the model picker. - -Selecting a router model is a drop-in replacement for any other model — OpenCode forwards your request and DigitalOcean picks the underlying model based on your router's policy. Learn more about [Inference Routers](https://docs.digitalocean.com/products/inference/how-to/use-inference-router/) - ---- - -### FrogBot - -1. Head over to the [FrogBot dashboard](https://app.frogbot.ai/signup), create an account, and generate an API key. - -2. Run the `/connect` command and search for **FrogBot**. - - ```txt - /connect - ``` - -3. Enter your FrogBot API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - ---- - -### Fireworks AI - -1. Head over to the [Fireworks AI console](https://app.fireworks.ai/), create an account, and click **Create API Key**. - -2. Run the `/connect` command and search for **Fireworks AI**. - - ```txt - /connect - ``` - -3. Enter your Fireworks AI API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Kimi K2 Instruct_. - - ```txt - /models - ``` - ---- - -### GitLab Duo - -:::caution[Experimental] -GitLab Duo support in OpenCode is experimental. Features, configuration, and -behavior may change in future releases. -::: - -OpenCode integrates with the [GitLab Duo Agent Platform](https://docs.gitlab.com/user/duo_agent_platform/), -providing AI-powered agentic chat with native tool calling capabilities. - -:::note[License requirements] -GitLab Duo Agent Platform requires a **Premium** or **Ultimate** GitLab -subscription. It is available on GitLab.com and GitLab Self-Managed. -See [GitLab Duo Agent Platform prerequisites](https://docs.gitlab.com/user/duo_agent_platform/#prerequisites) -for full requirements. -::: - -1. Run the `/connect` command and select GitLab. - - ```txt - /connect - ``` - -2. Choose your authentication method: - - ```txt - ┌ Select auth method - │ - │ OAuth (Recommended) - │ Personal Access Token - └ - ``` - - #### Using OAuth (Recommended) - - Select **OAuth** and your browser will open for authorization. - - #### Using Personal Access Token - 1. Go to [GitLab User Settings > Access Tokens](https://gitlab.com/-/user_settings/personal_access_tokens) - 2. Click **Add new token** - 3. Name: `OpenCode`, Scopes: `api` - 4. Copy the token (starts with `glpat-`) - 5. Enter it in the terminal - -3. Run the `/models` command to see available models. - - ```txt - /models - ``` - - Three Claude-based models are available: - - **duo-chat-haiku-4-5** (Default) - Fast responses for quick tasks - - **duo-chat-sonnet-4-5** - Balanced performance for most workflows - - **duo-chat-opus-4-5** - Most capable for complex analysis - -:::note -You can also specify 'GITLAB_TOKEN' environment variable if you don't want -to store token in opencode auth storage. -::: - -##### Self-Hosted GitLab - -:::note[compliance note] -OpenCode uses a small model for some AI tasks like generating the session title. -It is configured to use gpt-5-nano by default, hosted by Zen. To lock OpenCode -to only use your own GitLab-hosted instance, add the following to your -`opencode.json` file. It is also recommended to disable session sharing. - -```json -{ - "$schema": "https://opencode.ai/config.json", - "small_model": "gitlab/duo-chat-haiku-4-5", - "share": "disabled" -} -``` - -::: - -For self-hosted GitLab instances: - -```bash -export GITLAB_INSTANCE_URL=https://gitlab.company.com -export GITLAB_TOKEN=glpat-... -``` - -If your instance runs a custom AI Gateway: - -```bash -GITLAB_AI_GATEWAY_URL=https://ai-gateway.company.com -``` - -Or add to your bash profile: - -```bash title="~/.bash_profile" -export GITLAB_INSTANCE_URL=https://gitlab.company.com -export GITLAB_AI_GATEWAY_URL=https://ai-gateway.company.com -export GITLAB_TOKEN=glpat-... -``` - -:::note -Your GitLab administrator must: - -1. [Turn on GitLab Duo](https://docs.gitlab.com/user/duo_agent_platform/turn_on_off/#turn-gitlab-duo-on-or-off) - for the user, group, or instance -2. [Turn on the Agent Platform](https://docs.gitlab.com/user/duo_agent_platform/turn_on_off/#turn-gitlab-duo-agent-platform-on-or-off) - (GitLab 18.8+) or [enable beta and experimental features](https://docs.gitlab.com/user/duo_agent_platform/turn_on_off/#turn-on-beta-and-experimental-features) - (GitLab 18.7 and earlier) -3. For Self-Managed, [configure your instance](https://docs.gitlab.com/administration/gitlab_duo/configure/gitlab_self_managed/) - ::: - -##### OAuth for Self-Hosted instances - -In order to make Oauth working for your self-hosted instance, you need to create -a new application (Settings → Applications) with the -callback URL `http://127.0.0.1:8080/callback` and following scopes: - -- api (Access the API on your behalf) -- read_user (Read your personal information) -- read_repository (Allows read-only access to the repository) - -Then expose application ID as environment variable: - -```bash -export GITLAB_OAUTH_CLIENT_ID=your_application_id_here -``` - -More documentation on [opencode-gitlab-auth](https://www.npmjs.com/package/opencode-gitlab-auth) homepage. - -##### Configuration - -Customize through `opencode.json`: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "gitlab": { - "options": { - "instanceUrl": "https://gitlab.com" - } - } - } -} -``` - -##### GitLab Duo Agent Platform (DAP) Workflow Models - -DAP workflow models provide an alternative execution path that routes tool calls -through GitLab's Duo Workflow Service (DWS) instead of the standard agentic chat. -When a `duo-workflow-*` model is selected, OpenCode will: - -1. Discover available models from your GitLab namespace -2. Present a selection picker if multiple models are available -3. Cache the selected model to disk for fast subsequent startups -4. Route tool execution requests through OpenCode's permission-gated tool system - -Available DAP workflow models follow the `duo-workflow-*` naming convention and -are dynamically discovered from your GitLab instance. - -##### GitLab API Tools (Optional, but highly recommended) - -To access GitLab tools (merge requests, issues, pipelines, CI/CD, etc.): - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-gitlab-plugin"] -} -``` - -This plugin provides comprehensive GitLab repository management capabilities including MR reviews, issue tracking, pipeline monitoring, and more. - ---- - -### GitHub Copilot - -To use your GitHub Copilot subscription with opencode: - -:::note -Some models might need a [Pro+ -subscription](https://github.com/features/copilot/plans) to use. -::: - -1. Run the `/connect` command and search for GitHub Copilot. - - ```txt - /connect - ``` - -2. Navigate to [github.com/login/device](https://github.com/login/device) and enter the code. - - ```txt - ┌ Login with GitHub Copilot - │ - │ https://github.com/login/device - │ - │ Enter code: 8F43-6FCF - │ - └ Waiting for authorization... - ``` - -3. Now run the `/models` command to select the model you want. - - ```txt - /models - ``` - ---- - -### GMI Cloud - -To use GMI Cloud with OpenCode: - -1. Head over to the [GMI Cloud console](https://console.gmicloud.ai/) to create an API key. You can also review the [API reference](https://docs.gmicloud.ai/inference-engine/api-reference/llm-api-reference) for the endpoint details. - -2. Run the `/connect` command and search for **GMI Cloud**. - - ```txt - /connect - ``` - -3. Enter your GMI Cloud API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select the model you want. - - ```txt - /models - ``` - ---- - -### Google Vertex AI - -To use Google Vertex AI with OpenCode: - -1. Head over to the **Model Garden** in the Google Cloud Console and check the - models available in your region. - - :::note - You need to have a Google Cloud project with Vertex AI API enabled. - ::: - -2. Set the required environment variables: - - `GOOGLE_CLOUD_PROJECT`: Your Google Cloud project ID - - `VERTEX_LOCATION` (optional): The region for Vertex AI (defaults to `global`) - - Authentication (choose one): - - `GOOGLE_APPLICATION_CREDENTIALS`: Path to your service account JSON key file - - Authenticate using gcloud CLI: `gcloud auth application-default login` - - Set them while running opencode. - - ```bash - GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json GOOGLE_CLOUD_PROJECT=your-project-id opencode - ``` - - Or add them to your bash profile. - - ```bash title="~/.bash_profile" - export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json - export GOOGLE_CLOUD_PROJECT=your-project-id - export VERTEX_LOCATION=global - ``` - -:::tip -The `global` region improves availability and reduces errors at no extra cost. Use regional endpoints (e.g., `us-central1`) for data residency requirements. [Learn more](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models#regional_and_global_endpoints) -::: - -3. Run the `/models` command to select the model you want. - - ```txt - /models - ``` - ---- - -### Groq - -1. Head over to the [Groq console](https://console.groq.com/), click **Create API Key**, and copy the key. - -2. Run the `/connect` command and search for Groq. - - ```txt - /connect - ``` - -3. Enter the API key for the provider. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select the one you want. - - ```txt - /models - ``` - ---- - -### Hugging Face - -[Hugging Face Inference Providers](https://huggingface.co/docs/inference-providers) provides access to open models supported by 17+ providers. - -1. Head over to [Hugging Face settings](https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained) to create a token with permission to make calls to Inference Providers. - -2. Run the `/connect` command and search for **Hugging Face**. - - ```txt - /connect - ``` - -3. Enter your Hugging Face token. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Kimi-K2-Instruct_ or _GLM-4.6_. - - ```txt - /models - ``` - ---- - -### Helicone - -[Helicone](https://helicone.ai) is an LLM observability platform that provides logging, monitoring, and analytics for your AI applications. The Helicone AI Gateway routes your requests to the appropriate provider automatically based on the model. - -1. Head over to [Helicone](https://helicone.ai), create an account, and generate an API key from your dashboard. - -2. Run the `/connect` command and search for **Helicone**. - - ```txt - /connect - ``` - -3. Enter your Helicone API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - -For more providers and advanced features like caching and rate limiting, check the [Helicone documentation](https://docs.helicone.ai). - -#### Optional Configs - -In the event you see a feature or model from Helicone that isn't configured automatically through opencode, you can always configure it yourself. - -Here's [Helicone's Model Directory](https://helicone.ai/models), you'll need this to grab the IDs of the models you want to add. - -```jsonc title="~/.config/opencode/opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "helicone": { - "npm": "@ai-sdk/openai-compatible", - "name": "Helicone", - "options": { - "baseURL": "https://ai-gateway.helicone.ai", - }, - "models": { - "gpt-4o": { - // Model ID (from Helicone's model directory page) - "name": "GPT-4o", // Your own custom name for the model - }, - "claude-sonnet-4-20250514": { - "name": "Claude Sonnet 4", - }, - }, - }, - }, -} -``` - -#### Custom Headers - -Helicone supports custom headers for features like caching, user tracking, and session management. Add them to your provider config using `options.headers`: - -```jsonc title="~/.config/opencode/opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "helicone": { - "npm": "@ai-sdk/openai-compatible", - "name": "Helicone", - "options": { - "baseURL": "https://ai-gateway.helicone.ai", - "headers": { - "Helicone-Cache-Enabled": "true", - "Helicone-User-Id": "opencode", - }, - }, - }, - }, -} -``` - -##### Session tracking - -Helicone's [Sessions](https://docs.helicone.ai/features/sessions) feature lets you group related LLM requests together. Use the [opencode-helicone-session](https://github.com/H2Shami/opencode-helicone-session) plugin to automatically log each OpenCode conversation as a session in Helicone. - -```bash -npm install -g opencode-helicone-session -``` - -Add it to your config. - -```json title="opencode.json" -{ - "plugin": ["opencode-helicone-session"] -} -``` - -The plugin injects `Helicone-Session-Id` and `Helicone-Session-Name` headers into your requests. In Helicone's Sessions page, you'll see each OpenCode conversation listed as a separate session. - -##### Common Helicone headers - -| Header | Description | -| -------------------------- | ------------------------------------------------------------- | -| `Helicone-Cache-Enabled` | Enable response caching (`true`/`false`) | -| `Helicone-User-Id` | Track metrics by user | -| `Helicone-Property-[Name]` | Add custom properties (e.g., `Helicone-Property-Environment`) | -| `Helicone-Prompt-Id` | Associate requests with prompt versions | - -See the [Helicone Header Directory](https://docs.helicone.ai/helicone-headers/header-directory) for all available headers. - ---- - -### llama.cpp - -You can configure opencode to use local models through [llama.cpp's](https://github.com/ggml-org/llama.cpp) llama-server utility - -```json title="opencode.json" "llama.cpp" {5, 6, 8, 10-15} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "llama.cpp": { - "npm": "@ai-sdk/openai-compatible", - "name": "llama-server (local)", - "options": { - "baseURL": "http://127.0.0.1:8080/v1" - }, - "models": { - "qwen3-coder:a3b": { - "name": "Qwen3-Coder: a3b-30b (local)", - "limit": { - "context": 128000, - "output": 65536 - } - } - } - } - } -} -``` - -In this example: - -- `llama.cpp` is the custom provider ID. This can be any string you want. -- `npm` specifies the package to use for this provider. Here, `@ai-sdk/openai-compatible` is used for any OpenAI-compatible API. -- `name` is the display name for the provider in the UI. -- `options.baseURL` is the endpoint for the local server. -- `models` is a map of model IDs to their configurations. The model name will be displayed in the model selection list. - ---- - -### IO.NET - -IO.NET offers 17 models optimized for various use cases: - -1. Head over to the [IO.NET console](https://ai.io.net/), create an account, and generate an API key. - -2. Run the `/connect` command and search for **IO.NET**. - - ```txt - /connect - ``` - -3. Enter your IO.NET API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - ---- - -### LM Studio - -You can configure opencode to use local models through LM Studio. - -```json title="opencode.json" "lmstudio" {5, 6, 8, 10-14} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "lmstudio": { - "npm": "@ai-sdk/openai-compatible", - "name": "LM Studio (local)", - "options": { - "baseURL": "http://127.0.0.1:1234/v1" - }, - "models": { - "google/gemma-3n-e4b": { - "name": "Gemma 3n-e4b (local)" - } - } - } - } -} -``` - -In this example: - -- `lmstudio` is the custom provider ID. This can be any string you want. -- `npm` specifies the package to use for this provider. Here, `@ai-sdk/openai-compatible` is used for any OpenAI-compatible API. -- `name` is the display name for the provider in the UI. -- `options.baseURL` is the endpoint for the local server. -- `models` is a map of model IDs to their configurations. The model name will be displayed in the model selection list. - ---- - -### Moonshot AI - -To use Kimi K2 from Moonshot AI: - -1. Head over to the [Moonshot AI console](https://platform.moonshot.ai/console), create an account, and click **Create API key**. - -2. Run the `/connect` command and search for **Moonshot AI**. - - ```txt - /connect - ``` - -3. Enter your Moonshot API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select _Kimi K2_. - - ```txt - /models - ``` - ---- - -### MiniMax - -1. Head over to the [MiniMax API Console](https://platform.minimax.io/login), create an account, and generate an API key. - -2. Run the `/connect` command and search for **MiniMax**. - - ```txt - /connect - ``` - -3. Enter your MiniMax API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _M2.1_. - - ```txt - /models - ``` - ---- - -### NVIDIA - -NVIDIA provides access to Nemotron models and many other open models through [build.nvidia.com](https://build.nvidia.com) for free. - -1. Head over to [build.nvidia.com](https://build.nvidia.com), create an account, and generate an API key. - -2. Run the `/connect` command and search for **NVIDIA**. - - ```txt - /connect - ``` - -3. Enter your NVIDIA API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like nemotron-3-super-120b-a12b. - - ```txt - /models - ``` - -#### On-Prem / NIM - -You can also use NVIDIA models locally via [NVIDIA NIM](https://docs.nvidia.com/nim/) by setting a custom base URL. - -```json title="opencode.json" {6} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "nvidia": { - "options": { - "baseURL": "http://localhost:8000/v1" - } - } - } -} -``` - -#### Environment Variable - -Alternatively, set your API key as an environment variable. - -```bash frame="none" -export NVIDIA_API_KEY=nvapi-your-key-here -``` - ---- - -### Nebius Token Factory - -1. Head over to the [Nebius Token Factory console](https://tokenfactory.nebius.com/), create an account, and click **Add Key**. - -2. Run the `/connect` command and search for **Nebius Token Factory**. - - ```txt - /connect - ``` - -3. Enter your Nebius Token Factory API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Kimi K2 Instruct_. - - ```txt - /models - ``` - ---- - -### Ollama - -You can configure opencode to use local models through Ollama. - -:::tip -Ollama can automatically configure itself for OpenCode. See the [Ollama integration docs](https://docs.ollama.com/integrations/opencode) for details. -::: - -```json title="opencode.json" "ollama" {5, 6, 8, 10-14} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "ollama": { - "npm": "@ai-sdk/openai-compatible", - "name": "Ollama (local)", - "options": { - "baseURL": "http://localhost:11434/v1" - }, - "models": { - "llama2": { - "name": "Llama 2" - } - } - } - } -} -``` - -In this example: - -- `ollama` is the custom provider ID. This can be any string you want. -- `npm` specifies the package to use for this provider. Here, `@ai-sdk/openai-compatible` is used for any OpenAI-compatible API. -- `name` is the display name for the provider in the UI. -- `options.baseURL` is the endpoint for the local server. -- `models` is a map of model IDs to their configurations. The model name will be displayed in the model selection list. - -:::tip -If tool calls aren't working, try increasing `num_ctx` in Ollama. Start around 16k - 32k. -::: - ---- - -### Ollama Cloud - -To use Ollama Cloud with OpenCode: - -1. Head over to [https://ollama.com/](https://ollama.com/) and sign in or create an account. - -2. Navigate to **Settings** > **Keys** and click **Add API Key** to generate a new API key. - -3. Copy the API key for use in OpenCode. - -4. Run the `/connect` command and search for **Ollama Cloud**. - - ```txt - /connect - ``` - -5. Enter your Ollama Cloud API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -6. **Important**: Before using cloud models in OpenCode, you must pull the model information locally: - - ```bash - ollama pull gpt-oss:20b-cloud - ``` - -7. Run the `/models` command to select your Ollama Cloud model. - - ```txt - /models - ``` - ---- - -### OpenAI - -We recommend signing up for [ChatGPT Plus or Pro](https://chatgpt.com/pricing). - -1. Once you've signed up, run the `/connect` command and select OpenAI. - - ```txt - /connect - ``` - -2. Here you can select the **ChatGPT Plus/Pro** option and it'll open your browser - and ask you to authenticate. - - ```txt - ┌ Select auth method - │ - │ ChatGPT Plus/Pro - │ Manually enter API Key - └ - ``` - -3. Now all the OpenAI models should be available when you use the `/models` command. - - ```txt - /models - ``` - -##### Using API keys - -If you already have an API key, you can select **Manually enter API Key** and paste it in your terminal. - ---- - -### OpenCode Zen - -OpenCode Zen is a list of tested and verified models provided by the OpenCode team. [Learn more](/docs/zen). - -1. Sign in to **<a href={console}>OpenCode Zen</a>** and click **Create API Key**. - -2. Run the `/connect` command and search for **OpenCode Zen**. - - ```txt - /connect - ``` - -3. Enter your OpenCode API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Qwen 3 Coder 480B_. - - ```txt - /models - ``` - ---- - -### OpenRouter - -1. Head over to the [OpenRouter dashboard](https://openrouter.ai/settings/keys), click **Create API Key**, and copy the key. - -2. Run the `/connect` command and search for OpenRouter. - - ```txt - /connect - ``` - -3. Enter the API key for the provider. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Many OpenRouter models are preloaded by default, run the `/models` command to select the one you want. - - ```txt - /models - ``` - - You can also add additional models through your opencode config. - - ```json title="opencode.json" {6} - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "openrouter": { - "models": { - "somecoolnewmodel": {} - } - } - } - } - ``` - -5. You can also customize them through your opencode config. Here's an example of specifying a provider - - ```json title="opencode.json" - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "openrouter": { - "models": { - "moonshotai/kimi-k2": { - "options": { - "provider": { - "order": ["baseten"], - "allow_fallbacks": false - } - } - } - } - } - } - } - ``` - ---- - -### LLM Gateway - -1. Head over to the [LLM Gateway dashboard](https://llmgateway.io/dashboard), click **Create API Key**, and copy the key. - -2. Run the `/connect` command and search for LLM Gateway. - - ```txt - /connect - ``` - -3. Enter the API key for the provider. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Many LLM Gateway models are preloaded by default, run the `/models` command to select the one you want. - - ```txt - /models - ``` - - You can also add additional models through your opencode config. - - ```json title="opencode.json" {6} - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "llmgateway": { - "models": { - "somecoolnewmodel": {} - } - } - } - } - ``` - -5. You can also customize them through your opencode config. Here's an example of specifying a provider - - ```json title="opencode.json" - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "llmgateway": { - "models": { - "glm-4.7": { - "name": "GLM 4.7" - }, - "gpt-5.2": { - "name": "GPT-5.2" - }, - "gemini-2.5-pro": { - "name": "Gemini 2.5 Pro" - }, - "claude-3-5-sonnet-20241022": { - "name": "Claude 3.5 Sonnet" - } - } - } - } - } - ``` - ---- - -### SAP AI Core - -SAP AI Core provides access to 40+ models from OpenAI, Anthropic, Google, Amazon, Meta, Mistral, and AI21 through a unified platform. - -1. Go to your [SAP BTP Cockpit](https://account.hana.ondemand.com/), navigate to your SAP AI Core service instance, and create a service key. - - :::tip - The service key is a JSON object containing `clientid`, `clientsecret`, `url`, and `serviceurls.AI_API_URL`. You can find your AI Core instance under **Services** > **Instances and Subscriptions** in the BTP Cockpit. - ::: - -2. Run the `/connect` command and search for **SAP AI Core**. - - ```txt - /connect - ``` - -3. Enter your service key JSON. - - ```txt - ┌ Service key - │ - │ - └ enter - ``` - - Or set the `AICORE_SERVICE_KEY` environment variable: - - ```bash - AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' opencode - ``` - - Or add it to your bash profile: - - ```bash title="~/.bash_profile" - export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}' - ``` - -4. Optionally set deployment ID and resource group: - - ```bash - AICORE_DEPLOYMENT_ID=your-deployment-id AICORE_RESOURCE_GROUP=your-resource-group opencode - ``` - - :::note - These settings are optional and should be configured according to your SAP AI Core setup. - ::: - -5. Run the `/models` command to select from 40+ available models. - - ```txt - /models - ``` - ---- - -### STACKIT - -STACKIT AI Model Serving provides fully managed sovereign hosting environment for AI models, focusing on LLMs like Llama, Mistral, and Qwen, with maximum data sovereignty on European infrastructure. - -1. Head over to [STACKIT Portal](https://portal.stackit.cloud), navigate to **AI Model Serving**, and create an auth token for your project. - - :::tip - You need a STACKIT customer account, user account, and project before creating auth tokens. - ::: - -2. Run the `/connect` command and search for **STACKIT**. - - ```txt - /connect - ``` - -3. Enter your STACKIT AI Model Serving auth token. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select from available models like _Qwen3-VL 235B_ or _Llama 3.3 70B_. - - ```txt - /models - ``` - ---- - -### OVHcloud AI Endpoints - -1. Head over to the [OVHcloud panel](https://ovh.com/manager). Navigate to the `Public Cloud` section, `AI & Machine Learning` > `AI Endpoints` and in `API Keys` tab, click **Create a new API key**. - -2. Run the `/connect` command and search for **OVHcloud AI Endpoints**. - - ```txt - /connect - ``` - -3. Enter your OVHcloud AI Endpoints API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _gpt-oss-120b_. - - ```txt - /models - ``` - ---- - -### Scaleway - -To use [Scaleway Generative APIs](https://www.scaleway.com/en/docs/generative-apis/) with Opencode: - -1. Head over to the [Scaleway Console IAM settings](https://console.scaleway.com/iam/api-keys) to generate a new API key. - -2. Run the `/connect` command and search for **Scaleway**. - - ```txt - /connect - ``` - -3. Enter your Scaleway API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _devstral-2-123b-instruct-2512_ or _gpt-oss-120b_. - - ```txt - /models - ``` - ---- - -### Snowflake Cortex - -[Snowflake Cortex](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-rest-api) gives you access to frontier models (Claude, OpenAI GPT-5, and more) via an OpenAI-compatible API. All inference runs within the Snowflake perimeter and is billed in Snowflake credits. For per-model rates, see the [Snowflake Service Consumption Table](https://www.snowflake.com/legal-files/CreditConsumptionTable.pdf). - -Don't have a Snowflake account? [Sign up for a free trial](https://signup.snowflake.com/?utm_source=opencode&utm_medium=docs&utm_campaign=cortex-provider). - -opencode's core workflow for coding, editing files, and running commands relies on tool calling. Only the Claude and OpenAI families within Snowflake Cortex support this. The provider is limited to those families to support the core workflow. - -OpenCode supports two authentication methods: - -- **Browser OAuth (Recommended)** — sign in with your IdP/SSO; no secrets to manage, tokens refresh automatically. -- **Manual bearer token** — paste a PAT or JWT from the Snowflake console. - -#### Browser OAuth (Recommended) - -1. Run the `/connect` command and search for **Snowflake Cortex**. - - ```txt - /connect - ``` - -2. Select **Login with Snowflake (External Browser)**. - - ```txt - ┌ Select auth method - │ - │ Login with Snowflake (External Browser) - │ Paste PAT or bearer token manually - └ - ``` - -3. Enter your [account identifier](https://docs.snowflake.com/en/user-guide/admin-account-identifier) when prompted, for example `myorg-myaccount` or `xy12345.us-east-1`. - - ```txt - ┌ Snowflake Account Identifier - │ - │ - └ enter - ``` - -4. Optionally enter a Snowflake role to scope the session (e.g. `SYSADMIN`). Leave blank to use your default role. - -5. Complete sign-in in the browser that opens. OpenCode captures the OAuth callback automatically and stores the token — no copy/paste needed. - -6. Run the `/models` command to select a model. - - ```txt - /models - ``` - -:::note -Browser OAuth uses Snowflake's built-in `SNOWFLAKE$LOCAL_APPLICATION` security integration ([docs](https://docs.snowflake.com/en/user-guide/oauth-local-applications)), which is rolling out to all accounts. To check availability in your account: - -```sql -SHOW SECURITY INTEGRATIONS LIKE 'SNOWFLAKE$LOCAL_APPLICATION'; -``` - -If the result is empty, use the **Manual bearer token** method below while the integration rolls out to your account. -::: - -#### Manual bearer token - -If you prefer to paste a token directly, or if `SNOWFLAKE$LOCAL_APPLICATION` is not yet available in your account: - -1. Generate a [Programmatic Access Token (PAT)](https://docs.snowflake.com/en/user-guide/programmatic-access-tokens) in your Snowflake account. - -2. Run the `/connect` command, search for **Snowflake Cortex**, and select **Paste PAT or bearer token manually**. - -3. Enter your [account identifier](https://docs.snowflake.com/en/user-guide/admin-account-identifier) when prompted. - -4. Paste your PAT. - -5. Run the `/models` command to select a model. - - ```txt - /models - ``` - -#### Environment variable - -For CI or headless environments, set a PAT or JWT before starting opencode: - -```bash -export SNOWFLAKE_ACCOUNT=myorg-myaccount -export SNOWFLAKE_CORTEX_TOKEN=your-pat-or-jwt -``` - -:::note -`SNOWFLAKE_CORTEX_TOKEN` accepts a PAT or JWT only — the browser OAuth flow is available via `/connect` only and cannot be configured through an environment variable. `SNOWFLAKE_CORTEX_PAT` is still supported for backward compatibility. -::: - -The model catalog is provided automatically. A minimal `opencode.json` is all that's needed: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "model": "snowflake-cortex/claude-sonnet-4-6", - "small_model": "snowflake-cortex/claude-haiku-4-5" -} -``` - ---- - -### Together AI - -1. Head over to the [Together AI console](https://api.together.ai), create an account, and click **Add Key**. - -2. Run the `/connect` command and search for **Together AI**. - - ```txt - /connect - ``` - -3. Enter your Together AI API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Kimi K2 Instruct_. - - ```txt - /models - ``` - ---- - -### Venice AI - -1. Head over to the [Venice AI console](https://venice.ai), create an account, and generate an API key. - -2. Run the `/connect` command and search for **Venice AI**. - - ```txt - /connect - ``` - -3. Enter your Venice AI API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Llama 3.3 70B_. - - ```txt - /models - ``` - ---- - -### Vercel AI Gateway - -Vercel AI Gateway lets you access models from OpenAI, Anthropic, Google, xAI, and more through a unified endpoint. Models are offered at list price with no markup. - -1. Head over to the [Vercel dashboard](https://vercel.com/), navigate to the **AI Gateway** tab, and click **API keys** to create a new API key. - -2. Run the `/connect` command and search for **Vercel AI Gateway**. - - ```txt - /connect - ``` - -3. Enter your Vercel AI Gateway API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model. - - ```txt - /models - ``` - -You can also customize models through your opencode config. Here's an example of specifying provider routing order. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "vercel": { - "models": { - "anthropic/claude-sonnet-4": { - "options": { - "order": ["anthropic", "vertex"] - } - } - } - } - } -} -``` - -Some useful routing options: - -| Option | Description | -| ------------------- | ---------------------------------------------------- | -| `order` | Provider sequence to try | -| `only` | Restrict to specific providers | -| `zeroDataRetention` | Only use providers with zero data retention policies | - ---- - -### xAI - -Three ways to authenticate: a SuperGrok subscription via browser OAuth, the same SuperGrok subscription via a headless device-code flow (for VPS / SSH / Docker), or a pay-as-you-go API key from the xAI console. - -#### Option A — SuperGrok OAuth (browser login) - -1. Run the `/connect` command and search for **xAI**. - - ```txt - /connect - ``` - -2. Select **xAI Grok OAuth (SuperGrok Subscription)**. OpenCode opens xAI's consent screen in your browser and waits for the callback on `http://127.0.0.1:56121/callback`. - -3. Run the `/models` command to select a Grok model. - - ```txt - /models - ``` - -OpenCode refreshes the OAuth access token automatically. Any Grok or X Premium plan that includes Grok API access works; you do not need a separate `XAI_API_KEY`. - -#### Option B — SuperGrok device-code (headless / remote server / VPS) - -Use this when OpenCode is running somewhere a browser can't reach the loopback redirect: a VPS, a remote dev box over SSH, inside Docker, in CI, etc. No callback port is opened on the host running OpenCode — instead xAI hands the CLI a short code that you type into a browser on any other device (laptop, phone, …). - -1. Run the `/connect` command on the remote host and search for **xAI**. - - ```txt - /connect - ``` - -2. Select **xAI Grok OAuth (Headless / Remote / VPS)**. OpenCode prints a verification URL and a short user code. - - ```txt - Open https://x.ai/device on any device and enter code: ABCD-1234 - ``` - -3. Open the URL on a device that has a browser (your laptop or phone), enter the code, and approve the consent screen. OpenCode polls xAI's token endpoint and stores the resulting OAuth tokens once you approve. Token refresh works the same as Option A. - -#### Option C — API key - -1. Head over to the [xAI console](https://console.x.ai/), create an account, and generate an API key. - -2. Run the `/connect` command and search for **xAI**. - - ```txt - /connect - ``` - -3. Select **Manually enter API Key** and paste your xAI API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _Grok Beta_. - - ```txt - /models - ``` - ---- - -### Z.AI - -1. Head over to the [Z.AI API console](https://z.ai/manage-apikey/apikey-list), create an account, and click **Create a new API key**. - -2. Run the `/connect` command and search for **Z.AI**. - - ```txt - /connect - ``` - - If you are subscribed to the **GLM Coding Plan**, select **Z.AI Coding Plan**. - -3. Enter your Z.AI API key. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Run the `/models` command to select a model like _GLM-4.7_. - - ```txt - /models - ``` - ---- - -### ZenMux - -1. Head over to the [ZenMux dashboard](https://zenmux.ai/settings/keys), click **Create API Key**, and copy the key. - -2. Run the `/connect` command and search for ZenMux. - - ```txt - /connect - ``` - -3. Enter the API key for the provider. - - ```txt - ┌ API key - │ - │ - └ enter - ``` - -4. Many ZenMux models are preloaded by default, run the `/models` command to select the one you want. - - ```txt - /models - ``` - - You can also add additional models through your opencode config. - - ```json title="opencode.json" {6} - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "zenmux": { - "models": { - "somecoolnewmodel": {} - } - } - } - } - ``` - ---- - -## Custom provider - -To add any **OpenAI-compatible** provider that's not listed in the `/connect` command: - -:::tip -You can use any OpenAI-compatible provider with opencode. Most modern AI providers offer OpenAI-compatible APIs. -::: - -1. Run the `/connect` command and scroll down to **Other**. - - ```bash - $ /connect - - ┌ Add credential - │ - ◆ Select provider - │ ... - │ ● Other - └ - ``` - -2. Enter a unique ID for the provider. - - ```bash - $ /connect - - ┌ Add credential - │ - ◇ Enter provider id - │ myprovider - └ - ``` - - :::note - Choose a memorable ID, you'll use this in your config file. - ::: - -3. Enter your API key for the provider. - - ```bash - $ /connect - - ┌ Add credential - │ - ▲ This only stores a credential for myprovider - you will need to configure it in opencode.json, check the docs for examples. - │ - ◇ Enter your API key - │ sk-... - └ - ``` - -4. Create or update your `opencode.json` file in your project directory: - - ```json title="opencode.json" ""myprovider"" {5-15} - { - "$schema": "https://opencode.ai/config.json", - "provider": { - "myprovider": { - "npm": "@ai-sdk/openai-compatible", - "name": "My AI ProviderDisplay Name", - "options": { - "baseURL": "https://api.myprovider.com/v1" - }, - "models": { - "my-model-name": { - "name": "My Model Display Name" - } - } - } - } - } - ``` - - Here are the configuration options: - - **npm**: AI SDK package to use, `@ai-sdk/openai-compatible` for OpenAI-compatible providers (for `/v1/chat/completions`). If your provider/model uses `/v1/responses`, use `@ai-sdk/openai`. - - **name**: Display name in UI. - - **models**: Available models. - - **options.baseURL**: API endpoint URL. - - **options.apiKey**: Optionally set the API key, if not using auth. - - **options.headers**: Optionally set custom headers. - - More on the advanced options in the example below. - -5. Run the `/models` command and your custom provider and models will appear in the selection list. - ---- - -##### Example - -Here's an example setting the `apiKey`, `headers`, and model `limit` options. - -```json title="opencode.json" {9,11,17-20} -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "myprovider": { - "npm": "@ai-sdk/openai-compatible", - "name": "My AI ProviderDisplay Name", - "options": { - "baseURL": "https://api.myprovider.com/v1", - "apiKey": "{env:ANTHROPIC_API_KEY}", - "headers": { - "Authorization": "Bearer custom-token" - } - }, - "models": { - "my-model-name": { - "name": "My Model Display Name", - "limit": { - "context": 200000, - "output": 65536 - } - } - } - } - } -} -``` - -Configuration details: - -- **apiKey**: Set using `env` variable syntax, [learn more](/docs/config#env-vars). -- **headers**: Custom headers sent with each request. -- **limit.context**: Maximum input tokens the model accepts. -- **limit.output**: Maximum tokens the model can generate. - -The `limit` fields allow OpenCode to understand how much context you have left. Standard providers pull these from models.dev automatically. - ---- - -## Troubleshooting - -If you are having trouble with configuring a provider, check the following: - -1. **Check the auth setup**: Run `opencode auth list` to see if the credentials - for the provider are added to your config. - - This doesn't apply to providers like Amazon Bedrock, that rely on environment variables for their auth. - -2. For custom providers, check the opencode config and: - - Make sure the provider ID used in the `/connect` command matches the ID in your opencode config. - - The right npm package is used for the provider. For example, use `@ai-sdk/cerebras` for Cerebras. And for all other OpenAI-compatible providers, use `@ai-sdk/openai-compatible` (for `/v1/chat/completions`); if a model uses `/v1/responses`, use `@ai-sdk/openai`. For mixed setups under one provider, you can override per model via `provider.npm`. - - Check correct API endpoint is used in the `options.baseURL` field. diff --git a/.opencode/skills/opencode-docs/docs/references.mdx b/.opencode/skills/opencode-docs/docs/references.mdx deleted file mode 100644 index 2df002c..0000000 --- a/.opencode/skills/opencode-docs/docs/references.mdx +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: References -description: Add local directories and Git repositories as project references. ---- - -References give OpenCode access to directories outside the current project. Use them to make documentation, shared libraries, examples, or another repository available while you work. - -References are configured by alias in `opencode.json` or `opencode.jsonc`. - -```jsonc title="opencode.jsonc" -{ - "$schema": "https://opencode.ai/config.json", - "references": { - "docs": { - "path": "../product-docs", - "description": "Use for product behavior and documentation conventions", - }, - "sdk": { - "repository": "anomalyco/opencode-sdk-js", - "branch": "main", - "description": "Use for JavaScript SDK implementation details", - }, - }, -} -``` - ---- - -## Local directories - -Use `path` to reference a local directory. - -```jsonc title="opencode.jsonc" -{ - "references": { - "docs": { - "path": "../docs", - }, - }, -} -``` - -Paths can be: - -- Relative to the config file that defines the reference -- Absolute, such as `/home/user/docs` -- Relative to your home directory, such as `~/docs` - -You can also use a string shorthand: - -```jsonc title="opencode.jsonc" -{ - "references": { - "docs": "../docs", - }, -} -``` - ---- - -## Git repositories - -Use `repository` to reference a Git repository. OpenCode materializes the repository in its local repository cache and makes the checked-out source available as a reference directory. - -```jsonc title="opencode.jsonc" -{ - "references": { - "effect": { - "repository": "Effect-TS/effect", - "branch": "main", - }, - }, -} -``` - -`repository` accepts Git URLs, host/path references, and GitHub `owner/repo` shorthand. The optional `branch` field selects a branch or ref. Without `branch`, OpenCode uses the repository's default branch. - -You can use string shorthand when you do not need a branch, description, or other options: - -```jsonc title="opencode.jsonc" -{ - "references": { - "effect": "Effect-TS/effect", - }, -} -``` - -:::note -Git references are refreshed asynchronously. A newly configured repository may take a moment to finish cloning or updating. -::: - ---- - -## Describe usage - -Add `description` to explain when an agent should use a reference. - -```jsonc title="opencode.jsonc" -{ - "references": { - "design-system": { - "path": "../design-system", - "description": "Use when implementing UI components or design tokens", - }, - }, -} -``` - -OpenCode includes references with descriptions in agent context. Descriptions should be short and specific enough to distinguish references with similar content. References without descriptions remain available through autocomplete and direct use, but are not advertised to agents. - ---- - -## Hide autocomplete entries - -Set `hidden` to `true` to omit a reference from `@` autocomplete in the TUI. - -```jsonc title="opencode.jsonc" -{ - "references": { - "internal": { - "path": "../internal", - "description": "Use for internal implementation details", - "hidden": true, - }, - }, -} -``` - -`hidden` only affects autocomplete. A hidden reference with a description remains included in agent context. - ---- - -## Use references - -Configured references appear in TUI `@` autocomplete. Type `@alias` to attach the reference root, or `@alias/` to search for files inside it. - -```text -Compare this implementation with @sdk/src/client.ts -``` - -Agents also receive the resolved paths and descriptions of configured references that have descriptions in their system context, so they can inspect a reference when it is relevant without you attaching it manually. - -OpenCode automatically allows reference directories through its external-directory permission boundary. Normal tool permissions still apply; for example, an agent that cannot edit files does not gain edit access because a directory is configured as a reference. - ---- - -## Configure fields - -| Field | Local | Git | Description | -| ------------- | ----- | --- | ------------------------------------------------ | -| `path` | Yes | No | Local reference directory | -| `repository` | No | Yes | Git URL, host/path, or GitHub `owner/repo` value | -| `branch` | No | Yes | Optional Git branch or ref | -| `description` | Yes | Yes | Guidance describing when to use the reference | -| `hidden` | Yes | Yes | Hide the reference from TUI `@` autocomplete | - -Reference aliases cannot be empty or contain `/`, whitespace, backticks, or commas. diff --git a/.opencode/skills/opencode-docs/docs/rules.mdx b/.opencode/skills/opencode-docs/docs/rules.mdx deleted file mode 100644 index 6db5d45..0000000 --- a/.opencode/skills/opencode-docs/docs/rules.mdx +++ /dev/null @@ -1,188 +0,0 @@ ---- -title: Rules -description: Set custom instructions for opencode. ---- - -You can provide custom instructions to opencode by creating an `AGENTS.md` file. This is similar to Cursor's rules. It contains instructions that will be included in the LLM's context to customize its behavior for your specific project. - ---- - -## Initialize - -To create a new `AGENTS.md` file, you can run the `/init` command in opencode. - -:::tip -You should commit your project's `AGENTS.md` file to Git. -::: - -`/init` scans the important files in your repo, may ask a couple of targeted questions when the codebase cannot answer them, and then creates or updates `AGENTS.md` with concise project-specific guidance. - -It focuses on the things future agent sessions are most likely to need: - -- build, lint, and test commands -- command order and focused verification steps when they matter -- architecture and repo structure that are not obvious from filenames alone -- project-specific conventions, setup quirks, and operational gotchas -- references to existing instruction sources like Cursor or Copilot rules - -If you already have an `AGENTS.md`, `/init` will improve it in place instead of blindly replacing it. - ---- - -## Example - -You can also just create this file manually. Here's an example of some things you can put into an `AGENTS.md` file. - -```markdown title="AGENTS.md" -# SST v3 Monorepo Project - -This is an SST v3 monorepo with TypeScript. The project uses bun workspaces for package management. - -## Project Structure - -- `packages/` - Contains all workspace packages (functions, core, web, etc.) -- `infra/` - Infrastructure definitions split by service (storage.ts, api.ts, web.ts) -- `sst.config.ts` - Main SST configuration with dynamic imports - -## Code Standards - -- Use TypeScript with strict mode enabled -- Shared code goes in `packages/core/` with proper exports configuration -- Functions go in `packages/functions/` -- Infrastructure should be split into logical files in `infra/` - -## Monorepo Conventions - -- Import shared modules using workspace names: `@my-app/core/example` -``` - -We are adding project-specific instructions here and this will be shared across your team. - ---- - -## Types - -opencode also supports reading the `AGENTS.md` file from multiple locations. And this serves different purposes. - -### Project - -Place an `AGENTS.md` in your project root for project-specific rules. These only apply when you are working in this directory or its sub-directories. - -### Global - -You can also have global rules in a `~/.config/opencode/AGENTS.md` file. This gets applied across all opencode sessions. - -Since this isn't committed to Git or shared with your team, we recommend using this to specify any personal rules that the LLM should follow. - -### Claude Code Compatibility - -For users migrating from Claude Code, OpenCode supports Claude Code's file conventions as fallbacks: - -- **Project rules**: `CLAUDE.md` in your project directory (used if no `AGENTS.md` exists) -- **Global rules**: `~/.claude/CLAUDE.md` (used if no `~/.config/opencode/AGENTS.md` exists) -- **Skills**: `~/.claude/skills/` — see [Agent Skills](/docs/skills/) for details - -To disable Claude Code compatibility, set one of these environment variables: - -```bash -export OPENCODE_DISABLE_CLAUDE_CODE=1 # Disable all .claude support -export OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1 # Disable only ~/.claude/CLAUDE.md -export OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1 # Disable only .claude/skills -``` - ---- - -## Precedence - -When opencode starts, it looks for rule files in this order: - -1. **Local files** by traversing up from the current directory (`AGENTS.md`, `CLAUDE.md`) -2. **Global file** at `~/.config/opencode/AGENTS.md` -3. **Claude Code file** at `~/.claude/CLAUDE.md` (unless disabled) - -The first matching file wins in each category. For example, if you have both `AGENTS.md` and `CLAUDE.md`, only `AGENTS.md` is used. Similarly, `~/.config/opencode/AGENTS.md` takes precedence over `~/.claude/CLAUDE.md`. - ---- - -## Custom Instructions - -You can specify custom instruction files in your `opencode.json` or the global `~/.config/opencode/opencode.json`. This allows you and your team to reuse existing rules rather than having to duplicate them to AGENTS.md. - -Example: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "instructions": ["CONTRIBUTING.md", "docs/guidelines.md", ".cursor/rules/*.md"] -} -``` - -You can also use remote URLs to load instructions from the web. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "instructions": ["https://raw.githubusercontent.com/my-org/shared-rules/main/style.md"] -} -``` - -Remote instructions are fetched with a 5 second timeout. - -All instruction files are combined with your `AGENTS.md` files. - ---- - -## Referencing External Files - -While opencode doesn't automatically parse file references in `AGENTS.md`, you can achieve similar functionality in two ways: - -### Using opencode.json - -The recommended approach is to use the `instructions` field in `opencode.json`: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "instructions": ["docs/development-standards.md", "test/testing-guidelines.md", "packages/*/AGENTS.md"] -} -``` - -### Manual Instructions in AGENTS.md - -You can teach opencode to read external files by providing explicit instructions in your `AGENTS.md`. Here's a practical example: - -```markdown title="AGENTS.md" -# TypeScript Project Rules - -## External File Loading - -CRITICAL: When you encounter a file reference (e.g., @rules/general.md), use your Read tool to load it on a need-to-know basis. They're relevant to the SPECIFIC task at hand. - -Instructions: - -- Do NOT preemptively load all references - use lazy loading based on actual need -- When loaded, treat content as mandatory instructions that override defaults -- Follow references recursively when needed - -## Development Guidelines - -For TypeScript code style and best practices: @docs/typescript-guidelines.md -For React component architecture and hooks patterns: @docs/react-patterns.md -For REST API design and error handling: @docs/api-standards.md -For testing strategies and coverage requirements: @test/testing-guidelines.md - -## General Guidelines - -Read the following file immediately as it's relevant to all workflows: @rules/general-guidelines.md. -``` - -This approach allows you to: - -- Create modular, reusable rule files -- Share rules across projects via symlinks or git submodules -- Keep AGENTS.md concise while referencing detailed guidelines -- Ensure opencode loads files only when needed for the specific task - -:::tip -For monorepos or projects with shared standards, using `opencode.json` with glob patterns (like `packages/*/AGENTS.md`) is more maintainable than manual instructions. -::: diff --git a/.opencode/skills/opencode-docs/docs/sdk.mdx b/.opencode/skills/opencode-docs/docs/sdk.mdx deleted file mode 100644 index 2454621..0000000 --- a/.opencode/skills/opencode-docs/docs/sdk.mdx +++ /dev/null @@ -1,463 +0,0 @@ ---- -title: SDK -description: Type-safe JS client for opencode server. ---- - -import config from "../../../config.mjs" -export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts` - -The opencode JS/TS SDK provides a type-safe client for interacting with the server. -Use it to build integrations and control opencode programmatically. - -[Learn more](/docs/server) about how the server works. For examples, check out the [projects](/docs/ecosystem#projects) built by the community. - ---- - -## Install - -Install the SDK from npm: - -```bash -npm install @opencode-ai/sdk -``` - ---- - -## Create client - -Create an instance of opencode: - -```javascript -import { createOpencode } from "@opencode-ai/sdk" - -const { client } = await createOpencode() -``` - -This starts both a server and a client - -#### Options - -| Option | Type | Description | Default | -| ---------- | ------------- | ------------------------------ | ----------- | -| `hostname` | `string` | Server hostname | `127.0.0.1` | -| `port` | `number` | Server port | `4096` | -| `signal` | `AbortSignal` | Abort signal for cancellation | `undefined` | -| `timeout` | `number` | Timeout in ms for server start | `5000` | -| `config` | `Config` | Configuration object | `{}` | - ---- - -## Config - -You can pass a configuration object to customize behavior. The instance still picks up your `opencode.json`, but you can override or add configuration inline: - -```javascript -import { createOpencode } from "@opencode-ai/sdk" - -const opencode = await createOpencode({ - hostname: "127.0.0.1", - port: 4096, - config: { - model: "anthropic/claude-3-5-sonnet-20241022", - }, -}) - -console.log(`Server running at ${opencode.server.url}`) - -opencode.server.close() -``` - -## Client only - -If you already have a running instance of opencode, you can create a client instance to connect to it: - -```javascript -import { createOpencodeClient } from "@opencode-ai/sdk" - -const client = createOpencodeClient({ - baseUrl: "http://localhost:4096", -}) -``` - -#### Options - -| Option | Type | Description | Default | -| --------------- | ---------- | -------------------------------- | ----------------------- | -| `baseUrl` | `string` | URL of the server | `http://localhost:4096` | -| `fetch` | `function` | Custom fetch implementation | `globalThis.fetch` | -| `parseAs` | `string` | Response parsing method | `auto` | -| `responseStyle` | `string` | Return style: `data` or `fields` | `fields` | -| `throwOnError` | `boolean` | Throw errors instead of return | `false` | - ---- - -## Types - -The SDK includes TypeScript definitions for all API types. Import them directly: - -```typescript -import type { Session, Message, Part } from "@opencode-ai/sdk" -``` - -All types are generated from the server's OpenAPI specification and available in the <a href={typesUrl}>types file</a>. - ---- - -## Errors - -The SDK can throw errors that you can catch and handle: - -```typescript -try { - await client.session.get({ path: { id: "invalid-id" } }) -} catch (error) { - console.error("Failed to get session:", (error as Error).message) -} -``` - ---- - -## Structured Output - -You can request structured JSON output from the model by specifying an `format` with a JSON schema. The model will use a `StructuredOutput` tool to return validated JSON matching your schema. - -### Basic Usage - -```typescript -const result = await client.session.prompt({ - path: { id: sessionId }, - body: { - parts: [{ type: "text", text: "Research Anthropic and provide company info" }], - format: { - type: "json_schema", - schema: { - type: "object", - properties: { - company: { type: "string", description: "Company name" }, - founded: { type: "number", description: "Year founded" }, - products: { - type: "array", - items: { type: "string" }, - description: "Main products", - }, - }, - required: ["company", "founded"], - }, - }, - }, -}) - -// Access the structured output -console.log(result.data.info.structured_output) -// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] } -``` - -### Output Format Types - -| Type | Description | -| ------------- | ------------------------------------------------------ | -| `text` | Default. Standard text response (no structured output) | -| `json_schema` | Returns validated JSON matching the provided schema | - -### JSON Schema Format - -When using `type: 'json_schema'`, provide: - -| Field | Type | Description | -| ------------ | --------------- | ---------------------------------------------------------- | -| `type` | `'json_schema'` | Required. Specifies JSON schema mode | -| `schema` | `object` | Required. JSON Schema object defining the output structure | -| `retryCount` | `number` | Optional. Number of validation retries (default: 2) | - -### Error Handling - -If the model fails to produce valid structured output after all retries, the response will include a `StructuredOutputError`: - -```typescript -if (result.data.info.error?.name === "StructuredOutputError") { - console.error("Failed to produce structured output:", result.data.info.error.message) - console.error("Attempts:", result.data.info.error.retries) -} -``` - -### Best Practices - -1. **Provide clear descriptions** in your schema properties to help the model understand what data to extract -2. **Use `required`** to specify which fields must be present -3. **Keep schemas focused** - complex nested schemas may be harder for the model to fill correctly -4. **Set appropriate `retryCount`** - increase for complex schemas, decrease for simple ones - ---- - -## APIs - -The SDK exposes all server APIs through a type-safe client. - ---- - -### Global - -| Method | Description | Response | -| ----------------- | ------------------------------- | ------------------------------------ | -| `global.health()` | Check server health and version | `{ healthy: true, version: string }` | - ---- - -#### Examples - -```javascript -const health = await client.global.health() -console.log(health.data.version) -``` - ---- - -### App - -| Method | Description | Response | -| -------------- | ------------------------- | ------------------------------------------- | -| `app.log()` | Write a log entry | `boolean` | -| `app.agents()` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> | - ---- - -#### Examples - -```javascript -// Write a log entry -await client.app.log({ - body: { - service: "my-app", - level: "info", - message: "Operation completed", - }, -}) - -// List available agents -const agents = await client.app.agents() -``` - ---- - -### Project - -| Method | Description | Response | -| ------------------- | ------------------- | --------------------------------------------- | -| `project.list()` | List all projects | <a href={typesUrl}><code>Project[]</code></a> | -| `project.current()` | Get current project | <a href={typesUrl}><code>Project</code></a> | - ---- - -#### Examples - -```javascript -// List all projects -const projects = await client.project.list() - -// Get current project -const currentProject = await client.project.current() -``` - ---- - -### Path - -| Method | Description | Response | -| ------------ | ---------------- | ---------------------------------------- | -| `path.get()` | Get current path | <a href={typesUrl}><code>Path</code></a> | - ---- - -#### Examples - -```javascript -// Get current path information -const pathInfo = await client.path.get() -``` - ---- - -### Config - -| Method | Description | Response | -| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- | -| `config.get()` | Get config info | <a href={typesUrl}><code>Config</code></a> | -| `config.providers()` | List providers and default models | `{ providers: `<a href={typesUrl}><code>Provider[]</code></a>`, default: { [key: string]: string } }` | - ---- - -#### Examples - -```javascript -const config = await client.config.get() - -const { providers, default: defaults } = await client.config.providers() -``` - ---- - -### Sessions - -| Method | Description | Notes | -| ---------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> | -| `session.get({ path })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> | -| `session.children({ path })` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> | -| `session.create({ body })` | Create session | Returns <a href={typesUrl}><code>Session</code></a> | -| `session.delete({ path })` | Delete session | Returns `boolean` | -| `session.update({ path, body })` | Update session properties | Returns <a href={typesUrl}><code>Session</code></a> | -| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | -| `session.abort({ path })` | Abort a running session | Returns `boolean` | -| `session.share({ path })` | Share session | Returns <a href={typesUrl}><code>Session</code></a> | -| `session.unshare({ path })` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> | -| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | -| `session.messages({ path })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` | -| `session.message({ path })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` | -| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns <a href={typesUrl}><code>AssistantMessage</code></a> with AI response. Supports `body.outputFormat` for [structured output](#structured-output) | -| `session.command({ path, body })` | Send command to session | Returns `{ info: `<a href={typesUrl}><code>AssistantMessage</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` | -| `session.shell({ path, body })` | Run a shell command | Returns <a href={typesUrl}><code>AssistantMessage</code></a> | -| `session.revert({ path, body })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> | -| `session.unrevert({ path })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | - ---- - -#### Examples - -```javascript -// Create and manage sessions -const session = await client.session.create({ - body: { title: "My session" }, -}) - -const sessions = await client.session.list() - -// Send a prompt message -const result = await client.session.prompt({ - path: { id: session.id }, - body: { - model: { providerID: "anthropic", modelID: "claude-3-5-sonnet-20241022" }, - parts: [{ type: "text", text: "Hello!" }], - }, -}) - -// Inject context without triggering AI response (useful for plugins) -await client.session.prompt({ - path: { id: session.id }, - body: { - noReply: true, - parts: [{ type: "text", text: "You are a helpful assistant." }], - }, -}) -``` - ---- - -### Files - -| Method | Description | Response | -| ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------- | -| `find.text({ query })` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | -| `find.files({ query })` | Find files and directories by name | `string[]` (paths) | -| `find.symbols({ query })` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> | -| `file.read({ query })` | Read a file | `{ type: "raw" \| "patch", content: string }` | -| `file.status({ query? })` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> | - -`find.files` supports a few optional query fields: - -- `type`: `"file"` or `"directory"` -- `directory`: override the project root for the search -- `limit`: max results (1–200) - ---- - -#### Examples - -```javascript -// Search and read files -const textResults = await client.find.text({ - query: { pattern: "function.*opencode" }, -}) - -const files = await client.find.files({ - query: { query: "*.ts", type: "file" }, -}) - -const directories = await client.find.files({ - query: { query: "packages", type: "directory", limit: 20 }, -}) - -const content = await client.file.read({ - query: { path: "src/index.ts" }, -}) -``` - ---- - -### TUI - -| Method | Description | Response | -| ------------------------------ | ------------------------- | --------- | -| `tui.appendPrompt({ body })` | Append text to the prompt | `boolean` | -| `tui.openHelp()` | Open the help dialog | `boolean` | -| `tui.openSessions()` | Open the session selector | `boolean` | -| `tui.openThemes()` | Open the theme selector | `boolean` | -| `tui.openModels()` | Open the model selector | `boolean` | -| `tui.submitPrompt()` | Submit the current prompt | `boolean` | -| `tui.clearPrompt()` | Clear the prompt | `boolean` | -| `tui.executeCommand({ body })` | Execute a command | `boolean` | -| `tui.showToast({ body })` | Show toast notification | `boolean` | - ---- - -#### Examples - -```javascript -// Control TUI interface -await client.tui.appendPrompt({ - body: { text: "Add this to prompt" }, -}) - -await client.tui.showToast({ - body: { message: "Task completed", variant: "success" }, -}) -``` - ---- - -### Auth - -| Method | Description | Response | -| ------------------- | ------------------------------ | --------- | -| `auth.set({ ... })` | Set authentication credentials | `boolean` | - ---- - -#### Examples - -```javascript -await client.auth.set({ - path: { id: "anthropic" }, - body: { type: "api", key: "your-api-key" }, -}) -``` - ---- - -### Events - -| Method | Description | Response | -| ------------------- | ------------------------- | ------------------------- | -| `event.subscribe()` | Server-sent events stream | Server-sent events stream | - ---- - -#### Examples - -```javascript -// Listen to real-time events -const events = await client.event.subscribe() -for await (const event of events.stream) { - console.log("Event:", event.type, event.properties) -} -``` diff --git a/.opencode/skills/opencode-docs/docs/server.mdx b/.opencode/skills/opencode-docs/docs/server.mdx deleted file mode 100644 index 4510bd4..0000000 --- a/.opencode/skills/opencode-docs/docs/server.mdx +++ /dev/null @@ -1,287 +0,0 @@ ---- -title: Server -description: Interact with opencode server over HTTP. ---- - -import config from "../../../config.mjs" -export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts` - -The `opencode serve` command runs a headless HTTP server that exposes an OpenAPI endpoint that an opencode client can use. - ---- - -### Usage - -```bash -opencode serve [--port <number>] [--hostname <string>] [--cors <origin>] -``` - -#### Options - -| Flag | Description | Default | -| --------------- | ----------------------------------- | ---------------- | -| `--port` | Port to listen on | `4096` | -| `--hostname` | Hostname to listen on | `127.0.0.1` | -| `--mdns` | Enable mDNS discovery | `false` | -| `--mdns-domain` | Custom domain name for mDNS service | `opencode.local` | -| `--cors` | Additional browser origins to allow | `[]` | - -`--cors` can be passed multiple times: - -```bash -opencode serve --cors http://localhost:5173 --cors https://app.example.com -``` - ---- - -### Authentication - -Set `OPENCODE_SERVER_PASSWORD` to protect the server with HTTP basic auth. The username defaults to `opencode`, or set `OPENCODE_SERVER_USERNAME` to override it. This applies to both `opencode serve` and `opencode web`. - -```bash -OPENCODE_SERVER_PASSWORD=your-password opencode serve -``` - ---- - -### How it works - -When you run `opencode` it starts a TUI and a server. Where the TUI is the -client that talks to the server. The server exposes an OpenAPI 3.1 spec -endpoint. This endpoint is also used to generate an [SDK](/docs/sdk). - -:::tip -Use the opencode server to interact with opencode programmatically. -::: - -This architecture lets opencode support multiple clients and allows you to interact with opencode programmatically. - -You can run `opencode serve` to start a standalone server. If you have the -opencode TUI running, `opencode serve` will start a new server. - ---- - -#### Connect to an existing server - -When you start the TUI it randomly assigns a port and hostname. You can instead pass in the `--hostname` and `--port` [flags](/docs/cli). Then use this to connect to its server. - -The [`/tui`](#tui) endpoint can be used to drive the TUI through the server. For example, you can prefill or run a prompt. This setup is used by the OpenCode [IDE](/docs/ide) plugins. - ---- - -## Spec - -The server publishes an OpenAPI 3.1 spec that can be viewed at: - -``` -http://<hostname>:<port>/doc -``` - -For example, `http://localhost:4096/doc`. Use the spec to generate clients or inspect request and response types. Or view it in a Swagger explorer. - ---- - -## APIs - -The opencode server exposes the following APIs. - ---- - -### Global - -| Method | Path | Description | Response | -| ------ | ---------------- | ------------------------------ | ------------------------------------ | -| `GET` | `/global/health` | Get server health and version | `{ healthy: true, version: string }` | -| `GET` | `/global/event` | Get global events (SSE stream) | Event stream | - ---- - -### Project - -| Method | Path | Description | Response | -| ------ | ------------------ | ----------------------- | --------------------------------------------- | -| `GET` | `/project` | List all projects | <a href={typesUrl}><code>Project[]</code></a> | -| `GET` | `/project/current` | Get the current project | <a href={typesUrl}><code>Project</code></a> | - ---- - -### Path & VCS - -| Method | Path | Description | Response | -| ------ | ------- | ------------------------------------ | ------------------------------------------- | -| `GET` | `/path` | Get the current path | <a href={typesUrl}><code>Path</code></a> | -| `GET` | `/vcs` | Get VCS info for the current project | <a href={typesUrl}><code>VcsInfo</code></a> | - ---- - -### Instance - -| Method | Path | Description | Response | -| ------ | ------------------- | ---------------------------- | --------- | -| `POST` | `/instance/dispose` | Dispose the current instance | `boolean` | - ---- - -### Config - -| Method | Path | Description | Response | -| ------- | ------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- | -| `GET` | `/config` | Get config info | <a href={typesUrl}><code>Config</code></a> | -| `PATCH` | `/config` | Update config | <a href={typesUrl}><code>Config</code></a> | -| `GET` | `/config/providers` | List providers and default models | `{ providers: `<a href={typesUrl}>Provider[]</a>`, default: { [key: string]: string } }` | - ---- - -### Provider - -| Method | Path | Description | Response | -| ------ | -------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- | -| `GET` | `/provider` | List all providers | `{ all: `<a href={typesUrl}>Provider[]</a>`, default: {...}, connected: string[] }` | -| `GET` | `/provider/auth` | Get provider authentication methods | `{ [providerID: string]: `<a href={typesUrl}>ProviderAuthMethod[]</a>` }` | -| `POST` | `/provider/{id}/oauth/authorize` | Authorize a provider using OAuth | <a href={typesUrl}><code>ProviderAuthAuthorization</code></a> | -| `POST` | `/provider/{id}/oauth/callback` | Handle OAuth callback for a provider | `boolean` | - ---- - -### Sessions - -| Method | Path | Description | Notes | -| -------- | ---------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `GET` | `/session` | List all sessions | Returns <a href={typesUrl}><code>Session[]</code></a> | -| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns <a href={typesUrl}><code>Session</code></a> | -| `GET` | `/session/status` | Get session status for all sessions | Returns `{ [sessionID: string]: `<a href={typesUrl}>SessionStatus</a>` }` | -| `GET` | `/session/:id` | Get session details | Returns <a href={typesUrl}><code>Session</code></a> | -| `DELETE` | `/session/:id` | Delete a session and all its data | Returns `boolean` | -| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns <a href={typesUrl}><code>Session</code></a> | -| `GET` | `/session/:id/children` | Get a session's child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> | -| `GET` | `/session/:id/todo` | Get the todo list for a session | Returns <a href={typesUrl}><code>Todo[]</code></a> | -| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/fork` | Fork an existing session at a message | body: `{ messageID? }`, returns <a href={typesUrl}><code>Session</code></a> | -| `POST` | `/session/:id/abort` | Abort a running session | Returns `boolean` | -| `POST` | `/session/:id/share` | Share a session | Returns <a href={typesUrl}><code>Session</code></a> | -| `DELETE` | `/session/:id/share` | Unshare a session | Returns <a href={typesUrl}><code>Session</code></a> | -| `GET` | `/session/:id/diff` | Get the diff for this session | query: `messageID?`, returns <a href={typesUrl}><code>FileDiff[]</code></a> | -| `POST` | `/session/:id/summarize` | Summarize the session | body: `{ providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID, partID? }`, returns `boolean` | -| `POST` | `/session/:id/unrevert` | Restore all reverted messages | Returns `boolean` | -| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response, remember? }`, returns `boolean` | - ---- - -### Messages - -| Method | Path | Description | Notes | -| ------ | --------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GET` | `/session/:id/message` | List messages in a session | query: `limit?`, returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}[]` | -| `POST` | `/session/:id/message` | Send a message and wait for response | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` | -| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` | -| `POST` | `/session/:id/prompt_async` | Send a message asynchronously (no wait) | body: same as `/session/:id/message`, returns `204 No Content` | -| `POST` | `/session/:id/command` | Execute a slash command | body: `{ messageID?, agent?, model?, command, arguments }`, returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` | -| `POST` | `/session/:id/shell` | Run a shell command | body: `{ agent, model?, command }`, returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` | - ---- - -### Commands - -| Method | Path | Description | Response | -| ------ | ---------- | ----------------- | --------------------------------------------- | -| `GET` | `/command` | List all commands | <a href={typesUrl}><code>Command[]</code></a> | - ---- - -### Files - -| Method | Path | Description | Response | -| ------ | ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------- | -| `GET` | `/find?pattern=<pat>` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` | -| `GET` | `/find/file?query=<q>` | Find files and directories by name | `string[]` (paths) | -| `GET` | `/find/symbol?query=<q>` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> | -| `GET` | `/file?path=<path>` | List files and directories | <a href={typesUrl}><code>FileNode[]</code></a> | -| `GET` | `/file/content?path=<p>` | Read a file | <a href={typesUrl}><code>FileContent</code></a> | -| `GET` | `/file/status` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> | - -#### `/find/file` query parameters - -- `query` (required) — search string (fuzzy match) -- `type` (optional) — limit results to `"file"` or `"directory"` -- `directory` (optional) — override the project root for the search -- `limit` (optional) — max results (1–200) -- `dirs` (optional) — legacy flag (`"false"` returns only files) - ---- - -### Tools (Experimental) - -| Method | Path | Description | Response | -| ------ | ------------------------------------------- | ---------------------------------------- | -------------------------------------------- | -| `GET` | `/experimental/tool/ids` | List all tool IDs | <a href={typesUrl}><code>ToolIDs</code></a> | -| `GET` | `/experimental/tool?provider=<p>&model=<m>` | List tools with JSON schemas for a model | <a href={typesUrl}><code>ToolList</code></a> | - ---- - -### LSP, Formatters & MCP - -| Method | Path | Description | Response | -| ------ | ------------ | -------------------------- | -------------------------------------------------------- | -| `GET` | `/lsp` | Get LSP server status | <a href={typesUrl}><code>LSPStatus[]</code></a> | -| `GET` | `/formatter` | Get formatter status | <a href={typesUrl}><code>FormatterStatus[]</code></a> | -| `GET` | `/mcp` | Get MCP server status | `{ [name: string]: `<a href={typesUrl}>MCPStatus</a>` }` | -| `POST` | `/mcp` | Add MCP server dynamically | body: `{ name, config }`, returns MCP status object | - ---- - -### Agents - -| Method | Path | Description | Response | -| ------ | -------- | ------------------------- | ------------------------------------------- | -| `GET` | `/agent` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> | - ---- - -### Logging - -| Method | Path | Description | Response | -| ------ | ------ | ------------------------------------------------------------ | --------- | -| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` | - ---- - -### TUI - -| Method | Path | Description | Response | -| ------ | ----------------------- | ------------------------------------------- | ---------------------- | -| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` | -| `POST` | `/tui/open-help` | Open the help dialog | `boolean` | -| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` | -| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` | -| `POST` | `/tui/open-models` | Open the model selector | `boolean` | -| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` | -| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` | -| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` | -| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` | -| `GET` | `/tui/control/next` | Wait for the next control request | Control request object | -| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` | - ---- - -### Auth - -| Method | Path | Description | Response | -| ------ | ----------- | --------------------------------------------------------------- | --------- | -| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` | - ---- - -### Events - -| Method | Path | Description | Response | -| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- | -| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream | - ---- - -### Docs - -| Method | Path | Description | Response | -| ------ | ------ | ------------------------- | --------------------------- | -| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec | diff --git a/.opencode/skills/opencode-docs/docs/share.mdx b/.opencode/skills/opencode-docs/docs/share.mdx deleted file mode 100644 index b2c7933..0000000 --- a/.opencode/skills/opencode-docs/docs/share.mdx +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Share -description: Share your OpenCode conversations. ---- - -OpenCode's share feature allows you to create public links to your OpenCode conversations, so you can collaborate with teammates or get help from others. - -:::note -Shared conversations are publicly accessible to anyone with the link. -::: - ---- - -## How it works - -When you share a conversation, OpenCode: - -1. Creates a unique public URL for your session -2. Syncs your conversation history to our servers -3. Makes the conversation accessible via the shareable link — `opncd.ai/s/<share-id>` - ---- - -## Sharing - -OpenCode supports three sharing modes that control how conversations are shared: - ---- - -### Manual (default) - -By default, OpenCode uses manual sharing mode. Sessions are not shared automatically, but you can manually share them using the `/share` command: - -``` -/share -``` - -This will generate a unique URL that'll be copied to your clipboard. - -To explicitly set manual mode in your [config file](/docs/config): - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "share": "manual" -} -``` - ---- - -### Auto-share - -You can enable automatic sharing for all new conversations by setting the `share` option to `"auto"` in your [config file](/docs/config): - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "share": "auto" -} -``` - -With auto-share enabled, every new conversation will automatically be shared and a link will be generated. - ---- - -### Disabled - -You can disable sharing entirely by setting the `share` option to `"disabled"` in your [config file](/docs/config): - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "share": "disabled" -} -``` - -To enforce this across your team for a given project, add it to the `opencode.json` in your project and check into Git. - ---- - -## Un-sharing - -To stop sharing a conversation and remove it from public access: - -``` -/unshare -``` - -This will remove the share link and delete the data related to the conversation. - ---- - -## Privacy - -There are a few things to keep in mind when sharing a conversation. - ---- - -### Data retention - -Shared conversations remain accessible until you explicitly unshare them. This -includes: - -- Full conversation history -- All messages and responses -- Session metadata - ---- - -### Recommendations - -- Only share conversations that don't contain sensitive information. -- Review conversation content before sharing. -- Unshare conversations when collaboration is complete. -- Avoid sharing conversations with proprietary code or confidential data. -- For sensitive projects, disable sharing entirely. - ---- - -## For enterprises - -For enterprise deployments, the share feature can be: - -- **Disabled** entirely for security compliance -- **Restricted** to users authenticated through SSO only -- **Self-hosted** on your own infrastructure - -[Learn more](/docs/enterprise) about using opencode in your organization. diff --git a/.opencode/skills/opencode-docs/docs/skills.mdx b/.opencode/skills/opencode-docs/docs/skills.mdx deleted file mode 100644 index 2ce88ea..0000000 --- a/.opencode/skills/opencode-docs/docs/skills.mdx +++ /dev/null @@ -1,222 +0,0 @@ ---- -title: "Agent Skills" -description: "Define reusable behavior via SKILL.md definitions" ---- - -Agent skills let OpenCode discover reusable instructions from your repo or home directory. -Skills are loaded on-demand via the native `skill` tool—agents see available skills and can load the full content when needed. - ---- - -## Place files - -Create one folder per skill name and put a `SKILL.md` inside it. -OpenCode searches these locations: - -- Project config: `.opencode/skills/<name>/SKILL.md` -- Global config: `~/.config/opencode/skills/<name>/SKILL.md` -- Project Claude-compatible: `.claude/skills/<name>/SKILL.md` -- Global Claude-compatible: `~/.claude/skills/<name>/SKILL.md` -- Project agent-compatible: `.agents/skills/<name>/SKILL.md` -- Global agent-compatible: `~/.agents/skills/<name>/SKILL.md` - ---- - -## Understand discovery - -For project-local paths, OpenCode walks up from your current working directory until it reaches the git worktree. -It loads any matching `skills/*/SKILL.md` in `.opencode/` and any matching `.claude/skills/*/SKILL.md` or `.agents/skills/*/SKILL.md` along the way. - -Global definitions are also loaded from `~/.config/opencode/skills/*/SKILL.md`, `~/.claude/skills/*/SKILL.md`, and `~/.agents/skills/*/SKILL.md`. - ---- - -## Write frontmatter - -Each `SKILL.md` must start with YAML frontmatter. -Only these fields are recognized: - -- `name` (required) -- `description` (required) -- `license` (optional) -- `compatibility` (optional) -- `metadata` (optional, string-to-string map) - -Unknown frontmatter fields are ignored. - ---- - -## Validate names - -`name` must: - -- Be 1–64 characters -- Be lowercase alphanumeric with single hyphen separators -- Not start or end with `-` -- Not contain consecutive `--` -- Match the directory name that contains `SKILL.md` - -Equivalent regex: - -```text -^[a-z0-9]+(-[a-z0-9]+)*$ -``` - ---- - -## Follow length rules - -`description` must be 1-1024 characters. -Keep it specific enough for the agent to choose correctly. - ---- - -## Use an example - -Create `.opencode/skills/git-release/SKILL.md` like this: - -```markdown ---- -name: git-release -description: Create consistent releases and changelogs -license: MIT -compatibility: opencode -metadata: - audience: maintainers - workflow: github ---- - -## What I do - -- Draft release notes from merged PRs -- Propose a version bump -- Provide a copy-pasteable `gh release create` command - -## When to use me - -Use this when you are preparing a tagged release. -Ask clarifying questions if the target versioning scheme is unclear. -``` - ---- - -## Recognize tool description - -OpenCode lists available skills in the `skill` tool description. -Each entry includes the skill name and description: - -```xml -<available_skills> - <skill> - <name>git-release</name> - <description>Create consistent releases and changelogs</description> - </skill> -</available_skills> -``` - -The agent loads a skill by calling the tool: - -``` -skill({ name: "git-release" }) -``` - ---- - -## Configure permissions - -Control which skills agents can access using pattern-based permissions in `opencode.json`: - -```json -{ - "permission": { - "skill": { - "*": "allow", - "pr-review": "allow", - "internal-*": "deny", - "experimental-*": "ask" - } - } -} -``` - -| Permission | Behavior | -| ---------- | ----------------------------------------- | -| `allow` | Skill loads immediately | -| `deny` | Skill hidden from agent, access rejected | -| `ask` | User prompted for approval before loading | - -Patterns support wildcards: `internal-*` matches `internal-docs`, `internal-tools`, etc. - ---- - -## Override per agent - -Give specific agents different permissions than the global defaults. - -**For custom agents** (in agent frontmatter): - -```yaml ---- -permission: - skill: - "documents-*": "allow" ---- -``` - -**For built-in agents** (in `opencode.json`): - -```json -{ - "agent": { - "plan": { - "permission": { - "skill": { - "internal-*": "allow" - } - } - } - } -} -``` - ---- - -## Disable the skill tool - -Completely disable skills for agents that shouldn't use them: - -**For custom agents**: - -```yaml ---- -tools: - skill: false ---- -``` - -**For built-in agents**: - -```json -{ - "agent": { - "plan": { - "tools": { - "skill": false - } - } - } -} -``` - -When disabled, the `<available_skills>` section is omitted entirely. - ---- - -## Troubleshoot loading - -If a skill does not show up: - -1. Verify `SKILL.md` is spelled in all caps -2. Check that frontmatter includes `name` and `description` -3. Ensure skill names are unique across all locations -4. Check permissions—skills with `deny` are hidden from agents diff --git a/.opencode/skills/opencode-docs/docs/themes.mdx b/.opencode/skills/opencode-docs/docs/themes.mdx deleted file mode 100644 index 8a7c6a4..0000000 --- a/.opencode/skills/opencode-docs/docs/themes.mdx +++ /dev/null @@ -1,369 +0,0 @@ ---- -title: Themes -description: Select a built-in theme or define your own. ---- - -With OpenCode you can select from one of several built-in themes, use a theme that adapts to your terminal theme, or define your own custom theme. - -By default, OpenCode uses our own `opencode` theme. - ---- - -## Terminal requirements - -For themes to display correctly with their full color palette, your terminal must support **truecolor** (24-bit color). Most modern terminals support this by default, but you may need to enable it: - -- **Check support**: Run `echo $COLORTERM` - it should output `truecolor` or `24bit` -- **Enable truecolor**: Set the environment variable `COLORTERM=truecolor` in your shell profile -- **Terminal compatibility**: Ensure your terminal emulator supports 24-bit color (most modern terminals like iTerm2, Alacritty, Kitty, Windows Terminal, and recent versions of GNOME Terminal do) - -Without truecolor support, themes may appear with reduced color accuracy or fall back to the nearest 256-color approximation. - ---- - -## Built-in themes - -OpenCode comes with several built-in themes. - -| Name | Description | -| ---------------------- | ---------------------------------------------------------------------------- | -| `system` | Adapts to your terminal’s background color | -| `tokyonight` | Based on the [Tokyonight](https://github.com/folke/tokyonight.nvim) theme | -| `everforest` | Based on the [Everforest](https://github.com/sainnhe/everforest) theme | -| `ayu` | Based on the [Ayu](https://github.com/ayu-theme) dark theme | -| `catppuccin` | Based on the [Catppuccin](https://github.com/catppuccin) theme | -| `catppuccin-macchiato` | Based on the [Catppuccin](https://github.com/catppuccin) theme | -| `gruvbox` | Based on the [Gruvbox](https://github.com/morhetz/gruvbox) theme | -| `kanagawa` | Based on the [Kanagawa](https://github.com/rebelot/kanagawa.nvim) theme | -| `nord` | Based on the [Nord](https://github.com/nordtheme/nord) theme | -| `matrix` | Hacker-style green on black theme | -| `one-dark` | Based on the [Atom One](https://github.com/Th3Whit3Wolf/one-nvim) Dark theme | - -And more, we are constantly adding new themes. - ---- - -## System theme - -The `system` theme is designed to automatically adapt to your terminal's color scheme. Unlike traditional themes that use fixed colors, the _system_ theme: - -- **Generates gray scale**: Creates a custom gray scale based on your terminal's background color, ensuring optimal contrast. -- **Uses ANSI colors**: Leverages standard ANSI colors (0-15) for syntax highlighting and UI elements, which respect your terminal's color palette. -- **Preserves terminal defaults**: Uses `none` for text and background colors to maintain your terminal's native appearance. - -The system theme is for users who: - -- Want OpenCode to match their terminal's appearance -- Use custom terminal color schemes -- Prefer a consistent look across all terminal applications - ---- - -## Using a theme - -You can select a theme by bringing up the theme select with the `/theme` command. Or you can specify it in `tui.json`. - -```json title="tui.json" {3} -{ - "$schema": "https://opencode.ai/tui.json", - "theme": "tokyonight" -} -``` - ---- - -## Custom themes - -OpenCode supports a flexible JSON-based theme system that allows users to create and customize themes easily. - ---- - -### Hierarchy - -Themes are loaded from multiple directories in the following order where later directories override earlier ones: - -1. **Built-in themes** - These are embedded in the binary -2. **User config directory** - Defined in `~/.config/opencode/themes/*.json` or `$XDG_CONFIG_HOME/opencode/themes/*.json` -3. **Project root directory** - Defined in the `<project-root>/.opencode/themes/*.json` -4. **Current working directory** - Defined in `./.opencode/themes/*.json` - -If multiple directories contain a theme with the same name, the theme from the directory with higher priority will be used. - ---- - -### Creating a theme - -To create a custom theme, create a JSON file in one of the theme directories. - -For user-wide themes: - -```bash no-frame -mkdir -p ~/.config/opencode/themes -vim ~/.config/opencode/themes/my-theme.json -``` - -And for project-specific themes. - -```bash no-frame -mkdir -p .opencode/themes -vim .opencode/themes/my-theme.json -``` - ---- - -### JSON format - -Themes use a flexible JSON format with support for: - -- **Hex colors**: `"#ffffff"` -- **ANSI colors**: `3` (0-255) -- **Color references**: `"primary"` or custom definitions -- **Dark/light variants**: `{"dark": "#000", "light": "#fff"}` -- **No color**: `"none"` - Uses the terminal's default color or transparent - ---- - -### Color definitions - -The `defs` section is optional and it allows you to define reusable colors that can be referenced in the theme. - ---- - -### Terminal defaults - -The special value `"none"` can be used for any color to inherit the terminal's default color. This is particularly useful for creating themes that blend seamlessly with your terminal's color scheme: - -- `"text": "none"` - Uses terminal's default foreground color -- `"background": "none"` - Uses terminal's default background color - ---- - -### Example - -Here's an example of a custom theme: - -```json title="my-theme.json" -{ - "$schema": "https://opencode.ai/theme.json", - "defs": { - "nord0": "#2E3440", - "nord1": "#3B4252", - "nord2": "#434C5E", - "nord3": "#4C566A", - "nord4": "#D8DEE9", - "nord5": "#E5E9F0", - "nord6": "#ECEFF4", - "nord7": "#8FBCBB", - "nord8": "#88C0D0", - "nord9": "#81A1C1", - "nord10": "#5E81AC", - "nord11": "#BF616A", - "nord12": "#D08770", - "nord13": "#EBCB8B", - "nord14": "#A3BE8C", - "nord15": "#B48EAD" - }, - "theme": { - "primary": { - "dark": "nord8", - "light": "nord10" - }, - "secondary": { - "dark": "nord9", - "light": "nord9" - }, - "accent": { - "dark": "nord7", - "light": "nord7" - }, - "error": { - "dark": "nord11", - "light": "nord11" - }, - "warning": { - "dark": "nord12", - "light": "nord12" - }, - "success": { - "dark": "nord14", - "light": "nord14" - }, - "info": { - "dark": "nord8", - "light": "nord10" - }, - "text": { - "dark": "nord4", - "light": "nord0" - }, - "textMuted": { - "dark": "nord3", - "light": "nord1" - }, - "background": { - "dark": "nord0", - "light": "nord6" - }, - "backgroundPanel": { - "dark": "nord1", - "light": "nord5" - }, - "backgroundElement": { - "dark": "nord1", - "light": "nord4" - }, - "border": { - "dark": "nord2", - "light": "nord3" - }, - "borderActive": { - "dark": "nord3", - "light": "nord2" - }, - "borderSubtle": { - "dark": "nord2", - "light": "nord3" - }, - "diffAdded": { - "dark": "nord14", - "light": "nord14" - }, - "diffRemoved": { - "dark": "nord11", - "light": "nord11" - }, - "diffContext": { - "dark": "nord3", - "light": "nord3" - }, - "diffHunkHeader": { - "dark": "nord3", - "light": "nord3" - }, - "diffHighlightAdded": { - "dark": "nord14", - "light": "nord14" - }, - "diffHighlightRemoved": { - "dark": "nord11", - "light": "nord11" - }, - "diffAddedBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "diffRemovedBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "diffContextBg": { - "dark": "nord1", - "light": "nord5" - }, - "diffLineNumber": { - "dark": "nord2", - "light": "nord4" - }, - "diffAddedLineNumberBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "diffRemovedLineNumberBg": { - "dark": "#3B4252", - "light": "#E5E9F0" - }, - "markdownText": { - "dark": "nord4", - "light": "nord0" - }, - "markdownHeading": { - "dark": "nord8", - "light": "nord10" - }, - "markdownLink": { - "dark": "nord9", - "light": "nord9" - }, - "markdownLinkText": { - "dark": "nord7", - "light": "nord7" - }, - "markdownCode": { - "dark": "nord14", - "light": "nord14" - }, - "markdownBlockQuote": { - "dark": "nord3", - "light": "nord3" - }, - "markdownEmph": { - "dark": "nord12", - "light": "nord12" - }, - "markdownStrong": { - "dark": "nord13", - "light": "nord13" - }, - "markdownHorizontalRule": { - "dark": "nord3", - "light": "nord3" - }, - "markdownListItem": { - "dark": "nord8", - "light": "nord10" - }, - "markdownListEnumeration": { - "dark": "nord7", - "light": "nord7" - }, - "markdownImage": { - "dark": "nord9", - "light": "nord9" - }, - "markdownImageText": { - "dark": "nord7", - "light": "nord7" - }, - "markdownCodeBlock": { - "dark": "nord4", - "light": "nord0" - }, - "syntaxComment": { - "dark": "nord3", - "light": "nord3" - }, - "syntaxKeyword": { - "dark": "nord9", - "light": "nord9" - }, - "syntaxFunction": { - "dark": "nord8", - "light": "nord8" - }, - "syntaxVariable": { - "dark": "nord7", - "light": "nord7" - }, - "syntaxString": { - "dark": "nord14", - "light": "nord14" - }, - "syntaxNumber": { - "dark": "nord15", - "light": "nord15" - }, - "syntaxType": { - "dark": "nord7", - "light": "nord7" - }, - "syntaxOperator": { - "dark": "nord9", - "light": "nord9" - }, - "syntaxPunctuation": { - "dark": "nord4", - "light": "nord0" - } - } -} -``` diff --git a/.opencode/skills/opencode-docs/docs/tools.mdx b/.opencode/skills/opencode-docs/docs/tools.mdx deleted file mode 100644 index e8d5e09..0000000 --- a/.opencode/skills/opencode-docs/docs/tools.mdx +++ /dev/null @@ -1,345 +0,0 @@ ---- -title: Tools -description: Manage the tools an LLM can use. ---- - -Tools allow the LLM to perform actions in your codebase. OpenCode comes with a set of built-in tools, but you can extend it with [custom tools](/docs/custom-tools) or [MCP servers](/docs/mcp-servers). - -By default, all tools are **enabled** and don't need permission to run. You can control tool behavior through [permissions](/docs/permissions). - ---- - -## Configure - -Use the `permission` field to control tool behavior. You can allow, deny, or require approval for each tool. - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "deny", - "bash": "ask", - "webfetch": "allow" - } -} -``` - -You can also use wildcards to control multiple tools at once. For example, to require approval for all tools from an MCP server: - -```json title="opencode.json" -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "mymcp_*": "ask" - } -} -``` - -[Learn more](/docs/permissions) about configuring permissions. - ---- - -## Built-in - -Here are all the built-in tools available in OpenCode. - ---- - -### bash - -Execute shell commands in your project environment. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "bash": "allow" - } -} -``` - -This tool allows the LLM to run terminal commands like `npm install`, `git status`, or any other shell command. - ---- - -### edit - -Modify existing files using exact string replacements. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "allow" - } -} -``` - -This tool performs precise edits to files by replacing exact text matches. It's the primary way the LLM modifies code. - ---- - -### write - -Create new files or overwrite existing ones. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "allow" - } -} -``` - -Use this to allow the LLM to create new files. It will overwrite existing files if they already exist. - -:::note -The `write` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `apply_patch`). -::: - ---- - -### read - -Read file contents from your codebase. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "read": "allow" - } -} -``` - -This tool reads files and returns their contents. It supports reading specific line ranges for large files. - ---- - -### grep - -Search file contents using regular expressions. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "grep": "allow" - } -} -``` - -Fast content search across your codebase. Supports full regex syntax and file pattern filtering. - ---- - -### glob - -Find files by pattern matching. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "glob": "allow" - } -} -``` - -Search for files using glob patterns like `**/*.js` or `src/**/*.ts`. Returns matching file paths sorted by modification time. - ---- - -### lsp (experimental) - -Interact with your configured LSP servers to get code intelligence features like definitions, references, hover info, and call hierarchy. - -:::note -This tool is only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (or `OPENCODE_EXPERIMENTAL=true`). -::: - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "lsp": "allow" - } -} -``` - -Supported operations include `goToDefinition`, `findReferences`, `hover`, `documentSymbol`, `workspaceSymbol`, `goToImplementation`, `prepareCallHierarchy`, `incomingCalls`, and `outgoingCalls`. - -To configure which LSP servers are available for your project, see [LSP Servers](/docs/lsp). - ---- - -### apply_patch - -Apply patches to files. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "edit": "allow" - } -} -``` - -This tool applies patch files to your codebase. Useful for applying diffs and patches from various sources. - -When handling `tool.execute.before` or `tool.execute.after` hooks, check `input.tool === "apply_patch"` (not `"patch"`). - -`apply_patch` uses `output.args.patchText` instead of `output.args.filePath`. Paths are embedded in marker lines within `patchText` and are relative to the project root (for example: `*** Add File: src/new-file.ts`, `*** Update File: src/existing.ts`, `*** Move to: src/renamed.ts`, `*** Delete File: src/obsolete.ts`). - -:::note -The `apply_patch` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `apply_patch`). -::: - ---- - -### skill - -Load a [skill](/docs/skills) (a `SKILL.md` file) and return its content in the conversation. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "skill": "allow" - } -} -``` - ---- - -### todowrite - -Manage todo lists during coding sessions. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "todowrite": "allow" - } -} -``` - -Creates and updates task lists to track progress during complex operations. The LLM uses this to organize multi-step tasks. - -:::note -This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#permissions) -::: - ---- - -### webfetch - -Fetch web content. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "webfetch": "allow" - } -} -``` - -Allows the LLM to fetch and read web pages. Useful for looking up documentation or researching online resources. - ---- - -### websearch - -Search the web for information. - -:::note -This tool is only available when using the OpenCode provider or when the `OPENCODE_ENABLE_EXA` environment variable is set to any truthy value (e.g., `true` or `1`). - -To enable when launching OpenCode: - -```bash -OPENCODE_ENABLE_EXA=1 opencode -``` - -::: - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "websearch": "allow" - } -} -``` - -Performs web searches using Exa AI to find relevant information online. Useful for researching topics, finding current events, or gathering information beyond the training data cutoff. - -No API key is required — the tool connects directly to Exa AI's hosted MCP service without authentication. - -:::tip -Use `websearch` when you need to find information (discovery), and `webfetch` when you need to retrieve content from a specific URL (retrieval). -::: - ---- - -### question - -Ask the user questions during execution. - -```json title="opencode.json" {4} -{ - "$schema": "https://opencode.ai/config.json", - "permission": { - "question": "allow" - } -} -``` - -This tool allows the LLM to ask the user questions during a task. It's useful for: - -- Gathering user preferences or requirements -- Clarifying ambiguous instructions -- Getting decisions on implementation choices -- Offering choices about what direction to take - -Each question includes a header, the question text, and a list of options. Users can select from the provided options or type a custom answer. When there are multiple questions, users can navigate between them before submitting all answers. - ---- - -## Custom tools - -Custom tools let you define your own functions that the LLM can call. These are defined in your config file and can execute arbitrary code. - -[Learn more](/docs/custom-tools) about creating custom tools. - ---- - -## MCP servers - -MCP (Model Context Protocol) servers allow you to integrate external tools and services. This includes database access, API integrations, and third-party services. - -[Learn more](/docs/mcp-servers) about configuring MCP servers. - ---- - -## Internals - -Internally, tools like `grep` and `glob` use [ripgrep](https://github.com/BurntSushi/ripgrep) under the hood. By default, ripgrep respects `.gitignore` patterns, which means files and directories listed in your `.gitignore` will be excluded from searches and listings. - ---- - -### Ignore patterns - -To include files that would normally be ignored, create a `.ignore` file in your project root. This file can explicitly allow certain paths. - -```text title=".ignore" -!node_modules/ -!dist/ -!build/ -``` - -For example, this `.ignore` file allows ripgrep to search within `node_modules/`, `dist/`, and `build/` directories even if they're listed in `.gitignore`. diff --git a/.opencode/skills/opencode-docs/docs/troubleshooting.mdx b/.opencode/skills/opencode-docs/docs/troubleshooting.mdx deleted file mode 100644 index c4d504b..0000000 --- a/.opencode/skills/opencode-docs/docs/troubleshooting.mdx +++ /dev/null @@ -1,314 +0,0 @@ ---- -title: Troubleshooting -description: Common issues and how to resolve them. ---- - -To debug issues with OpenCode, start by checking the logs and local data it stores on disk. - ---- - -## Logs - -Log files are written to: - -- **macOS/Linux**: `~/.local/share/opencode/log/` -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.local\share\opencode\log` - -Log files are named with timestamps (e.g., `2025-01-09T123456.log`) and the most recent 10 log files are kept. - -You can set the log level with the `--log-level` command-line option to get more detailed debug information. For example, `opencode --log-level DEBUG`. - ---- - -## Storage - -opencode stores session data and other application data on disk at: - -- **macOS/Linux**: `~/.local/share/opencode/` -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.local\share\opencode` - -This directory contains: - -- `auth.json` - Authentication data like API keys, OAuth tokens -- `log/` - Application logs -- `project/` - Project-specific data like session and message data - - If the project is within a Git repo, it is stored in `./<project-slug>/storage/` - - If it is not a Git repo, it is stored in `./global/storage/` - ---- - -## Uninstall - -To uninstall the OpenCode CLI and remove its related files, run: - -```bash -opencode uninstall -``` - -The command shows what will be removed and asks for confirmation. See the [CLI reference](/docs/cli#uninstall) for options to keep your configuration or application data. - -To remove OpenCode Desktop, uninstall the application through your operating system's app management tools. - ---- - -## Desktop app - -OpenCode Desktop runs a local OpenCode server (the `opencode-cli` sidecar) in the background. Most issues are caused by a misbehaving plugin, a corrupted cache, or a bad server setting. - -### Quick checks - -- Fully quit and relaunch the app. -- If the app shows an error screen, click **Restart** and copy the error details. -- macOS only: `OpenCode` menu -> **Reload Webview** (helps if the UI is blank/frozen). - ---- - -### Disable plugins - -If the desktop app is crashing on launch, hanging, or behaving strangely, start by disabling plugins. - -#### Check the global config - -Open your global config file and look for a `plugin` key. - -- **macOS/Linux**: `~/.config/opencode/opencode.jsonc` (or `~/.config/opencode/opencode.json`) -- **macOS/Linux** (older installs): `~/.local/share/opencode/opencode.jsonc` -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.config\opencode\opencode.jsonc` - -If you have plugins configured, temporarily disable them by removing the key or setting it to an empty array: - -```jsonc -{ - "$schema": "https://opencode.ai/config.json", - "plugin": [], -} -``` - -#### Check plugin directories - -OpenCode can also load local plugins from disk. Temporarily move these out of the way (or rename the folder) and restart the desktop app: - -- **Global plugins** - - **macOS/Linux**: `~/.config/opencode/plugins/` - - **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.config\opencode\plugins` -- **Project plugins** (only if you use per-project config) - - `<your-project>/.opencode/plugins/` - -If the app starts working again, re-enable plugins one at a time to find which one is causing the issue. - ---- - -### Clear the cache - -If disabling plugins doesn't help (or a plugin install is stuck), clear the cache so OpenCode can rebuild it. - -1. Quit OpenCode Desktop completely. -2. Delete the cache directory: - -- **macOS**: Finder -> `Cmd+Shift+G` -> paste `~/.cache/opencode` -- **Linux**: delete `~/.cache/opencode` (or run `rm -rf ~/.cache/opencode`) -- **Windows**: Press `WIN+R` and paste `%USERPROFILE%\.cache\opencode` - -3. Restart OpenCode Desktop. - ---- - -### Fix server connection issues - -OpenCode Desktop can either start its own local server (default) or connect to a server URL you configured. - -If you see a **"Connection Failed"** dialog (or the app never gets past the splash screen), check for a custom server URL. - -#### Clear the desktop default server URL - -From the Home screen, click the server name (with the status dot) to open the Server picker. In the **Default server** section, click **Clear**. - -#### Remove `server.port` / `server.hostname` from your config - -If your `opencode.json(c)` contains a `server` section, temporarily remove it and restart the desktop app. - -#### Check environment variables - -If you have `OPENCODE_PORT` set in your environment, the desktop app will try to use that port for the local server. - -- Unset `OPENCODE_PORT` (or pick a free port) and restart. - ---- - -### Linux: Wayland / X11 issues - -On Linux, some Wayland setups can cause blank windows or compositor errors. - -- If you're on Wayland and the app is blank/crashing, try launching with `OC_ALLOW_WAYLAND=1`. -- If that makes things worse, remove it and try launching under an X11 session instead. - ---- - -### Windows: WebView2 runtime - -On Windows, OpenCode Desktop requires the Microsoft Edge **WebView2 Runtime**. If the app opens to a blank window or won't start, install/update WebView2 and try again. - ---- - -### Windows: General performance issues - -If you're experiencing slow performance, file access issues, or terminal problems on Windows, try using [WSL (Windows Subsystem for Linux)](/docs/windows-wsl). WSL provides a Linux environment that works more seamlessly with OpenCode's features. - ---- - -### Notifications not showing - -OpenCode Desktop only shows system notifications when: - -- notifications are enabled for OpenCode in your OS settings, and -- the app window is not focused. - ---- - -### Reset desktop app storage (last resort) - -If the app won't start and you can't clear settings from inside the UI, reset the desktop app's saved state. - -1. Quit OpenCode Desktop. -2. Find and delete these files (they live in the OpenCode Desktop app data directory): - -- `opencode.settings.dat` (desktop default server URL) -- `opencode.global.dat` and `opencode.workspace.*.dat` (UI state like recent servers/projects) - -To find the directory quickly: - -- **macOS**: Finder -> `Cmd+Shift+G` -> `~/Library/Application Support` (then search for the filenames above) -- **Linux**: search under `~/.local/share` for the filenames above -- **Windows**: Press `WIN+R` -> `%APPDATA%` (then search for the filenames above) - ---- - -## Getting help - -If you're experiencing issues with OpenCode: - -1. **Report issues on GitHub** - - The best way to report bugs or request features is through our GitHub repository: - - [**github.com/anomalyco/opencode/issues**](https://github.com/anomalyco/opencode/issues) - - Before creating a new issue, search existing issues to see if your problem has already been reported. - -2. **Join our Discord** - - For real-time help and community discussion, join our Discord server: - - [**opencode.ai/discord**](https://opencode.ai/discord) - ---- - -## Common issues - -Here are some common issues and how to resolve them. - ---- - -### OpenCode won't start - -1. Check the logs for error messages -2. Try running with `--print-logs` to see output in the terminal -3. Ensure you have the latest version with `opencode upgrade` - ---- - -### Authentication issues - -1. Try re-authenticating with the `/connect` command in the TUI -2. Check that your API keys are valid -3. Ensure your network allows connections to the provider's API - ---- - -### Model not available - -1. Check that you've authenticated with the provider -2. Verify the model name in your config is correct -3. Some models may require specific access or subscriptions - -If you encounter `ProviderModelNotFoundError` you are most likely incorrectly -referencing a model somewhere. -Models should be referenced like so: `<providerId>/<modelId>` - -Examples: - -- `openai/gpt-4.1` -- `openrouter/google/gemini-2.5-flash` -- `opencode/kimi-k2` - -To figure out what models you have access to, run `opencode models` - ---- - -### ProviderInitError - -If you encounter a ProviderInitError, you likely have an invalid or corrupted configuration. - -To resolve this: - -1. First, verify your provider is set up correctly by following the [providers guide](/docs/providers) -2. If the issue persists, try clearing your stored configuration: - - ```bash - rm -rf ~/.local/share/opencode - ``` - - On Windows, press `WIN+R` and delete: `%USERPROFILE%\.local\share\opencode` - -3. Re-authenticate with your provider using the `/connect` command in the TUI. - ---- - -### AI_APICallError and provider package issues - -If you encounter API call errors, this may be due to outdated provider packages. opencode dynamically installs provider packages (OpenAI, Anthropic, Google, etc.) as needed and caches them locally. - -To resolve provider package issues: - -1. Clear the provider package cache: - - ```bash - rm -rf ~/.cache/opencode - ``` - - On Windows, press `WIN+R` and delete: `%USERPROFILE%\.cache\opencode` - -2. Restart opencode to reinstall the latest provider packages - -This will force opencode to download the most recent versions of provider packages, which often resolves compatibility issues with model parameters and API changes. - ---- - -### Copy/paste not working on Linux - -Linux users need to have one of the following clipboard utilities installed for copy/paste functionality to work: - -**For X11 systems:** - -```bash -apt install -y xclip -# or -apt install -y xsel -``` - -**For Wayland systems:** - -```bash -apt install -y wl-clipboard -``` - -**For headless environments:** - -```bash -apt install -y xvfb -# and run: -Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & -export DISPLAY=:99.0 -``` - -opencode will detect if you're using Wayland and prefer `wl-clipboard`, otherwise it will try to find clipboard tools in order of: `xclip` and `xsel`. diff --git a/.opencode/skills/opencode-docs/docs/tui.mdx b/.opencode/skills/opencode-docs/docs/tui.mdx deleted file mode 100644 index a03797b..0000000 --- a/.opencode/skills/opencode-docs/docs/tui.mdx +++ /dev/null @@ -1,427 +0,0 @@ ---- -title: TUI -description: Using the OpenCode terminal user interface. ---- - -import { Tabs, TabItem } from "@astrojs/starlight/components" - -OpenCode provides an interactive terminal interface or TUI for working on your projects with an LLM. - -Running OpenCode starts the TUI for the current directory. - -```bash -opencode -``` - -Or you can start it for a specific working directory. - -```bash -opencode /path/to/project -``` - -Once you're in the TUI, you can prompt it with a message. - -```text -Give me a quick summary of the codebase. -``` - ---- - -## File references - -You can reference files in your messages using `@`. This does a fuzzy file search in the current working directory. - -:::tip -You can also use `@` to reference files in your messages. -::: - -```text "@packages/functions/src/api/index.ts" -How is auth handled in @packages/functions/src/api/index.ts? -``` - -The content of the file is added to the conversation automatically. - -Configured [references](/docs/references) also appear in `@` autocomplete. Type `@alias` to add the reference root as context, or type `@alias/` to autocomplete files inside that reference. - -```text "@docs/README.md" -Compare our setup with @docs/README.md -``` - ---- - -## Bash commands - -Start a message with `!` to run a shell command. - -```bash frame="none" -!ls -la -``` - -The output of the command is added to the conversation as a tool result. - ---- - -## Commands - -When using the OpenCode TUI, you can type `/` followed by a command name to quickly execute actions. For example: - -```bash frame="none" -/help -``` - -Most commands also have keyboard shortcuts using `ctrl+x` as the default leader key. [Learn more](/docs/keybinds). - -Here are all available slash commands: - ---- - -### connect - -Add a provider to OpenCode. Allows you to select from available providers and add their API keys. - -```bash frame="none" -/connect -``` - ---- - -### compact - -Compact the current session. _Alias_: `/summarize` - -```bash frame="none" -/compact -``` - -**Keybind:** `ctrl+x c` - ---- - -### details - -Toggle tool execution details. - -```bash frame="none" -/details -``` - ---- - -### editor - -Open external editor for composing messages. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup). - -```bash frame="none" -/editor -``` - -**Keybind:** `ctrl+x e` - ---- - -### exit - -Exit OpenCode. _Aliases_: `/quit`, `/q` - -```bash frame="none" -/exit -``` - -**Keybind:** `ctrl+x q` - ---- - -### export - -Export current conversation to Markdown and open in your default editor. Uses the editor set in your `EDITOR` environment variable. [Learn more](#editor-setup). - -```bash frame="none" -/export -``` - -**Keybind:** `ctrl+x x` - ---- - -### help - -Show the help dialog. - -```bash frame="none" -/help -``` - ---- - -### init - -Guided setup for creating or updating `AGENTS.md`. [Learn more](/docs/rules). - -```bash frame="none" -/init -``` - ---- - -### models - -List available models. - -```bash frame="none" -/models -``` - -**Keybind:** `ctrl+x m` - ---- - -### new - -Start a new session. _Alias_: `/clear` - -```bash frame="none" -/new -``` - -**Keybind:** `ctrl+x n` - ---- - -### redo - -Redo a previously undone message. Only available after using `/undo`. - -:::tip -Any file changes will also be restored. -::: - -Internally, this uses Git to manage the file changes. So your project **needs to -be a Git repository**. - -```bash frame="none" -/redo -``` - -**Keybind:** `ctrl+x r` - ---- - -### sessions - -List and switch between sessions. _Aliases_: `/resume`, `/continue` - -```bash frame="none" -/sessions -``` - -**Keybind:** `ctrl+x l` - ---- - -### share - -Share current session. [Learn more](/docs/share). - -```bash frame="none" -/share -``` - ---- - -### themes - -List available themes. - -```bash frame="none" -/themes -``` - -**Keybind:** `ctrl+x t` - ---- - -### thinking - -Toggle the visibility of thinking/reasoning blocks in the conversation. When enabled, you can see the model's reasoning process for models that support extended thinking. - -:::note -This command only controls whether thinking blocks are **displayed** - it does not enable or disable the model's reasoning capabilities. To toggle actual reasoning capabilities, use `ctrl+t` to cycle through model variants. -::: - -```bash frame="none" -/thinking -``` - ---- - -### undo - -Undo last message in the conversation. Removes the most recent user message, all subsequent responses, and any file changes. - -:::tip -Any file changes made will also be reverted. -::: - -Internally, this uses Git to manage the file changes. So your project **needs to -be a Git repository**. - -```bash frame="none" -/undo -``` - -**Keybind:** `ctrl+x u` - ---- - -### unshare - -Unshare current session. [Learn more](/docs/share#un-sharing). - -```bash frame="none" -/unshare -``` - ---- - -## Editor setup - -Both the `/editor` and `/export` commands use the editor specified in your `EDITOR` environment variable. - -<Tabs> - <TabItem label="Linux/macOS"> - ```bash - # Example for nano or vim - export EDITOR=nano - export EDITOR=vim - - # For GUI editors, VS Code, Cursor, VSCodium, Windsurf, Zed, etc. - # include --wait - export EDITOR="code --wait" - ``` - - To make it permanent, add this to your shell profile; - `~/.bashrc`, `~/.zshrc`, etc. - - </TabItem> - - <TabItem label="Windows (CMD)"> - ```bash - set EDITOR=notepad - - # For GUI editors, VS Code, Cursor, VSCodium, Windsurf, Zed, etc. - # include --wait - set EDITOR=code --wait - ``` - - To make it permanent, use **System Properties** > **Environment - Variables**. - - </TabItem> - - <TabItem label="Windows (PowerShell)"> - ```powershell - $env:EDITOR = "notepad" - - # For GUI editors, VS Code, Cursor, VSCodium, Windsurf, Zed, etc. - # include --wait - $env:EDITOR = "code --wait" - ``` - - To make it permanent, add this to your PowerShell profile. - - </TabItem> -</Tabs> - -Popular editor options include: - -- `code` - Visual Studio Code -- `cursor` - Cursor -- `windsurf` - Windsurf -- `nvim` - Neovim editor -- `vim` - Vim editor -- `nano` - Nano editor -- `notepad` - Windows Notepad -- `subl` - Sublime Text - -:::note -Some editors like VS Code need to be started with the `--wait` flag. -::: - -Some editors need command-line arguments to run in blocking mode. The `--wait` flag makes the editor process block until closed. - ---- - -## Configure - -You can customize TUI behavior through `tui.json` (or `tui.jsonc`). - -```json title="tui.json" -{ - "$schema": "https://opencode.ai/tui.json", - "theme": "opencode", - "leader_timeout": 2000, - "keybinds": { - "leader": "ctrl+x", - "command_list": "ctrl+p" - }, - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": false - }, - "diff_style": "auto", - "mouse": true, - "attention": { - "enabled": true, - "notifications": true, - "sound": true, - "volume": 0.4, - "sound_pack": "opencode.default", - "sounds": { - "error": "./sounds/error.mp3" - } - } -} -``` - -This is separate from `opencode.json`, which configures server/runtime behavior. - -`keybinds` is merged with built-in defaults, so you only need to configure the shortcuts you want to change. - -### Options - -- `theme` - Sets your UI theme. [Learn more](/docs/themes). -- `keybinds` - Customizes keyboard shortcuts. [Learn more](/docs/keybinds). -- `leader_timeout` - Controls how long OpenCode waits after the leader key. Defaults to `2000`. -- `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.** -- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (minimum: `0.001`, supports decimal values). Defaults to `3`. **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.** -- `diff_style` - Controls diff rendering. `"auto"` adapts to terminal width, `"stacked"` always shows a single-column layout. -- `mouse` - Enable or disable mouse capture in the TUI (default: `true`). When disabled, the terminal's native mouse selection/scrolling behavior is preserved. -- `attention` - Configures TUI desktop notifications and sounds. Disabled by default. - -Use `OPENCODE_TUI_CONFIG` to load a custom TUI config path. - -### Attention - -The TUI can request attention for questions, permissions, session errors, and completed sessions. Enable it with `attention.enabled`; built-in events play sounds when triggered, and non-subagent events request desktop notifications only when the terminal is blurred. - -- `enabled` - Enable all attention notifications and sounds. Defaults to `false`. -- `notifications` - Allow terminal-mediated desktop notifications when attention is enabled. Defaults to `true`. -- `sound` - Allow attention sounds when attention is enabled. Defaults to `true`. -- `volume` - Default sound volume from `0` to `1`. Defaults to `0.4`. -- `sound_pack` - Sound pack ID to use. Defaults to `opencode.default`. -- `sounds` - Override sound files for `default`, `question`, `permission`, `error`, `done`, or `subagent_done`. Paths can be absolute, `file://` URLs, or relative to `tui.json`. - ---- - -## Customization - -You can customize various aspects of the TUI view using the command palette (`ctrl+p`). These settings persist across restarts. - ---- - -#### Username display - -Toggle whether your username appears in chat messages. Access this through: - -- Command palette: Search for "username" or "hide username" -- The setting persists automatically and will be remembered across TUI sessions diff --git a/.opencode/skills/opencode-docs/docs/web.mdx b/.opencode/skills/opencode-docs/docs/web.mdx deleted file mode 100644 index 52b9746..0000000 --- a/.opencode/skills/opencode-docs/docs/web.mdx +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Web -description: Using OpenCode in your browser. ---- - -OpenCode can run as a web application in your browser, providing the same powerful AI coding experience without needing a terminal. - -![OpenCode Web - New Session](../../assets/web/web-homepage-new-session.png) - -## Getting Started - -Start the web interface by running: - -```bash -opencode web -``` - -This starts a local server on `127.0.0.1` with a random available port and automatically opens OpenCode in your default browser. - -:::caution -If `OPENCODE_SERVER_PASSWORD` is not set, the server will be unsecured. This is fine for local use but should be set for network access. -::: - -:::tip[Windows Users] -For the best experience, run `opencode web` from [WSL](/docs/windows-wsl) rather than PowerShell. This ensures proper file system access and terminal integration. -::: - ---- - -## Configuration - -You can configure the web server using command line flags or in your [config file](/docs/config). - -### Port - -By default, OpenCode picks an available port. You can specify a port: - -```bash -opencode web --port 4096 -``` - -### Hostname - -By default, the server binds to `127.0.0.1` (localhost only). To make OpenCode accessible on your network: - -```bash -opencode web --hostname 0.0.0.0 -``` - -When using `0.0.0.0`, OpenCode will display both local and network addresses: - -``` - Local access: http://localhost:4096 - Network access: http://192.168.1.100:4096 -``` - -### mDNS Discovery - -Enable mDNS to make your server discoverable on the local network: - -```bash -opencode web --mdns -``` - -This automatically sets the hostname to `0.0.0.0` and advertises the server as `opencode.local`. - -You can customize the mDNS domain name to run multiple instances on the same network: - -```bash -opencode web --mdns --mdns-domain myproject.local -``` - -### CORS - -To allow additional domains for CORS (useful for custom frontends): - -```bash -opencode web --cors https://example.com -``` - -### Authentication - -To protect access, set a password using the `OPENCODE_SERVER_PASSWORD` environment variable: - -```bash -OPENCODE_SERVER_PASSWORD=secret opencode web -``` - -The username defaults to `opencode` but can be changed with `OPENCODE_SERVER_USERNAME`. - ---- - -## Using the Web Interface - -Once started, the web interface provides access to your OpenCode sessions. - -### Sessions - -View and manage your sessions from the homepage. You can see active sessions and start new ones. - -![OpenCode Web - Active Session](../../assets/web/web-homepage-active-session.png) - -### Server Status - -Click "See Servers" to view connected servers and their status. - -![OpenCode Web - See Servers](../../assets/web/web-homepage-see-servers.png) - ---- - -## Attaching a Terminal - -You can attach a terminal TUI to a running web server: - -```bash -# Start the web server -opencode web --port 4096 - -# In another terminal, attach the TUI -opencode attach http://localhost:4096 -``` - -This allows you to use both the web interface and terminal simultaneously, sharing the same sessions and state. - ---- - -## Config File - -You can also configure server settings in your `opencode.json` config file: - -```json -{ - "server": { - "port": 4096, - "hostname": "0.0.0.0", - "mdns": true, - "cors": ["https://example.com"] - } -} -``` - -Command line flags take precedence over config file settings. diff --git a/.opencode/skills/opencode-docs/docs/windows-wsl.mdx b/.opencode/skills/opencode-docs/docs/windows-wsl.mdx deleted file mode 100644 index ca3b6a4..0000000 --- a/.opencode/skills/opencode-docs/docs/windows-wsl.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Windows (WSL) -description: Run OpenCode on Windows using WSL for the best experience. ---- - -import { Steps } from "@astrojs/starlight/components" - -While OpenCode can run directly on Windows, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install) for the best experience. WSL provides a Linux environment that works seamlessly with OpenCode's features. - -:::tip[Why WSL?] -WSL offers better file system performance, full terminal support, and compatibility with development tools that OpenCode relies on. -::: - ---- - -## Setup - -<Steps> - -1. **Install WSL** - - If you haven't already, [install WSL](https://learn.microsoft.com/en-us/windows/wsl/install) using the official Microsoft guide. - -2. **Install OpenCode in WSL** - - Once WSL is set up, open your WSL terminal and install OpenCode using one of the [installation methods](/docs/). - - ```bash - curl -fsSL https://opencode.ai/install | bash - ``` - -3. **Use OpenCode from WSL** - - Navigate to your project directory (access Windows files via `/mnt/c/`, `/mnt/d/`, etc.) and run OpenCode. - - ```bash - cd /mnt/c/Users/YourName/project - opencode - ``` - -</Steps> - ---- - -## Desktop App + WSL Server - -If you prefer using the OpenCode Desktop app but want to run the server in WSL: - -1. **Start the server in WSL** with `--hostname 0.0.0.0` to allow external connections: - - ```bash - opencode serve --hostname 0.0.0.0 --port 4096 - ``` - -2. **Connect the Desktop app** to `http://localhost:4096` - -:::note -If `localhost` does not work in your setup, connect using the WSL IP address instead (from WSL: `hostname -I`) and use `http://<wsl-ip>:4096`. -::: - -:::caution -When using `--hostname 0.0.0.0`, set `OPENCODE_SERVER_PASSWORD` to secure the server. -::: - -```bash -OPENCODE_SERVER_PASSWORD=your-password opencode serve --hostname 0.0.0.0 -``` - ---- - -## Web Client + WSL - -For the best web experience on Windows: - -1. **Run `opencode web` in the WSL terminal** rather than PowerShell: - - ```bash - opencode web --hostname 0.0.0.0 - ``` - -2. **Access from your Windows browser** at `http://localhost:<port>` (OpenCode prints the URL) - -Running `opencode web` from WSL ensures proper file system access and terminal integration while still being accessible from your Windows browser. - ---- - -## Accessing Windows Files - -WSL can access all your Windows files through the `/mnt/` directory: - -- `C:` drive → `/mnt/c/` -- `D:` drive → `/mnt/d/` -- And so on... - -Example: - -```bash -cd /mnt/c/Users/YourName/Documents/project -opencode -``` - -:::tip -For the smoothest experience, consider cloning/copying your repo into the WSL filesystem (for example under `~/code/`) and running OpenCode there. -::: - ---- - -## Tips - -- Keep OpenCode running in WSL for projects stored on Windows drives - file access is seamless -- Use VS Code's [WSL extension](https://code.visualstudio.com/docs/remote/wsl) alongside OpenCode for an integrated development workflow -- Your OpenCode config and sessions are stored within the WSL environment at `~/.local/share/opencode/` diff --git a/.opencode/skills/opencode-docs/docs/zen.mdx b/.opencode/skills/opencode-docs/docs/zen.mdx deleted file mode 100644 index 49ca11a..0000000 --- a/.opencode/skills/opencode-docs/docs/zen.mdx +++ /dev/null @@ -1,319 +0,0 @@ ---- -title: Zen -description: Curated list of models provided by OpenCode. ---- - -import config from "../../../config.mjs" -export const console = config.console -export const email = `mailto:${config.email}` - -OpenCode Zen is a list of tested and verified models provided by the OpenCode team. - -Zen works like any other provider in OpenCode. You login to OpenCode Zen and get -your API key. It's **completely optional** and you don't need to use it to use -OpenCode. - ---- - -## Background - -There are a large number of models out there but only a few of -these models work well as coding agents. Additionally, most providers are -configured very differently; so you get very different performance and quality. - -:::tip -We tested a select group of models and providers that work well with OpenCode. -::: - -So if you are using a model through something like OpenRouter, you can never be -sure if you are getting the best version of the model you want. - -To fix this, we did a couple of things: - -1. We tested a select group of models and talked to their teams about how to - best run them. -2. We then worked with a few providers to make sure these were being served - correctly. -3. Finally, we benchmarked the combination of the model/provider and came up - with a list that we feel good recommending. - -OpenCode Zen is an AI gateway that gives you access to these models. - ---- - -## How it works - -OpenCode Zen works like any other provider in OpenCode. - -1. You sign in to **<a href={console}>OpenCode Zen</a>**, add your billing - details, and copy your API key. -2. You run the `/connect` command in the TUI, select OpenCode Zen, and paste your API key. -3. Run `/models` in the TUI to see the list of models we recommend. - -You are charged per request and you can add credits to your account. - ---- - -## Endpoints - -You can also access our models through the following API endpoints. - -| Model | Model ID | Endpoint | AI SDK Package | -| ---------------------- | ---------------------- | ---------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Fable 5 | claude-fable-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.8 | claude-opus-4-8 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 5 | claude-sonnet-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M3 | minimax-m3 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.2 | glm-5.2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.7 Code | kimi-k2.7-code | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 Free | mimo-v2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| North Mini Code Free | north-mini-code-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Nemotron 3 Ultra Free | nemotron-3-ultra-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | - -The [model id](/docs/config/#models) in your OpenCode config -uses the format `opencode/<model-id>`. For example, for GPT 5.5, you would -use `opencode/gpt-5.5` in your config. - ---- - -### Models - -You can fetch the full list of available models and their metadata from: - -``` -https://opencode.ai/zen/v1/models -``` - ---- - -## Pricing - -We support a pay-as-you-go model. Below are the prices **per 1M tokens**. - -| Model | Input | Output | Cached Read | Cached Write | -| --------------------------------- | ------ | ------- | ----------- | ------------ | -| Big Pickle | Free | Free | Free | - | -| DeepSeek V4 Flash Free | Free | Free | Free | - | -| MiMo-V2.5 Free | Free | Free | Free | - | -| North Mini Code Free | Free | Free | Free | - | -| Nemotron 3 Ultra Free | Free | Free | Free | - | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | - | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - | -| GLM 5.2 | $1.40 | $4.40 | $0.26 | - | -| GLM 5.1 | $1.40 | $4.40 | $0.26 | - | -| GLM 5 | $1.00 | $3.20 | $0.20 | - | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | -| Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | -| DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | -| DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | -| Grok Build 0.1 | $1.00 | $2.00 | $0.20 | - | -| Claude Fable 5 | $10.00 | $50.00 | $1.00 | $12.50 | -| Claude Opus 4.8 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | -| Claude Sonnet 5 | $2.00 | $10.00 | $0.20 | $2.50 | -| Claude Sonnet 4.6 | $3.00 | $15.00 | $0.30 | $3.75 | -| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | -| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | -| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | -| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - | -| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | -| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | -| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | -| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | -| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | -| GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | -| GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | -| GPT 5.3 Codex Spark | $1.75 | $14.00 | $0.175 | - | -| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - | -| GPT 5.2 | $1.75 | $14.00 | $0.175 | - | -| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - | -| GPT 5.1 | $1.07 | $8.50 | $0.107 | - | -| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - | -| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - | -| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - | -| GPT 5 | $1.07 | $8.50 | $0.107 | - | -| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - | -| GPT 5 Nano | $0.05 | $0.40 | $0.005 | - | - -You might notice _Claude Haiku 3.5_ in your usage history. This is a [low cost model](/docs/config/#models) that's used to generate the titles of your sessions. - -:::note -Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don't charge anything beyond that. -::: - -The free models: - -- DeepSeek V4 Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- MiMo-V2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- North Mini Code Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- Nemotron 3 Ultra Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - -<a href={email}>Contact us</a> if you have any questions. - ---- - -### Auto-reload - -If your balance goes below $5, Zen will automatically reload $20. - -You can change the auto-reload amount. You can also disable auto-reload entirely. - ---- - -### Monthly limits - -You can also set a monthly usage limit for the entire workspace and for each -member of your team. - -For example, let's say you set a monthly usage limit to $20, Zen will not use -more than $20 in a month. But if you have auto-reload enabled, Zen might end up -charging you more than $20 if your balance goes below $5. - ---- - -### Deprecated models - -| Model | Deprecation date | -| ------------------ | ----------------- | -| GPT 5.2 Codex | July 23, 2026 | -| GPT 5.1 Codex | July 23, 2026 | -| GPT 5.1 Codex Max | July 23, 2026 | -| GPT 5.1 Codex Mini | July 23, 2026 | -| GPT 5 Codex | July 23, 2026 | -| Claude Opus 4.1 | August 5, 2026 | -| Claude Sonnet 4 | June 15, 2026 | -| Claude Haiku 3.5 | February 16, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| MiniMax M2.5 | August 5, 2026 | -| MiniMax M2.1 | March 15, 2026 | -| GLM 5 | May 14, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Kimi K2.5 | August 5, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | February 6, 2026 | - ---- - -## Privacy - -All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions: - -- Big Pickle: During its free period, collected data may be used to improve the model. -- DeepSeek V4 Flash Free: During its free period, collected data may be used to improve the model. -- MiMo-V2.5 Free: During its free period, collected data may be used to improve the model. -- North Mini Code Free: During its free period, collected data may be retained and used to improve the model. Do not submit personal or confidential data. See our [Terms of Use](https://cohere.com/terms-of-use) and [Privacy Policy](https://cohere.com/privacy). -- Nemotron 3 Ultra Free (NVIDIA free endpoints): Trial use only — do not submit personal or confidential data. Your use is logged for security purposes and to improve NVIDIA products and services. The logged session data for improvement purposes is not linked to your identity or any persistent identifier. For more information about our data processing practices, see our [Privacy Policy](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). By interacting with this endpoint, you consent to our collection, recording, and use of such information and the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). -- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). -- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). - ---- - -## For Teams - -Zen also works great for teams. You can invite teammates, assign roles, curate -the models your team uses, and more. - -:::note -Workspaces are currently free for teams as a part of the beta. -::: - -Managing your workspace is currently free for teams as a part of the beta. We'll be -sharing more details on the pricing soon. - ---- - -### Roles - -You can invite teammates to your workspace and assign roles: - -- **Admin**: Manage models, members, API keys, and billing -- **Member**: Manage only their own API keys - -Admins can also set monthly spending limits for each member to keep costs under control. - ---- - -### Model access - -Admins can enable or disable specific models for the workspace. Requests made to a disabled model will return an error. - -This is useful for cases where you want to disable the use of a model that -collects data. - ---- - -### Bring your own key - -You can use your own OpenAI or Anthropic API keys while still accessing other models in Zen. - -When you use your own keys, tokens are billed directly by the provider, not by Zen. - -For example, your organization might already have a key for OpenAI or Anthropic -and you want to use that instead of the one that Zen provides. - ---- - -## Goals - -We created OpenCode Zen to: - -1. **Benchmark** the best models/providers for coding agents. -2. Have access to the **highest quality** options and not downgrade performance or route to cheaper providers. -3. Pass along any **price drops** by selling at cost; so the only markup is to cover our processing fees. -4. Have **no lock-in** by allowing you to use it with any other coding agent. And always let you use any other provider with OpenCode as well. diff --git a/.opencode/skills/opencode-docs/fetch.sh b/.opencode/skills/opencode-docs/fetch.sh deleted file mode 100644 index 025be4c..0000000 --- a/.opencode/skills/opencode-docs/fetch.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -# fetch.sh — Refresh vendored OpenCode docs from the anomalyco/opencode repo. -# -# Shallow-clones with sparse checkout, extracts the English top-level .mdx -# files from packages/web/src/content/docs/, and cleans up. -# -# Usage: bash .opencode/skills/opencode-docs/fetch.sh -# -# Derived from: obra/superpowers-developing-for-claude-code (MIT, © Jesse Vincent) -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DOCS_DIR="${SCRIPT_DIR}/docs" -TEMP_DIR="${SCRIPT_DIR}/.temp-clone" - -echo "==> Fetching OpenCode docs from anomalyco/opencode (dev branch)..." - -# Clean any previous temp clone -rm -rf "${TEMP_DIR}" - -# Shallow sparse clone — only the docs directory -git clone \ - --depth 1 \ - --filter=blob:none \ - --sparse \ - https://github.com/anomalyco/opencode.git \ - "${TEMP_DIR}" 2>&1 | sed 's/^/ /' - -cd "${TEMP_DIR}" -git sparse-checkout set packages/web/src/content/docs 2>&1 | sed 's/^/ /' - -# Clear existing docs -rm -rf "${DOCS_DIR:?}"/* -mkdir -p "${DOCS_DIR}" - -# Copy only the top-level .mdx files (not translation directories) -for file in packages/web/src/content/docs/*.mdx; do - if [ -f "$file" ]; then - cp "$file" "${DOCS_DIR}/" - echo " -> $(basename "$file")" - fi -done - -# Clean up -cd "${SCRIPT_DIR}" -rm -rf "${TEMP_DIR}" - -file_count=$(ls -1 "${DOCS_DIR}"/*.mdx 2>/dev/null | wc -l) -echo "==> Done. ${file_count} doc files in ${DOCS_DIR}/" diff --git a/.opencode/skills/pest-browser/SKILL.md b/.opencode/skills/pest-browser/SKILL.md deleted file mode 100644 index 5126bb3..0000000 --- a/.opencode/skills/pest-browser/SKILL.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -name: pest-browser -description: Use when writing browser tests with Pest v4. Covers plugin installation, Playwright setup, global config, CI workflow additions, and test examples. Reserve browser tests for critical UI flows only. ---- - -## When to Use Browser Tests - -Reserve for critical UI flows only: login, checkout, critical forms. -Not for every page. Unit and Feature tests cover everything else. - -## Installation - -```bash -composer require pestphp/pest-plugin-browser --dev -npm install playwright@latest -npx playwright install -``` - -Verify `.gitignore` includes: -```text -tests/Browser/Screenshots/ -``` -(The template ships with this entry already present.) - -## Global Config (`tests/Pest.php`) - -```php -pest()->browser()->timeout(10000); -// pest()->browser()->headed(); // uncomment locally for visual debugging -``` - -## CI Setup (GitHub Actions) - -Add to your workflow before the Pest run step: - -```yaml -- uses: actions/setup-node@v4 - with: - node-version: lts/* -- name: Install JS dependencies - run: npm ci -- name: Install Playwright browsers - run: npx playwright install --with-deps -``` - -## Browser Test Examples - -```php -it('loads the homepage without JS errors', function () { - $page = visit('/'); - $page->assertSee('Expected Heading') - ->assertNoJavaScriptErrors() - ->assertNoConsoleLogs(); -}); - -it('loads all public pages without smoke', function () { - $pages = visit(['/', '/about', '/contact']); - $pages->assertNoSmoke(); -}); -``` - -## Run in Debug / Headed Mode - -```bash -php vendor/bin/pest --debug -``` diff --git a/.opencode/skills/prototype/SKILL.md b/.opencode/skills/prototype/SKILL.md deleted file mode 100644 index 183621b..0000000 --- a/.opencode/skills/prototype/SKILL.md +++ /dev/null @@ -1,221 +0,0 @@ ---- -name: prototype -description: Use when you need to answer a technical viability question with throwaway code before committing to an implementation plan. Builds a disposable prototype — logic (PHP CLI), UI (HTML+CSS+JS variants), or integration (DB/API boundary test) — to learn fast and then delete. -derived-from: mattpocock/skills (MIT, © Matt Pocock) ---- - -# Prototype - -A prototype is **throwaway code that answers a question**. The question -decides the shape. This is not production code — it exists to de-risk a -design decision before `writing-plans` commits to a detailed TDD plan. - -## When to use - -After `brainstorming` has produced a design but before `writing-plans` writes -the task breakdown. Use when you're uncertain about: - -- Whether a state model or logic flow feels right. -- What a UI should look like or how it should behave. -- Whether a library, API, or DB query pattern actually works at the boundary. - -If you can write the plan with confidence, skip prototyping — go straight to -`writing-plans`. - -## Pick a branch - -Identify which question is being answered — from the user's prompt, the -surrounding code, or by asking: - -- **"Does this logic / state model feel right?"** → [Logic branch](#logic-branch) -- **"What should this look like?"** → [UI branch](#ui-branch) -- **"Does this work at the boundary?"** → [Integration branch](#integration-branch) - -The three branches produce very different artifacts — getting this wrong -wastes the whole prototype. If the question is genuinely ambiguous and the -user isn't reachable, default to whichever branch better matches the -surrounding code (a backend module → logic; a page or component → UI; a -service or data layer → integration) and state the assumption at the top. - -## Rules that apply to all branches - -1. **Throwaway from day one, and clearly marked as such.** Locate the - prototype code close to where it will actually be used (next to the - module or page it's prototyping for) so context is obvious — but name it - so a casual reader can see it's a prototype, not production. Use a - `prototype_` prefix or a `prototypes/` directory. -2. **One command to run.** The user must be able to start it without - thinking: `php prototype_logic.php`, `php -S localhost:8080 - prototype_ui.php`, etc. -3. **No persistence by default.** State lives in memory. Persistence is the - thing the prototype is *checking*, not something it should depend on. If - the question explicitly involves a database, hit a scratch DB or a local - file with a clear "PROTOTYPE — wipe me" name. -4. **Skip the polish.** No tests, no error handling beyond what makes the - prototype *runnable*, no abstractions, no RCS headers, no vim modelines. - The point is to learn something fast and then delete it. -5. **Surface the state.** After every action, print or render the full - relevant state so the user can see what changed. -6. **Delete or absorb when done.** When the prototype has answered its - question, either delete it or fold the validated decision into the real - code — don't leave it rotting in the repo. - -## Logic branch - -Build a tiny interactive PHP CLI script that pushes the state machine or -logic through cases that are hard to reason about on paper. - -```php -<?php -// prototype_<concern>.php — THROWAWAY: answers "<question>" -// Run: php prototype_<concern>.php - -$state = 'idle'; -$actions = ['start', 'pause', 'resume', 'complete', 'cancel']; - -function transition(string $state, string $action): string { - // ... the logic under test ... - return $newState; -} - -foreach ($actions as $action) { - $new = transition($state, $action); - echo "{$state} + {$action} → {$new}\n"; - $state = $new; -} -``` - -- Print every transition so the user can see the flow. -- Include edge cases that are hard to reason about: empty input, concurrent - actions, invalid transitions. -- If the logic involves a class or function from the real codebase, require - it directly — don't copy-paste. - -## UI branch - -Generate several radically different UI variations on a single PHP page, -switchable via a `?variant=N` URL search param and a floating bottom bar. - -```php -<?php -// prototype_<page>.php — THROWAWAY: answers "what should <page> look like?" -// Run: php -S localhost:8080 prototype_<page>.php - -$variant = $_GET['variant'] ?? '1'; -$variants = ['1' => 'Minimal', '2' => 'Card-based', '3' => 'Dense table']; -?> -<!DOCTYPE html> -<html> -<head> - <style> - /* Variant-specific styles inline — this is throwaway */ - body { font-family: sans-serif; margin: 2rem; } - <?php if ($variant === '1'): ?> - .item { padding: 0.5rem; border-bottom: 1px solid #ccc; } - <?php elseif ($variant === '2'): ?> - .item { display: inline-block; width: 200px; margin: 1rem; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 1rem; } - <?php else: ?> - .item { display: table-row; } - .item span { display: table-cell; padding: 0.25rem 1rem; } - <?php endif; ?> - </style> -</head> -<body> - <h1>Variant <?= htmlspecialchars($variant) ?>: <?= $variants[$variant] ?></h1> - <!-- render sample content in the variant's style --> - - <div style="position:fixed;bottom:0;left:0;right:0;background:#333;padding:0.5rem;text-align:center"> - <?php foreach ($variants as $n => $name): ?> - <a href="?variant=<?= $n ?>" style="color:white;margin:0 1rem"><?= $name ?></a> - <?php endforeach; ?> - </div> -</body> -</html> -``` - -- Serve with `php -S localhost:8080 prototype_<page>.php`. -- Make variants **radically different** — not minor tweaks. The point is to - see which direction feels right. -- Use inline styles — this is throwaway, don't create SCSS files. -- Don't wire up real data — use hardcoded sample data that represents the - shape. - -## Integration branch - -Build a one-file throwaway that exercises a real DB query, API call, or -external service interaction to verify behavior at the boundary. This is -unique to this stack — PHP + MariaDB + Aurora's SQL handler. - -```php -<?php -// prototype_<boundary>.php — THROWAWAY: answers "does <pattern> work?" -// Run: php prototype_<boundary>.php - -require_once(__DIR__ . '/../aurora/aurora.inc.php'); - -// Test a specific query pattern or API interaction -$db = new KYAULabs\Aurora\SQL(); - -// The pattern under test: -$result = $db->execute( - "SELECT u.id, u.email, COUNT(o.id) AS order_count - FROM users u - LEFT JOIN orders o ON o.user_id = u.id - WHERE u.created_at >= ? - GROUP BY u.id - ORDER BY order_count DESC - LIMIT 10", - [date('Y-m-d', strtotime('-30 days'))] -); - -foreach ($result as $row) { - echo "{$row['id']}\t{$row['email']}\t{$row['order_count']} orders\n"; -} -``` - -- Use the real Aurora SQL handler or real PDO — the point is to verify the - actual boundary behaves as expected. -- If testing an external API, use `curl` or `file_get_contents` directly — - don't build a client class. -- Print the raw result so the user can see exactly what comes back. -- If the prototype touches a real database, use a scratch table with a - `prototype_` prefix and clean it up when done. - -## When done - -The *answer* is the only thing worth keeping from a prototype. Capture it -somewhere durable (commit message, ADR, `CONTEXT.md` note, or a `NOTES.md` -next to the prototype) along with the question it was answering: - -```markdown -# Prototype notes: <question> - -**Question:** <what we were trying to answer> -**Answer:** <what we learned> -**Decision:** <what we'll do as a result> -**Prototype file:** <path> (delete after capturing the answer) -``` - -If the user is around, that capture is a quick conversation; if not, leave -the placeholder so they can fill in the verdict before deleting the -prototype. - -## Gotchas - -Known failure modes that compound over time. Add entries when this skill -causes a preventable mistake. - -- *Prototype code accidentally committed to production* — always use a - `prototype_` prefix or `prototypes/` directory, and delete after capturing - the answer. -- *Prototype UI variant styles leak into the real SCSS* — use inline styles - only; never create `.scss` files for throwaway prototypes. - -## Cross-refs - -- `brainstorming` skill — the step before this one (produces the design). -- `writing-plans` skill — the step after (produces the TDD plan, informed by - the prototype's answer). -- `@architect` agent — for non-trivial prototypes that touch system - boundaries, suggest an architect review of the approach. diff --git a/.opencode/skills/rcs-header/SKILL.md b/.opencode/skills/rcs-header/SKILL.md deleted file mode 100644 index f7de850..0000000 --- a/.opencode/skills/rcs-header/SKILL.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -name: rcs-header -description: Use when creating or modifying any source file. Provides the required RCS-style header format, vim modeline, and PHPDoc conventions. ---- - -## RCS-Style Header (REQUIRED on every source file) - -Every source file must begin with an RCS-style creation stamp. Write it once when -the file is first created. Never update it. The pre-commit hook auto-adds the -header if you miss one. - -Applies to **source files only**: `.php`, `.js`, `.scss`, `.sh`. Markdown, JSON, -YAML, and other non-source files do not carry RCS headers. - -```text -PHP: <?php # $KYAULabs: filename.php creator@host YYYY/MM/DD ±TZ Exp $ -SCSS: // $KYAULabs: filename.scss creator@host YYYY/MM/DD ±TZ Exp $ -JS: // $KYAULabs: filename.js creator@host YYYY/MM/DD ±TZ Exp $ -Bash: # $KYAULabs: filename.sh creator@host YYYY/MM/DD ±TZ Exp $ -``` - -Use the actual filename (not a path). The fields are: - -- `creator@host` — `git config user.name`@`hostname` at creation time. -- `YYYY/MM/DD ±TZ` — creation date and system timezone offset. - -The header is a one-time creation marker, like a `Signed-off-by` trailer at the -file level. Version and modification history are tracked by git. Do not bump the -version, update the timestamp, or otherwise modify the header after creation. - -### Placement - -- **PHP**: after `<?php` and optional `declare(strict_types=1);`, before code. -- **SCSS/JS**: first line of the file. -- **Bash**: after the shebang line (`#!/usr/bin/env bash`), before code. - -### Automation - -The `.github/hooks/pre-commit` hook checks staged source files. If a file is -missing an RCS header, the hook inserts one automatically. Run -`bash .github/scripts/install-hooks.sh` once after cloning to activate it. - -## Vim Modeline (REQUIRED at end of every source file) - -```text -PHP: // vim: ft=php sts=4 sw=4 ts=4 et : -SCSS: // vim: ft=scss sts=2 sw=2 ts=2 et : -JS: // vim: ft=javascript sts=4 sw=4 ts=4 noet : -Bash: # vim: ft=sh sts=4 sw=4 ts=4 et : -``` - -The modeline is the very last line of the file, after all code. - -## PHPDoc (PSR-5) — Required on all PHP classes, methods, and functions - -```php -/** - * Short one-line description. - * - * Longer description if needed. - * - * @param string $email User email address. - * @param string $password Plain-text password. - * @return string Session token. - * @throws InvalidCredentialsException If credentials are invalid. - * @throws InvalidArgumentException If email is empty. - */ -``` - -Document all `@param`, `@return`, and `@throws`. Align the descriptions. - -The "no explanatory comments" policy is owned by `AGENTS.md` (Commenting section) and -`.opencode/docs/conventions.md` (PHP Standards). Refer to those authoritative sources. diff --git a/.opencode/skills/receiving-code-review/SKILL.md b/.opencode/skills/receiving-code-review/SKILL.md deleted file mode 100644 index 1e5ba08..0000000 --- a/.opencode/skills/receiving-code-review/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: receiving-code-review -description: Use when @code-review returns findings and you need to triage and respond to them — before acting on anything. Governs how to consume review feedback without over-complying or thrashing. -derived-from: obra/superpowers (MIT, © Jesse Vincent) ---- - -# Receiving Code Review - -Consume review findings from `@code-review` with discipline: triage each finding -by severity, fix what must be fixed, and push back on what doesn't apply. Do not -blindly apply every suggestion — and do not argue with security or correctness -findings. - -**Announce at start:** "I'm using the receiving-code-review skill to triage -findings from `@code-review`." - -## Triage matrix - -Classify each finding — exactly one category: - -| Severity | Action required | Example | -|---|---|---| -| **Blocking** | Must fix before proceeding. Do not push back. | SQL injection, XSS, secret exposure, missing RCS header, logic error, hard-boundary violation | -| **Suggested** | Fix if clean and low-risk; otherwise defer with a one-line reason. | Style drift not caught by linters, missing test coverage for new logic, unclear naming | -| **Informational** | Acknowledge and move on. No action needed. | Minor style preferences, future refactor suggestions, "consider" notes | - -If you cannot articulate the bug or regression a finding would prevent, -it is at most **Suggested** — not Blocking. - -## Process - -1. **Read all findings** before acting on any. Group by severity. -2. **Fix Blocking** first — security, correctness, hard boundaries, missing - RCS headers on new files. -3. **Review Suggested** one by one: - - If the fix is clean and low-risk (under 5 lines, obvious correctness) → - apply it. - - If the fix is risky, introduces new surface area, or doesn't map to a bug → - defer with a one-line reason. -4. **Acknowledge Informational** — read them, then move on. Do not implement. -5. **Re-run `@code-review`** after fixes to confirm Blocking is resolved. -6. **Present a summary** to the user: what was fixed, what was deferred and why. - -## Response format - -For the user-facing summary after triage: - -``` -## Code Review Response - -### Fixed (N) -- [finding description] — [brief note on the fix] - -### Deferred (N) -- [finding description] — [one-line reason for deferral] - -### Informational (N) -- [finding description] — acknowledged -``` - -## Rules - -- Never apply a suggestion without understanding why it is correct. -- Never push back on Blocking findings. Fix them. -- Do not delegate review-follow-up to `@tdd`. This skill runs in the build - agent after `@code-review` returns. -- The deferral reason must be substantive, not a dismissal. "Too risky given - the timeline" is fine; "I don't agree" without reasoning is not. -- If a finding falls between categories, err toward the lower severity — - deferring a Suggested finding is fine; deferring a Blocking finding is not. - -## Cross-refs - -- `@code-review` agent — generates the findings you consume here. -- `verification-before-completion` skill — run after fixing Blocking items. -- `/check` command — pre-push gate; must be green after all fixes. -- `rcs-header` skill — fix missing RCS headers (common Blocking finding). - -## Gotchas - -- *Blindly applying every finding* — a review tool flags patterns, not bugs. - Some findings are false positives or don't apply to this codebase. Triage - them. -- *Arguing with Blocking findings* — a security finding is not a style - preference. Fix it, then argue if you still disagree. -- *Deferring without a reason* — "deferred" without explanation reads as - laziness. Every deferral gets a one-line reason. -- *Forgetting to re-run @code-review* — after fixing Blocking items, run it - again. A fix can introduce a new finding. diff --git a/.opencode/skills/scss-mobile-first/SKILL.md b/.opencode/skills/scss-mobile-first/SKILL.md deleted file mode 100644 index cdae984..0000000 --- a/.opencode/skills/scss-mobile-first/SKILL.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: scss-mobile-first -description: Use when writing or reviewing SCSS. Covers mobile-first design rules, responsive breakpoints, unit preferences, touch targets, and stylelint configuration. ---- - -## Mobile-First Design Rules - -All styling is developed mobile-first. This means: - -- **Base styles target the smallest viewport** (320px mobile) -- **Scale up with `min-width` media queries** — never `max-width` for layout -- Use `min-width` breakpoints: `768px` (tablet), `1024px` (desktop) - -```scss -// Correct — mobile-first -.container { - padding: 1rem; - - @media (min-width: 768px) { - padding: 2rem; - } -} - -// Wrong — desktop-first -.container { - padding: 2rem; - - @media (max-width: 767px) { - padding: 1rem; - } -} -``` - -## Units - -- Use `rem`, `%`, `vw`, `vh`, `clamp()` for layout and spacing -- Avoid fixed `px` values for widths and margins -- Use `clamp()` for fluid typography and spacing: - -```scss -font-size: clamp(1rem, 2.5vw, 1.5rem); -padding: clamp(1rem, 4vw, 2rem); -``` - -- Avoid fixed `px` widths on layout containers — use `max-width` + responsive padding -- Touch targets ≥ 44×44px — see the `accessibility` skill for the WCAG minimum and adjacent-target gap rule - -## Stylelint - -Config: `.stylelintrc.json` -Run: `npx stylelint "cdn/sass/**/*.scss"` - -Stylelint runs automatically in the pre-commit hook on staged `.scss` files. -Fix violations before committing — the hook blocks on failure. - -## SCSS Source Location - -Edit files in `cdn/sass/`. Never edit `cdn/css/*.min.css` — those are generated. - -Compile: -```bash -sass --style=compressed cdn/sass/source.scss cdn/css/output.min.css -``` diff --git a/.opencode/skills/security-coding/SKILL.md b/.opencode/skills/security-coding/SKILL.md deleted file mode 100644 index 435e4be..0000000 --- a/.opencode/skills/security-coding/SKILL.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -name: security-coding -description: Use when writing or reviewing PHP that handles user input, sessions, auth, cookies, or SQL in this no-framework stack. Covers parameterized SQL, CSRF tokens, XSS output escaping, password hashing, session hardening, and input validation. Detection lives in the @semgrep agent; this skill is the defensive-coding guidance that prevents the findings. ---- - -This project has **no framework** — there is no ORM, no CSRF middleware, no -templating engine, no router. Every defensive control below must be applied by -hand. The `@semgrep` agent detects failures; this skill prevents them. - -## SQL — parameterized, always - -Never interpolate user data into a query string. Use Aurora's SQL handler or -PDO with bound parameters. - -```php -// GOOD -$db->execute("SELECT id FROM users WHERE email = ?", [$email]); - -// BAD — SQL injection -$db->query("SELECT id FROM users WHERE email = '" . $email . "'"); -``` - -- Whitelist column/table names if they must be dynamic — never bind them from - user input. -- `LIKE` wildcards (`%`, `_`) in user input must be escaped with - `addcslashes($s, '%_')` before binding. - -## XSS — escape on output - -Escape for the **context** you're emitting into. Escaping happens at output -time, not input time. - -- HTML body / attribute: `htmlspecialchars($s, ENT_QUOTES | ENT_HTML5, 'UTF-8')` -- JavaScript string: encode with `json_encode($s)` (never hand-roll) -- URL: `rawurlencode($s)` -- CSS: avoid putting user data in CSS; if unavoidable, hex-escape. - -Never use `echo $userData` unescaped. If you must render trusted HTML, route -it through an explicit allowlist sanitizer and note the exception. - -## CSRF — tokens on every state-changing request - -- Every POST/PUT/DELETE form includes a CSRF token rendered as a hidden input. -- Token is session-scoped, generated with `random_bytes()`, and validated on - the server before any state change. -- On mismatch, reject with 419 and do not reveal whether the session exists. -- Same-origin via `SameSite` cookies is a complement, not a replacement. - -```php -$token = bin2hex(random_bytes(32)); -$_SESSION['csrf'] = $token; -// in the form: <input type="hidden" name="csrf" value="<?= htmlspecialchars($token) ?>"> -``` - -## Passwords — hash, never store plaintext - -- Hash with `password_hash($pw, PASSWORD_DEFAULT)` (bcrypt/argon2id). -- Verify with `password_verify($pw, $hash)`. -- Never roll your own crypto. Never MD5/SHA1 a password. -- Reset tokens: `random_bytes(32)`, single-use, short TTL, invalidated on - login. - -## Session hardening - -- `session.cookie_httponly = 1`, `session.cookie_samesite = "Lax"` (or - `"Strict"` where feasible), `session.cookie_secure = 1` on HTTPS. -- Regenerate the session ID on privilege change (login, role escalation). -- Set a reasonable idle timeout; garbage-collect expired sessions. -- Never put secrets in the session; store only an identifier. - -## Input validation — deny by default - -- Validate on the server, every time. Client-side validation is UX only. -- Use a positive allowlist (validate the expected shape), not a denylist. -- Fail closed: on invalid input, reject and log — do not sanitize-and-proceed. -- Bounds-check integers, length-check strings, allowlist enums. - -```php -$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT); -if ($id === false || $id < 1) { - http_response_code(400); - exit; -} -``` - -## File & command safety - -- Never pass user input to `include`/`require`/`eval`/`exec`/`shell_exec`/ - `system`/`passthru` or backticks. -- File uploads: validate MIME and extension against an allowlist, generate a - random stored filename, store outside the webroot, never execute uploaded - content. -- `unserialize()` on untrusted data is forbidden — use `json_decode`. - -## Headers - -Send security headers on every response (via Aurora or nginx): - -- `Content-Security-Policy` — restricts script/style/image sources. -- `X-Content-Type-Options: nosniff` -- `X-Frame-Options: DENY` (or CSP `frame-ancestors`) -- `Referrer-Policy: strict-origin-when-cross-origin` -- `Strict-Transport-Security` on HTTPS - -### CSP and Aurora - -Aurora emits **only external** `<script src>` tags with SRI hashes -(`integrity="sha512-..."` + `crossorigin="anonymous"`). No inline scripts -are emitted. The canonical CSP for every Aurora page is: - -``` -Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none' -``` - -- `'unsafe-inline'` is forbidden in production. -- No nonce is required today — Aurora's output is already strict-CSP-compatible. -- If a future feature requires inline scripts, implement a per-request - nonce in Aurora (generate a random nonce, emit it on the `<script>` tag, and - reflect it in `script-src 'nonce-<value>'`). See ADR-0001. - -Cross-ref: `frontend-architecture` skill — CSP-friendly script rules for -page authors (inline scripts forbidden, SRI on external scripts). - -## Secrets - -- Never hardcode credentials. Load from environment or a non-web-root config. -- `.env` is gitignored — use `.env.example` only (see `AGENTS.md`). -- Never log secrets, tokens, or password hashes. - -## Known sharp edges - -These are documented footguns in first-party code that have caused production -incidents. The patterns below are unsafe and must not be used. Detection is -split between Semgrep rules (`.semgrep/kyaulabs.yml`) and Pest tests. - -### Aurora constructor positional bools - -```php -// DON'T — positional bools are ambiguous. Which is $status? Which is $html? -$site = new KYAULabs\Aurora("index.html", "/cdn", true, true); -// FIXED — named arguments make the dangerous $status param explicit -$site = new KYAULabs\Aurora(template: "index.html", cdn: "/cdn", status: (bool)($_ENV['APP_DEBUG'] ?? false), html: true); -``` - -- `$status=true` leaks stack traces, absolute paths, and SQL fragments to - visitors on an unhandled error. -- Always use named arguments. Never use positional `true`/`false` for `$status` - and `$html`. -- Detected by: `kyaulabs-aurora-status-true-literal` Semgrep rule + Pest - `AuroraConstructorStatusTest.php`. See ADR-0002. - -### Aurora's `ini_set('display_errors','1')` at constructor start - -- Aurora's constructor unconditionally enables error display before the - `$status` gate (lines 66-69 of `aurora.inc.php`). If an `AuroraException` - is thrown during initialization (missing template, bad CDN directory), the - error renders verbosely even when `$status=false`. This is an upstream - issue in `kyaulabs/aurora`. -- Application code must never compound this by calling `ini_set('display_errors', '1')` - directly — detected by `kyaulabs-hardcoded-display-errors-on`. -- Deploy only with the template file and CDN directory confirmed present. - -## Suppressing findings - -The `@semgrep` agent may flag patterns that are genuinely safe in context -(false positives). Suppress inline with a mandatory justification: - -```php -// nosemgrep: <rule-id> -- <one-line justification> -``` - -- `rule-id` is required — bare `// nosemgrep` is forbidden (it silences all - rules at that line). -- Justification is auditable and committed alongside the code. "I think it's - fine" is not a valid justification; explain *why* the pattern is safe. -- Suppressions are re-reviewed when the named rule is updated — the - `/security` command logs extant suppressions in its report. -- See the `/security` command for the full adjudication protocol. - -## Cross-refs - -- `@semgrep` agent — detects violations of the above. -- `audit-deps` skill — scans Composer/npm for known CVEs. -- `database` skill — schema and SQL style (this skill covers injection). -- `.opencode/docs/mocking.md` — boundary design for testable security code. diff --git a/.opencode/skills/systems-design/SKILL.md b/.opencode/skills/systems-design/SKILL.md deleted file mode 100644 index 0f6a47a..0000000 --- a/.opencode/skills/systems-design/SKILL.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -name: systems-design -description: Use when designing a non-trivial change — a new module, a data model, a cross-cutting decision, or a system boundary. Decides ADR vs RFC, applies C4-lite notation, deep-modules heuristic, and interface-design checks. ---- - -## Decide: ADR, RFC, or neither? - -| Signal | Artifact | -| --- | --- | -| Hard-to-reverse or expensive-to-change decision | **ADR** (see `adr` skill) | -| Forecloses other options, cross-cutting | **ADR** | -| Large, still-being-explored design needing feedback | **RFC** (see `.opencode/docs/design.md`) | -| Routine implementation, naming, or refactor extraction | Neither — just commit + PR | - -When in doubt, write an ADR. They are cheap and future readers thank you. - -## C4-lite notation - -Three zoom levels. Sketch in the ADR/RFC; don't aim for completeness. - -1. **Context** — the system and its external actors/users. One box, arrows in/out. -2. **Container** — major deployable/runnable units (the app, the DB, the CDN, - any worker). One box each, with the tech noted inside. -3. **Component** — within a container, the key modules/classes and their - dependencies. Only draw the ones touched by the decision. - -Use ASCII or Mermaid in the ADR. Keep diagrams legible in plain text. - -## Deep modules heuristic (Ousterhout) - -A module's value is its interface relative to its implementation. - -- **Deep module:** small interface, hides significant complexity. Good. -- **Shallow module:** interface as complex as the implementation. Bad — it - adds indirection without abstraction. Merge or deepen it. - -When designing a new class or boundary, ask: *is the interface materially -smaller than the implementation?* If not, redesign or merge. - -## Interface-design checklist - -Before committing a new public interface (a class API, a backend function, a -URL, a DB schema): - -- [ ] Hides a decision that could change. -- [ ] Interface is the smallest expression of the capability. -- [ ] Does not leak internals (types, error shapes, ordering, state). -- [ ] Errors are part of the interface — documented, typed, narrow. -- [ ] One responsibility; if you describe it with "and", split it. -- [ ] Mockable at the boundary if it touches the outside world (see - `.opencode/docs/mocking.md`). -- [ ] Named using the project's ubiquitous language (see `CONTEXT.md`). - -## Cross-refs - -- `adr` skill — format and status transitions. -- `.opencode/docs/design.md` — RFC template and when a design doc is required. -- `.opencode/docs/mocking.md` — boundary interface design. -- `domain-context` skill — read `CONTEXT.md` before designing. diff --git a/.opencode/skills/verification-before-completion/SKILL.md b/.opencode/skills/verification-before-completion/SKILL.md deleted file mode 100644 index b76128f..0000000 --- a/.opencode/skills/verification-before-completion/SKILL.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -name: verification-before-completion -description: Use before declaring a task done. Verifies the work is actually complete — re-runs the relevant test/loop, confirms green, confirms no debug instrumentation remains, confirms the original repro no longer reproduces. Prevents false "done" claims. -derived-from: obra/superpowers (MIT, © Jesse Vincent) ---- - -# Verification Before Completion - -Before declaring a task done, verify it is **actually** done. Agents -frequently claim success without verifying — this skill is the gate that -prevents that. - -## Checklist - -Run through every item. If any fails, the task is NOT done — go back and -fix it before claiming completion. - -### 1. Tests pass - -Re-run the relevant test suite (not from memory — actually run it): - -```bash -php vendor/bin/pest --filter <TestName> -``` - -Or the full suite if the change is cross-cutting: - -```bash -php -d pcov.enabled=1 vendor/bin/pest --coverage -``` - -- [ ] All tests pass (green). -- [ ] Coverage for the changed files is ≥ 80%. Intersect the coverage - report with `git diff --name-only` (staged, fallback to working - tree) to identify changed PHP files; check each. -- [ ] No new tests are failing that were passing before. - -### 2. Original repro no longer reproduces - -If this was a bug fix, re-run the original reproduction from the `@debug` -agent's Phase 1 feedback loop: - -- [ ] The command that went **red** on the bug now goes **green**. -- [ ] The user's original symptom is gone. - -### 3. No debug instrumentation left behind - -Search for temporary debug artifacts: - -```bash -grep -rn '\[DEBUG-' . --include='*.php' --include='*.js' --include='*.scss' -``` - -- [ ] No `[DEBUG-...]` tagged logs remain. -- [ ] No throwaway prototypes or debug scripts remain in the working tree - (or they're clearly marked and in a debug location). -- [ ] No `dd()`, `dump()`, `var_dump()`, `print_r()`, or `console.log()` - left in the code. - -### 4. Lint passes - -```bash -php-cs-fixer fix . --dry-run --diff -npx stylelint "cdn/sass/**/*.scss" -npx eslint "cdn/js/**/*.js" --ignore-pattern "*.min.js" -``` - -- [ ] PHP CS Fixer reports no violations. -- [ ] Stylelint reports no violations (or SKIPPED with reason). -- [ ] ESLint reports no violations (or SKIPPED with reason). - -### 5. Files are well-formed - -- [ ] Every new or modified source file has an RCS header with the correct - filename at the top (see `rcs-header` skill). The creator and date - fields are a one-time creation stamp, never updated — staleness is not - a defect. The pre-commit hook auto-adds missing headers. -- [ ] Every new or modified source file has a vim modeline at the end. -- [ ] PHP classes/methods/functions have PHPDoc (PSR-5). -- [ ] No generated files (`cdn/css/*.min.css`, `cdn/javascript/*.min.js`) - were edited directly — only their sources. - -### 6. No secrets or sensitive data - -- [ ] No `.env` files staged. -- [ ] No hardcoded credentials, API keys, or tokens in the diff. -- [ ] No secrets in log statements. - -## Output - -After running through the checklist, report: - -```text -## Verification: <task name> - -**Tests:** PASS / FAIL (<N> tests, <coverage>% coverage) -**Repro:** N/A / PASS (no longer reproduces) / FAIL (still reproduces) -**Debug artifacts:** CLEAN / FOUND (<list>) -**Lint:** PASS / FAIL (<which tool>) -**File hygiene:** PASS / FAIL (<what's missing>) -**Secrets:** CLEAN / FOUND (<list>) - -**Verdict:** VERIFIED / NOT DONE -``` - -If the verdict is NOT DONE, list exactly what needs to be fixed. Do not claim -the task is complete until every item passes. - -## Rules - -- Actually run the commands — do not assume the result from memory. -- If a check is not applicable (e.g. no SCSS changed), mark it N/A rather - than PASS. -- This skill is the last gate before `/check` and `@code-review`. Don't - shortcut it. -- **Verification vs /check:** This skill is the per-task gate. `/check` is - the aggregate pre-push gate. Both run because task-level green can rot by - push time. - -## Gotchas - -Known failure modes that compound over time. Add entries when this skill -causes a preventable mistake. - -- *Claiming "done" from memory without re-running tests* — the whole point - of this skill is to verify, not assume. Actually run the commands. -- *Forgetting to grep for `[DEBUG-]` tags* — debug instrumentation from - `@debug` sessions survives if not explicitly cleaned up. Always grep. diff --git a/.opencode/skills/writing-plans/SKILL.md b/.opencode/skills/writing-plans/SKILL.md deleted file mode 100644 index f89da94..0000000 --- a/.opencode/skills/writing-plans/SKILL.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -name: writing-plans -description: Use when you have an approved spec or requirements for a multi-step task, before touching code. Produces a bite-sized, TDD-oriented implementation plan with exact file paths, interfaces, complete code, and verification commands. Sits between brainstorming approval and @tdd execution. -derived-from: obra/superpowers (MIT, © Jesse Vincent) ---- - -# Writing Plans - -Write comprehensive implementation plans assuming the engineer has zero context -for the codebase and questionable taste. Document everything they need to know: -which files to touch for each task, code, testing, docs to check, how to test it. -Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits. - -Assume they are a skilled developer, but know almost nothing about the toolset -or problem domain. Assume they don't know good test design very well. - -**Announce at start:** "I'm using the writing-plans skill to create the -implementation plan." - -**Context:** This skill is invoked after the `brainstorming` skill has produced -an approved spec. The spec lives at `docs/specs/YYYY-MM-DD-<topic>-spec.md`. - -**Save plans to:** `docs/plans/YYYY-MM-DD-<feature-name>.md` -- If `docs/plans/` doesn't exist, create it. -- User preferences for plan location override this default. - -## Scope check - -If the spec covers multiple independent subsystems, it should have been broken -into sub-project specs during brainstorming. If it wasn't, suggest breaking -this into separate plans — one per subsystem. Each plan should produce working, -testable software on its own. - -## File structure - -Before defining tasks, map out which files will be created or modified and -what each one is responsible for. This is where decomposition decisions get -locked in. - -- Design units with clear boundaries and well-defined interfaces. Each file - should have one clear responsibility. -- You reason best about code you can hold in context at once, and your edits - are more reliable when files are focused. Prefer smaller, focused files over - large ones that do too much. -- Files that change together should live together. Split by responsibility, - not by technical layer. -- In existing codebases, follow established patterns. If the codebase uses - large files, don't unilaterally restructure — but if a file you're modifying - has grown unwieldy, including a split in the plan is reasonable. - -This structure informs the task decomposition. Each task should produce -self-contained changes that make sense independently. - -## Task right-sizing - -A task is the smallest unit that carries its own test cycle and is worth a -fresh reviewer's gate. When drawing task boundaries: fold setup, configuration, -scaffolding, and documentation steps into the task whose deliverable needs -them; split only where a reviewer could meaningfully reject one task while -approving its neighbor. Each task ends with an independently testable -deliverable. - -## Bite-sized task granularity - -**Each step is one action (2–5 minutes):** - -- "Write the failing test" — step -- "Run it to make sure it fails" — step -- "Implement the minimal code to make the test pass" — step -- "Run the tests and make sure they pass" — step -- "Commit" — step - -## Plan document header - -**Every plan MUST start with this header:** - -```markdown -# [Feature Name] Implementation Plan - -> **For agentic workers:** Implement this plan task-by-task using the @tdd -> agent. Steps use checkbox (`- [ ]`) syntax for tracking. Each task follows -> Red → Green → Refactor. - -**Goal:** [One sentence describing what this builds] - -**Architecture:** [2–3 sentences about approach] - -**Tech Stack:** [Key technologies/libraries] - -## Global constraints - -[The spec's project-wide requirements — version floors, dependency limits, -naming and copy rules, platform requirements — one line each, with exact -values copied verbatim from the spec. Every task's requirements implicitly -include this section.] - ---- -``` - -## Task structure - -````markdown -### Task N: [Component Name] - -**Files:** -- Create: `exact/path/to/file.php` -- Modify: `exact/path/to/existing.php:123-145` -- Test: `tests/exact/path/to/Test.php` - -**Interfaces:** -- Consumes: [what this task uses from earlier tasks — exact signatures] -- Produces: [what later tasks rely on — exact function names, parameter and - return types. A task's implementer sees only their own task; this block is - how they learn the names and types neighboring tasks use.] - -- [ ] **Step 1: Write the failing test** - -```php -it('does the specific behavior', function () { - $result = function($input); - expect($result)->toBe($expected); -}); -``` - -- [ ] **Step 2: Run test to verify it fails** - -Run: `php vendor/bin/pest --filter TestName` -Expected: FAIL with "function not defined" - -- [ ] **Step 3: Write minimal implementation** - -```php -function function($input) { - return $expected; -} -``` - -- [ ] **Step 4: Run test to verify it passes** - -Run: `php vendor/bin/pest --filter TestName` -Expected: PASS - -- [ ] **Step 5: Commit** - -```bash -git add tests/path/to/Test.php backend/path/to/file.php -git commit -S -m "feat(scope): concise subject describing the change - -Plan-by: glm-5.2 -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` -```` - -## No placeholders - -Every step must contain the actual content an engineer needs. These are **plan -failures** — never write them: - -- "TBD", "TODO", "implement later", "fill in details" -- "Add appropriate error handling" / "add validation" / "handle edge cases" -- "Write tests for the above" (without actual test code) -- "Similar to Task N" (repeat the code — the engineer may be reading tasks out - of order) -- Steps that describe what to do without showing how (code blocks required for - code steps) -- References to types, functions, or methods not defined in any task -- Bare commit messages missing scope or required footers — use the full - conventional-commits format (type[scope]: subject + Plan-by + Acked-by + Signed-off-by) - -## Self-review - -After writing the complete plan, look at the spec with fresh eyes and check -the plan against it. This is a checklist you run yourself — not a subagent -dispatch. - -1. **Spec coverage:** Skim each section/requirement in the spec. Can you point - to a task that implements it? List any gaps. -2. **Placeholder scan:** Search your plan for red flags — any of the patterns - from "No placeholders" above. Fix them. -3. **Type consistency:** Do the types, method signatures, and property names - you used in later tasks match what you defined in earlier tasks? A function - called `clearLayers()` in Task 3 but `clearFullLayers()` in Task 7 is a bug. - -If you find issues, fix them inline. No need to re-review — just fix and move -on. If you find a spec requirement with no task, add the task. - -## Execution handoff - -After saving the plan, hand off to the `executing-plans` skill: - -> "Plan complete and saved to `docs/plans/<filename>.md`. Invoking the -> `executing-plans` skill to execute it." - -Load the `executing-plans` skill and follow its process — it defines two -execution modes (inline batch-with-checkpoints and @tdd-dispatch with -two-stage review), per-task review gates, halt/re-plan thresholds, and -context management across long plans. - -## Remember - -- Exact file paths always. -- Complete code in every step — if a step changes code, show the code. -- Exact commands with expected output. -- DRY, YAGNI, TDD, frequent commits. -- Signed commits (`git commit -S`). - -## Cross-refs - -- `brainstorming` skill — the step before this one (produces the spec). -- `executing-plans` skill — the step after this one (executes the plan). -- `@tdd` agent — executes each task in Red → Green → Refactor cycles. -- `verification-before-completion` skill — run after each task is green. -- `rcs-header` skill — apply RCS header + vim modeline to every new file. -- `domain-context` skill — use `CONTEXT.md` vocabulary in task/variable names. - -## Gotchas - -Known failure modes that compound over time. Add entries when this skill -causes a preventable mistake. - -- *Placeholder creep* — "TBD", "add error handling", "similar to Task N" are - plan failures. Every step must contain actual content the implementer needs. -- *Type drift between tasks* — `clearLayers()` in Task 3 becomes - `clearFullLayers()` in Task 7. Run the self-review type-consistency check. -- *Tasks too large* — a task should be 2–5 minutes of work. If a task has - more than ~5 steps, it's probably two tasks. diff --git a/.opencode/skills/writing-skills/SKILL.md b/.opencode/skills/writing-skills/SKILL.md deleted file mode 100644 index e88075f..0000000 --- a/.opencode/skills/writing-skills/SKILL.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -name: writing-skills -description: Use when creating or modifying skills, agents, commands, or docs in .opencode/. Provides the frontmatter schema, mode/permission conventions, cross-ref rules, and quality checks to keep the harness consistent as it grows. -derived-from: anthropics/skills (MIT, © Anthropic); glebis/claude-skills (MIT, © Gleb) ---- - -# Writing Skills, Agents, and Commands - -Reference for authoring new pieces of the harness consistently. Follow these -conventions so new skills/agents/commands slot in without drift. - -## File locations - -| Type | Location | Filename | -|---|---|---| -| Skill | `.opencode/skills/<name>/` | `SKILL.md` | -| Agent | `.opencode/agents/` | `<name>.md` | -| Command | `.opencode/commands/` | `<name>.md` | -| Reference doc | `.opencode/docs/` | `<name>.md` | - -## Skill frontmatter - -```yaml ---- -name: <skill-name> -description: Use when <trigger>. <What it provides — one sentence>. ---- -``` - -- `name` must match the directory name (kebab-case). -- `description` starts with "Use when" so the agent knows when to load it. -- Skills are on-demand (loaded via the Skill tool) — they cost zero tokens - per session unless invoked. - -## Agent frontmatter - -```yaml ---- -description: <What the agent does — one sentence.> -mode: subagent -temperature: 0.1 -permission: - edit: deny # for read-only agents - bash: - "*": deny - "<pattern>": allow - webfetch: deny - task: deny ---- -``` - -- `mode: subagent` — agents are invoked via `@mention`. -- `temperature`: 0.1 for analysis/review agents, 0.2 for TDD/implementation. -- `permission` — scope tightly. Read-only agents deny `edit` and restrict - `bash` to safe read patterns (`ls`, `cat`, `grep`, `git log`, etc.). -- Agents that should not invoke other agents: `task: deny`. - -## Command frontmatter - -```yaml ---- -description: <What the command does — one sentence.> -agent: build # or "subtask: true" for subtask commands ---- -``` - -- `agent: build` — the command runs in the build agent context (full tool - access). -- `subtask: true` — the command is a subtask (no agent context switch). - -## Body structure - -### Skills - -1. **One-line summary** of what the skill does. -2. **When to use** (trigger conditions). -3. **The process/checklist** — numbered steps or a checklist. -4. **Rules** — hard constraints. -5. **Cross-refs** — links to related skills/agents/docs by name (not markdown - links — use `skill-name` or `path/to/file.md`). - -### Agents - -1. **Role statement** — "You are a ...". -2. **The task** — `$ARGUMENTS` placeholder (agents receive their invocation - args here). -3. **Workflow** — numbered steps. -4. **Output format** — a template for the agent's report. -5. **Rules** — hard constraints. - -### Commands - -1. **One-line summary** of what the command does. -2. **Steps** — numbered, with bash commands in code blocks. -3. **Output** — what to report. -4. **Rules** — hard constraints. - -## Quality checks - -Before merging a new skill/agent/command: - -- [ ] Frontmatter is complete and correct. -- [ ] `description` starts with "Use when" (skills) or a clear one-liner - (agents/commands). -- [ ] No content duplicated from `AGENTS.md`, `CONTEXT.md`, or other - skills — reference by name instead. -- [ ] Cross-refs use skill/doc names, not markdown links. -- [ ] No placeholders ("TBD", "TODO") in the body. -- [ ] The body is as short as possible while remaining unambiguous — every - line costs tokens when loaded. -- [ ] If the skill/agent/command references a project convention (indentation, - hard boundaries, RCS headers), it points to `AGENTS.md` or the relevant - skill rather than restating the rule. - -## Token discipline - -- Skills are on-demand — they cost tokens only when loaded. Keep them - focused and self-contained. -- Agents are loaded when invoked — keep them tight. -- Commands run in the build agent context — their content is read at - invocation time. -- The only always-loaded files are `AGENTS.md` and `.opencode/docs/conventions.md` - (via `opencode.json` instructions). Keep these minimal — everything else - should be on-demand. - -## Cross-ref conventions - -- Reference skills by name: "see the `systems-design` skill". -- Reference docs by path: "see `.opencode/docs/tests.md`". -- Reference agents by mention: "invoke the `@tdd` agent". -- Reference commands by slash: "run `/check`". -- Do NOT use markdown links (`[text](path)`) — the agent reads files via the - Read tool, not by following links. - -## Rules - -- Every new skill/agent/command must be added to the tables in `AGENTS.md` - and `CODING_HARNESS.md`. -- If a new skill introduces a project-wide convention, update `AGENTS.md` - (the authoritative source). -- If a new skill introduces a domain term, update `CONTEXT.md`. -- Test new agents/commands by invoking them on a real task before merging. - -## Gotchas - -Known failure modes that compound over time. Add entries when this skill -causes a preventable mistake. - -- *Skill description written as a summary, not a trigger* — the description - field is how the model decides when to load the skill. Write it as "Use - when <trigger>." not "This skill provides <summary>." -- *Skill restates rules from AGENTS.md* — always reference the authoritative - source instead of duplicating. Duplicated rules drift out of sync. -- *New skill not added to AGENTS.md tables* — the skill exists but the agent - doesn't know to load it. Always update the tables. - -## Mandatory: Gotchas section - -Every new skill MUST include a `## Gotchas` section at the end. It captures -known failure modes specific to that skill — the highest-signal content for -preventing repeated mistakes. Start empty if no failure modes are known yet; -add entries as they're discovered in real use. diff --git a/.opencodereview/rule.json b/.opencodereview/rule.json deleted file mode 100644 index 46ab1d2..0000000 --- a/.opencodereview/rule.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "rules": [ - { - "path": "**/*.php", - "rule": "Check PSR-12 compliance, RCS-style file header, PHPDoc on all classes/methods/functions with params and return types.", - "merge_system_rule": true - }, - { - "path": "**/*.scss", - "rule": "Flag non-mobile-first patterns: max-width queries, fixed px container widths, missing min-width breakpoints. Require 2-space indentation.", - "merge_system_rule": true - }, - { - "path": "**/*.js", - "rule": "Flag jQuery usage where vanilla JavaScript would suffice. Require tab indentation at tab-stop 4.", - "merge_system_rule": true - } - ], - "exclude": [ - "**/vendor/**", - "**/node_modules/**", - "**/cdn/css/*.min.css", - "**/cdn/javascript/*.min.js", - "aurora/**" - ] -} diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 38e26a7..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,246 +0,0 @@ -# AGENTS.md - -## Stack - -- OS: Linux / Shell: bash -- Web Server: nginx / Database: MariaDB -- Backend: PHP 8.5+ (flat procedural / class-based, no MVC, no router) -- Frontend: HTML5, CSS3, JS ES6+, jQuery only when vanilla JS is insufficient -- CSS: SCSS → Dart Sass → minified / JS: uglify-js → minified -- Tests: Pest PHP v4 on PHPUnit 12 -- Version Control: Git + Conventional Commits + signed commits - -## Production Environment - -- OS: Linux -- Server provisioned via https://github.com/kyaulabs/aarch/blob/master/pkg/nginx.pkg -- Web root: `/nginx/https/<domain>/www` (symlinked from `/nginx/git/<app>/`) -- Logs: `/nginx/logs/<domain>/` (one directory per domain, dots in domain → underscores) - - PHP: `php.log` - - nginx access: `access-<app>_<domain>.log` - - nginx error: `error-<app>_<domain>.log` - - Rotated: `.N.zstd` suffix (e.g. `php.log.1.zstd`) -- Temp directory: `/tmp` - -## No MVC - -PHP pages include Aurora, output HTML directly, and interact with the DB via raw SQL or Aurora's SQL handler. No controllers, no templating engine, no router. `backend/` holds PHP logic not web-accessible. - -## Project Context - -- `CONTEXT.md` (root) — domain glossary, entities, invariants, boundaries, non-goals. Read before domain-coupled work (see `domain-context` skill). Draft or refresh it via `/prime`. -- `adr/` — Architecture Decision Records (Nygard format). Write one for hard-to-reverse or cross-cutting decisions (see `adr` skill). - -## Directory Structure - -```text -├── AGENTS.md ← Stack, boundaries, pointers (loaded every session) -├── CONTEXT.md ← Domain glossary, entities, invariants, non-goals -├── opencode.json ← Wires instructions + agent definitions + permissions -├── adr/ ← Architecture Decision Records (Nygard format) -├── aurora/ ← Aurora PHP Framework (git submodule) -├── backend/ ← Backend PHP logic (not web-accessible) -│ └── migrations/ ← Forward-only SQL migrations (timestamp-prefixed) -├── cdn/ -│ ├── css/ ← GENERATED — do not edit -│ ├── javascript/ ← GENERATED — do not edit -│ ├── sass/ ← SCSS source (edit these) -│ └── js/ ← JS source (edit these) -├── tests/ -│ ├── Unit/ -│ ├── Feature/ -│ ├── Integration/ -│ └── Browser/ -├── <app>/ ← Public webroot (<app>.<domain>) -├── <app>.sql -└── <app>.nginx.conf -``` - -Projects live in `/nginx/git/<app>`, symlinked into `/nginx/https/<domain>`. - -## Hard Boundaries - -> [!IMPORTANT] -> -> - NEVER edit `cdn/css/*.min.css` or `cdn/javascript/*.min.js` — these are generated (edit source in `cdn/sass/` and `cdn/js/`; see `conventions.md` for details) -> - NEVER commit `.env` files — use `.env.example` only -> - Do not access external APIs without explicit permission -> - Do not modify files outside the project directory -> - New dependencies must be explicitly noted -> - When glob/grep returns unexpected empty results, verify with `ls` before concluding a file does not exist - -## File Naming - -See `.opencode/docs/conventions.md` for file naming conventions. - -## Commenting - -> [!IMPORTANT] -> -> - Every file starts with an RCS-style header — see `rcs-header` skill -> - Every file ends with a vim modeline — see `rcs-header` skill -> - PHP classes/methods: PHPDoc (PSR-5) with params, return types, exceptions -> - No explanatory comments unless explicitly requested - -## Indentation - -Covered in `conventions.md`. - -## Testing — MANDATORY TDD - -> [!IMPORTANT] -> All new code follows Red → Green → Refactor. No exceptions. -> Use the `@tdd` agent for any new feature or bug fix. -> Minimum 80% line coverage. Run: `php -d pcov.enabled=1 vendor/bin/pest --coverage` - -Use the `@test-audit` agent to review an existing test suite. -Pre-push gate: `/check` (php-cs-fixer + stylelint + eslint + pest --coverage). - -## Engineering Pipeline - -The full methodology, end to end. Follow this sequence for changes with a -behavior delta. Purely trivial changes with no behavior delta (typos, docs, -RCS headers, style-only, patch deps, test-only fixes) follow a fast-path — -see the brainstorming skill for the full definition. - -```text -brainstorming → prototype (if needed) → writing-plans → executing-plans → @tdd (per task) → verification-before-completion → /check → @code-review -``` - -1. **Brainstorm** the change (grilling skill) → spec in `docs/specs/`. -2. **Prototype** (if technical viability is uncertain) → throwaway code to answer the question, then delete (prototype skill). -3. **Plan** the implementation (writing-plans skill) → plan in `docs/plans/`. -4. **Execute** the plan (executing-plans skill) → dispatch tasks to `@tdd`, review between tasks. -5. **Implement** each task via `@tdd` (Red → Green → Refactor, vertical slices). -6. **Verify** completion (verification-before-completion skill). -7. **Gate** with `/check` (lint + coverage 80%). -8. **Review** with `@code-review` before push. - -For non-trivial or cross-cutting changes, insert `@architect` before step 4. -For bugs, use `@debug` (disciplined 6-phase loop) before `@tdd` on the fix. -For architectural entropy, run `/improve-architecture` on a cadence. - -## Linting & Enforcement - -Linting is enforced by `.github/hooks/pre-commit` — it blocks commits on failure. -Commit message format is enforced by `.github/hooks/commit-msg` via commitlint. -To activate hooks after cloning: `bash .github/scripts/install-hooks.sh` - -For linting details and responsive/mobile-first CSS rules, see `scss-mobile-first` skill. - -## Git Workflow - -- Branches: `main` (production), `develop` (integration) -- Features: `feat/<username>-<hash>-<description>` -- Commits: Conventional Commits format (type[scope]: subject) — see `conventional-commits` skill -- Signed commits required -- Every commit must include `Plan-by:` (sourced from `agent.plan.model` in `opencode.json`), `Acked-by:` (sourced from `agent.build.model` in `opencode.json` — model ID segment after the last `/`), and `Signed-off-by:` (user) footers. Default Signed-off-by: `kyau <git@kyaulabs.com>`. -- No squash merges. Each logical change is its own atomic commit — the git history serves as the development and evaluation log. A pre-push hook warns on single-commit branches that look like squashes. - -After implementing any change — whether via @tdd, a direct fix, an issue -tracker resolution, or a fast-path trivial change — produce a commit message -in conventional commits format before committing. Load the -`conventional-commits` skill and produce: type[scope]: subject + Plan-by + -Acked-by + Signed-off-by footers. The commit-msg hook blocks invalid messages, -but the -message should be well-formed before you reach the hook. - -### Commit and push permissions - -- **`@tdd`** and **`@resolve-merge-conflicts`** are permitted to `git add` and - `git commit` — commits happen inside disciplined cycles where the commit - message is presented to the user before execution. -- The **`build`** primary agent prompts (`ask`) before `git add` or - `git commit` — the user sees the full command including the commit message in - the approval dialog. Used by `/release`, `/build-assets`, and design-document - commits from `brainstorming`. -- **`git push`** is denied to **every agent**. Only the human pushes. - -## Build Pipeline - -SCSS: `sass --style=compressed cdn/sass/source.scss cdn/css/output.min.css` -JS: `uglifyjs cdn/js/source.js -o cdn/javascript/output.min.js -c -m` -Assets are built manually. No watchers. - -## Dependency Lockfiles - -> [!IMPORTANT] -> `composer.lock` and `package-lock.json` are committed to the repository. -> This ensures deterministic, auditable dependency trees and allows -> `audit-deps` to scan known vulnerabilities on a fresh clone without -> installing unvetted packages first. - -After any dependency change (adding, removing, or updating a package), -regenerate and commit the updated lockfiles: - -```bash -composer update # regenerates composer.lock -npm install # regenerates package-lock.json -git add composer.lock package-lock.json -``` - -## Aurora Framework - -Submodule at `aurora/`. Entry: `require_once(__DIR__ . "/../aurora/aurora.inc.php")` -For the standard page template, see the `aurora-page` skill. - -## Skills Available - -Load these on demand when the task requires them: - -| Skill | When to use | -| --- | --- | -| `brainstorming` | Before any creative work — features, components, behavior changes. Grilling → design → spec | -| `prototype` | Answering a technical viability question with throwaway code before committing to a plan | -| `writing-plans` | After brainstorming approval — produces a bite-sized TDD implementation plan | -| `executing-plans` | After writing-plans — dispatches tasks to @tdd with two-mode execution (inline or dispatch), per-task review gates, and halt/re-plan policy | -| `finding-duplicate-functions` | Scanning for semantic duplication — two-phase (classical extraction + LLM intent-clustering), complements /improve-architecture's deletion test | -| `finishing-a-development-branch` | When a feature branch is complete — verify readiness (checklist), present disposal options (merge/PR/keep/discard), enforce no-squash policy | -| `verification-before-completion` | Before declaring a task done — verifies tests pass, no debug artifacts, lint clean | -| `rcs-header` | Creating or modifying any source file | -| `receiving-code-review` | Triaging and responding to @code-review findings — severity triage matrix, anti-over-compliance rules, deferral discipline | -| `aurora-page` | Creating a new PHP page | -| `scss-mobile-first` | Writing or reviewing SCSS (breakpoints, units, build) | -| `frontend-design` | Writing or reviewing visual language — responsive/mobile-first, CSS transitions, CSS-driven flow, neumorphism, default theme + tokens | -| `frontend-architecture` | Structuring frontend JS — progressive enhancement, module pattern, jQuery policy, token consumption, CSP rules | -| `accessibility` | Writing or reviewing markup/SCSS/JS for UI — WCAG 2.2 AA, focus, motion, neumorphism contrast floor | -| `security-coding` | Defensive coding in the no-framework stack — SQL/XSS/CSRF, sessions, passwords, headers | -| `database` | Schema design, `<app>.sql`, migrations, indexing, SQL style | -| `domain-context` | Before domain-coupled work — read/update `CONTEXT.md` | -| `adr` | Writing, reviewing, or superseding an Architecture Decision Record | -| `systems-design` | Designing a non-trivial change — ADR vs RFC, C4-lite, interface design | -| `conventional-commits` | Writing or reviewing commit messages | -| `opencode-docs` | Vendored opencode.ai/docs reference — config schemas, plugin hooks, permission rules, SDK API. Load instead of guessing or calling /research | -| `pest-browser` | Writing browser tests | -| `audit-deps` | Scanning PHP/JS dependencies for known CVEs | -| `writing-skills` | Authoring new skills, agents, commands, or docs in `.opencode/` | - -## Agents Available - -| Agent | Mode | When to use | -| --- | --- | --- | -| `@tdd` | subagent | Any new feature or bug fix requiring tests | -| `@test-audit` | subagent | Auditing an existing test suite for quality | -| `@code-review` | subagent | Reviewing staged changes before push | -| `@architect` | subagent | Read-only evaluation of a proposed change against `CONTEXT.md` + ADRs before implementation | -| `@resolve-merge-conflicts` | subagent | Resolving in-progress git merge/rebase conflicts | -| `@semgrep` | subagent | SAST scanning — diff audit + full scan (PHP/JS/secrets) | -| `@debug` | subagent | Investigating bugs — disciplined 6-phase loop: feedback loop → reproduce → hypothesise → instrument → fix → post-mortem. Build-mode agent with scoped investigation write (repro tests, harnesses, instrumentation); not invocable from Plan mode. | -| `@docs-writer` | subagent | Generating PHPDoc, RCS headers, and documentation | - -## Commands - -| Command | Purpose | -| --- | --- | -| `/prime` | Draft or regenerate `CONTEXT.md` from the codebase | -| `/check` | Pre-push gate: php-cs-fixer + stylelint + eslint + pest --coverage (80%) | -| `/release` | git-cliff changelog + signed tag + `gh release` command | -| `/deploy` | Post-pull production deploy — asset rebuild, opcache clear, log tail | -| `/research` | Cited research via `@scout` + web (see `.opencode/docs/research.md`) | -| `/build-assets` | Rebuild minified CSS and JS from source | -| `/security` | SAST scan + dependency CVE audit in one pass | -| `/improve-architecture` | Scan codebase for deepening opportunities → Obsidian markdown report | -| `/handoff` | Compact current conversation into a handoff document for another session | -| `/setup` | Interactive project configurator — replaces `<app>`/`<domain>`/`[EMAIL]` placeholders across the harness, sets accent theme | -| `/plan-to-issues` | Parse a plan from `docs/plans/` and create a GitHub epic + task issues via `gh` | -| `/teach` | Explain recently completed work at the user's level — what changed, why this approach, what trade-offs were considered | diff --git a/CODING_HARNESS.md b/CODING_HARNESS.md deleted file mode 100644 index f706faa..0000000 --- a/CODING_HARNESS.md +++ /dev/null @@ -1,115 +0,0 @@ -# Coding Harness - -Orientation guide for the KYAULabs coding-agent harness. This is a human -reference — agents load `AGENTS.md` (authoritative) every session, so this -file carries no per-session token cost. - -## How the pieces fit together - -The full engineering pipeline, end to end: - -```text -brainstorming → prototype (if needed) → writing-plans → executing-plans → @tdd (per task) → verification-before-completion → /check → @code-review -``` - -1. **Brainstorm** the change (grilling skill) → spec in `docs/specs/`. -2. **Prototype** (if technical viability is uncertain) → throwaway code to answer the question, then delete (prototype skill). -3. **Plan** the implementation (writing-plans skill) → plan in `docs/plans/`. -4. **Execute** the plan (executing-plans skill) → dispatch tasks to `@tdd`, review between tasks. -5. **Implement** each task via `@tdd` (Red → Green → Refactor, vertical slices). -6. **Verify** completion (verification-before-completion skill). -7. **Gate** with `/check` (lint + coverage 80%). -8. **Review** with `@code-review` before push. - -For non-trivial or cross-cutting changes, insert `@architect` before step 4. -For bugs, use `@debug` (disciplined 6-phase loop) before `@tdd` on the fix. - -## Where things live - -| File | Purpose | -| --- | --- | -| `AGENTS.md` | Stack, boundaries, directory structure, skills/agents/commands index (authoritative) | -| `CONTEXT.md` | Domain glossary, entities, invariants, non-goals | -| `opencode.json` | Wires instructions + agent definitions + permissions | -| `adr/` | Architecture Decision Records (Nygard format) | -| `.opencode/agents/` | Custom subagent definitions | -| `.opencode/commands/` | Custom slash commands | -| `.opencode/skills/` | On-demand skills (loaded via the Skill tool) | -| `.opencode/docs/` | Supporting reference docs for agents/commands | - -## Built-in OpenCode features - -### Primary agents (Tab to switch) - -| Agent | Purpose | -| --- | --- | -| **Build** | Default mode — full tool access for development; enforces mandatory `@tdd` + hard boundaries | -| **Plan** | Restricted mode — analysis and planning, no file changes | - -Plan mode is restricted from invoking code-modifying subagents (`@tdd`, -`@resolve-merge-conflicts`) — it can only invoke read-only/audit -agents (`@test-audit`, `@code-review`, `@semgrep`, `@architect`, `@explore`, -`@scout`, `@docs-writer`). `@debug` is a build-mode investigation agent -with scoped write access (repro tests, throwaway harnesses, `[DEBUG-]` -instrumentation) and is not invocable from Plan mode. - -### Built-in subagents - -| Agent | Purpose | -| --- | --- | -| **Explore** | Read-only codebase exploration — file patterns, keyword search | -| **Scout** | External docs + dependency research (clones upstream repos) | -| **General** | Multi-step research, full tool access | - -Invoke via `@mention`: `@explore find the auth implementation`. - -### Built-in slash commands - -| Command | Purpose | -| --- | --- | -| `/init` | Analyze project and generate AGENTS.md | -| `/undo` | Revert the last change made by the agent | -| `/redo` | Redo a previously undone change | -| `/share` | Create a shareable link to the current conversation | -| `/help` | Show available commands and help | - -## Custom additions - -Custom skills, agents, and commands are defined under `.opencode/`. The -authoritative table of what's available is in `AGENTS.md` § Skills / Agents / -Commands. This section exists so `writing-skills`'s cross-table-update rule -has a landing point; the canonical list is in `AGENTS.md`. - -### Skills (process + domain) - -| Category | Skills | -|---|---| -| Pipeline (plan → execute → verify) | `brainstorming`, `writing-plans`, `executing-plans`, `verification-before-completion` | -| Review triage | `receiving-code-review` | -| Branch lifecycle | `finishing-a-development-branch` | -| Architecture hygiene | `systems-design`, `finding-duplicate-functions` | -| Stack-specific | `aurora-page`, `rcs-header`, `security-coding`, `database` | -| Frontend | `scss-mobile-first`, `frontend-design`, `frontend-architecture`, `accessibility` | -| Testing | `pest-browser` | -| Docs / process | `domain-context`, `adr`, `conventional-commits`, `audit-deps`, `writing-skills`, `prototype` | - -### Custom agents - -All custom agents live under `.opencode/agents/` and are invoked via `@mention`. -See `AGENTS.md` for the complete list with purpose descriptions. - -### Custom commands - -All custom commands live under `.opencode/commands/` and are invoked via `/slash`. -See `AGENTS.md` for the complete list with purpose descriptions. - -| Command | Purpose | -| --- | --- | -| `/prime` | Draft or regenerate `CONTEXT.md` from the codebase | -| `/check` | Pre-push gate: lint + test + coverage | -| `/release` | Changelog + signed tag + `gh release` | -| `/deploy` | Post-pull production deploy | -| `/build-assets` | Rebuild minified CSS/JS | -| `/setup` | Interactive project configurator (replaces placeholders) | -| `/plan-to-issues` | Parse a plan into GitHub issues | -| `/teach` | Explain completed work pedagogically | diff --git a/CONTEXT.md b/CONTEXT.md deleted file mode 100644 index 2fb9bf8..0000000 --- a/CONTEXT.md +++ /dev/null @@ -1,60 +0,0 @@ -# Project Context - -> Living document. Update when domain language, entities, or boundaries change. -> Read by agents before domain-coupled work (see `domain-context` skill). - -## Purpose - -<one-to-three sentences: what this application does and for whom> - -## Domain Glossary - -Ubiquitous language. Terms here are the canonical names used in code, tests, -UI copy, and conversation. When a term is introduced, add it here first. - -| Term | Definition | -| --- | --- | -| <Term> | <definition> | - -## Entities & Invariants - -Core domain objects and the rules that always hold for them. - -### <Entity> -- **Shape:** <key fields / columns> -- **Invariants:** - - <rule that must always be true> - - <rule that must always be true> -- **Lifecycle:** <created when… / transitions… / archived when…> - -## System Boundaries - -What this system owns vs. what it delegates to external services. - -- **Owns:** <list> -- **Delegates:** <external APIs, services, the Aurora framework, etc.> -- **Boundary interfaces:** <where mocking is permitted — see `.opencode/docs/mocking.md`> - -## Non-Goals - -Explicit things this project will **not** do. Prevents scope creep and -spurious "features" during implementation. - -- <non-goal> - -## Architectural Decisions - -Significant decisions live as ADRs in `adr/`. List accepted ADRs here with a -one-line summary; the full record is in `adr/NNNN-*.md`. - -- `adr/0001-<title>.md` — <one-line summary> - -## When to update this file - -- A new domain term enters the codebase or UI. -- An entity is added, removed, or its invariants change. -- A new external dependency or boundary is introduced. -- An ADR is accepted (add it to the list above). - -Do **not** put implementation details, file paths, or stack choices here — -those belong in `AGENTS.md` or `.opencode/docs/`. diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 31e5fa9..0000000 --- a/NOTICE +++ /dev/null @@ -1,114 +0,0 @@ -KYAULabs Template -Copyright (C) 2026 kyau <git@kyaulabs.com> - -This project is licensed under the GNU Affero General Public License -v3.0 (AGPL-3.0). A copy of the license is available in the LICENSE -file at the root of this repository. - -This project includes and adapts material from the following -third-party open-source projects. The original copyright notices and -license terms are preserved here as required by those licenses. - ---- - -obra/superpowers - URL: https://github.com/obra/superpowers - Copyright: Copyright (c) Jesse Vincent - License: MIT - What was adapted: Engineering pipeline methodology — the - brainstorming-to-plan-to-TDD-to-verification pipeline sequence, - Red-Green-Refactor TDD discipline, verification gate, and the - zero-context implementation planning methodology. Also the - receiving-code-review triage discipline and the - finishing-a-development-branch checklist workflow. - Used in: - - .opencode/skills/executing-plans/SKILL.md (pipeline execution) - - .opencode/skills/verification-before-completion/SKILL.md (verification gate) - - .opencode/skills/writing-plans/SKILL.md (planning methodology) - - .opencode/skills/receiving-code-review/SKILL.md (review triage) - - .opencode/skills/finishing-a-development-branch/SKILL.md (branch completion) - - .opencode/agents/tdd.md (Red-Green-Refactor TDD discipline) - -obra/superpowers-lab - URL: https://github.com/obra/superpowers-lab - Copyright: Copyright (c) Jesse Vincent - License: MIT - What was adapted: The two-phase semantic-duplication detection - pattern — classical structural extraction (Phase 1) followed by - LLM intent-clustering (Phase 2) with a deletion-test gate. - Used in: - - .opencode/skills/finding-duplicate-functions/SKILL.md (duplicate detection) - -obra/superpowers-developing-for-claude-code - URL: https://github.com/obra/superpowers-developing-for-claude-code - Copyright: Copyright (c) Jesse Vincent - License: MIT - What was adapted: The vendored-official-docs skill pattern — a local - docs/ directory populated by a fetch.sh script that shallow-clones - the upstream documentation repo with sparse checkout. The skill body - provides a quick-reference table with progressive disclosure to the - vendored files. - Used in: - - .opencode/skills/opencode-docs/SKILL.md (vendored docs skill) - - .opencode/skills/opencode-docs/fetch.sh (fetch script) - -mattpocock/skills - URL: https://github.com/mattpocock/skills - Copyright: Copyright (c) Matt Pocock - License: MIT - What was adapted: The one-question-at-a-time grilling pattern for - collaborative design refinement and the three-branch throwaway - prototype pattern (logic, UI, integration). - Used in: - - .opencode/skills/brainstorming/SKILL.md (grilling pattern) - - .opencode/skills/prototype/SKILL.md (prototype pattern) - -anthropics/skills - URL: https://github.com/anthropics/skills - Copyright: Copyright (c) Anthropic - License: MIT - What was adapted: The SKILL.md file format specification — YAML - frontmatter with `name` and `description` fields, directory-per-skill - convention. Adopted as the harness's skill file format. - Used in: - - .opencode/skills/writing-skills/SKILL.md (format documentation) - - Applied as the structural format for all 19 skill files under - .opencode/skills/ - -glebis/claude-skills - URL: https://github.com/glebis/claude-skills - Copyright: Copyright (c) Gleb - License: MIT - What was adapted: The multi-agent architecture — subagent frontmatter - schema (`mode`, `temperature`, `permission` with scoped `edit`, - `bash`, `webfetch`, `task` keys) and the TDD agent as a dispatchable - worker in the pipeline. - Used in: - - .opencode/skills/writing-skills/SKILL.md (schema documentation) - - .opencode/agents/tdd.md (TDD agent architecture) - - Applied as the structural schema for all 8 agent files under - .opencode/agents/ - ---- - -The MIT license terms applicable to the above projects: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/adr/0000-template.md b/adr/0000-template.md deleted file mode 100644 index ff34388..0000000 --- a/adr/0000-template.md +++ /dev/null @@ -1,32 +0,0 @@ -# NNNN. <title> - -Date: YYYY-MM-DD - -## Status - -Proposed - -## Context - -What is the issue we're facing? What forces are at play — technical, -organizational, constraints, prior decisions? State the problem, not the -solution. - -## Decision - -What we decided to do. Write in the active voice and present tense: "We -use…", "We adopt…", "We will not…". - -## Consequences - -What follows from this decision — positive, negative, and neutral. - -- New dependencies or required work. -- Things that become easier. -- Things that become harder or impossible. -- Follow-up ADRs or tasks implied by this decision. - -## Alternatives Considered - -Other options that were on the table and why they were rejected. Name them; -don't leave them implicit. diff --git a/adr/0001-csp-policy-for-aurora-stack.md b/adr/0001-csp-policy-for-aurora-stack.md deleted file mode 100644 index 28d419f..0000000 --- a/adr/0001-csp-policy-for-aurora-stack.md +++ /dev/null @@ -1,76 +0,0 @@ -# 0001. CSP Policy for the Aurora Stack - -Date: 2026-07-04 - -## Status - -Accepted - -## Context - -Two skills give contradictory guidance on Content Security Policy: - -- `security-coding` mandates a `Content-Security-Policy` header but does not - specify the policy (only a one-line reminder that CSP exists). -- `frontend-architecture` forbids inline `<script>` tags "outside the - controlled header/footer emitted by Aurora" — implying Aurora might emit - inline scripts, which would collide with a strict CSP. - -Inspecting Aurora's code (`aurora/aurora.inc.php:247-294`) reveals that -Aurora emits **only** external `<script src>` tags with SRI hashes -(`integrity="sha512-..."` + `crossorigin="anonymous"`). The `comment()` -method (`:439-444`) emits an HTML comment, not JavaScript. No inline scripts, -event handlers, or `eval`-style patterns exist in Aurora's output. - -The gap is a documentation gap: neither skill states the canonical CSP, so -the first implementer must reverse-engineer Aurora's output to write one, -risking either an over-permissive policy or a broken site. - -## Decision - -We adopt a strict CSP as the canonical default for every Aurora page: - -``` -Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none' -``` - -- `script-src 'self'` — Aurora's external script tags with SRI hashes pass - cleanly. No `'unsafe-inline'`, no `'unsafe-eval'`. -- `object-src 'none'` — blocks legacy plugin vectors. -- `base-uri 'self'` — prevents base-tag injection. -- `frame-ancestors 'none'` — replaces `X-Frame-Options: DENY` at the CSP - level (the `X-Frame-Options` header remains as a fallback for older - browsers). - -**`'unsafe-inline'` is forbidden in production.** There is no valid reason -for inline scripts in this stack: Aurora handles all script emission, and -pages register scripts declaratively via `$site->js`. - -**Nonce escape hatch (for the future).** If a future feature requires inline -script content (e.g., a JSON configuration blob), Aurora SHOULD generate a -per-request cryptographically-random nonce, emit it on the inline `<script>` -tag (`<script nonce="...">`), and reflect it in the CSP header -(`script-src 'self' 'nonce-<value>'`). Hash-based allowlisting is acceptable -only for fully-static inline scripts, but is discouraged — nonce is the -preferred mechanism for dynamic content. - -## Consequences - -- **Easier:** every new Aurora project starts with a strict CSP; no - `'unsafe-inline'` drift; external scripts with SRI are already compatible. -- **Harder:** if inline scripts are ever needed (a new Aurora feature or a - third-party integration that requires them), a nonce implementation in - Aurora is required upfront. No shortcut via `'unsafe-inline'`. -- **Follow-up:** implement the nonce mechanism in Aurora (`aurora/` - submodule) if inline script emission is ever added. - -## Alternatives Considered - -- **`'unsafe-inline'`** — defeats the XSS protection value of CSP. Rejected. -- **Hash-based allowlisting** — works for static inline scripts but does not - cover dynamic inline content (the most likely Aurora scenario). Rejected as - the primary mechanism; acceptable as a secondary option for known-static - snippets. -- **Per-request nonce today** — overengineered for current needs (Aurora - emits no inline content). Rejected as unnecessary complexity; documented - above as the escape hatch when needed. diff --git a/adr/0002-first-party-semgrep-rules-pack.md b/adr/0002-first-party-semgrep-rules-pack.md deleted file mode 100644 index c038301..0000000 --- a/adr/0002-first-party-semgrep-rules-pack.md +++ /dev/null @@ -1,87 +0,0 @@ -# 0002. First-Party Semgrep Rules Pack - -Date: 2026-07-05 - -## Status - -Accepted - -## Context - -The `@semgrep` agent currently relies entirely on generic registry packs -(`p/php`, `p/secrets`, `p/javascript`) for SAST scanning. These packs are -language-generic — they cannot encode: - -- **Aurora-specific footguns**: the constructor's positional `$status` bool - (a literal `true` leaks stack traces and SQL fragments to visitors). -- **No-framework sinks**: `$db->query("...$var...")` without bound - parameters, unescaped `echo $_REQUEST[...]`, and `unserialize(` on - request-reachable data are caught by generic rules only in their most - obvious forms, if at all. -- **Project conventions**: `ini_set('display_errors', '1')` in application - code is a violation of Aurora's error-handling contract (the constructor's - `$status` parameter, wired to `APP_DEBUG`, is the single point of control). - -The `security-coding` skill (`.opencode/skills/security-coding/SKILL.md`) -documents these defensive patterns in prose, but the detection layer (`@semgrep`) -has no project-specific teeth. The skill and the scanner are decoupled. - -Trail of Bits offers a `semgrep-rule-creator` skill in their Codex marketplace, -advocating test-driven rule authoring — write positive/negative fixtures, then -implement the rule, validated by a test harness. This approach maps cleanly to -the repository's TDD conventions (Pest + Red→Green→Refactor). - -## Decision - -We maintain a first-party Semgrep rules pack at `.semgrep/kyaulabs.yml`, loaded -by `@semgrep` on every invocation alongside the registry packs. Every rule has -positive and negative PHP fixtures in `tests/Semgrep/<RuleId>/` and is -validated by `tests/Unit/Semgrep/RulesPackTest.php` — a Pest test that asserts -each rule fires on its positive fixture and does not fire on its negative -fixture. - -**Dual coverage design**: some rules overlap with Pest arch/integration tests -(e.g., `AuroraConstructorStatusTest.php` scans for hardcoded `$status=true` at -the PHP level). The Semgrep rules provide the same detection at a different -pipeline point — during `@semgrep` diff audits (pre-commit review) — while -Pest tests provide the hard gate at `/check` time. Both are intentional: -diff-time early warning + test-time enforcement. - -**Rule-authoring convention**: new rules follow TDD: -1. Write positive fixture (code that SHOULD trigger the rule). -2. Write negative fixture (code that SHOULD NOT trigger). -3. Add the fixture directory to the `semgrepRulesProvider()` in - `RulesPackTest.php`. -4. Run the test — it fails (Red). -5. Implement the rule in `.semgrep/kyaulabs.yml`. -6. Run the test — it passes (Green). -7. Refactor the rule for precision/performance. - -**Suppression policy**: findings may be suppressed inline with -`// nosemgrep: <rule-id> -- <justification>`. Bare `// nosemgrep` is -forbidden. Suppressions are re-reviewed when the named rule is updated. -See the `/security` command's false-positive adjudication protocol. - -## Consequences - -- **Easier:** Aurora-specific footguns and no-framework sinks are detected - automatically at review time; the `security-coding` skill's defensive - guidance now has machine-enforced backup; the TDD convention keeps rules - honest (no untested rules). -- **Harder:** new contributors must understand the TDD rule-authoring - convention; the rules pack adds a maintenance surface (though fixtures make - regressions immediately visible). -- **Dependency:** requires `semgrep` on the build/review host. Already a - soft-requirement for `/check` and `@semgrep` — no new tooling. - -## Alternatives Considered - -- **Registry-only (status quo)** — no project-specific detection. Rejected: - the gap between `security-coding` prose and `@semgrep` scans is real and - growing. -- **Pest-only detection** — writing PHP tests for every pattern duplicates - what Semgrep does at the AST level. Rejected: complement, don't replace. - Dual coverage at different pipeline points is the design. -- **External rules repository** — a separate repo for the rules pack adds an - indirection and a versioning problem. Rejected: keeping rules colocated with - the code they protect ensures they evolve together. diff --git a/adr/README.md b/adr/README.md deleted file mode 100644 index 096c3a5..0000000 --- a/adr/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Architecture Decision Records - -An ADR captures a single architectural decision: the context, the choice, and -its consequences. ADRs are immutable once accepted — supersede, don't edit. - -## Format (Nygard) - -Each record: `adr/NNNN-kebab-case-title.md` where `NNNN` is the next sequence -number (zero-padded). Start at `0001`. - -## Statuses - -| Status | Meaning | -| --- | --- | -| `Proposed` | Drafted, not yet ratified. Open for discussion. | -| `Accepted` | Ratified and in effect. | -| `Deprecated` | No longer in effect; no replacement. | -| `Superseded` | Replaced by a later ADR. Add `Superseded by ADR-NNNN`. | - -When superseding: leave the original file unchanged, change its status to -`Superseded` with a pointer to the new one, and create the new ADR. - -## When to write an ADR - -- A decision is hard to reverse or expensive to change (data model, auth - strategy, framework adoption, deployment topology). -- A choice forecloses other options (e.g., "we use MariaDB, not Postgres"). -- A decision affects more than one module or has cross-cutting consequences. - -## When NOT to write an ADR - -- Routine implementation choices (naming, refactor extractions). -- Decisions fully covered by an existing ADR or `AGENTS.md` rule. -- Anything that fits in a commit message or PR description. - -## Workflow - -1. Copy `0000-template.md` to `adr/NNNN-title.md`. -2. Fill in the sections. -3. Set status `Proposed`. -4. On acceptance, set status `Accepted` and add a one-liner to `CONTEXT.md`. - -See the `adr` skill (`.opencode/skills/adr/SKILL.md`) for the rules summary. diff --git a/composer.json b/composer.json index 9879a60..bbd96bd 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "kyaulabs/template", - "description": "Template — Change This Description", + "name": "kyaulabs/aurora", + "description": "Aurora PHP Framework — Bootstrap layer for applications at KYAULabs", "type": "project", "require": { "php": ">=8.5" @@ -11,9 +11,6 @@ "pestphp/pest-plugin-arch": "^4", "pestphp/pest-plugin-browser": "^4" }, - "autoload": { - "classmap": [".opencode/evals/bin/includes/"] - }, "autoload-dev": { "classmap": ["tests/"] }, diff --git a/composer.lock b/composer.lock index 68d0863..7baee59 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6d618a7e324cb90460eeb7a85fb5cc10", + "content-hash": "9e58310e96a1528ec7958ff66b2f927c", "packages": [], "packages-dev": [ { diff --git a/docs/POSITIONING.md b/docs/POSITIONING.md deleted file mode 100644 index 896e188..0000000 --- a/docs/POSITIONING.md +++ /dev/null @@ -1,124 +0,0 @@ -# Why This Stack and Harness Exist - -KYAULabs Template is an **OpenCode-native PHP coding harness** — a complete -development environment where the AI agent's tooling, methodology, and -conventions are defined as code under `.opencode/`. This document explains -the design decisions that make it different from typical project scaffolds. - -## Why OpenCode? - -Most AI coding tools (Cursor, Copilot, Claude Code) run as a single monolithic -agent with god-mode access to your filesystem, shell, and network. They have no -per-agent permission model, no structural anti-drift enforcement, and no -pipeline scaffolding — you get raw LLM output and you hope for the best. - -OpenCode gives us **per-agent permissions** (`opencode.json`) — the `plan` -agent is read-only, the `build` agent can write files but must ask before -committing, `@tdd` can commit but cannot push, and so on. Every subagent -(architect, code-review, semgrep, debug) operates within explicit capability -boundaries. This is the "least privilege" principle applied to your AI pair -programmer. - -OpenCode also runs **TypeScript plugins** that structurally inject behavior -the model cannot forget or skip. `session-bootstrap.ts` pushes the -anti-rationalization red-flags table into every system prompt and into every -compaction context — the model cannot "drift" past a pipeline gate by -rationalizing that it doesn't need one. This is structural enforcement, not -a prompt that the model can ignore under cognitive load. - -## Why No Framework? - -This stack uses **raw PHP 8.5 + Aurora's SQL handler**. There is no ORM, no -router, no templating engine, no dependency injection container, and no MVC -framework. Every database query is a parameterized SQL string you can grep, -audit, and explain. Every HTML page is a `.php` file in the webroot that -outputs markup directly. - -The trade-off: -- **You write more boilerplate**, but every line is intentional. -- **You get less magic**, but nothing hides behind middleware chains you - didn't read. -- **You carry more discipline** (no framework CSRF protection, no framework - session management, no framework input validation — the `security-coding` - skill covers what Laravel/Symfony abstract away). - -This is the right trade-off for a single-developer team that wants full -auditability of every request path and every query. If you need a framework, -this template is not for you. - -## The Fast-Path - -Not every change needs the full engineering pipeline. A typo fix, an RCS header -update, a style-only SCSS adjustment, a docs-only commit — these have **zero -behavior delta**. The fast-path lets you skip brainstorming, planning, TDD, and -`@code-review` for these changes. - -Why two pipelines? Because every pipeline step carries a cognitive and time -cost. Running a 96-line `/check` gate for a one-character typo fix is waste. -Forcing a brainstorming session for an RCS header update burns the reviewer's -patience. The fast-path keeps velocity high on chores so the heavy pipeline's -signal stays clean on behavior deltas. - -The fast-path is defined in `session-bootstrap.md` — zero-behavior-delta -changes (typos, docs, RCS headers, style-only, patch deps, test-only fixes) -skip the full pipeline. Everything else goes through: - -``` -brainstorming → prototype (if needed) → writing-plans → executing-plans → @tdd → verification → /check → @code-review -``` - -## Validate-Harness CI - -The harness is code. Skills, agents, commands, and plugins under `.opencode/` -all carry YAML frontmatter that must be valid. Names must not collide across -categories (a skill named `debug` and an agent named `debug` can't coexist). -Cross-references in `## Cross-refs` sections must point to existing names. - -`validate-harness.sh` runs these checks — frontmatter delimiters, required -fields (`name`/`description` for skills, `description`/`mode` for agents, -`description` for commands), name uniqueness, and cross-reference integrity. -It is the harness's own CI gate, runnable locally and in CI. - -If your skill file has no `---` delimiters, validate-harness catches it. If -two agents share the same name, it catches it. If a `## Cross-refs` section -references a skill that doesn't exist, it catches it. This is the same -discipline we apply to application code, applied to the agent's own toolchain. - -## Prototype Integration Branch - -Before committing to an implementation plan, you can prototype — throwaway -code that answers a single technical viability question. The `prototype` skill -defines three branches: -- **Logic prototype** (PHP CLI) — answers "can this algorithm work?" -- **UI prototype** (HTML+CSS+JS variants) — answers "does this layout feel right?" -- **Integration prototype** (DB/API boundary test) — answers "does this - protocol integrate cleanly?" - -The prototype is deleted after answering the question. It is NOT a draft PR, -NOT a feature branch that might accidentally ship, and NOT a place to park -half-baked code. The separation of prototype (learn) from plan (design) from -implementation (TDD) prevents the common failure mode where "prototype" code -ships to production because nobody had the discipline to throw it away first. - -## The Full Stack, At a Glance - -| Layer | Technology | Rationale | -|---|---|---| -| AI harness | OpenCode + agents/skills/commands/plugins | Per-agent permissions, structural anti-drift, pipeline scaffolding | -| Web server | nginx | Fast, stable, project-proven (see `*.nginx.conf`) | -| Database | MariaDB | InnoDB, indexed, fully auditable (no ORM) | -| Backend | PHP 8.5 (procedural / class-based) | No MVC, no router, raw SQL via Aurora's handler | -| Frontend | HTML5 + SCSS + vanilla JS | Mobile-first, neumorphic, CSS-driven transitions | -| Tests | Pest PHP v4 on PHPUnit 12 | Red-Green-Refactor, minimum 80% line coverage | -| Lint/CI | php-cs-fixer, stylelint, eslint, commitlint | Enforced by git hooks (`.github/hooks/`) | -| Changelog | git-cliff | Automated from Conventional Commits history | -| Secrets | gitleaks | Pre-commit scan through git hook | -| SAST | Semgrep | Diff-based audit + full scan via `@semgrep` agent | -| Code review | OpenCodeReview (`ocr`) | Automated review via `@code-review` agent | - -## How to Contribute - -See [`README.md`](README.md) for the full setup guide, including Composer/npm -installation, git hook setup (`bash .github/scripts/install-hooks.sh`), and the -first-build workflow. The [`CODING_HARNESS.md`](CODING_HARNESS.md) orientation -doc covers the harness architecture and pipeline flow. diff --git a/docs/plans/2025-07-05-eval-runner-plan.md b/docs/plans/2025-07-05-eval-runner-plan.md deleted file mode 100644 index 17e7205..0000000 --- a/docs/plans/2025-07-05-eval-runner-plan.md +++ /dev/null @@ -1,1501 +0,0 @@ -# Eval Runner Implementation Plan - -> **For agentic workers:** Implement this plan task-by-task using the @tdd -> agent. Steps use checkbox (`- [ ]`) syntax for tracking. Each task follows -> Red → Green → Refactor. - -**Goal:** Build an automated eval runner that executes `.opencode/evals/smoke/*.json` cases against `opencode run` and reports pass/fail per expected behavior. - -**Architecture:** Two PHP CLI scripts sharing a common class include. `run-eval.php` parses an eval case JSON, invokes `opencode run` non-interactively, applies a deterministic gate, then optionally invokes an LLM judge for `all behaviors observed` criteria. `run-suite.php` shells out to `run-eval.php` per case, aggregates results, and produces a markdown summary + JSON report. - -**Tech Stack:** PHP 8.5+ CLI, no external PHP dependencies. Invokes `opencode run` which must be in `PATH`. - -## Global constraints - -- PHP 8.5+ (typed properties, match expressions, named arguments) -- Shell out to `opencode run` — must be available in `PATH` or runner skips -- In-repo execution (no sandbox — agent needs `.opencode/` and `AGENTS.md`) -- RCS header + vim modeline on every PHP file (see `rcs-header` skill) -- Unit tests live in `tests/Unit/Eval/`, integration in `tests/Integration/Eval/` -- PSR-12 code style, `declare(strict_types=1)` on all classes - ---- - -### Task 1: Shared include — EvalCase, EvalResult, and command builder - -**Files:** -- Create: `.opencode/evals/bin/includes/EvalRunner.php` -- Test: `tests/Unit/Eval/EvalCaseTest.php` - -**Interfaces:** -- Produces: `EvalCase::fromFile(string $path): self`, `EvalCase::validate(): array`, `EvalResult` data class, `Runner::buildCommand(EvalCase $case): string`, `Runner::parseArgs(array $argv): array` - -- [ ] **Step 1: Write the failing test** - -```php -<?php - -# $KYAULabs: EvalCaseTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -use KYAULabs\Eval\EvalCase; - -it('parses a valid eval case JSON file', function () { - $json = json_encode([ - 'name' => 'test-case', - 'description' => 'A test case', - 'agent' => '@tdd', - 'input' => 'Write a function', - 'expected_behavior' => ['Agent writes code', 'Agent runs tests'], - 'pass_criteria' => 'all behaviors observed', - 'tags' => ['smoke'], - ]); - $file = tempnam(sys_get_temp_dir(), 'eval_'); - file_put_contents($file, $json); - - $case = EvalCase::fromFile($file); - - expect($case->name)->toBe('test-case'); - expect($case->passCriteria)->toBe('all behaviors observed'); - expect($case->expectedBehavior)->toHaveCount(2); - - unlink($file); -}); - -it('validates required fields', function () { - $json = json_encode(['name' => 'missing-fields']); - $file = tempnam(sys_get_temp_dir(), 'eval_'); - file_put_contents($file, $json); - - $errors = EvalCase::fromFile($file)->validate(); - - expect($errors)->toHaveCount(5); // description, agent, input, expected_behavior, pass_criteria - unlink($file); -}); - -it('validates pass_criteria against allowed values', function () { - $json = json_encode([ - 'name' => 'bad-criteria', - 'description' => 'test', - 'agent' => '@tdd', - 'input' => 'test', - 'expected_behavior' => ['test'], - 'pass_criteria' => 'invalid criteria', - ]); - $file = tempnam(sys_get_temp_dir(), 'eval_'); - file_put_contents($file, $json); - - $errors = EvalCase::fromFile($file)->validate(); - - expect($errors)->toContain("pass_criteria 'invalid criteria' is not a valid value"); - unlink($file); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 2: Run test to verify it fails** - -```bash -php vendor/bin/pest tests/Unit/Eval/EvalCaseTest.php -``` -Expected: FAIL — class `KYAULabs\Eval\EvalCase` not found. - -- [ ] **Step 3: Create directory and shared include** - -```bash -mkdir -p .opencode/evals/bin/includes -``` - -- [ ] **Step 4: Write EvalRunner.php with EvalCase, EvalResult, Runner** - -```php -<?php - -# $KYAULabs: EvalRunner.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -namespace KYAULabs\Eval; - -/** - * Parsed eval case from a JSON file. - */ -class EvalCase -{ - /** @param string[] $expectedBehavior */ - public function __construct( - public readonly string $name, - public readonly string $description, - public readonly string $agent, - public readonly string $input, - public readonly array $expectedBehavior, - public readonly string $passCriteria, - public readonly array $tags = [], - ) {} - - /** - * Parse an eval case from a JSON file. - * - * @param string $path Path to the JSON case file. - * @return self - * @throws \RuntimeException If the file cannot be read or decoded. - */ - public static function fromFile(string $path): self - { - $contents = file_get_contents($path); - if ($contents === false) { - throw new \RuntimeException("Cannot read eval case file: {$path}"); - } - - $data = json_decode($contents, true); - if (!is_array($data)) { - throw new \RuntimeException("Invalid JSON in eval case file: {$path}"); - } - - return new self( - name: $data['name'] ?? '', - description: $data['description'] ?? '', - agent: $data['agent'] ?? '', - input: $data['input'] ?? '', - expectedBehavior: $data['expected_behavior'] ?? [], - passCriteria: $data['pass_criteria'] ?? '', - tags: $data['tags'] ?? [], - ); - } - - /** - * Validate this case against the schema. Returns an array of error - * messages; empty array means valid. - * - * @return string[] - */ - public function validate(): array - { - $errors = []; - $required = ['name', 'description', 'agent', 'input', 'expected_behavior', 'pass_criteria']; - - foreach ($required as $field) { - $value = match ($field) { - 'name' => $this->name, - 'description' => $this->description, - 'agent' => $this->agent, - 'input' => $this->input, - 'expected_behavior' => $this->expectedBehavior, - 'pass_criteria' => $this->passCriteria, - }; - - if ($field === 'expected_behavior') { - if (empty($value)) { - $errors[] = "required field 'expected_behavior' is empty"; - } - } elseif ($value === '') { - $errors[] = "required field '{$field}' is empty"; - } - } - - $validCriteria = [ - 'all behaviors observed', - 'no errors in output', - 'exit code zero', - 'output contains expected string', - 'manual inspection required', - ]; - - if (!in_array($this->passCriteria, $validCriteria, true)) { - $errors[] = "pass_criteria '{$this->passCriteria}' is not a valid value"; - } - - return $errors; - } -} - -/** - * Result of running a single eval case. - */ -class EvalResult -{ - /** @param array<int, array{behavior: string, verdict: string, rationale: string}> $behaviors */ - /** @param array<string, array<string, mixed>> $deterministicChecks */ - public function __construct( - public string $name, - public string $agent, - public string $passCriteria, - public string $verdict, - public array $behaviors = [], - public array $deterministicChecks = [], - public int $durationMs = 0, - public bool $judgeUsed = false, - public ?string $error = null, - ) {} - - /** - * @return array<string, mixed> - */ - public function toArray(): array - { - return [ - 'name' => $this->name, - 'agent' => $this->agent, - 'pass_criteria' => $this->passCriteria, - 'verdict' => $this->verdict, - 'behaviors' => $this->behaviors, - 'deterministic_checks' => $this->deterministicChecks, - 'duration_ms' => $this->durationMs, - 'judge_used' => $this->judgeUsed, - 'error' => $this->error, - ]; - } - - public function isPass(): bool - { - return $this->verdict === 'PASS'; - } - - public function isFail(): bool - { - return in_array($this->verdict, ['FAIL', 'TIMEOUT', 'INVALID'], true); - } -} - -/** - * Runs a single eval case through opencode run. - */ -class Runner -{ - private string $repoRoot; - - public function __construct( - string $repoRoot, - private int $timeout = 120, - ) { - $this->repoRoot = realpath($repoRoot) ?: $repoRoot; - } - - /** - * Build the opencode run command for a case (dry-run mode). - * - * @param EvalCase $case - * @return string Shell command string. - */ - public function buildCommand(EvalCase $case): string - { - $prompt = escapeshellarg($case->input); - $path = escapeshellarg($this->repoRoot); - - return "opencode run --prompt {$prompt} --mode build --path {$path} " . - "--permissions \"bash: allow, edit: allow, task: allow\""; - } - - /** - * Parse CLI arguments for run-eval.php. - * - * @param array<int, string> $argv - * @return array{caseFile: string, timeout: int, dryRun: bool} - */ - public static function parseArgs(array $argv): array - { - $caseFile = ''; - $timeout = 120; - $dryRun = false; - - for ($i = 1; $i < count($argv); $i++) { - if ($argv[$i] === '--timeout' && isset($argv[$i + 1])) { - $timeout = (int) $argv[++$i]; - } elseif ($argv[$i] === '--dry-run') { - $dryRun = true; - } elseif (!str_starts_with($argv[$i], '--')) { - $caseFile = $argv[$i]; - } - } - - return ['caseFile' => $caseFile, 'timeout' => $timeout, 'dryRun' => $dryRun]; - } -} - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 5: Run test to verify it passes** - -```bash -php vendor/bin/pest tests/Unit/Eval/EvalCaseTest.php -``` -Expected: PASS — 3 tests pass. - -- [ ] **Step 6: Commit** - -```bash -git add .opencode/evals/bin/includes/EvalRunner.php tests/Unit/Eval/EvalCaseTest.php -git commit -S -m "feat(evals): add EvalCase, EvalResult, and Runner base classes - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` - ---- - -### Task 2: Runner — execute and deterministic gate - -**Files:** -- Modify: `.opencode/evals/bin/includes/EvalRunner.php` — add execute + checkDeterministic -- Test: `tests/Unit/Eval/RunnerTest.php` - -**Interfaces:** -- Consumes: `Runner::buildCommand()`, `EvalCase`, `EvalResult` -- Produces: `Runner::executeCommand(string $cmd, int $timeout): array{stdout: string, stderr: string, exitCode: int}`, `Runner::checkDeterministic(EvalCase, string, string, int): ?EvalResult` — returns null if criteria not deterministically resolvable - -- [ ] **Step 1: Write the failing test** - -```php -<?php - -# $KYAULabs: RunnerTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -use KYAULabs\Eval\Runner; -use KYAULabs\Eval\EvalCase; -use KYAULabs\Eval\EvalResult; - -it('builds correct opencode run command', function () { - $runner = new Runner('/path/to/repo'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'Write a function add(a, b)', - expectedBehavior: ['test'], - passCriteria: 'all behaviors observed', - ); - - $cmd = $runner->buildCommand($case); - - expect($cmd)->toContain('opencode run'); - expect($cmd)->toContain('--mode build'); - expect($cmd)->toContain('--prompt'); - expect($cmd)->toContain('Write a function add(a, b)'); -}); - -it('deterministic gate: exit code zero', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'exit code zero', - ); - - $result = $runner->checkDeterministic($case, 'output', '', 0); - - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('PASS'); - expect($result->judgeUsed)->toBeFalse(); -}); - -it('deterministic gate: exit code zero fails on non-zero', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'exit code zero', - ); - - $result = $runner->checkDeterministic($case, 'output', 'error', 1); - - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('FAIL'); -}); - -it('deterministic gate: no errors in output', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'no errors in output', - ); - - $result = $runner->checkDeterministic($case, '', '', 0); - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('PASS'); - - $result2 = $runner->checkDeterministic($case, '', 'some error', 0); - expect($result2)->not->toBeNull(); - expect($result2->verdict)->toBe('FAIL'); -}); - -it('deterministic gate: all behaviors observed returns null', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: ['do thing'], - passCriteria: 'all behaviors observed', - ); - - $result = $runner->checkDeterministic($case, 'output', '', 0); - - expect($result)->toBeNull(); // needs LLM judge -}); - -it('deterministic gate: manual inspection returns undetermined', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'manual inspection required', - ); - - $result = $runner->checkDeterministic($case, '', '', 0); - - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('UNDETERMINED'); -}); - -it('executeCommand runs a command and captures output', function () { - $runner = new Runner(__DIR__); - - $output = $runner->executeCommand('echo "hello world"', 5); - - expect($output['stdout'])->toContain('hello world'); - expect($output['exitCode'])->toBe(0); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 2: Run test to verify it fails** - -```bash -php vendor/bin/pest tests/Unit/Eval/RunnerTest.php -``` -Expected: FAIL — `checkDeterministic` and `executeCommand` methods not found on `Runner`. - -- [ ] **Step 3: Add executeCommand and checkDeterministic to Runner class** in `EvalRunner.php` - -Append these methods inside the `Runner` class (before the closing `}`): - -```php - /** - * Execute a shell command and capture stdout, stderr, and exit code. - * - * @param string $cmd Shell command to execute. - * @param int $timeout Timeout in seconds. - * @return array{stdout: string, stderr: string, exitCode: int} - */ - public function executeCommand(string $cmd, int $timeout): array - { - $descriptors = [ - 0 => ['pipe', 'r'], // stdin - 1 => ['pipe', 'w'], // stdout - 2 => ['pipe', 'w'], // stderr - ]; - - $process = proc_open($cmd, $descriptors, $pipes); - if (!is_resource($process)) { - return ['stdout' => '', 'stderr' => "Failed to start process: {$cmd}", 'exitCode' => -1]; - } - - fclose($pipes[0]); - - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - - $exitCode = proc_close($process); - - return [ - 'stdout' => $stdout !== false ? trim($stdout) : '', - 'stderr' => $stderr !== false ? trim($stderr) : '', - 'exitCode' => $exitCode, - ]; - } - - /** - * Check if the pass criteria can be resolved deterministically (no LLM judge). - * - * Returns an EvalResult if the criteria can be resolved, or null if an - * LLM judge is required. - * - * @param EvalCase $case - * @param string $stdout Captured stdout from the agent run. - * @param string $stderr Captured stderr from the agent run. - * @param int $exitCode Exit code from the agent run. - * @return EvalResult|null - */ - public function checkDeterministic( - EvalCase $case, - string $stdout, - string $stderr, - int $exitCode, - ): ?EvalResult { - $checks = []; - - $verdict = match ($case->passCriteria) { - 'exit code zero' => ($exitCode === 0) ? 'PASS' : 'FAIL', - 'no errors in output' => ($stderr === '') ? 'PASS' : 'FAIL', - 'output contains expected string' => (str_contains($stdout, $case->expectedBehavior[0] ?? '')) ? 'PASS' : 'FAIL', - 'manual inspection required' => 'UNDETERMINED', - default => null, - }; - - if ($verdict === null) { - return null; // needs LLM judge - } - - $checks['exit_code'] = ['expected' => 0, 'actual' => $exitCode]; - - return new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: $verdict, - deterministicChecks: $checks, - judgeUsed: false, - ); - } -``` - -- [ ] **Step 4: Run test to verify it passes** - -```bash -php vendor/bin/pest tests/Unit/Eval/RunnerTest.php -``` -Expected: PASS — all 7 tests pass. - -- [ ] **Step 5: Commit** - -```bash -git add .opencode/evals/bin/includes/EvalRunner.php tests/Unit/Eval/RunnerTest.php -git commit -S -m "feat(evals): add Runner method executeCommand and deterministic gate - -Implements the deterministic pass_criteria dispatch (exit code zero, no errors -in output, output contains expected string, manual inspection required). Returns -null for 'all behaviors observed' to signal that the LLM judge is needed. - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` - ---- - -### Task 3: Runner — LLM judge - -**Files:** -- Modify: `.opencode/evals/bin/includes/EvalRunner.php` — add `buildJudgePrompt()` and `runJudge()` -- Test: `tests/Unit/Eval/JudgeTest.php` - -**Interfaces:** -- Consumes: `EvalCase`, `EvalResult`, `Runner::executeCommand()` -- Produces: `Runner::buildJudgePrompt(EvalCase, string): string`, `Runner::runJudge(EvalCase, string): EvalResult` - -- [ ] **Step 1: Write the failing test** - -```php -<?php - -# $KYAULabs: JudgeTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -use KYAULabs\Eval\Runner; -use KYAULabs\Eval\EvalCase; - -it('builds a judge prompt containing expected behaviors', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test-case', - description: 'a test', - agent: '@tdd', - input: 'Write code', - expectedBehavior: ['Agent announces skill', 'Agent writes code'], - passCriteria: 'all behaviors observed', - ); - - $prompt = $runner->buildJudgePrompt($case, 'the agent output'); - - expect($prompt)->toContain('test-case'); - expect($prompt)->toContain('Agent announces skill'); - expect($prompt)->toContain('Agent writes code'); - expect($prompt)->toContain('the agent output'); - expect($prompt)->toContain('YES'); - expect($prompt)->toContain('JSON array'); -}); - -it('parses judge JSON response correctly', function () { - $json = json_encode([ - ['behavior' => 'Agent announces skill', 'verdict' => 'YES', 'rationale' => 'clearly announced'], - ['behavior' => 'Agent writes code', 'verdict' => 'NO', 'rationale' => 'no code written'], - ]); - - $behaviors = Runner::parseJudgeResponse($json); - - expect($behaviors)->toHaveCount(2); - expect($behaviors[0]['verdict'])->toBe('YES'); - expect($behaviors[1]['verdict'])->toBe('NO'); -}); - -it('parses messy judge response with markdown fences', function () { - $json = "```json\n" . json_encode([ - ['behavior' => 'test', 'verdict' => 'YES', 'rationale' => 'ok'], - ]) . "\n```"; - - $behaviors = Runner::parseJudgeResponse($json); - - expect($behaviors)->toHaveCount(1); - expect($behaviors[0]['verdict'])->toBe('YES'); -}); - -it('runJudge returns PASS when all behaviors are YES', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: ['do thing'], - passCriteria: 'all behaviors observed', - ); - - // Inject a mock judge response — we test prompt building separately; - // the actual opencode run call is tested in integration. - $result = $runner->buildJudgeResult($case, [ - ['behavior' => 'do thing', 'verdict' => 'YES', 'rationale' => 'did it'], - ], 5000); - - expect($result->verdict)->toBe('PASS'); - expect($result->judgeUsed)->toBeTrue(); -}); - -it('runJudge returns FAIL when any behavior is NO', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: ['do thing', 'do other'], - passCriteria: 'all behaviors observed', - ); - - $result = $runner->buildJudgeResult($case, [ - ['behavior' => 'do thing', 'verdict' => 'YES', 'rationale' => 'did it'], - ['behavior' => 'do other', 'verdict' => 'NO', 'rationale' => 'missed'], - ], 5000); - - expect($result->verdict)->toBe('FAIL'); - expect($result->behaviors[1]['verdict'])->toBe('NO'); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 2: Run test to verify it fails** - -```bash -php vendor/bin/pest tests/Unit/Eval/JudgeTest.php -``` -Expected: FAIL — `buildJudgePrompt`, `parseJudgeResponse`, `buildJudgeResult` not found on Runner. - -- [ ] **Step 3: Add judge methods to Runner class** in `EvalRunner.php` - -Append these methods inside the `Runner` class: - -```php - /** - * Build the prompt for the LLM judge. - * - * @param EvalCase $case - * @param string $agentOutput Captured stdout + stderr from the agent run. - * @return string - */ - public function buildJudgePrompt(EvalCase $case, string $agentOutput): string - { - $behaviors = ''; - foreach ($case->expectedBehavior as $i => $behavior) { - $n = $i + 1; - $behaviors .= "{$n}. {$behavior}\n"; - } - - return <<<PROMPT -You are evaluating whether an AI agent's output satisfies expected behaviors. -Below is the eval case and the agent's full output. For each expected behavior, -answer YES if the output demonstrates it, NO if it does not, or UNCLEAR if -you cannot determine. Provide a one-sentence rationale per answer. - -Eval case: {$case->name} -Description: {$case->description} - -Expected behaviors: -{$behaviors} -Agent output: ---- -{$agentOutput} ---- - -Respond with ONLY a valid JSON array. No prose, no markdown fences. -[{"behavior": "<exact text>", "verdict": "YES|NO|UNCLEAR", "rationale": "<one sentence>"}, ...] -PROMPT; - } - - /** - * Parse the judge's JSON response into a behaviors array. - * - * @param string $response Raw response from the judge (may include markdown fences). - * @return array<int, array{behavior: string, verdict: string, rationale: string}> - */ - public static function parseJudgeResponse(string $response): array - { - // Strip markdown code fences if present - $response = trim($response); - $response = preg_replace('/^```(?:json)?\s*\n?/', '', $response); - $response = preg_replace('/\n?```\s*$/', '', $response); - - $decoded = json_decode($response, true); - - if (!is_array($decoded)) { - return []; - } - - return array_map(function (array $item): array { - return [ - 'behavior' => $item['behavior'] ?? '', - 'verdict' => strtoupper($item['verdict'] ?? 'UNCLEAR'), - 'rationale' => $item['rationale'] ?? '', - ]; - }, $decoded); - } - - /** - * Build an EvalResult from the judge's parsed behaviors. - * - * @param EvalCase $case - * @param array<int, array{behavior: string, verdict: string, rationale: string}> $behaviors - * @param int $durationMs - * @return EvalResult - */ - public function buildJudgeResult(EvalCase $case, array $behaviors, int $durationMs): EvalResult - { - $allYes = true; - foreach ($behaviors as $b) { - if ($b['verdict'] !== 'YES') { - $allYes = false; - break; - } - } - - return new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: $allYes ? 'PASS' : 'FAIL', - behaviors: $behaviors, - deterministicChecks: [], - durationMs: $durationMs, - judgeUsed: true, - ); - } - - /** - * Run the LLM judge against the captured agent output. - * - * @param EvalCase $case - * @param string $agentOutput Captured stdout + stderr from the agent run. - * @return EvalResult - */ - public function runJudge(EvalCase $case, string $agentOutput): EvalResult - { - $prompt = $this->buildJudgePrompt($case, $agentOutput); - $judgeCmd = "opencode run --prompt " . escapeshellarg($prompt) . " --mode build --path " . escapeshellarg($this->repoRoot); - - $start = hrtime(true); - $output = $this->executeCommand($judgeCmd, $this->timeout); - $elapsed = (int) ((hrtime(true) - $start) / 1_000_000); - - $behaviors = self::parseJudgeResponse($output['stdout']); - $result = $this->buildJudgeResult($case, $behaviors, $elapsed); - - return $result; - } -``` - -- [ ] **Step 4: Run test to verify it passes** - -```bash -php vendor/bin/pest tests/Unit/Eval/JudgeTest.php -``` -Expected: PASS — all 5 tests pass. - -- [ ] **Step 5: Commit** - -```bash -git add .opencode/evals/bin/includes/EvalRunner.php tests/Unit/Eval/JudgeTest.php -git commit -S -m "feat(evals): add LLM judge to Runner - -buildJudgePrompt constructs a structured prompt for an LLM to score each -expected behavior. parseJudgeResponse strips markdown fences and decodes -the JSON verdict. runJudge executes the full judge pipeline. - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` - ---- - -### Task 4: run-eval.php CLI entry point - -**Files:** -- Create: `.opencode/evals/bin/run-eval.php` -- Test: `tests/Unit/Eval/RunEvalCliTest.php` - -**Interfaces:** -- Consumes: `Runner`, `EvalCase`, `EvalResult` -- Produces: CLI script that reads a case file, runs it, outputs JSON result to stdout - -- [ ] **Step 1: Write the failing test** - -```php -<?php - -# $KYAULabs: RunEvalCliTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -it('run-eval.php exists and is executable', function () { - $script = __DIR__ . '/../../.opencode/evals/bin/run-eval.php'; - expect(file_exists($script))->toBeTrue(); -}); - -it('run-eval.php with --dry-run prints the command', function () { - $caseFile = tempnam(sys_get_temp_dir(), 'eval_'); - $json = json_encode([ - 'name' => 'dry-run-test', - 'description' => 'test', - 'agent' => '@tdd', - 'input' => 'Write a function', - 'expected_behavior' => ['test'], - 'pass_criteria' => 'all behaviors observed', - ]); - file_put_contents($caseFile, $json); - - $script = __DIR__ . '/../../.opencode/evals/bin/run-eval.php'; - $repoRoot = realpath(__DIR__ . '/../../'); - $output = []; - $exitCode = 0; - exec("php {$script} {$caseFile} --dry-run 2>&1", $output, $exitCode); - - $joined = implode("\n", $output); - expect($joined)->toContain('opencode run'); - expect($joined)->toContain('DRY RUN'); - - unlink($caseFile); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 2: Run test to verify it fails** - -```bash -php vendor/bin/pest tests/Unit/Eval/RunEvalCliTest.php -``` -Expected: FAIL — `run-eval.php` does not exist. - -- [ ] **Step 3: Write run-eval.php** - -```php -<?php - -# $KYAULabs: run-eval.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -/** - * run-eval.php — Execute a single eval case against opencode run. - * - * Usage: php run-eval.php <case-file> [--timeout <seconds>] [--dry-run] - * - * Reads a JSON eval case, invokes opencode run with the case's input prompt, - * applies deterministic pass criteria checks (exit code, output content, etc.), - * and optionally invokes an LLM judge for 'all behaviors observed' criteria. - * Outputs a JSON result object to stdout. - * - * Exit codes: - * 0 — PASS - * 1 — FAIL, TIMEOUT, or INVALID - * 2 — SKIPPED (opencode not available) - */ - -require_once __DIR__ . '/includes/EvalRunner.php'; - -use KYAULabs\Eval\Runner; -use KYAULabs\Eval\EvalCase; -use KYAULabs\Eval\EvalResult; - -// ── Parse arguments ────────────────────────────────────────────────────── -$args = Runner::parseArgs($argv); - -if ($args['caseFile'] === '' || !file_exists($args['caseFile'])) { - $result = new EvalResult( - name: basename($args['caseFile'] ?: 'unknown'), - agent: 'unknown', - passCriteria: '', - verdict: 'INVALID', - error: $args['caseFile'] === '' ? 'No case file specified.' : "Case file not found: {$args['caseFile']}", - ); - fwrite(STDERR, json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"); - exit(1); -} - -// ── Parse and validate case file ───────────────────────────────────────── -try { - $case = EvalCase::fromFile($args['caseFile']); -} catch (\RuntimeException $e) { - $result = new EvalResult( - name: basename($args['caseFile']), - agent: 'unknown', - passCriteria: '', - verdict: 'INVALID', - error: $e->getMessage(), - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(1); -} - -$errors = $case->validate(); -if (!empty($errors)) { - $result = new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: 'INVALID', - error: implode('; ', $errors), - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(1); -} - -// ── Build runner ───────────────────────────────────────────────────────── -$repoRoot = dirname(__DIR__, 3); // .opencode/evals/bin → repo root -$runner = new Runner($repoRoot, $args['timeout']); - -// ── Dry-run mode ───────────────────────────────────────────────────────── -if ($args['dryRun']) { - $cmd = $runner->buildCommand($case); - echo "DRY RUN — would execute:\n"; - echo " {$cmd}\n"; - $judgeCmd = "opencode run --prompt '<judge prompt>' --mode build --path {$repoRoot}"; - echo "DRY RUN — judge would execute:\n"; - echo " {$judgeCmd}\n"; - exit(0); -} - -// ── Check for opencode ─────────────────────────────────────────────────── -if (!$runner->isOpenCodeAvailable()) { - $result = new EvalResult( - name: $case->name, - agent: $case->agent, - passCriteria: $case->passCriteria, - verdict: 'SKIPPED', - error: 'opencode not found in PATH. Install opencode to run evals.', - ); - echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - exit(2); -} - -// ── Run the agent ──────────────────────────────────────────────────────── -$start = hrtime(true); -$cmd = $runner->buildCommand($case); -$agentOutput = $runner->executeCommand($cmd, $args['timeout']); -$elapsedMs = (int) ((hrtime(true) - $start) / 1_000_000); - -// ── Deterministic gate ─────────────────────────────────────────────────── -$result = $runner->checkDeterministic( - $case, - $agentOutput['stdout'], - $agentOutput['stderr'], - $agentOutput['exitCode'], -); - -// ── LLM judge ──────────────────────────────────────────────────────────── -if ($result === null) { - $combinedOutput = $agentOutput['stdout']; - if ($agentOutput['stderr'] !== '') { - $combinedOutput .= "\n\n[stderr]\n" . $agentOutput['stderr']; - } - - $result = $runner->runJudge($case, $combinedOutput); - $result->durationMs = $elapsedMs; -} else { - $result->durationMs = $elapsedMs; -} - -// ── Output ─────────────────────────────────────────────────────────────── -echo json_encode($result->toArray(), JSON_PRETTY_PRINT) . "\n"; - -exit($result->isPass() ? 0 : 1); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 4: Add isOpenCodeAvailable to Runner** in `EvalRunner.php` - -Append this method inside the `Runner` class: - -```php - /** - * Check if opencode is available in PATH. - * - * @return bool - */ - public function isOpenCodeAvailable(): bool - { - $output = $this->executeCommand('command -v opencode', 5); - return $output['exitCode'] === 0 && $output['stdout'] !== ''; - } -``` - -- [ ] **Step 5: Run test to verify it passes** - -```bash -php vendor/bin/pest tests/Unit/Eval/RunEvalCliTest.php -``` -Expected: PASS — script exists and --dry-run prints commands. - -- [ ] **Step 6: Commit** - -```bash -git add .opencode/evals/bin/run-eval.php .opencode/evals/bin/includes/EvalRunner.php tests/Unit/Eval/RunEvalCliTest.php -git commit -S -m "feat(evals): add run-eval.php CLI entry point - -Reads an eval case JSON, invokes opencode run, applies deterministic gate, -and optionally invokes LLM judge. Outputs JSON result to stdout. - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` - ---- - -### Task 5: run-suite.php — batch runner with markdown output - -**Files:** -- Create: `.opencode/evals/bin/run-suite.php` -- Test: `tests/Unit/Eval/RunSuiteTest.php` - -**Interfaces:** -- Consumes: `run-eval.php` (shells out via `exec()`), `EvalResult` -- Produces: markdown summary table to stdout, JSON results file to `results/` - -- [ ] **Step 1: Write the failing test** - -```php -<?php - -# $KYAULabs: RunSuiteTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -it('run-suite.php exists', function () { - $script = __DIR__ . '/../../.opencode/evals/bin/run-suite.php'; - expect(file_exists($script))->toBeTrue(); -}); - -it('run-suite.php discovers JSON files in a directory', function () { - // Create a temp smoke directory with one eval case - $tmpDir = sys_get_temp_dir() . '/eval_suite_test_' . uniqid(); - mkdir($tmpDir); - $casePath = $tmpDir . '/test-case.json'; - file_put_contents($casePath, json_encode([ - 'name' => 'test-case', - 'description' => 'test', - 'agent' => '@tdd', - 'input' => 'test', - 'expected_behavior' => ['test'], - 'pass_criteria' => 'all behaviors observed', - ])); - - // We can't run real opencode evals in unit tests, but we can verify - // run-suite.php discovers the file and runs without crashing (it will - // skip if opencode is not available, producing SKIPPED output). - $script = __DIR__ . '/../../.opencode/evals/bin/run-suite.php'; - $output = []; - $exitCode = 0; - exec("php {$script} {$tmpDir} --timeout 5 2>&1", $output, $exitCode); - - $joined = implode("\n", $output); - - // The script should produce output (either SKIPPED if no opencode, or a - // markdown table) - expect($joined)->not->toBeEmpty(); - - // Clean up - unlink($casePath); - rmdir($tmpDir); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 2: Run test to verify it fails** - -```bash -php vendor/bin/pest tests/Unit/Eval/RunSuiteTest.php -``` -Expected: FAIL — `run-suite.php` does not exist. - -- [ ] **Step 3: Write run-suite.php** - -```php -<?php - -# $KYAULabs: run-suite.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -/** - * run-suite.php — Batch eval suite runner. - * - * Usage: php run-suite.php <directory> [--tag <tag>] [--timeout <seconds>] [--dry-run] - * - * Discovers all .json eval case files in the given directory, optionally - * filtered by --tag. Runs each through run-eval.php, aggregates results, - * prints a markdown summary table to stdout, and writes a detailed JSON - * results file. - * - * Exit codes: - * 0 — all cases PASS - * 1 — one or more cases FAIL, TIMEOUT, or INVALID - */ - -$repoRoot = realpath(dirname(__DIR__, 3)); -$runEvalScript = __DIR__ . '/run-eval.php'; - -// ── Parse arguments ────────────────────────────────────────────────────── -$directory = ''; -$tag = null; -$timeout = 120; -$dryRun = false; - -for ($i = 1; $i < count($argv); $i++) { - if ($argv[$i] === '--tag' && isset($argv[$i + 1])) { - $tag = $argv[++$i]; - } elseif ($argv[$i] === '--timeout' && isset($argv[$i + 1])) { - $timeout = (int) $argv[++$i]; - } elseif ($argv[$i] === '--dry-run') { - $dryRun = true; - } elseif (!str_starts_with($argv[$i], '--')) { - $directory = $argv[$i]; - } -} - -if ($directory === '' || !is_dir($directory)) { - fwrite(STDERR, "Error: directory not found: {$directory}\n"); - fwrite(STDERR, "Usage: php run-suite.php <directory> [--tag <tag>] [--timeout <seconds>] [--dry-run]\n"); - exit(1); -} - -// ── Discover case files ────────────────────────────────────────────────── -$files = glob($directory . '/*.json'); -$cases = []; - -foreach ($files as $file) { - $contents = file_get_contents($file); - if ($contents === false) { - continue; - } - - $data = json_decode($contents, true); - if (!is_array($data)) { - continue; - } - - // Tag filter - if ($tag !== null && !in_array($tag, $data['tags'] ?? [], true)) { - continue; - } - - $cases[] = ['file' => $file, 'name' => $data['name'] ?? basename($file)]; -} - -if (empty($cases)) { - echo "No eval cases found in {$directory}" . ($tag !== null ? " with tag '{$tag}'" : '') . ".\n"; - exit(0); -} - -// ── Run each case ───────────────────────────────────────────────────────── -$results = []; -$dryFlag = $dryRun ? ' --dry-run' : ''; - -foreach ($cases as $i => $caseInfo) { - $num = $i + 1; - $total = count($cases); - echo "Running [{$num}/{$total}] {$caseInfo['name']}...\n"; - - $cmd = "php {$runEvalScript} " . escapeshellarg($caseInfo['file']) . - " --timeout {$timeout}{$dryFlag} 2>&1"; - $output = []; - $exitCode = 0; - exec($cmd, $output, $exitCode); - - $joined = implode("\n", $output); - $decoded = json_decode($joined, true); - - if (is_array($decoded)) { - $results[] = $decoded; - } else { - $results[] = [ - 'name' => $caseInfo['name'], - 'verdict' => 'INVALID', - 'error' => 'Failed to parse run-eval output', - ]; - } -} - -// ── Markdown summary ───────────────────────────────────────────────────── -echo "\n"; -echo str_repeat('-', 60) . "\n"; -echo "\n| # | Eval Case | Verdict | Behaviors | Duration | Judge |\n"; -echo "|---|---|---|---|---|---|\n"; - -$passCount = 0; -$failCount = 0; -$skipCount = 0; -$timeoutCount = 0; -$invalidCount = 0; - -foreach ($results as $i => $r) { - $num = $i + 1; - $name = $r['name'] ?? 'unknown'; - $verdict = $r['verdict'] ?? 'UNKNOWN'; - $behaviors = count($r['behaviors'] ?? []); - $yesBehaviors = count(array_filter($r['behaviors'] ?? [], fn($b) => ($b['verdict'] ?? '') === 'YES')); - $duration = isset($r['duration_ms']) ? sprintf('%.1fs', $r['duration_ms'] / 1000) : '-'; - $judge = ($r['judge_used'] ?? false) ? 'yes' : 'no'; - - echo "| {$num} | {$name} | {$verdict} | {$yesBehaviors}/{$behaviors} | {$duration} | {$judge} |\n"; - - match ($verdict) { - 'PASS' => $passCount++, - 'FAIL' => $failCount++, - 'TIMEOUT' => $timeoutCount++, - 'SKIPPED' => $skipCount++, - default => $invalidCount++, - }; -} - -$total = count($results); -echo "\n**Suite: {$passCount}/{$total} passed ({$failCount} failed, {$timeoutCount} timeout, {$skipCount} skipped, {$invalidCount} invalid)**\n"; -echo "\n" . str_repeat('-', 60) . "\n"; - -// ── Write JSON results ─────────────────────────────────────────────────── -$resultsDir = dirname(__DIR__) . '/results'; -if (!is_dir($resultsDir)) { - mkdir($resultsDir, 0755, true); -} - -$timestamp = date('Y-m-d\THis'); -$resultsFile = $resultsDir . "/{$timestamp}.json"; -file_put_contents( - $resultsFile, - json_encode(['timestamp' => $timestamp, 'results' => $results], JSON_PRETTY_PRINT), -); - -echo "\nDetailed results: {$resultsFile}\n"; - -// ── Exit code ──────────────────────────────────────────────────────────── -$anyNonPass = $failCount > 0 || $timeoutCount > 0 || $invalidCount > 0; -exit($anyNonPass ? 1 : 0); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 4: Run test to verify it passes** - -```bash -php vendor/bin/pest tests/Unit/Eval/RunSuiteTest.php -``` -Expected: PASS — script exists and runs without error (skips actual opencode call if not installed). - -- [ ] **Step 5: Commit** - -```bash -git add .opencode/evals/bin/run-suite.php tests/Unit/Eval/RunSuiteTest.php -git commit -S -m "feat(evals): add run-suite.php batch runner - -Discovers .json eval cases, runs each through run-eval.php, aggregates -results into a markdown summary table and writes JSON results to -.opencode/evals/results/. Supports --tag filtering and --dry-run. - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` - ---- - -### Task 6: Scaffolding and docs — .gitignore, results dir, README update - -**Files:** -- Modify: `.opencode/.gitignore` — add `evals/results/` -- Create: `.opencode/evals/results/.gitkeep` -- Modify: `.opencode/evals/README.md` — update to Phase 2 status - -- [ ] **Step 1: Create results directory and .gitignore entry** - -```bash -mkdir -p .opencode/evals/results -touch .opencode/evals/results/.gitkeep -``` - -Add to `.opencode/.gitignore`: -``` -evals/results/ -``` - -- [ ] **Step 2: Update README** - -Replace the Status line and add a Usage section in `.opencode/evals/README.md`. The existing content from line 7 ("**Status:** Phase 1...") should be updated to: - -```markdown -**Status:** Phase 2 — automated runner implemented. Run evals with the PHP CLI -scripts under `bin/`. See Usage below. - -## Usage - -### Run a single eval case - -```bash -php .opencode/evals/bin/run-eval.php .opencode/evals/smoke/tdd-red-green.json -``` - -Options: `--timeout <seconds>` (default 120), `--dry-run` (print command, don't execute). - -Output: JSON result object to stdout. Exit code 0 = PASS, 1 = FAIL, 2 = SKIPPED. - -### Run a suite - -```bash -php .opencode/evals/bin/run-suite.php .opencode/evals/smoke/ -``` - -Options: `--tag <tag>` (filter by tags field), `--timeout <seconds>` (per case). - -Output: markdown summary table to stdout, detailed JSON to `results/<timestamp>.json`. -Exit code 0 = all passed, 1 = one or more failures. - -### In pre-commit/pre-push hooks - -```bash -php .opencode/evals/bin/run-suite.php .opencode/evals/smoke/ --tag smoke -if [ $? -ne 0 ]; then - echo "Eval suite failed — review results before pushing." - exit 1 -fi -``` -``` - -- [ ] **Step 3: Commit** - -```bash -git add .opencode/evals/bin/run-eval.php .opencode/evals/bin/run-suite.php -git add .opencode/evals/bin/includes/EvalRunner.php -git add .opencode/evals/results/ .opencode/evals/README.md .opencode/.gitignore -git add tests/Unit/Eval/ -git commit -S -m "feat(evals): add results directory, .gitignore, and README update - -Updates evals README to Phase 2 status with CLI usage documentation. -Adds evals/results/ to .gitignore. - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` - ---- - -### Task 7: Integration test (slow) - -**Files:** -- Create: `tests/Integration/Eval/RunEvalIntegrationTest.php` - -- [ ] **Step 1: Write the integration test** - -```php -<?php - -# $KYAULabs: RunEvalIntegrationTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -/** - * @group slow - * - * Requires opencode in PATH and a configured LLM provider. - * Skip in default test runs; run manually or in CI on a schedule. - */ -it('runs tdd-red-green smoke case through full pipeline', function () { - $caseFile = dirname(__DIR__, 3) . '/.opencode/evals/smoke/tdd-red-green.json'; - $script = dirname(__DIR__, 3) . '/.opencode/evals/bin/run-eval.php'; - - // Skip if opencode is not available - $check = []; - exec('command -v opencode 2>&1', $check, $checkExit); - if ($checkExit !== 0) { - $this->markTestSkipped('opencode not available in PATH — integration test skipped.'); - } - - $output = []; - $exitCode = 0; - exec("php {$script} " . escapeshellarg($caseFile) . " --timeout 180 2>&1", $output, $exitCode); - - $joined = implode("\n", $output); - $result = json_decode($joined, true); - - expect($result)->toBeArray(); - expect($result['name'] ?? '')->toBe('tdd-red-green'); - - // The test may pass or fail — the integration test verifies the runner - // produces valid JSON with expected fields, not that the agent behaves - // perfectly (that's what the LLM judge does). - expect($result)->toHaveKey('verdict'); - expect($result)->toHaveKey('behaviors'); - expect($result)->toHaveKey('duration_ms'); - expect(in_array($result['verdict'], ['PASS', 'FAIL', 'SKIPPED', 'TIMEOUT']))->toBeTrue(); -})->group('slow'); - -// vim: ft=php sts=4 sw=4 ts=4 et : -``` - -- [ ] **Step 2: Run to verify (skipped by default)** - -```bash -php vendor/bin/pest tests/Integration/Eval/ --group slow -``` -Expected: SKIPPED (opencode not available) or PASS/FAIL (if opencode is configured). - -- [ ] **Step 3: Commit** - -```bash -git add tests/Integration/Eval/RunEvalIntegrationTest.php -git commit -S -m "test(evals): add slow integration test for eval runner - -Runs tdd-red-green smoke case through the full pipeline. @group slow — skipped -by default. Requires opencode in PATH. - -Acked-by: deepseek-v4-pro -Signed-off-by: kyau <git@kyaulabs.com>" -``` diff --git a/docs/specs/2025-07-05-eval-runner-spec.md b/docs/specs/2025-07-05-eval-runner-spec.md deleted file mode 100644 index db96333..0000000 --- a/docs/specs/2025-07-05-eval-runner-spec.md +++ /dev/null @@ -1,149 +0,0 @@ -# Eval Runner — Phase 2 Specification - -**Date:** 2025-07-05 -**Status:** Approved -**Scope:** Build an automated, non-interactive eval runner that executes -`.opencode/evals/smoke/*.json` cases against `opencode run` and reports -pass/fail per expected behavior. - -## Decisions - -| Decision | Choice | Rationale | -|---|---|---| -| Behavior checking | Two-pass: deterministic gate + LLM judge | "All behaviors observed" requires LLM judgment; deterministic criteria (exit code, string match) don't | -| Language | PHP CLI | No new dependencies — PHP is the project stack; json_decode is built-in | -| Sandboxing | In-repo | Agent needs to read .opencode/ skills and AGENTS.md to function; CI runs in a fresh clone | -| Output | Markdown summary to stdout + JSON results file | Human-readable for manual review; machine-parseable for CI | -| Filtering | `--tag <tag>` on run-suite.php | Matches against eval case `tags` field | - -## Files - -| File | Type | Purpose | -|---|---|---| -| `.opencode/evals/bin/run-eval.php` | New | Single-case eval runner | -| `.opencode/evals/bin/run-suite.php` | New | Batch suite runner with aggregation | -| `.opencode/evals/README.md` | Modified | Update to Phase 2 status, document CLI usage | -| `.opencode/evals/results/` | New directory | Generated result files (gitignored) | -| `.opencode/.gitignore` | Modified | Add `evals/results/` | -| `tests/Unit/Eval/RunEvalTest.php` | New | Unit tests for parsing, judge prompt, dispatch | -| `tests/Unit/Eval/RunSuiteTest.php` | New | Unit tests for filtering, aggregation, formatting | - -## run-eval.php - -### Interface - -``` -php .opencode/evals/bin/run-eval.php <case-file> [--timeout <seconds>] [--dry-run] -``` - -- `<case-file>` — required, path to a single eval case JSON file -- `--timeout` — optional, default 120 seconds -- `--dry-run` — optional, construct opencode run command but don't execute; print it -- Exit code: 0 on PASS, 1 on FAIL/TIMEOUT/INVALID, 2 on SKIPPED (opencode not found) -- Stdout: JSON result object - -### Flow - -1. **Parse** — read and json_decode the case file. Validate required fields against schema.json. If invalid, emit INVALID result and exit 1. -2. **Build command** — construct `opencode run --prompt "<input>" --mode build` with --permissions for bash, edit, and task. Use the repo root as --path. -3. **Execute** — run opencode, capture stdout, stderr, exit code. Apply timeout. If timeout exceeded, emit TIMEOUT result and exit 1. If opencode not found in PATH, emit SKIPPED and exit 2. -4. **Deterministic gate** — dispatch on pass_criteria: - - `exit code zero`: PASS if exit code is 0 - - `no errors in output`: PASS if stderr is empty - - `output contains expected string`: PASS if the expected_string field is found in stdout - - `manual inspection required`: return a result with verdict UNDETERMINED (human must review) - - `all behaviors observed`: skip this gate, proceed to LLM judge -5. **LLM judge** — construct a judge prompt containing the expected_behavior array and the captured agent output. Run `opencode run` with the judge prompt. Parse the judge's JSON response. For each behavior, extract verdict (YES/NO/UNCLEAR) and rationale. Overall PASS if all behaviors are YES. -6. **Output JSON result** — see Result Schema below. - -### Judge prompt template - -``` -You are evaluating whether an AI agent's output satisfies expected behaviors. -Below is the eval case and the agent's full output. For each expected behavior, -answer YES if the output demonstrates it, NO if it does not, or UNCLEAR if -you cannot determine. Provide a one-sentence rationale per answer. - -Eval case: <name> -Description: <description> - -Expected behaviors: -<numbered list> - -Agent output: ---- -<captured stdout + stderr> ---- - -Respond with ONLY a valid JSON array. No prose, no markdown fences. -[{"behavior": "<exact text>", "verdict": "YES|NO|UNCLEAR", "rationale": "<one sentence>"}, ...] -``` - -### Result schema - -```json -{ - "name": "<eval case name>", - "agent": "<agent under test>", - "pass_criteria": "<criteria string>", - "verdict": "PASS|FAIL|TIMEOUT|INVALID|SKIPPED|UNDETERMINED", - "behaviors": [ - {"behavior": "<text>", "verdict": "YES|NO|UNCLEAR", "rationale": "<sentence>"} - ], - "deterministic_checks": { - "exit_code": {"expected": 0, "actual": 0, "pass": true} - }, - "duration_ms": 12345, - "judge_used": true|false, - "error": "<if runner-level error>" -} -``` - -## run-suite.php - -### Interface - -``` -php .opencode/evals/bin/run-suite.php <directory> [--tag <tag>] [--timeout <seconds>] [--dry-run] -``` - -- `<directory>` — required, path to directory containing eval case JSON files -- `--tag` — optional, filter by `tags` field -- `--timeout` — optional, default 120 seconds per case -- Exit code: 0 if ALL PASS, 1 if ANY non-PASS (FAIL/TIMEOUT/INVALID) -- Stdout: markdown summary table -- Side effect: writes JSON to `results/<YYYY-MM-DDTHHmmss>.json` - -### Markdown table format - -``` -| # | Eval Case | Agent | Verdict | Behaviors | Duration | Judge | -|---|---|---|---|---|---|---| -| 1 | tdd-red-green | @tdd | PASS | 6/6 | 12.3s | yes | -| 2 | receiving-code-review-triage | receiving-code-review | FAIL | 5/7 | 18.1s | yes | -| 3 | opencode-docs-reference | opencode-docs | PASS | 7/7 | 8.7s | yes | - -**Suite: 2/3 passed (1 failed, 0 timeout, 0 skipped, 0 invalid)** - -Detailed results: .opencode/evals/results/2025-07-05T120000.json -``` - -## Testing - -### Unit tests (fast, no LLM) - -- `tests/Unit/Eval/RunEvalTest.php`: JSON parsing + validation, deterministic check dispatch (exit code zero, output contains, no errors), judge prompt construction, markdown table row formatting -- `tests/Unit/Eval/RunSuiteTest.php`: file discovery, --tag filtering, aggregation logic, exit code logic - -### Integration test (slow, requires LLM) - -- `tests/Integration/Eval/RunEvalIntegrationTest.php`: runs `tdd-red-green.json` through the full pipeline. `@group slow`, skipped in default pest runs. Verified manually or on pre-commit/pre-push hook. - -## Non-goals (for this phase) - -- Watch mode / file-watching. Re-run manually or via CI trigger. -- Before/after diffing. The suite runs all cases; diffing is a human or CI concern. -- CI workflow definition. CI integration is a separate task — this runner produces the artifacts CI consumes (exit code, JSON results). -- Parallel execution. Sequential is fine for the current 5-case suite. -- Sub-suite directories beyond `smoke/`. The directory structure supports them, but none exist yet. -- Schema evolution for the `expected_string` field on `output contains expected string` criteria. Add it when the first eval case needs it. diff --git a/opencode.json b/opencode.json deleted file mode 100644 index 51c13fc..0000000 --- a/opencode.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "model": "deepseek/deepseek-v4-pro", - "instructions": [ - ".opencode/docs/conventions.md" - ], - "agent": { - "build": { - "mode": "primary", - "model": "deepseek/deepseek-v4-pro", - "variant": "max", - "temperature": 0.2, - "permission": { - "bash": { - "*": "allow", - "git add *": "ask", - "git stage *": "deny", - "git commit *": "ask", - "git push *": "deny" - } - }, - "prompt": "You are the primary build agent for a KYAULabs PHP project.\n\nStack: PHP 8.5+ (no MVC, no router), MariaDB, nginx, SCSS + vanilla JS, Pest v4.\n\n`AGENTS.md` (loaded every session) is the authoritative source for stack, boundaries, directory structure, hard boundaries, indentation, and the skills/agents/commands available. Do not restate those rules to the user — just enforce them.\n\nEnforcement posture (these are the triggers you actively gate on):\n- Any new feature or bug fix requiring tests → invoke the @tdd agent (Red → Green → Refactor, vertical slices).\n- Before creating or modifying any source file → load the rcs-header skill (RCS header + vim modeline on every file).\n- Before domain-coupled work → read CONTEXT.md; if missing, suggest /prime. Update it when domain terms or entities change.\n- Non-trivial or cross-cutting change → suggest an @architect review before implementing; write an ADR when a decision is hard to reverse.\n- Frontend visual work → load the frontend-design skill first.\n- Auth, session, SQL, or user-input handling → load the security-coding skill first.\n- Follow the full pipeline when applicable: brainstorming → prototype (if needed) → writing-plans → executing-plans → @tdd → verification-before-completion → /check → @code-review. See AGENTS.md for the complete flow.\n- Context management → if the session is approaching degradation (long conversation, many file reads), load .opencode/docs/context-management.md and follow its guidance (compact with a hint, use subagents, or /handoff and start fresh)." - }, - "plan": { - "mode": "primary", - "model": "openrouter/z-ai/glm-5.2", - "variant": "medium", - "temperature": 0.1, - "permission": { - "edit": "deny", - "bash": "deny", - "task": { - "*": "deny", - "test-audit": "allow", - "code-review": "allow", - "semgrep": "allow", - "architect": "allow", - "explore": "allow", - "scout": "allow", - "docs-writer": "allow" - } - } - }, - "general": { - "model": "deepseek/deepseek-v4-pro", - "variant": "max" - }, - "explore": { - "model": "deepseek/deepseek-v4-pro", - "variant": "max" - }, - "compaction": { - "model": "deepseek/deepseek-v4-flash", - "variant": "medium" - }, - "title": { - "model": "deepseek/deepseek-v4-flash", - "variant": "medium" - }, - "summary": { - "model": "deepseek/deepseek-v4-flash", - "variant": "medium" - } - } -} diff --git a/package-lock.json b/package-lock.json index fb3f3b2..89c616a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,16 @@ { - "name": "template", + "name": "aurora", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "template", + "name": "aurora", "devDependencies": { "@commitlint/config-conventional": "^21", "@eslint/js": "^10", "commitlint": "^21", "eslint": "^10", "git-cliff": "^2", - "js-yaml": "^5.2.1", "playwright": "^1", "sass": "^1", "stylelint": "^17", @@ -2701,29 +2700,6 @@ "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.1.tgz", - "integrity": "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.mjs" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", diff --git a/package.json b/package.json index f832dd6..c54cf27 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,13 @@ { - "name": "template", + "name": "aurora", "private": true, - "description": "Template — Change This Description", + "description": "Aurora PHP Framework — Bootstrap layer for applications at KYAULabs", "devDependencies": { "@commitlint/config-conventional": "^21", "@eslint/js": "^10", "commitlint": "^21", "eslint": "^10", "git-cliff": "^2", - "js-yaml": "^5.2.1", "playwright": "^1", "sass": "^1", "stylelint": "^17", diff --git a/tests/Integration/AuroraConstructorStatusTest.php b/tests/Integration/AuroraConstructorStatusTest.php index dee136e..9d7e08f 100644 --- a/tests/Integration/AuroraConstructorStatusTest.php +++ b/tests/Integration/AuroraConstructorStatusTest.php @@ -11,7 +11,7 @@ * — never a literal true. This test prevents that class of production * error-display bug from silently recurring as apps are added. * The kyaulabs-aurora-status-true-literal Semgrep rule provides early - * warning at diff-audit time (see ADR-0002). + * warning at diff-audit time. */ test('Aurora constructor must not hardcode $status=true in web-accessible files', function () { diff --git a/tests/Integration/AuroraSkillSignatureTest.php b/tests/Integration/AuroraSkillSignatureTest.php deleted file mode 100644 index 306104d..0000000 --- a/tests/Integration/AuroraSkillSignatureTest.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php - -declare(strict_types=1); - -# $KYAULabs: AuroraSkillSignatureTest.php kyau@nova 2026/07/05 -0700 Exp $ - -// Aurora class is loaded by tests/Pest.php bootstrap — no require needed here. - -use KYAULabs\Aurora; - -/** - * Asserts that the Aurora constructor signature documented in the - * aurora-page skill matches the actual source code. Catches the §1.2 - * failure class — hand-written skill documentation drifting from - * source — at the /check gate on every run. Pairs with the - * AuroraConstructorStatusTest for the $status sharp edge. - */ - -test('aurora-page skill constructor signature matches actual source', function () { - // 1. Get the real signature via reflection - $reflected = new ReflectionMethod(Aurora::class, '__construct'); - $params = $reflected->getParameters(); - - $realSignature = ''; - foreach ($params as $param) { - $type = $param->getType(); - $typeStr = ''; - assert($type instanceof ReflectionNamedType, sprintf( - 'Parameter $%s has a %s, which this test does not support. Extend the type rendering logic.', - $param->getName(), - $type::class, - )); - if ($type->allowsNull()) { - $typeStr = '?' . $type->getName(); - } else { - $typeStr = $type->getName(); - } - - $sig = '$' . $param->getName(); - if ($param->isDefaultValueAvailable()) { - $default = $param->getDefaultValue(); - $sig .= ' = '; - if ($default === null) { - $sig .= 'null'; - } elseif ($default === false) { - $sig .= 'false'; - } elseif ($default === true) { - $sig .= 'true'; - } else { - $sig .= var_export($default, true); - } - } - - $realSignature .= ($realSignature === '' ? '' : ' ') . ltrim($typeStr . ' ' . $sig); - } - - // 2. Parse the documented signature from the skill file - $skillPath = __DIR__ . '/../../.opencode/skills/aurora-page/SKILL.md'; - $skillContent = file_get_contents($skillPath); - expect($skillContent)->not->toBeFalse("Could not read aurora-page SKILL.md at {$skillPath}"); - - // Extract the constructor signature from the fenced PHP block. - // Must match the documented signature (with type-hinted params like - // "?string $template = null"), NOT the named-argument usage call - // ("template: ...") that appears earlier in the skill file. - preg_match('/new\s+KYAULabs\\\\Aurora\(\s*(\??\w+\s+\$\w+(?:\s*=\s*(?:null|true|false|\'[^\']*\'|"[^"]*"|\d+))?\s*,?\s*)+\)/', $skillContent, $matches); - $matchText = $matches[0] ?? ''; - preg_match('/new\s+KYAULabs\\\\Aurora\(([^)]+)\)/', $matchText, $matches); - - expect($matches)->toHaveCount(2, 'Could not find Aurora constructor call in aurora-page SKILL.md'); - - $docArgs = $matches[1]; - // Remove whitespace - $docArgs = preg_replace('/\s+/', ' ', trim($docArgs)); - - // 3. Build the expected signature from the doc - $docParams = array_map('trim', explode(',', $docArgs)); - $docSignature = implode(' ', $docParams); - - // 4. Compare - $realSignature = str_replace(' ', ' ', trim(preg_replace('/\s+/', ' ', $realSignature))); - $docSignature = trim($docSignature); - - expect($docSignature)->toBe( - $realSignature, - "aurora-page SKILL.md constructor signature does not match actual Aurora source.\n" . - " Documented: {$docSignature}\n" . - " Actual: {$realSignature}\n" . - 'Fix: update the signature in .opencode/skills/aurora-page/SKILL.md', - ); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/tests/Integration/Eval/RunEvalIntegrationTest.php b/tests/Integration/Eval/RunEvalIntegrationTest.php deleted file mode 100644 index 344c8f6..0000000 --- a/tests/Integration/Eval/RunEvalIntegrationTest.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php - -# $KYAULabs: RunEvalIntegrationTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -/** - * @group slow - * - * Requires opencode in PATH and a configured LLM provider. - * Skip in default test runs; run manually or in pre-commit/pre-push hook. - */ -it('runs tdd-red-green smoke case through full pipeline', function () { - $repoRoot = dirname(__DIR__, 3); - $caseFile = $repoRoot . '/.opencode/evals/smoke/tdd-red-green.json'; - $script = $repoRoot . '/.opencode/evals/bin/run-eval.php'; - - if (!file_exists($caseFile)) { - $this->markTestSkipped('tdd-red-green.json not found — eval case missing.'); - } - - if (!file_exists($script)) { - $this->markTestSkipped('run-eval.php not found — runner not built.'); - } - - // Skip if opencode is not available - $check = []; - exec('command -v opencode 2>&1', $check, $checkExit); - if ($checkExit !== 0) { - $this->markTestSkipped('opencode not available in PATH — integration test skipped.'); - } - - $output = []; - $exitCode = 0; - exec("php {$script} " . escapeshellarg($caseFile) . " --timeout 180 2>&1", $output, $exitCode); - - $joined = implode("\n", $output); - $result = json_decode($joined, true); - - expect($result)->toBeArray(); - expect($result['name'] ?? '')->toBe('tdd-red-green'); - - // The test may pass or fail — the integration test verifies the runner - // produces valid JSON with expected fields, not that the agent behaves - // perfectly (that's what the LLM judge does). - expect($result)->toHaveKey('verdict'); - expect($result)->toHaveKey('behaviors'); - expect($result)->toHaveKey('duration_ms'); - expect(in_array($result['verdict'], ['PASS', 'FAIL', 'SKIPPED', 'TIMEOUT']))->toBeTrue(); -})->group('slow'); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/tests/Pest.php b/tests/Pest.php index 9f2769a..6c45165 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -17,7 +17,7 @@ |-------------------------------------------------------------------------- | | Architecture tests enforce invariants across the entire codebase without -| requiring per-class test files. See .opencode/docs/conventions.md. +| requiring per-class test files. | */ diff --git a/tests/Shell/validate-harness_test.sh b/tests/Shell/validate-harness_test.sh deleted file mode 100644 index 8cf9676..0000000 --- a/tests/Shell/validate-harness_test.sh +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env bash -# $KYAULabs: validate-harness_test.sh kyau@nova 2026/07/05 -0700 Exp $ - -# ── Repro-first tests for validate-harness.sh ────────────────────────────────── -# Bugs under test (from Fable 5 audit): -# 3. Vacuous PASS on empty/missing .opencode (HARNESS_DIR is relative) -# 4. Frontmatter parser re-enters on --- in Markdown body -# 6. Unescaped name in grep regex causes false collisions -# 9. grep -c || echo 0 yields 0\n0, breaking integer test - -set -euo pipefail - -RESULT_FILE=$(mktemp) -trap 'rm -f "$RESULT_FILE"' EXIT - -RED=$'\033[1;31m' -GREEN=$'\033[1;32m' -RESET=$'\033[0m' - -pass() { echo " ${GREEN}PASS${RESET} $*"; echo "PASS" >> "$RESULT_FILE"; } -fail() { echo " ${RED}FAIL${RESET} $*" >&2; echo "FAIL" >> "$RESULT_FILE"; } - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -REAL_VALIDATOR="$REPO_ROOT/.github/scripts/validate-harness.sh" - -if [ ! -f "$REAL_VALIDATOR" ]; then - fail "Cannot find validate-harness.sh at $REAL_VALIDATOR" - exit 1 -fi - -# ── Test 1: Finding 3 — vacuous PASS on relative HARNESS_DIR ────────────────── - -echo "" -echo "── Test 1: Vacuous PASS when run from non-repo-root ──" -T1=$(mktemp -d) -( - cd "$T1" - git init --quiet - mkdir -p .opencode/skills/test-skill - cat > .opencode/skills/test-skill/SKILL.md <<'EOF' ---- -name: test-skill -description: A valid test skill with all required fields. ---- -EOF - git add -A - git commit --quiet -m "init" - - # Copy the validator - mkdir -p .github/scripts - cp "$REAL_VALIDATOR" .github/scripts/validate-harness.sh - - # Run from a SUBDIRECTORY (not repo root) — should resolve HARNESS_DIR via git - mkdir subdir - ( - cd subdir - output=$(bash ../.github/scripts/validate-harness.sh 2>&1) || true - # After fix: validator resolves to repo root and finds the skill - if echo "$output" | grep -q "1 skill(s) checked"; then - pass "Found skills from subdirectory (HARNESS_DIR resolved via git)" - elif echo "$output" | grep -q "0 skill(s) checked"; then - fail "Vacuous PASS — found 0 skills from subdirectory (relative HARNESS_DIR bug)" - else - fail "Unexpected output from subdirectory run" - fi - ) - -) -rm -rf "$T1" - -# ── Test 2: Finding 4 — frontmatter parser re-enters on body --- ────────────── - -echo "── Test 2: Frontmatter parser re-enters on body '---' ──" -T2=$(mktemp -d) -( - cd "$T2" - git init --quiet - cp "$REAL_VALIDATOR" .github/scripts/validate-harness.sh 2>/dev/null || { - mkdir -p .github/scripts - cp "$REAL_VALIDATOR" .github/scripts/validate-harness.sh - } - - # Create an AGENT file with missing mode: but a --- rule in the body - # that has "mode: subagent" after it — the toggling parser would - # re-enter frontmatter mode and read that body line as mode: subagent - mkdir -p .opencode/agents - cat > .opencode/agents/test-agent.md <<'EOF' ---- -description: An agent file missing the mode field ---- - -## Instructions - -Some body text. - ---- - -Then a horizontal rule followed by text that happens to say: -mode: subagent — this is NOT frontmatter, it's body prose. -EOF - - output=$(bash .github/scripts/validate-harness.sh 2>&1) || true - - # Bug: the validator should report "missing or empty 'mode' field" - # But the toggling parser reads the body "mode: subagent" and accepts it - if echo "$output" | grep -q "missing or empty 'mode'" ; then - pass "Correctly detected missing mode field (parser stops at 2nd ---)" - elif echo "$output" | grep -q "expected 'subagent'" && ! echo "$output" | grep -q "missing.*mode"; then - # If it says "expected subagent" but NOT "missing mode", - # the body line "mode: subagent" was parsed as frontmatter - fail "Parser re-entered frontmatter mode on body '---' (found 'mode: subagent' in body)" - else - # The bug may manifest differently depending on exact behavior - if echo "$output" | grep -qi "error"; then - pass "Validator reported errors (some detection working)" - else - fail "Validator missed missing mode field entirely" - fi - fi -) -rm -rf "$T2" - -# ── Test 3: Finding 6 — dotted name causes false collision ──────────────────── - -echo "── Test 3: Dotted name causes false regex collision ──" -T3=$(mktemp -d) -( - cd "$T3" - git init --quiet - mkdir -p .opencode/agents .github/scripts - cp "$REAL_VALIDATOR" .github/scripts/validate-harness.sh - - # Create two agent files — '-' sorts before '.' in C locale, - # so the hyphenated file is processed FIRST and the dotted file SECOND. - # When checking 'agent.bar', grep "^agent.bar " matches 'agent-bar ' - # because '.' in regex matches '-' in the registry entry. - cat > .opencode/agents/agent-bar.md <<'EOF' ---- -description: First agent with hyphenated name -mode: subagent ---- -EOF - - cat > .opencode/agents/agent.bar.md <<'EOF' ---- -description: Second agent with dotted name -mode: subagent ---- -EOF - - output=$(bash .github/scripts/validate-harness.sh 2>&1) || true - - # Bug: foo.bar falsely collides with fooXbar due to unescaped regex - if echo "$output" | grep -qi "already registered"; then - fail "False 'already registered' collision for dotted name (unescaped regex bug)" - else - pass "No false collision for dotted filename" - fi - - # Also verify both agents were counted - if echo "$output" | grep -q "2 agent(s) checked"; then - pass "Both agents counted correctly (2)" - else - fail "Agent count mismatch (expected 2)" - fi -) -rm -rf "$T3" - -# ── Test 4: Finding 9 — grep -c || echo 0 yields 0\n0 ──────────────────────── - -echo "── Test 4: grep -c || echo 0 integer expression error ──" -T4=$(mktemp -d) -( - cd "$T4" - git init --quiet - mkdir -p .opencode/agents .github/scripts - cp "$REAL_VALIDATOR" .github/scripts/validate-harness.sh - - # Create an agent file with NO frontmatter at all - # check_frontmatter_delimiters does grep -c '^---$' which yields 0\n0 - cat > .opencode/agents/no-frontmatter.md <<'EOF' -# No frontmatter here - -Just a markdown file with no YAML delimiters. -EOF - - output=$(bash .github/scripts/validate-harness.sh 2>&1) || true - - # Bug: grep -c || echo 0 produces "integer expression expected" on stderr - if echo "$output" | grep -q "integer expression expected"; then - fail "Integer expression error from grep -c || echo 0 bug" - else - pass "No integer expression error (grep -c bug fixed)" - fi - - # The file should still be detected as having malformed frontmatter - if echo "$output" | grep -q "malformed" || echo "$output" | grep -q "missing.*YAML"; then - pass "No-frontmatter file correctly detected as malformed" - else - fail "No-frontmatter file not detected (expected malformed/missing YAML error)" - fi -) -rm -rf "$T4" - -# ── Summary ─────────────────────────────────────────────────────────────────── - -total_pass=$(grep -c "PASS" "$RESULT_FILE" 2>/dev/null || true) -total_fail=$(grep -c "FAIL" "$RESULT_FILE" 2>/dev/null || true) -: "${total_pass:=0}" -: "${total_fail:=0}" - -echo "" -echo "═══════════════════════════════════════════════════════════" -if [ "$total_fail" -eq 0 ]; then - echo "✓ validate-harness tests PASSED — $total_pass assertion(s), 0 failures" - echo "═══════════════════════════════════════════════════════════" - exit 0 -else - echo "✗ validate-harness tests FAILED — $total_pass passed, $total_fail failure(s)" - echo "═══════════════════════════════════════════════════════════" - exit 1 -fi - -# vim: ft=sh sts=4 sw=4 ts=4 et : diff --git a/tests/Unit/Eval/EvalCaseTest.php b/tests/Unit/Eval/EvalCaseTest.php deleted file mode 100644 index 16c4faf..0000000 --- a/tests/Unit/Eval/EvalCaseTest.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -# $KYAULabs: EvalCaseTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -use KYAULabs\Eval\EvalCase; - -it('parses a valid eval case JSON file', function () { - $json = json_encode([ - 'name' => 'test-case', - 'description' => 'A test case', - 'agent' => '@tdd', - 'input' => 'Write a function', - 'expected_behavior' => ['Agent writes code', 'Agent runs tests'], - 'pass_criteria' => 'all behaviors observed', - 'tags' => ['smoke'], - ]); - $file = tempnam(sys_get_temp_dir(), 'eval_'); - file_put_contents($file, $json); - - $case = EvalCase::fromFile($file); - - expect($case->name)->toBe('test-case'); - expect($case->passCriteria)->toBe('all behaviors observed'); - expect($case->expectedBehavior)->toHaveCount(2); - - unlink($file); -}); - -it('validates required fields', function () { - $json = json_encode(['name' => 'missing-fields']); - $file = tempnam(sys_get_temp_dir(), 'eval_'); - file_put_contents($file, $json); - - $errors = EvalCase::fromFile($file)->validate(); - - expect($errors)->toHaveCount(6); - unlink($file); -}); - -it('validates pass_criteria against allowed values', function () { - $json = json_encode([ - 'name' => 'bad-criteria', - 'description' => 'test', - 'agent' => '@tdd', - 'input' => 'test', - 'expected_behavior' => ['test'], - 'pass_criteria' => 'invalid criteria', - ]); - $file = tempnam(sys_get_temp_dir(), 'eval_'); - file_put_contents($file, $json); - - $errors = EvalCase::fromFile($file)->validate(); - - expect($errors)->toContain("pass_criteria 'invalid criteria' is not a valid value"); - unlink($file); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/tests/Unit/Eval/JudgeTest.php b/tests/Unit/Eval/JudgeTest.php deleted file mode 100644 index 31587f3..0000000 --- a/tests/Unit/Eval/JudgeTest.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php - -# $KYAULabs: JudgeTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -use KYAULabs\Eval\Runner; -use KYAULabs\Eval\EvalCase; - -it('builds a judge prompt containing expected behaviors', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test-case', - description: 'a test', - agent: '@tdd', - input: 'Write code', - expectedBehavior: ['Agent announces skill', 'Agent writes code'], - passCriteria: 'all behaviors observed', - ); - - $prompt = $runner->buildJudgePrompt($case, 'the agent output'); - - expect($prompt)->toContain('test-case'); - expect($prompt)->toContain('Agent announces skill'); - expect($prompt)->toContain('Agent writes code'); - expect($prompt)->toContain('the agent output'); - expect($prompt)->toContain('YES'); - expect($prompt)->toContain('JSON array'); -}); - -it('parses judge JSON response correctly', function () { - $json = json_encode([ - ['behavior' => 'Agent announces skill', 'verdict' => 'YES', 'rationale' => 'clearly announced'], - ['behavior' => 'Agent writes code', 'verdict' => 'NO', 'rationale' => 'no code written'], - ]); - - $behaviors = Runner::parseJudgeResponse($json); - - expect($behaviors)->toHaveCount(2); - expect($behaviors[0]['verdict'])->toBe('YES'); - expect($behaviors[1]['verdict'])->toBe('NO'); -}); - -it('parses messy judge response with markdown fences', function () { - $json = "```json\n" . json_encode([ - ['behavior' => 'test', 'verdict' => 'YES', 'rationale' => 'ok'], - ]) . "\n```"; - - $behaviors = Runner::parseJudgeResponse($json); - - expect($behaviors)->toHaveCount(1); - expect($behaviors[0]['verdict'])->toBe('YES'); -}); - -it('runJudge returns PASS when all behaviors are YES', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: ['do thing'], - passCriteria: 'all behaviors observed', - ); - - $result = $runner->buildJudgeResult($case, [ - ['behavior' => 'do thing', 'verdict' => 'YES', 'rationale' => 'did it'], - ], 5000); - - expect($result->verdict)->toBe('PASS'); - expect($result->judgeUsed)->toBeTrue(); -}); - -it('runJudge returns FAIL when any behavior is NO', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: ['do thing', 'do other'], - passCriteria: 'all behaviors observed', - ); - - $result = $runner->buildJudgeResult($case, [ - ['behavior' => 'do thing', 'verdict' => 'YES', 'rationale' => 'did it'], - ['behavior' => 'do other', 'verdict' => 'NO', 'rationale' => 'missed'], - ], 5000); - - expect($result->verdict)->toBe('FAIL'); - expect($result->behaviors[1]['verdict'])->toBe('NO'); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/tests/Unit/Eval/RunEvalCliTest.php b/tests/Unit/Eval/RunEvalCliTest.php deleted file mode 100644 index e211b08..0000000 --- a/tests/Unit/Eval/RunEvalCliTest.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -# $KYAULabs: RunEvalCliTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -it('run-eval.php exists and is executable', function () { - $script = dirname(__DIR__, 3) . '/.opencode/evals/bin/run-eval.php'; - expect(file_exists($script))->toBeTrue(); -}); - -it('run-eval.php with --dry-run prints the command', function () { - $caseFile = tempnam(sys_get_temp_dir(), 'eval_'); - $json = json_encode([ - 'name' => 'dry-run-test', - 'description' => 'test', - 'agent' => '@tdd', - 'input' => 'Write a function', - 'expected_behavior' => ['test'], - 'pass_criteria' => 'all behaviors observed', - ]); - file_put_contents($caseFile, $json); - - $script = dirname(__DIR__, 3) . '/.opencode/evals/bin/run-eval.php'; - $output = []; - $exitCode = 0; - exec("php {$script} {$caseFile} --dry-run 2>&1", $output, $exitCode); - - $joined = implode("\n", $output); - expect($joined)->toContain('opencode run'); - expect($joined)->toContain('DRY RUN'); - - unlink($caseFile); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/tests/Unit/Eval/RunSuiteTest.php b/tests/Unit/Eval/RunSuiteTest.php deleted file mode 100644 index 3504701..0000000 --- a/tests/Unit/Eval/RunSuiteTest.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -# $KYAULabs: RunSuiteTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -it('run-suite.php exists', function () { - $script = dirname(__DIR__, 3) . '/.opencode/evals/bin/run-suite.php'; - expect(file_exists($script))->toBeTrue(); -}); - -it('run-suite.php discovers JSON files in a directory', function () { - $tmpDir = sys_get_temp_dir() . '/eval_suite_test_' . uniqid(); - mkdir($tmpDir); - $casePath = $tmpDir . '/test-case.json'; - file_put_contents($casePath, json_encode([ - 'name' => 'test-case', - 'description' => 'test', - 'agent' => '@tdd', - 'input' => 'test', - 'expected_behavior' => ['test'], - 'pass_criteria' => 'all behaviors observed', - ])); - - $script = dirname(__DIR__, 3) . '/.opencode/evals/bin/run-suite.php'; - $output = []; - $exitCode = 0; - exec("php {$script} {$tmpDir} --timeout 5 2>&1", $output, $exitCode); - - $joined = implode("\n", $output); - expect($joined)->not->toBeEmpty(); - - unlink($casePath); - rmdir($tmpDir); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et : diff --git a/tests/Unit/Eval/RunnerTest.php b/tests/Unit/Eval/RunnerTest.php deleted file mode 100644 index 022e95d..0000000 --- a/tests/Unit/Eval/RunnerTest.php +++ /dev/null @@ -1,127 +0,0 @@ -<?php - -# $KYAULabs: RunnerTest.php creator@host YYYY/MM/DD ±TZ Exp $ - -declare(strict_types=1); - -use KYAULabs\Eval\Runner; -use KYAULabs\Eval\EvalCase; -use KYAULabs\Eval\EvalResult; - -it('builds correct opencode run command', function () { - $runner = new Runner('/path/to/repo'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'Write a function add(a, b)', - expectedBehavior: ['test'], - passCriteria: 'all behaviors observed', - ); - - $cmd = $runner->buildCommand($case); - - expect($cmd)->toContain('opencode run'); - expect($cmd)->toContain('--mode build'); - expect($cmd)->toContain('--prompt'); - expect($cmd)->toContain('Write a function add(a, b)'); -}); - -it('deterministic gate: exit code zero', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'exit code zero', - ); - - $result = $runner->checkDeterministic($case, 'output', '', 0); - - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('PASS'); - expect($result->judgeUsed)->toBeFalse(); -}); - -it('deterministic gate: exit code zero fails on non-zero', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'exit code zero', - ); - - $result = $runner->checkDeterministic($case, 'output', 'error', 1); - - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('FAIL'); -}); - -it('deterministic gate: no errors in output', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'no errors in output', - ); - - $result = $runner->checkDeterministic($case, '', '', 0); - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('PASS'); - - $result2 = $runner->checkDeterministic($case, '', 'some error', 0); - expect($result2)->not->toBeNull(); - expect($result2->verdict)->toBe('FAIL'); -}); - -it('deterministic gate: all behaviors observed returns null', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: ['do thing'], - passCriteria: 'all behaviors observed', - ); - - $result = $runner->checkDeterministic($case, 'output', '', 0); - - expect($result)->toBeNull(); -}); - -it('deterministic gate: manual inspection returns undetermined', function () { - $runner = new Runner('/tmp'); - $case = new EvalCase( - name: 'test', - description: 'desc', - agent: '@tdd', - input: 'test', - expectedBehavior: [], - passCriteria: 'manual inspection required', - ); - - $result = $runner->checkDeterministic($case, '', '', 0); - - expect($result)->not->toBeNull(); - expect($result->verdict)->toBe('UNDETERMINED'); -}); - -it('executeCommand runs a command and captures output', function () { - $runner = new Runner(__DIR__); - - $output = $runner->executeCommand('echo "hello world"', 5); - - expect($output['stdout'])->toContain('hello world'); - expect($output['exitCode'])->toBe(0); -}); - -// vim: ft=php sts=4 sw=4 ts=4 et :