Files
The-Agency/lib/src/skills/skill_registry.dart
T

437 lines
14 KiB
Dart

import "skill_loader.dart";
import "skill_types.dart";
// ported from old_repo/skills/bundled/*.ts
// each skill has a name, description, and a prompt template
// the prompts are trimmed down versions - dynamic sections (schema generation,
// keybinding tables) are replaced with static placeholders since we dont have
// the TS runtime context here
const _simplifyPrompt = """
# Simplify: Code Review and Cleanup
Review all changed files for reuse, quality, and efficiency. Fix any issues found.
## Phase 1: Identify Changes
Run `git diff` (or `git diff HEAD` if there are staged changes) to see what changed. If there are no git changes, review the most recently modified files that the user mentioned or that you edited earlier in this conversation.
## Phase 2: Launch Three Review Agents in Parallel
Use the Agent tool to launch all three agents concurrently in a single message. Pass each agent the full diff so it has the complete context.
### Agent 1: Code Reuse Review
For each change:
1. **Search for existing utilities and helpers** that could replace newly written code.
2. **Flag any new function that duplicates existing functionality.**
3. **Flag any inline logic that could use an existing utility.**
### Agent 2: Code Quality Review
Review the same changes for hacky patterns:
1. **Redundant state**
2. **Parameter sprawl**
3. **Copy-paste with slight variation**
4. **Leaky abstractions**
5. **Stringly-typed code**
6. **Unnecessary nesting**
7. **Unnecessary comments**
### Agent 3: Efficiency Review
Review the same changes for efficiency:
1. **Unnecessary work**
2. **Missed concurrency**
3. **Hot-path bloat**
4. **Recurring no-op updates**
5. **Unnecessary existence checks**
6. **Memory leaks**
7. **Overly broad operations**
## Phase 3: Fix Issues
Wait for all three agents to complete. Aggregate their findings and fix each issue directly. If a finding is a false positive or not worth addressing, note it and move on.
When done, briefly summarize what was fixed (or confirm the code was already clean).
""";
const _updateConfigPrompt = """
# Update Config Skill
Modify Claude Code configuration by updating settings.json files.
## When Hooks Are Required (Not Memory)
If the user wants something to happen automatically in response to an EVENT, they need a **hook** configured in settings.json. Memory/preferences cannot trigger automated actions.
**These require hooks:**
- "Before compacting, ask me what to preserve" → PreCompact hook
- "After writing files, run prettier" → PostToolUse hook with Write|Edit matcher
- "When I run bash commands, log them" → PreToolUse hook with Bash matcher
- "Always run tests after code changes" → PostToolUse hook
**Hook events:** PreToolUse, PostToolUse, PreCompact, PostCompact, Stop, Notification, SessionStart
## CRITICAL: Read Before Write
**Always read the existing settings file before making changes.** Merge new settings with existing ones - never replace the entire file.
## Settings File Locations
| File | Scope | Use For |
|------|-------|---------|
| `~/.claude/settings.json` | Global | Personal preferences for all projects |
| `.claude/settings.json` | Project | Team-wide hooks, permissions, plugins |
| `.claude/settings.local.json` | Project | Personal overrides for this project |
## Workflow
1. **Clarify intent** - Ask if the request is ambiguous
2. **Read existing file** - Use Read tool on the target settings file
3. **Merge carefully** - Preserve existing settings, especially arrays
4. **Edit file** - Use Edit tool (if file doesn't exist, ask user to create it first)
5. **Confirm** - Tell user what was changed
## Common Mistakes to Avoid
1. **Replacing instead of merging** - Always preserve existing settings
2. **Wrong file** - Ask user if scope is unclear
3. **Invalid JSON** - Validate syntax after changes
4. **Forgetting to read first** - Always read before write
""";
const _keybindingsPrompt = """
# Keybindings Skill
Create or modify `~/.claude/keybindings.json` to customize keyboard shortcuts.
## CRITICAL: Read Before Write
**Always read `~/.claude/keybindings.json` first** (it may not exist yet). Merge changes with existing bindings — never replace the entire file.
## File Format
```json
{
"\$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"bindings": [
{
"context": "Chat",
"bindings": {
"ctrl+e": "chat:externalEditor"
}
}
]
}
```
## Keystroke Syntax
**Modifiers** (combine with `+`): `ctrl`, `alt`, `shift`, `meta`
**Special keys**: `escape`, `enter`, `tab`, `space`, `backspace`, `delete`, arrow keys
**Chords**: Space-separated keystrokes, e.g. `ctrl+k ctrl+s`
## How User Bindings Interact with Defaults
- User bindings are **additive** — appended after the default bindings
- To **move** a binding: unbind the old key (`null`) AND add the new binding
- Set a key to `null` to remove its default binding
## Behavioral Rules
1. Only include contexts the user wants to change
2. Validate that actions and contexts are from the known lists
3. Warn the user if they choose a key that conflicts with reserved shortcuts
4. When adding a new binding, the new binding is additive
5. To fully replace a default binding, unbind old AND add new
""";
const _debugPrompt = """
# Debug Skill
Help the user debug an issue they're encountering in this current Claude Code session.
## Session Debug Log
The debug log for the current session is at: `~/.claude/debug/<session-id>.txt`
Read the debug log and look for errors, warnings, and failure patterns.
## Instructions
1. Review the user's issue description
2. Look for [ERROR] and [WARN] entries, stack traces, and failure patterns in the debug log
3. Explain what you found in plain language
4. Suggest concrete fixes or next steps
## Settings
Settings are in:
* user - `~/.claude/settings.json`
* project - `.claude/settings.json`
* local - `.claude/settings.local.json`
""";
const _rememberPrompt = """
# Memory Review
## Goal
Review the user's memory landscape and produce a clear report of proposed changes, grouped by action type. Do NOT apply changes — present proposals for user approval.
## Steps
### 1. Gather all memory layers
Read CLAUDE.md and CLAUDE.local.md from the project root (if they exist). Your auto-memory content is already in your system prompt — review it there.
### 2. Classify each auto-memory entry
| Destination | What belongs there |
|---|---|
| **CLAUDE.md** | Project conventions for all contributors |
| **CLAUDE.local.md** | Personal instructions for this user only |
| **Stay in auto-memory** | Working notes, temporary context |
### 3. Identify cleanup opportunities
- **Duplicates**: entries already in CLAUDE.md or CLAUDE.local.md
- **Outdated**: entries contradicted by newer entries
- **Conflicts**: contradictions between any two layers
### 4. Present the report
1. **Promotions** — entries to move, with destination and rationale
2. **Cleanup** — duplicates, outdated entries, conflicts to resolve
3. **Ambiguous** — entries where user input is needed
4. **No action needed** — entries that should stay put
## Rules
- Present ALL proposals before making any changes
- Do NOT modify files without explicit user approval
- Ask about ambiguous entries — don't guess
""";
const _skillifyPrompt = """
# Skillify
You are capturing this session's repeatable process as a reusable skill.
## Your Task
### Step 1: Analyze the Session
Before asking questions, analyze the session to identify:
- What repeatable process was performed
- What inputs/parameters were needed
- The distinct steps (in order)
- The success criteria for each step
- Where the user corrected or steered you
- What tools and permissions were needed
### Step 2: Interview the User
Use AskUserQuestion for ALL questions.
**Round 1:** Suggest a name and description, confirm high-level goals.
**Round 2:** Present steps, suggest arguments if needed, ask about inline vs forked context, and where to save (repo or personal).
**Round 3:** For each step, clarify what it produces, how success is verified, and whether human confirmation is needed.
**Round 4:** Confirm when to invoke, trigger phrases, and any gotchas.
### Step 3: Write the SKILL.md
Format:
```markdown
---
name: skill-name
description: one-line description
allowed-tools:
- Bash(git:*)
when_to_use: Use when...
argument-hint: "\$arg_name"
arguments:
- arg_name
context: fork
---
# Skill Title
## Goal
...
## Steps
### 1. Step Name
What to do.
**Success criteria**: How to know this step is done.
```
### Step 4: Confirm and Save
Show the complete SKILL.md content before writing. Ask for confirmation with AskUserQuestion. After writing, tell the user where it was saved and how to invoke it.
""";
const _stuckPrompt = """
# /stuck — diagnose frozen/slow Claude Code sessions
The user thinks another Claude Code session on this machine is frozen, stuck, or very slow. Investigate and post a report to #claude-code-feedback.
## What to look for
Scan for other Claude Code processes. Process names are typically `claude` (installed) or `cli` (native dev build).
Signs of a stuck session:
- **High CPU (≥90%) sustained** — likely an infinite loop
- **Process state `D`** — I/O hang
- **Process state `T`** — user probably hit Ctrl+Z
- **Process state `Z`** — zombie
- **Very high RSS (≥4GB)** — possible memory leak
## Investigation steps
1. List all Claude Code processes:
```
ps -axo pid=,pcpu=,rss=,etime=,state=,comm=,command= | grep -E '(claude|cli)' | grep -v grep
```
2. For anything suspicious, gather more context:
- Child processes: `pgrep -lP <pid>`
- Check debug log: `~/.claude/debug/<session-id>.txt`
## Report
Only post to Slack if you found something stuck. If everything looks healthy, tell the user directly.
If stuck, post to **#claude-code-feedback** using Slack MCP tool with a two-message structure:
1. **Top-level**: hostname, version, terse symptom
2. **Thread reply**: full diagnostic dump
""";
// registry - holds all skills keyed by name
class SkillRegistry {
SkillRegistry._();
static final SkillRegistry instance = SkillRegistry._();
final _skills = <String, Skill>{};
void register(Skill skill) {
_skills[skill.name] = skill;
for (final alias in skill.aliases) {
_skills[alias] = skill;
}
}
// looks up a skill by name or alias
Skill? lookup(String name) => _skills[name];
// all registered skills (deduped by name)
List<Skill> get all {
final seen = <String>{};
final result = <Skill>[];
for (final skill in _skills.values) {
if (seen.add(skill.name)) {
result.add(skill);
}
}
return result;
}
// merge in user and project skills, user skills take precedence over project
// bundled skills take lowest precedence
void mergeExternalSkills(List<Skill> skills) {
for (final skill in skills) {
// external skills override bundled ones with same name
_skills[skill.name] = skill;
}
}
}
// registers the built-in bundled skills
// mirrors old_repo/skills/bundled/index.ts initBundledSkills()
void registerBundledSkills() {
final reg = SkillRegistry.instance;
reg.register(const Skill(
name: "update-config",
description: "Use this skill to configure the Claude Code harness via settings.json. Automated behaviors require hooks configured in settings.json. Also use for permissions, env vars, hook troubleshooting, or any changes to settings.json files.",
source: SkillSource.bundled,
promptTemplate: _updateConfigPrompt,
allowedTools: ["Read"],
userInvocable: true,
));
reg.register(const Skill(
name: "keybindings-help",
description: "Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json.",
source: SkillSource.bundled,
promptTemplate: _keybindingsPrompt,
allowedTools: ["Read"],
userInvocable: false,
));
reg.register(const Skill(
name: "simplify",
description: "Review changed code for reuse, quality, and efficiency, then fix any issues found.",
source: SkillSource.bundled,
promptTemplate: _simplifyPrompt,
userInvocable: true,
));
reg.register(const Skill(
name: "debug",
description: "Enable debug logging for this session and help diagnose issues",
source: SkillSource.bundled,
promptTemplate: _debugPrompt,
allowedTools: ["Read", "Grep", "Glob"],
argumentHint: "[issue description]",
disableModelInvocation: true,
userInvocable: true,
));
reg.register(const Skill(
name: "remember",
description: "Review auto-memory entries and propose promotions to CLAUDE.md, CLAUDE.local.md, or shared memory. Also detects outdated, conflicting, and duplicate entries across memory layers.",
source: SkillSource.bundled,
promptTemplate: _rememberPrompt,
whenToUse: "Use when the user wants to review, organize, or promote their auto-memory entries.",
userInvocable: true,
));
reg.register(const Skill(
name: "skillify",
description: "Capture this session's repeatable process into a skill.",
source: SkillSource.bundled,
promptTemplate: _skillifyPrompt,
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "AskUserQuestion", "Bash(mkdir:*)"],
argumentHint: "[description of the process you want to capture]",
disableModelInvocation: true,
userInvocable: true,
));
// stuck is ant-only in the original but we register it here anyway
// the caller can filter by checking USER_TYPE env var
reg.register(const Skill(
name: "stuck",
description: "[ANT-ONLY] Investigate frozen/stuck/slow Claude Code sessions on this machine.",
source: SkillSource.bundled,
promptTemplate: _stuckPrompt,
userInvocable: true,
));
}
// loads user and project skills and merges them into the registry
Future<void> loadAndMergeExternalSkills() async {
final userSkills = await loadUserSkills();
final projectSkills = await loadProjectSkills();
// project skills override user skills for same name
final all = [...userSkills, ...projectSkills];
SkillRegistry.instance.mergeExternalSkills(all);
}