diff --git a/CHANGELOG.md b/CHANGELOG.md index 6800e48..f00422c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.9.0 +- Add /release command to guide release workflow +- Replace code-release skill with ask-questions skill +- Show a toast when loading a skill + ## 0.8.0 - Add pdf-to-markdown tool for PDF conversion diff --git a/README.md b/README.md index 26c94a7..42d6278 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ npm version

-OpenCode plugin providing hooks, specialized agents (architect, doc-writer, rubber-duck, partner, code-reviewer, code-simplifier), skills (code-release), and tools (gitingest, pdf-to-markdown, blockchain queries, agent-promote). +OpenCode plugin providing hooks, specialized agents (architect, doc-writer, rubber-duck, partner, code-reviewer, code-simplifier), skills (ask-questions-if-underspecified), and tools (gitingest, pdf-to-markdown, blockchain queries, agent-promote). --- @@ -17,7 +17,11 @@ OpenCode plugin providing hooks, specialized agents (architect, doc-writer, rubb - [Commands](#commands) - [Agents](#agents) - [Skills](#skills) - - [code-release](#code-release) + - [Overview](#overview) + - [Available Skills](#available-skills) + - [Discovery Locations](#discovery-locations) + - [Creating a Skill](#creating-a-skill) + - [Automatic Activation](#automatic-activation) - [Tools](#tools) - [gitingest](#gitingest) - [prompt-session](#prompt-session) @@ -117,15 +121,84 @@ Shows stats overview, commits, files changed, and full diff between branches. ## Skills -Skills are loaded on-demand to provide specialized capabilities during a session. +Skills are contextual instructions loaded on demand via the `skill` tool. The agent invokes `skill({ name: "skill-name" })` to load the instructions when needed. -### code-release +### Overview -Prepare releases with version bumps, changelog updates, and tags. +- Skills provide specialized guidance for specific tasks +- Instructions are loaded only when explicitly requested +- Multiple skills can exist with the same name; the highest-priority location wins -- **Purpose**: Guide release preparation steps and require confirmation before changing release artifacts -- **Activation**: On user request to prepare or perform a release -- **Constraints**: Avoid changing versions, tags, or changelogs without explicit confirmation +### Available Skills + +| Skill | Description | +|-------|-------------| +| `ask-questions-if-underspecified` | Clarify requirements before implementing. Use when serious doubts arise. | + +### Discovery Locations + +Skills are discovered from the following locations, in order of increasing priority: + +| Priority | Scope | Location | +|----------|-------|----------| +| 1 (lowest) | plugin | `/skill/` | +| 2 | global | `~/.config/opencode/skill/` | +| 3 (highest) | project | `/.opencode/skill/` | + +If multiple skills share the same name, the one from the highest-priority location takes precedence. + +### Creating a Skill + +Each skill lives in its own directory with a `SKILL.md` file: + +``` +skill/ +└── my-skill/ + └── SKILL.md +``` + +The `SKILL.md` file uses YAML frontmatter for metadata: + +```markdown +--- +name: my-skill +description: Short description of the skill (required) +use_when: > + Condition for automatic activation (optional). +--- + +# Detailed Instructions + +Markdown content with step-by-step guidance... +``` + +#### Frontmatter Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `name` | Yes | Unique identifier for the skill | +| `description` | Yes | Short description (displayed in skill listings) | +| `use_when` | No | Condition for automatic activation | + +A skill without `name` or `description` will be ignored. + +### Automatic Activation + +If a skill defines `use_when`, a directive is injected into the system prompt: + +``` +MANDATORY: Call skill({ name: "my-skill" }) +``` + +This instructs the agent to load the skill when the specified condition is met. + +**What is injected:** +- The skill `name` +- The `use_when` text (normalized: multiple spaces collapsed to single space) + +**What is NOT injected:** +- The `description` +- The markdown content (loaded only when the skill is invoked) --- diff --git a/command/commit-push.md b/command/commit-push.md index 658213b..c9370b2 100644 --- a/command/commit-push.md +++ b/command/commit-push.md @@ -9,7 +9,7 @@ agent: build ## Your task -1. Run `/diff-summary` to analyze all working tree changes +1. /diff-summary to analyze all working tree changes 2. Present a summary to the user: - Files modified/added/deleted with stats - Proposed commit message based on the changes diff --git a/skill/code-release/SKILL.md b/command/release.md similarity index 67% rename from skill/code-release/SKILL.md rename to command/release.md index 2d33a13..6dca615 100644 --- a/skill/code-release/SKILL.md +++ b/command/release.md @@ -1,13 +1,12 @@ --- -name: code-release -description: > - Prepare and execute a release with version bumping, changelog updates, and tags. -use_when: > - REQUIRED: When the user asks to prepare or perform a release, - call skill({ name: "code-release" }) before changing any release artifacts. +description: Prepare and execute a release with version bumping, changelog updates, and tags --- -# Code Release Skill +## Context + +- Current version: !`node -p "require('./package.json').version"` +- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo "none"` +- Commits since last tag: !`git log $(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~10")..HEAD --oneline 2>/dev/null || git log --oneline -10` ## CRITICAL CONSTRAINT @@ -17,13 +16,15 @@ Any destructive or remote action requires confirmation, including: - `git tag` - `git push` -## Step 1: Determine last released version +## Your task + +### Step 1: Determine last released version 1. Prefer the latest Git tag that matches `v`. 2. If no matching tag exists, use the version in `package.json`. 3. Collect commits since the last version (tag to `HEAD`). -## Step 2: Propose version bump +### Step 2: Propose version bump Analyze commits since the last version and recommend a semver bump: - **major**: breaking changes or incompatible behavior changes. @@ -32,14 +33,14 @@ Analyze commits since the last version and recommend a semver bump: Present the recommendation and ask the user to confirm before changing any files. -## Step 3: Update release artifacts (after confirmation) +### Step 3: Update release artifacts (after confirmation) - Update the version in `package.json`. - Update `CHANGELOG` with a new section for the version. - Summarize changes based on the commit range. - Preserve the existing changelog format. -## Step 4: Tag and publish (after confirmation) +### Step 4: Tag and publish (after confirmation) - Commit release changes with a clear release message. - Create an annotated tag (for example, `vX.Y.Z`). diff --git a/package.json b/package.json index c8eceed..d7b07e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-froggy", - "version": "0.8.0", + "version": "0.9.0", "description": "OpenCode plugin with a hook layer (tool.before.*, session.idle...), agents (code-reviewer, doc-writer), and commands (/review-pr, /commit)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/skill/ask-questions-if-underspecified/SKILL.md b/skill/ask-questions-if-underspecified/SKILL.md new file mode 100644 index 0000000..00429e4 --- /dev/null +++ b/skill/ask-questions-if-underspecified/SKILL.md @@ -0,0 +1,59 @@ +--- +name: ask-questions-if-underspecified +description: Clarify requirements before implementing. Use when serious doubts arise. +use_when: > + When a request has multiple plausible interpretations or key details + (objective, scope, constraints, environment, or safety) are unclear, + load this skill before starting implementation. + Do NOT use when the request is already clear or when a quick, low-risk + discovery read can answer the missing details. +--- + +# Ask Questions If Underspecified + +## Goal + +Ask the minimum set of clarifying questions needed to avoid wrong work; do not start implementing until the must-have questions are answered (or the user explicitly approves proceeding with stated assumptions). + +## Workflow + +### 1) Decide whether the request is underspecified + +Treat a request as underspecified if after exploring how to perform the work, some or all of the following are not clear: +- Define the objective (what should change vs stay the same) +- Define "done" (acceptance criteria, examples, edge cases) +- Define scope (which files/components/users are in/out) +- Define constraints (compatibility, performance, style, deps, time) +- Identify environment (language/runtime versions, OS, build/test runner) +- Clarify safety/reversibility (data migration, rollout/rollback, risk) + +If multiple plausible interpretations exist, assume it is underspecified. + +### 2) Ask must-have questions first (keep it small) + +Ask 1-5 questions in the first pass. Prefer questions that eliminate whole branches of work. + +Use the `question` tool to present structured choices: +- Offer multiple-choice options with clear labels +- Mark recommended defaults with "(Recommended)" in the label +- Use `multiple: true` when the user can select several options +- The user can always select "Other" for custom input + +### 3) Pause before acting + +Until must-have answers arrive: +- Do not run commands, edit files, or produce a detailed plan that depends on unknowns +- Do perform a clearly labeled, low-risk discovery step only if it does not commit you to a direction (e.g., inspect repo structure, read relevant config files) + +If the user explicitly asks you to proceed without answers: +- State your assumptions as a short numbered list +- Ask for confirmation; proceed only after they confirm or correct them + +### 4) Confirm interpretation, then proceed + +Once you have answers, restate the requirements in 1-3 sentences (including key constraints and what success looks like), then start work. + +## Anti-patterns + +- Don't ask questions you can answer with a quick, low-risk discovery read (e.g., configs, existing patterns, docs). +- Don't ask open-ended questions if a tight multiple-choice or yes/no would eliminate ambiguity faster. diff --git a/src/index.ts b/src/index.ts index f3f6fa5..b27daa8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -105,6 +105,7 @@ const SmartfrogPlugin: Plugin = async (ctx) => { const skillTool = createSkillTool({ pluginSkills: skills, pluginDir: PLUGIN_ROOT, + client: ctx.client, }) log("[init] Plugin loaded", { diff --git a/src/tools/skill.ts b/src/tools/skill.ts index 3f8121f..875d100 100644 --- a/src/tools/skill.ts +++ b/src/tools/skill.ts @@ -2,7 +2,11 @@ import { tool, type ToolContext } from "@opencode-ai/plugin" import { existsSync, readdirSync, readFileSync } from "node:fs" import { join, dirname } from "node:path" import { homedir } from "node:os" +import type { createOpencodeClient } from "@opencode-ai/sdk" import { parseFrontmatter, type LoadedSkill } from "../loaders" +import { log } from "../logger" + +type Client = ReturnType export interface SkillInfo { name: string @@ -118,18 +122,20 @@ function loadSkillContent(location: string): string { export interface CreateSkillToolOptions { pluginSkills: LoadedSkill[] pluginDir: string + client: Client } export function createSkillTool(options: CreateSkillToolOptions) { let cachedSkills: SkillInfo[] | null = null let cachedDescription: string | null = null + const { client, pluginDir, pluginSkills } = options const getSkills = (cwd: string): SkillInfo[] => { if (cachedSkills) return cachedSkills // Merge order: plugin defaults < global < project (later entries override earlier on name collision) const allSkills = [ - ...pluginSkillsToInfo(options.pluginSkills, options.pluginDir), + ...pluginSkillsToInfo(pluginSkills, pluginDir), ...discoverClaudeGlobalSkills(), ...discoverOpencodeGlobalSkills(), ...discoverClaudeProjectSkills(cwd), @@ -183,6 +189,18 @@ export function createSkillTool(options: CreateSkillToolOptions) { const body = loadSkillContent(skill.location) const dir = dirname(skill.location) + try { + await client.tui.showToast({ + body: { + message: `Skill "${skill.name}" loaded`, + variant: "info", + duration: 3000, + }, + }) + } catch (error) { + log("[skill] Failed to show toast", { error: String(error) }) + } + return [ `## Skill: ${skill.name}`, "",