diff --git a/context/SKILL-SEARCH-PLAN.md b/context/SKILL-SEARCH-PLAN.md new file mode 100644 index 00000000..918c7e62 --- /dev/null +++ b/context/SKILL-SEARCH-PLAN.md @@ -0,0 +1,129 @@ + Plan: Migrate to Skill-Based Search (Deprecate MCP) + + Goal + + Replace MCP search tools with a skill-based approach, reducing session + start context from ~2,500 tokens to ~250 tokens. Clean migration, no + toggles. + + Implementation Steps + + 1. Add HTTP API Endpoints to Worker Service + + File: src/services/worker-service.ts + + Add 10 new routes that wrap existing SessionSearch methods: + - GET /api/search/observations?query=...&format=index&limit=20&project=... + - GET /api/search/sessions?query=...&format=index&limit=20 + - GET /api/search/prompts?query=...&format=index&limit=20 + - GET /api/search/by-concept?concept=discovery&format=index&limit=5 + - GET /api/search/by-file?filePath=...&format=index&limit=10 + - GET /api/search/by-type?type=bugfix&format=index&limit=10 + - GET /api/context/recent?project=...&limit=3 + - GET /api/context/timeline?anchor=123&depth_before=10&depth_after=10 + - GET + /api/timeline/by-query?query=...&mode=auto&depth_before=10&depth_after=10 + - GET /api/search/help - Returns available endpoints and usage docs + + All endpoints return JSON. Skill parses and formats for readability. + + 2. Create Search Skill + + File: plugin/skills/search/SKILL.md + + Frontmatter: + --- + name: search + description: Search claude-mem persistent memory for past sessions, + observations, bugs fixed, features implemented, decisions made, code + changes, and previous work. Use when answering questions about history, + finding past decisions, or researching previous implementations. + --- + + Content: Instructions for all 9 search types using curl to call HTTP + endpoints, formatting guidelines, common workflows. + + 3. Remove MCP Search Server + + Files to modify: + - Remove plugin/.mcp.json entry for claude-mem-search + - Keep src/servers/search-server.ts for reference but don't build it + - Update scripts/build-plugin.js to skip building search-server.mjs + - Archive search-server implementation (don't delete, for reference) + + 4. Update Documentation + + File: CLAUDE.md + + Remove MCP search references, add skill search explanation: + - Token savings: ~2,250 tokens per session + - How skill auto-invokes (model-driven, not user-driven) + - Available search operations + - Examples of triggering searches + + 5. Add Migration Notice + + File: CHANGELOG.md or release notes + + Document the breaking change: + ## v5.4.0 - Skill-Based Search Migration + + **BREAKING CHANGE**: MCP search tools have been replaced with a + skill-based approach. + + **What changed**: + - Removed 9 MCP search tools (search_observations, search_sessions, etc.) + - Added `search` skill that provides the same functionality + - Reduced session start context by ~2,250 tokens + + **Migration**: None required. Claude automatically uses the search skill + when needed. + The skill provides the same search capabilities with better token + efficiency. + + **Why**: Skill-based search uses progressive disclosure (~250 tokens for + frontmatter) + instead of loading all 9 tool definitions (~2,500 tokens) on every session + start. + + 6. Testing Checklist + + - All 10 HTTP endpoints return correct data + - Skill auto-invokes when asking about past work + - Skill successfully calls endpoints via curl + - Skill formats results as readable markdown + - Worker restart updates endpoints + - Skill distributed correctly with plugin + - No MCP search server registered + - Session start context reduced by ~2,250 tokens + + Token Impact + + - Before: ~2,500 tokens (9 MCP tool definitions) + - After: ~250 tokens (skill frontmatter only) + - Savings: ~2,250 tokens per session start + + User Experience + + New behavior: + - User: "What bug did we fix last session?" + - Claude sees skill description matches → invokes search skill + - Skill loads full instructions → uses curl to call HTTP API → formats + results + - User sees formatted answer + + No user action required: Migration is transparent, searches work + automatically. + + Build & Deploy + + npm run build # Builds skill, skips MCP server + npm run sync-marketplace # Syncs plugin with skill + npm run worker:restart # Restart worker with new HTTP endpoints + + Rollout + + 1. Ship as breaking change in v5.4.0 + 2. Update plugin marketplace listing + 3. All users get automatic token savings on update + 4. Archive MCP search implementation for reference \ No newline at end of file diff --git a/context/agent-skills-in-the-sdk.md b/context/agent-skills-in-the-sdk.md new file mode 100644 index 00000000..719661cf --- /dev/null +++ b/context/agent-skills-in-the-sdk.md @@ -0,0 +1,302 @@ +# Agent Skills in the SDK + +> Extend Claude with specialized capabilities using Agent Skills in the Claude Agent SDK + +## Overview + +Agent Skills extend Claude with specialized capabilities that Claude autonomously invokes when relevant. Skills are packaged as `SKILL.md` files containing instructions, descriptions, and optional supporting resources. + +For comprehensive information about Skills, including benefits, architecture, and authoring guidelines, see the [Agent Skills overview](/en/docs/agents-and-tools/agent-skills/overview). + +## How Skills Work with the SDK + +When using the Claude Agent SDK, Skills are: + +1. **Defined as filesystem artifacts**: Created as `SKILL.md` files in specific directories (`.claude/skills/`) +2. **Loaded from filesystem**: Skills are loaded from configured filesystem locations. You must specify `settingSources` (TypeScript) or `setting_sources` (Python) to load Skills from the filesystem +3. **Automatically discovered**: Once filesystem settings are loaded, Skill metadata is discovered at startup from user and project directories; full content loaded when triggered +4. **Model-invoked**: Claude autonomously chooses when to use them based on context +5. **Enabled via allowed\_tools**: Add `"Skill"` to your `allowed_tools` to enable Skills + +Unlike subagents (which can be defined programmatically), Skills must be created as filesystem artifacts. The SDK does not provide a programmatic API for registering Skills. + + + **Default behavior**: By default, the SDK does not load any filesystem settings. To use Skills, you must explicitly configure `settingSources: ['user', 'project']` (TypeScript) or `setting_sources=["user", "project"]` (Python) in your options. + + +## Using Skills with the SDK + +To use Skills with the SDK, you need to: + +1. Include `"Skill"` in your `allowed_tools` configuration +2. Configure `settingSources`/`setting_sources` to load Skills from the filesystem + +Once configured, Claude automatically discovers Skills from the specified directories and invokes them when relevant to the user's request. + + + ```python Python theme={null} + import asyncio + from claude_agent_sdk import query, ClaudeAgentOptions + + async def main(): + options = ClaudeAgentOptions( + cwd="/path/to/project", # Project with .claude/skills/ + setting_sources=["user", "project"], # Load Skills from filesystem + allowed_tools=["Skill", "Read", "Write", "Bash"] # Enable Skill tool + ) + + async for message in query( + prompt="Help me process this PDF document", + options=options + ): + print(message) + + asyncio.run(main()) + ``` + + ```typescript TypeScript theme={null} + import { query } from "@anthropic-ai/claude-agent-sdk"; + + for await (const message of query({ + prompt: "Help me process this PDF document", + options: { + cwd: "/path/to/project", // Project with .claude/skills/ + settingSources: ["user", "project"], // Load Skills from filesystem + allowedTools: ["Skill", "Read", "Write", "Bash"] // Enable Skill tool + } + })) { + console.log(message); + } + ``` + + +## Skill Locations + +Skills are loaded from filesystem directories based on your `settingSources`/`setting_sources` configuration: + +* **Project Skills** (`.claude/skills/`): Shared with your team via git - loaded when `setting_sources` includes `"project"` +* **User Skills** (`~/.claude/skills/`): Personal Skills across all projects - loaded when `setting_sources` includes `"user"` +* **Plugin Skills**: Bundled with installed Claude Code plugins + +## Creating Skills + +Skills are defined as directories containing a `SKILL.md` file with YAML frontmatter and Markdown content. The `description` field determines when Claude invokes your Skill. + +**Example directory structure**: + +```bash theme={null} +.claude/skills/processing-pdfs/ +└── SKILL.md +``` + +For complete guidance on creating Skills, including SKILL.md structure, multi-file Skills, and examples, see: + +* [Agent Skills in Claude Code](https://code.claude.com/docs/skills): Complete guide with examples +* [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices): Authoring guidelines and naming conventions + +## Tool Restrictions + + + The `allowed-tools` frontmatter field in SKILL.md is only supported when using Claude Code CLI directly. **It does not apply when using Skills through the SDK**. + + When using the SDK, control tool access through the main `allowedTools` option in your query configuration. + + +To restrict tools for Skills in SDK applications, use the `allowedTools` option: + + + Import statements from the first example are assumed in the following code snippets. + + + + ```python Python theme={null} + options = ClaudeAgentOptions( + setting_sources=["user", "project"], # Load Skills from filesystem + allowed_tools=["Skill", "Read", "Grep", "Glob"] # Restricted toolset + ) + + async for message in query( + prompt="Analyze the codebase structure", + options=options + ): + print(message) + ``` + + ```typescript TypeScript theme={null} + // Skills can only use Read, Grep, and Glob tools + for await (const message of query({ + prompt: "Analyze the codebase structure", + options: { + settingSources: ["user", "project"], // Load Skills from filesystem + allowedTools: ["Skill", "Read", "Grep", "Glob"] // Restricted toolset + } + })) { + console.log(message); + } + ``` + + +## Discovering Available Skills + +To see which Skills are available in your SDK application, simply ask Claude: + + + ```python Python theme={null} + options = ClaudeAgentOptions( + setting_sources=["user", "project"], # Load Skills from filesystem + allowed_tools=["Skill"] + ) + + async for message in query( + prompt="What Skills are available?", + options=options + ): + print(message) + ``` + + ```typescript TypeScript theme={null} + for await (const message of query({ + prompt: "What Skills are available?", + options: { + settingSources: ["user", "project"], // Load Skills from filesystem + allowedTools: ["Skill"] + } + })) { + console.log(message); + } + ``` + + +Claude will list the available Skills based on your current working directory and installed plugins. + +## Testing Skills + +Test Skills by asking questions that match their descriptions: + + + ```python Python theme={null} + options = ClaudeAgentOptions( + cwd="/path/to/project", + setting_sources=["user", "project"], # Load Skills from filesystem + allowed_tools=["Skill", "Read", "Bash"] + ) + + async for message in query( + prompt="Extract text from invoice.pdf", + options=options + ): + print(message) + ``` + + ```typescript TypeScript theme={null} + for await (const message of query({ + prompt: "Extract text from invoice.pdf", + options: { + cwd: "/path/to/project", + settingSources: ["user", "project"], // Load Skills from filesystem + allowedTools: ["Skill", "Read", "Bash"] + } + })) { + console.log(message); + } + ``` + + +Claude automatically invokes the relevant Skill if the description matches your request. + +## Troubleshooting + +### Skills Not Found + +**Check settingSources configuration**: Skills are only loaded when you explicitly configure `settingSources`/`setting_sources`. This is the most common issue: + + + ```python Python theme={null} + # Wrong - Skills won't be loaded + options = ClaudeAgentOptions( + allowed_tools=["Skill"] + ) + + # Correct - Skills will be loaded + options = ClaudeAgentOptions( + setting_sources=["user", "project"], # Required to load Skills + allowed_tools=["Skill"] + ) + ``` + + ```typescript TypeScript theme={null} + // Wrong - Skills won't be loaded + const options = { + allowedTools: ["Skill"] + }; + + // Correct - Skills will be loaded + const options = { + settingSources: ["user", "project"], // Required to load Skills + allowedTools: ["Skill"] + }; + ``` + + +For more details on `settingSources`/`setting_sources`, see the [TypeScript SDK reference](/en/docs/agent-sdk/typescript#settingsource) or [Python SDK reference](/en/docs/agent-sdk/python#settingsource). + +**Check working directory**: The SDK loads Skills relative to the `cwd` option. Ensure it points to a directory containing `.claude/skills/`: + + + ```python Python theme={null} + # Ensure your cwd points to the directory containing .claude/skills/ + options = ClaudeAgentOptions( + cwd="/path/to/project", # Must contain .claude/skills/ + setting_sources=["user", "project"], # Required to load Skills + allowed_tools=["Skill"] + ) + ``` + + ```typescript TypeScript theme={null} + // Ensure your cwd points to the directory containing .claude/skills/ + const options = { + cwd: "/path/to/project", // Must contain .claude/skills/ + settingSources: ["user", "project"], // Required to load Skills + allowedTools: ["Skill"] + }; + ``` + + +See the "Using Skills with the SDK" section above for the complete pattern. + +**Verify filesystem location**: + +```bash theme={null} +# Check project Skills +ls .claude/skills/*/SKILL.md + +# Check personal Skills +ls ~/.claude/skills/*/SKILL.md +``` + +### Skill Not Being Used + +**Check the Skill tool is enabled**: Confirm `"Skill"` is in your `allowedTools`. + +**Check the description**: Ensure it's specific and includes relevant keywords. See [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices#writing-effective-descriptions) for guidance on writing effective descriptions. + +### Additional Troubleshooting + +For general Skills troubleshooting (YAML syntax, debugging, etc.), see the [Claude Code Skills troubleshooting section](https://code.claude.com/docs/skills#troubleshooting). + +## Related Documentation + +### Skills Guides + +* [Agent Skills in Claude Code](https://code.claude.com/docs/skills): Complete Skills guide with creation, examples, and troubleshooting +* [Agent Skills Overview](/en/docs/agents-and-tools/agent-skills/overview): Conceptual overview, benefits, and architecture +* [Agent Skills Best Practices](/en/docs/agents-and-tools/agent-skills/best-practices): Authoring guidelines for effective Skills +* [Agent Skills Cookbook](https://github.com/anthropics/claude-cookbooks/tree/main/skills): Example Skills and templates + +### SDK Resources + +* [Subagents in the SDK](/en/docs/agent-sdk/subagents): Similar filesystem-based agents with programmatic options +* [Slash Commands in the SDK](/en/docs/agent-sdk/slash-commands): User-invoked commands +* [SDK Overview](/en/docs/agent-sdk/overview): General SDK concepts +* [TypeScript SDK Reference](/en/docs/agent-sdk/typescript): Complete API documentation +* [Python SDK Reference](/en/docs/agent-sdk/python): Complete API documentation diff --git a/context/agent-skills.md b/context/agent-skills.md new file mode 100644 index 00000000..926df512 --- /dev/null +++ b/context/agent-skills.md @@ -0,0 +1,607 @@ +# Agent Skills + +> Create, manage, and share Skills to extend Claude's capabilities in Claude Code. + +This guide shows you how to create, use, and manage Agent Skills in Claude Code. Skills are modular capabilities that extend Claude's functionality through organized folders containing instructions, scripts, and resources. + +## Prerequisites + +* Claude Code version 1.0 or later +* Basic familiarity with [Claude Code](/en/quickstart) + +## What are Agent Skills? + +Agent Skills package expertise into discoverable capabilities. Each Skill consists of a `SKILL.md` file with instructions that Claude reads when relevant, plus optional supporting files like scripts and templates. + +**How Skills are invoked**: Skills are **model-invoked**—Claude autonomously decides when to use them based on your request and the Skill's description. This is different from slash commands, which are **user-invoked** (you explicitly type `/command` to trigger them). + +**Benefits**: + +* Extend Claude's capabilities for your specific workflows +* Share expertise across your team via git +* Reduce repetitive prompting +* Compose multiple Skills for complex tasks + +Learn more in the [Agent Skills overview](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview). + + + For a deep dive into the architecture and real-world applications of Agent Skills, read our engineering blog: [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills). + + +## Create a Skill + +Skills are stored as directories containing a `SKILL.md` file. + +### Personal Skills + +Personal Skills are available across all your projects. Store them in `~/.claude/skills/`: + +```bash theme={null} +mkdir -p ~/.claude/skills/my-skill-name +``` + +**Use personal Skills for**: + +* Your individual workflows and preferences +* Experimental Skills you're developing +* Personal productivity tools + +### Project Skills + +Project Skills are shared with your team. Store them in `.claude/skills/` within your project: + +```bash theme={null} +mkdir -p .claude/skills/my-skill-name +``` + +**Use project Skills for**: + +* Team workflows and conventions +* Project-specific expertise +* Shared utilities and scripts + +Project Skills are checked into git and automatically available to team members. + +### Plugin Skills + +Skills can also come from [Claude Code plugins](/en/plugins). Plugins may bundle Skills that are automatically available when the plugin is installed. These Skills work the same way as personal and project Skills. + +## Write SKILL.md + +Create a `SKILL.md` file with YAML frontmatter and Markdown content: + +```yaml theme={null} +--- +name: your-skill-name +description: Brief description of what this Skill does and when to use it +--- + +# Your Skill Name + +## Instructions +Provide clear, step-by-step guidance for Claude. + +## Examples +Show concrete examples of using this Skill. +``` + +**Field requirements**: + +* `name`: Must use lowercase letters, numbers, and hyphens only (max 64 characters) +* `description`: Brief description of what the Skill does and when to use it (max 1024 characters) + +The `description` field is critical for Claude to discover when to use your Skill. It should include both what the Skill does and when Claude should use it. + +See the [best practices guide](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices) for complete authoring guidance including validation rules. + +## Add supporting files + +Create additional files alongside SKILL.md: + +``` +my-skill/ +├── SKILL.md (required) +├── reference.md (optional documentation) +├── examples.md (optional examples) +├── scripts/ +│ └── helper.py (optional utility) +└── templates/ + └── template.txt (optional template) +``` + +Reference these files from SKILL.md: + +````markdown theme={null} +For advanced usage, see [reference.md](reference.md). + +Run the helper script: +```bash +python scripts/helper.py input.txt +``` +```` + +Claude reads these files only when needed, using progressive disclosure to manage context efficiently. + +## Restrict tool access with allowed-tools + +Use the `allowed-tools` frontmatter field to limit which tools Claude can use when a Skill is active: + +```yaml theme={null} +--- +name: safe-file-reader +description: Read files without making changes. Use when you need read-only file access. +allowed-tools: Read, Grep, Glob +--- + +# Safe File Reader + +This Skill provides read-only file access. + +## Instructions +1. Use Read to view file contents +2. Use Grep to search within files +3. Use Glob to find files by pattern +``` + +When this Skill is active, Claude can only use the specified tools (Read, Grep, Glob) without needing to ask for permission. This is useful for: + +* Read-only Skills that shouldn't modify files +* Skills with limited scope (e.g., only data analysis, no file writing) +* Security-sensitive workflows where you want to restrict capabilities + +If `allowed-tools` is not specified, Claude will ask for permission to use tools as normal, following the standard permission model. + + + `allowed-tools` is only supported for Skills in Claude Code. + + +## View available Skills + +Skills are automatically discovered by Claude from three sources: + +* Personal Skills: `~/.claude/skills/` +* Project Skills: `.claude/skills/` +* Plugin Skills: bundled with installed plugins + +**To view all available Skills**, ask Claude directly: + +``` +What Skills are available? +``` + +or + +``` +List all available Skills +``` + +This will show all Skills from all sources, including plugin Skills. + +**To inspect a specific Skill**, you can also check the filesystem: + +```bash theme={null} +# List personal Skills +ls ~/.claude/skills/ + +# List project Skills (if in a project directory) +ls .claude/skills/ + +# View a specific Skill's content +cat ~/.claude/skills/my-skill/SKILL.md +``` + +## Test a Skill + +After creating a Skill, test it by asking questions that match your description. + +**Example**: If your description mentions "PDF files": + +``` +Can you help me extract text from this PDF? +``` + +Claude autonomously decides to use your Skill if it matches the request—you don't need to explicitly invoke it. The Skill activates automatically based on the context of your question. + +## Debug a Skill + +If Claude doesn't use your Skill, check these common issues: + +### Make description specific + +**Too vague**: + +```yaml theme={null} +description: Helps with documents +``` + +**Specific**: + +```yaml theme={null} +description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction. +``` + +Include both what the Skill does and when to use it in the description. + +### Verify file path + +**Personal Skills**: `~/.claude/skills/skill-name/SKILL.md` +**Project Skills**: `.claude/skills/skill-name/SKILL.md` + +Check the file exists: + +```bash theme={null} +# Personal +ls ~/.claude/skills/my-skill/SKILL.md + +# Project +ls .claude/skills/my-skill/SKILL.md +``` + +### Check YAML syntax + +Invalid YAML prevents the Skill from loading. Verify the frontmatter: + +```bash theme={null} +cat SKILL.md | head -n 10 +``` + +Ensure: + +* Opening `---` on line 1 +* Closing `---` before Markdown content +* Valid YAML syntax (no tabs, correct indentation) + +### View errors + +Run Claude Code with debug mode to see Skill loading errors: + +```bash theme={null} +claude --debug +``` + +## Share Skills with your team + +**Recommended approach**: Distribute Skills through [plugins](/en/plugins). + +To share Skills via plugin: + +1. Create a plugin with Skills in the `skills/` directory +2. Add the plugin to a marketplace +3. Team members install the plugin + +For complete instructions, see [Add Skills to your plugin](/en/plugins#add-skills-to-your-plugin). + +You can also share Skills directly through project repositories: + +### Step 1: Add Skill to your project + +Create a project Skill: + +```bash theme={null} +mkdir -p .claude/skills/team-skill +# Create SKILL.md +``` + +### Step 2: Commit to git + +```bash theme={null} +git add .claude/skills/ +git commit -m "Add team Skill for PDF processing" +git push +``` + +### Step 3: Team members get Skills automatically + +When team members pull the latest changes, Skills are immediately available: + +```bash theme={null} +git pull +claude # Skills are now available +``` + +## Update a Skill + +Edit SKILL.md directly: + +```bash theme={null} +# Personal Skill +code ~/.claude/skills/my-skill/SKILL.md + +# Project Skill +code .claude/skills/my-skill/SKILL.md +``` + +Changes take effect the next time you start Claude Code. If Claude Code is already running, restart it to load the updates. + +## Remove a Skill + +Delete the Skill directory: + +```bash theme={null} +# Personal +rm -rf ~/.claude/skills/my-skill + +# Project +rm -rf .claude/skills/my-skill +git commit -m "Remove unused Skill" +``` + +## Best practices + +### Keep Skills focused + +One Skill should address one capability: + +**Focused**: + +* "PDF form filling" +* "Excel data analysis" +* "Git commit messages" + +**Too broad**: + +* "Document processing" (split into separate Skills) +* "Data tools" (split by data type or operation) + +### Write clear descriptions + +Help Claude discover when to use Skills by including specific triggers in your description: + +**Clear**: + +```yaml theme={null} +description: Analyze Excel spreadsheets, create pivot tables, and generate charts. Use when working with Excel files, spreadsheets, or analyzing tabular data in .xlsx format. +``` + +**Vague**: + +```yaml theme={null} +description: For files +``` + +### Test with your team + +Have teammates use Skills and provide feedback: + +* Does the Skill activate when expected? +* Are the instructions clear? +* Are there missing examples or edge cases? + +### Document Skill versions + +You can document Skill versions in your SKILL.md content to track changes over time. Add a version history section: + +```markdown theme={null} +# My Skill + +## Version History +- v2.0.0 (2025-10-01): Breaking changes to API +- v1.1.0 (2025-09-15): Added new features +- v1.0.0 (2025-09-01): Initial release +``` + +This helps team members understand what changed between versions. + +## Troubleshooting + +### Claude doesn't use my Skill + +**Symptom**: You ask a relevant question but Claude doesn't use your Skill. + +**Check**: Is the description specific enough? + +Vague descriptions make discovery difficult. Include both what the Skill does and when to use it, with key terms users would mention. + +**Too generic**: + +```yaml theme={null} +description: Helps with data +``` + +**Specific**: + +```yaml theme={null} +description: Analyze Excel spreadsheets, generate pivot tables, create charts. Use when working with Excel files, spreadsheets, or .xlsx files. +``` + +**Check**: Is the YAML valid? + +Run validation to check for syntax errors: + +```bash theme={null} +# View frontmatter +cat .claude/skills/my-skill/SKILL.md | head -n 15 + +# Check for common issues +# - Missing opening or closing --- +# - Tabs instead of spaces +# - Unquoted strings with special characters +``` + +**Check**: Is the Skill in the correct location? + +```bash theme={null} +# Personal Skills +ls ~/.claude/skills/*/SKILL.md + +# Project Skills +ls .claude/skills/*/SKILL.md +``` + +### Skill has errors + +**Symptom**: The Skill loads but doesn't work correctly. + +**Check**: Are dependencies available? + +Claude will automatically install required dependencies (or ask for permission to install them) when it needs them. + +**Check**: Do scripts have execute permissions? + +```bash theme={null} +chmod +x .claude/skills/my-skill/scripts/*.py +``` + +**Check**: Are file paths correct? + +Use forward slashes (Unix style) in all paths: + +**Correct**: `scripts/helper.py` +**Wrong**: `scripts\helper.py` (Windows style) + +### Multiple Skills conflict + +**Symptom**: Claude uses the wrong Skill or seems confused between similar Skills. + +**Be specific in descriptions**: Help Claude choose the right Skill by using distinct trigger terms in your descriptions. + +Instead of: + +```yaml theme={null} +# Skill 1 +description: For data analysis + +# Skill 2 +description: For analyzing data +``` + +Use: + +```yaml theme={null} +# Skill 1 +description: Analyze sales data in Excel files and CRM exports. Use for sales reports, pipeline analysis, and revenue tracking. + +# Skill 2 +description: Analyze log files and system metrics data. Use for performance monitoring, debugging, and system diagnostics. +``` + +## Examples + +### Simple Skill (single file) + +``` +commit-helper/ +└── SKILL.md +``` + +```yaml theme={null} +--- +name: generating-commit-messages +description: Generates clear commit messages from git diffs. Use when writing commit messages or reviewing staged changes. +--- + +# Generating Commit Messages + +## Instructions + +1. Run `git diff --staged` to see changes +2. I'll suggest a commit message with: + - Summary under 50 characters + - Detailed description + - Affected components + +## Best practices + +- Use present tense +- Explain what and why, not how +``` + +### Skill with tool permissions + +``` +code-reviewer/ +└── SKILL.md +``` + +```yaml theme={null} +--- +name: code-reviewer +description: Review code for best practices and potential issues. Use when reviewing code, checking PRs, or analyzing code quality. +allowed-tools: Read, Grep, Glob +--- + +# Code Reviewer + +## Review checklist + +1. Code organization and structure +2. Error handling +3. Performance considerations +4. Security concerns +5. Test coverage + +## Instructions + +1. Read the target files using Read tool +2. Search for patterns using Grep +3. Find related files using Glob +4. Provide detailed feedback on code quality +``` + +### Multi-file Skill + +``` +pdf-processing/ +├── SKILL.md +├── FORMS.md +├── REFERENCE.md +└── scripts/ + ├── fill_form.py + └── validate.py +``` + +**SKILL.md**: + +````yaml theme={null} +--- +name: pdf-processing +description: Extract text, fill forms, merge PDFs. Use when working with PDF files, forms, or document extraction. Requires pypdf and pdfplumber packages. +--- + +# PDF Processing + +## Quick start + +Extract text: +```python +import pdfplumber +with pdfplumber.open("doc.pdf") as pdf: + text = pdf.pages[0].extract_text() +``` + +For form filling, see [FORMS.md](FORMS.md). +For detailed API reference, see [REFERENCE.md](REFERENCE.md). + +## Requirements + +Packages must be installed in your environment: +```bash +pip install pypdf pdfplumber +``` +```` + + + List required packages in the description. Packages must be installed in your environment before Claude can use them. + + +Claude loads additional files only when needed. + +## Next steps + + + + Write Skills that Claude can use effectively + + + + Learn how Skills work across Claude products + + + + Use Skills programmatically with TypeScript and Python + + + + Create your first Skill + + diff --git a/docs/usage/getting-started.mdx b/docs/usage/getting-started.mdx index 20b516ea..a91ab7d3 100644 --- a/docs/usage/getting-started.mdx +++ b/docs/usage/getting-started.mdx @@ -54,10 +54,21 @@ When Claude finishes responding (triggering the Stop hook), a summary is automat When you start a new Claude Code session, the SessionStart hook: -1. Queries the database for recent sessions in your project -2. Retrieves the last 10 session summaries -3. Formats them with three-tier verbosity (most recent = most detail) -4. Injects them into Claude's initial context +1. Queries the database for recent observations in your project (default: 50) +2. Retrieves recent session summaries for context +3. Displays observations in a chronological timeline with session markers +4. Shows full summary details (Investigated, Learned, Completed, Next Steps) **only if the summary was generated after the last observation** +5. Injects formatted context into Claude's initial context + +**Summary Display Logic:** + +The most recent summary's full details appear at the end of the context display **only when** the summary was generated after the most recent observation. This ensures you see summary details when they represent the latest state of your project, but not when new observations have been captured since the last summary. + +For example: +- ✅ **Shows summary**: Last observation at 2:00 PM, summary generated at 2:05 PM → Summary details appear +- ❌ **Hides summary**: Summary generated at 2:00 PM, new observation at 2:05 PM → Summary details hidden (outdated) + +This prevents showing stale summaries when new work has been captured but not yet summarized. This means Claude "remembers" what happened in previous sessions! @@ -138,26 +149,29 @@ FROM observations WHERE session_id = 'YOUR_SESSION_ID'; ``` -## Understanding Verbosity Levels +## Understanding Progressive Disclosure -Context injection uses three-tier verbosity for efficient token usage: +Context injection uses progressive disclosure for efficient token usage: -### Tier 1 (Most Recent Session) -- Full summary with all details -- Request, investigated, learned, completed, next_steps, notes -- ~500-1000 tokens +### Layer 1: Index Display (Session Start) +- Shows observation titles with token cost estimates +- Displays session markers in chronological timeline +- Groups observations by file for visual clarity +- Shows full summary details **only if** generated after last observation +- Token cost: ~50-200 tokens for index view -### Tier 2 (Sessions 2-5) -- Medium detail -- Request, learned, completed -- ~200-400 tokens +### Layer 2: On-Demand Details (MCP Search) +- Fetch full observation narratives when needed +- Search by concept, file, type, or keyword +- Timeline context around specific observations +- Token cost: ~100-500 tokens per observation fetched -### Tier 3 (Sessions 6-10) -- Brief summary -- Request and completed only -- ~100-200 tokens +### Layer 3: Perfect Recall (Code Access) +- Read source files directly when needed +- Access original transcripts and raw data +- Full context available on-demand -This ensures you get maximum detail for recent work while still having context from older sessions. +This ensures efficient token usage while maintaining access to complete history when needed. ## Multi-Prompt Sessions & `/clear` Behavior diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index bb8ea051..fa0152c8 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import x from"path";import{homedir as se}from"os";import{existsSync as te,readFileSync as re}from"fs";import{stdin as F}from"process";import ee from"better-sqlite3";import{join as b,dirname as J,basename as Te}from"path";import{homedir as j}from"os";import{existsSync as Se,mkdirSync as Q}from"fs";import{fileURLToPath as z}from"url";function Z(){return typeof __dirname<"u"?__dirname:J(z(import.meta.url))}var fe=Z(),N=process.env.CLAUDE_MEM_DATA_DIR||b(j(),".claude-mem"),$=process.env.CLAUDE_CONFIG_DIR||b(j(),".claude"),Ne=b(N,"archives"),Oe=b(N,"logs"),Ie=b(N,"trash"),Le=b(N,"backups"),ye=b(N,"settings.json"),P=b(N,"claude-mem.db"),Ae=b(N,"vector-db"),ve=b($,"settings.json"),Ce=b($,"commands"),De=b($,"CLAUDE.md");function H(c){Q(c,{recursive:!0})}var M=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(M||{}),w=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=M[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e0&&(O=` {${Object.entries(a).map(([T,m])=>`${T}=${m}`).join(", ")}}`)}let L=`[${d}] [${p}] [${_}] ${E}${t}${O}${n}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},G=new w;var C=class{db;constructor(){H(N),this.db=new ee(P),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(` +import x from"path";import{homedir as re}from"os";import{existsSync as ne,readFileSync as ie}from"fs";import{stdin as X}from"process";import te from"better-sqlite3";import{join as b,dirname as z,basename as he}from"path";import{homedir as H}from"os";import{existsSync as fe,mkdirSync as Z}from"fs";import{fileURLToPath as ee}from"url";function se(){return typeof __dirname<"u"?__dirname:z(ee(import.meta.url))}var Oe=se(),N=process.env.CLAUDE_MEM_DATA_DIR||b(H(),".claude-mem"),M=process.env.CLAUDE_CONFIG_DIR||b(H(),".claude"),Ie=b(N,"archives"),Le=b(N,"logs"),ye=b(N,"trash"),Ae=b(N,"backups"),ve=b(N,"settings.json"),G=b(N,"claude-mem.db"),Ce=b(N,"vector-db"),De=b(M,"settings.json"),xe=b(M,"commands"),ke=b(M,"CLAUDE.md");function W(c){Z(c,{recursive:!0})}var w=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(w||{}),F=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=w[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e0&&(O=` {${Object.entries(a).map(([B,u])=>`${B}=${u}`).join(", ")}}`)}let L=`[${d}] [${p}] [${_}] ${E}${t}${O}${n}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},Y=new F;var C=class{db;constructor(){W(N),this.db=new te(G),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -299,7 +299,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL - `).run(s,e).changes===0?(G.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` + `).run(s,e).changes===0?(Y.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` UPDATE sdk_sessions SET worker_port = ? WHERE id = ? @@ -369,7 +369,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE id >= ? ${d} ORDER BY id ASC LIMIT ? - `;try{let l=this.db.prepare(S).all(e,...p,t+1),a=this.db.prepare(R).all(e,...p,r+1);if(l.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary observations:",l.message),{observations:[],sessions:[],prompts:[]}}}else{let S=` + `;try{let m=this.db.prepare(S).all(e,...p,t+1),a=this.db.prepare(R).all(e,...p,r+1);if(m.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=m.length>0?m[m.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(m){return console.error("[SessionStore] Error getting boundary observations:",m.message),{observations:[],sessions:[],prompts:[]}}}else{let S=` SELECT created_at_epoch FROM observations WHERE created_at_epoch <= ? ${d} @@ -381,7 +381,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE created_at_epoch >= ? ${d} ORDER BY created_at_epoch ASC LIMIT ? - `;try{let l=this.db.prepare(S).all(s,...p,t),a=this.db.prepare(R).all(s,...p,r+1);if(l.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary timestamps:",l.message),{observations:[],sessions:[],prompts:[]}}}let n=` + `;try{let m=this.db.prepare(S).all(s,...p,t),a=this.db.prepare(R).all(s,...p,r+1);if(m.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=m.length>0?m[m.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(m){return console.error("[SessionStore] Error getting boundary timestamps:",m.message),{observations:[],sessions:[],prompts:[]}}}let n=` SELECT * FROM observations WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${d} @@ -397,7 +397,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${d.replace("project","s.project")} ORDER BY up.created_at_epoch ASC - `;try{let S=this.db.prepare(n).all(_,E,...p),R=this.db.prepare(O).all(_,E,...p),l=this.db.prepare(L).all(_,E,...p);return{observations:S,sessions:R.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:l.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function ne(){try{let c=x.join(se(),".claude","settings.json");if(te(c)){let e=JSON.parse(re(c,"utf-8"));if(e.env?.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let s=parseInt(e.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(!isNaN(s)&&s>0)return s}}}catch{}return parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10)}var ie=ne(),W=10,oe=4,ae=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function de(c){if(!c)return[];try{let e=JSON.parse(c);return Array.isArray(e)?e:[]}catch{return[]}}function ce(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function pe(c){return new Date(c).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function _e(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function ue(c){return c?Math.ceil(c.length/oe):0}function le(c,e){return x.isAbsolute(c)?x.relative(e,c):c}function D(c,e,s,t){return e?t?[`${s}${c}:${o.reset} ${e}`,""]:[`**${c}**: ${e}`,""]:[]}async function Y(c,e=!1){let s=c?.cwd??process.cwd(),t=s?x.basename(s):"unknown-project",r=new C,i=r.db.prepare(` + `;try{let S=this.db.prepare(n).all(_,E,...p),R=this.db.prepare(O).all(_,E,...p),m=this.db.prepare(L).all(_,E,...p);return{observations:S,sessions:R.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:m.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function oe(){try{let c=x.join(re(),".claude","settings.json");if(ne(c)){let e=JSON.parse(ie(c,"utf-8"));if(e.env?.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let s=parseInt(e.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(!isNaN(s)&&s>0)return s}}}catch{}return parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10)}var ae=oe(),V=10,de=4,ce=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function pe(c){if(!c)return[];try{let e=JSON.parse(c);return Array.isArray(e)?e:[]}catch{return[]}}function _e(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function ue(c){return new Date(c).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function me(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function le(c){return c?Math.ceil(c.length/de):0}function Ee(c,e){return x.isAbsolute(c)?x.relative(e,c):c}function D(c,e,s,t){return e?t?[`${s}${c}:${o.reset} ${e}`,""]:[`**${c}**: ${e}`,""]:[]}async function q(c,e=!1){let s=c?.cwd??process.cwd(),t=s?x.basename(s):"unknown-project",r=new C,i=r.db.prepare(` SELECT id, sdk_session_id, type, title, subtitle, narrative, facts, concepts, files_read, files_modified, @@ -406,18 +406,18 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(t,ie),d=r.db.prepare(` + `).all(t,ae),d=r.db.prepare(` SELECT id, sdk_session_id, request, investigated, learned, completed, next_steps, created_at, created_at_epoch FROM session_summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(t,W+ae);if(i.length===0&&d.length===0)return r.close(),e?` + `).all(t,V+ce);if(i.length===0&&d.length===0)return r.close(),e?` ${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset} ${o.gray}${"\u2500".repeat(60)}${o.reset} ${o.dim}No previous sessions found for this project yet.${o.reset} `:`# [${t}] recent context -No previous sessions found for this project yet.`;let p=i,_=d.slice(0,W),E=p,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${t}] recent context`),n.push("")),E.length>0){e?(n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision${o.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),n.push("")),e?(n.push(`${o.dim}\u{1F4A1} Progressive Disclosure: This index shows WHAT exists (titles) and retrieval COST (token counts).${o.reset}`),n.push(`${o.dim} \u2192 Use MCP search tools to fetch full observation details on-demand (Layer 2)${o.reset}`),n.push(`${o.dim} \u2192 Prefer searching observations over re-reading code for past decisions and learnings${o.reset}`),n.push(`${o.dim} \u2192 Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately${o.reset}`),n.push("")):(n.push("\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists (titles) and retrieval COST (token counts)."),n.push("- Use MCP search tools to fetch full observation details on-demand (Layer 2)"),n.push("- Prefer searching observations over re-reading code for past decisions and learnings"),n.push("- Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately"),n.push(""));let O=d[0]?.id,L=_.map((u,T)=>{let m=T===0?null:d[T+1];return{...u,displayEpoch:m?m.created_at_epoch:u.created_at_epoch,displayTime:m?m.created_at:u.created_at,shouldShowLink:u.id!==O}}),S=[...E.map(u=>({type:"observation",data:u})),...L.map(u=>({type:"summary",data:u}))];S.sort((u,T)=>{let m=u.type==="observation"?u.data.created_at_epoch:u.data.displayEpoch,I=T.type==="observation"?T.data.created_at_epoch:T.data.displayEpoch;return m-I});let R=new Map;for(let u of S){let T=u.type==="observation"?u.data.created_at:u.data.displayTime,m=_e(T);R.has(m)||R.set(m,[]),R.get(m).push(u)}let l=Array.from(R.entries()).sort((u,T)=>{let m=new Date(u[0]).getTime(),I=new Date(T[0]).getTime();return m-I});for(let[u,T]of l){e?(n.push(`${o.bright}${o.cyan}${u}${o.reset}`),n.push("")):(n.push(`### ${u}`),n.push(""));let m=null,I="",y=!1;for(let k of T)if(k.type==="summary"){y&&(n.push(""),y=!1,m=null,I="");let g=k.data,A=`${g.request||"Session started"} (${ce(g.displayTime)})`,f=g.shouldShowLink?`claude-mem://session-summary/${g.id}`:"";if(e){let h=f?`${o.dim}[${f}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${g.id}${o.reset} ${A} ${h}`)}else{let h=f?` [\u2192](${f})`:"";n.push(`**\u{1F3AF} #S${g.id}** ${A}${h}`)}n.push("")}else{let g=k.data,A=de(g.files_modified),f=A.length>0?le(A[0],s):"General";f!==m&&(y&&n.push(""),e?n.push(`${o.dim}${f}${o.reset}`):n.push(`**${f}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|")),m=f,y=!0,I="");let h="\u2022";switch(g.type){case"bugfix":h="\u{1F534}";break;case"feature":h="\u{1F7E3}";break;case"refactor":h="\u{1F504}";break;case"change":h="\u2705";break;case"discovery":h="\u{1F535}";break;case"decision":h="\u{1F9E0}";break;default:h="\u2022"}let v=pe(g.created_at),X=g.title||"Untitled",U=ue(g.narrative),B=v!==I,V=B?v:"";if(I=v,e){let q=B?`${o.dim}${v}${o.reset}`:" ".repeat(v.length),K=U>0?`${o.dim}(~${U}t)${o.reset}`:"";n.push(` ${o.dim}#${g.id}${o.reset} ${q} ${h} ${X} ${K}`)}else n.push(`| #${g.id} | ${V||"\u2033"} | ${h} | ${X} | ~${U} |`)}y&&n.push("")}let a=d[0];a&&(a.investigated||a.learned||a.completed||a.next_steps)&&(n.push(...D("Investigated",a.investigated,o.blue,e)),n.push(...D("Learned",a.learned,o.yellow,e)),n.push(...D("Completed",a.completed,o.green,e)),n.push(...D("Next Steps",a.next_steps,o.magenta,e))),e?n.push(`${o.dim}Use claude-mem MCP search to access records with the given ID${o.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*")}return r.close(),n.join(` -`).trimEnd()}var me=process.argv.includes("--colors");if(F.isTTY||me)Y(void 0,!0).then(c=>{console.log(c),process.exit(0)});else{let c="";F.on("data",e=>c+=e),F.on("end",async()=>{let e=c.trim()?JSON.parse(c):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await Y(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})} +No previous sessions found for this project yet.`;let p=i,_=d.slice(0,V),E=p,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${t}] recent context`),n.push("")),E.length>0){e?(n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision${o.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),n.push("")),e?(n.push(`${o.dim}\u{1F4A1} Progressive Disclosure: This index shows WHAT exists (titles) and retrieval COST (token counts).${o.reset}`),n.push(`${o.dim} \u2192 Use MCP search tools to fetch full observation details on-demand (Layer 2)${o.reset}`),n.push(`${o.dim} \u2192 Prefer searching observations over re-reading code for past decisions and learnings${o.reset}`),n.push(`${o.dim} \u2192 Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately${o.reset}`),n.push("")):(n.push("\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists (titles) and retrieval COST (token counts)."),n.push("- Use MCP search tools to fetch full observation details on-demand (Layer 2)"),n.push("- Prefer searching observations over re-reading code for past decisions and learnings"),n.push("- Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately"),n.push(""));let O=d[0]?.id,L=_.map((u,h)=>{let l=h===0?null:d[h+1];return{...u,displayEpoch:l?l.created_at_epoch:u.created_at_epoch,displayTime:l?l.created_at:u.created_at,shouldShowLink:u.id!==O}}),S=[...E.map(u=>({type:"observation",data:u})),...L.map(u=>({type:"summary",data:u}))];S.sort((u,h)=>{let l=u.type==="observation"?u.data.created_at_epoch:u.data.displayEpoch,I=h.type==="observation"?h.data.created_at_epoch:h.data.displayEpoch;return l-I});let R=new Map;for(let u of S){let h=u.type==="observation"?u.data.created_at:u.data.displayTime,l=me(h);R.has(l)||R.set(l,[]),R.get(l).push(u)}let m=Array.from(R.entries()).sort((u,h)=>{let l=new Date(u[0]).getTime(),I=new Date(h[0]).getTime();return l-I});for(let[u,h]of m){e?(n.push(`${o.bright}${o.cyan}${u}${o.reset}`),n.push("")):(n.push(`### ${u}`),n.push(""));let l=null,I="",y=!1;for(let U of h)if(U.type==="summary"){y&&(n.push(""),y=!1,l=null,I="");let T=U.data,A=`${T.request||"Session started"} (${_e(T.displayTime)})`,f=T.shouldShowLink?`claude-mem://session-summary/${T.id}`:"";if(e){let g=f?`${o.dim}[${f}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${T.id}${o.reset} ${A} ${g}`)}else{let g=f?` [\u2192](${f})`:"";n.push(`**\u{1F3AF} #S${T.id}** ${A}${g}`)}n.push("")}else{let T=U.data,A=pe(T.files_modified),f=A.length>0?Ee(A[0],s):"General";f!==l&&(y&&n.push(""),e?n.push(`${o.dim}${f}${o.reset}`):n.push(`**${f}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|")),l=f,y=!0,I="");let g="\u2022";switch(T.type){case"bugfix":g="\u{1F534}";break;case"feature":g="\u{1F7E3}";break;case"refactor":g="\u{1F504}";break;case"change":g="\u2705";break;case"discovery":g="\u{1F535}";break;case"decision":g="\u{1F9E0}";break;default:g="\u2022"}let v=ue(T.created_at),j=T.title||"Untitled",$=le(T.narrative),P=v!==I,K=P?v:"";if(I=v,e){let J=P?`${o.dim}${v}${o.reset}`:" ".repeat(v.length),Q=$>0?`${o.dim}(~${$}t)${o.reset}`:"";n.push(` ${o.dim}#${T.id}${o.reset} ${J} ${g} ${j} ${Q}`)}else n.push(`| #${T.id} | ${K||"\u2033"} | ${g} | ${j} | ~${$} |`)}y&&n.push("")}let a=d[0],k=p[0];a&&(a.investigated||a.learned||a.completed||a.next_steps)&&(!k||a.created_at_epoch>k.created_at_epoch)&&(n.push(...D("Investigated",a.investigated,o.blue,e)),n.push(...D("Learned",a.learned,o.yellow,e)),n.push(...D("Completed",a.completed,o.green,e)),n.push(...D("Next Steps",a.next_steps,o.magenta,e))),e?n.push(`${o.dim}Use claude-mem MCP search to access records with the given ID${o.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*")}return r.close(),n.join(` +`).trimEnd()}var Te=process.argv.includes("--colors");if(X.isTTY||Te)q(void 0,!0).then(c=>{console.log(c),process.exit(0)});else{let c="";X.on("data",e=>c+=e),X.on("end",async()=>{let e=c.trim()?JSON.parse(c):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await q(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})} diff --git a/src/hooks/context-hook.ts b/src/hooks/context-hook.ts index 89db5b95..fb791fee 100644 --- a/src/hooks/context-hook.ts +++ b/src/hooks/context-hook.ts @@ -441,8 +441,15 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false } // Add full summary details for most recent session + // Only show if summary was generated AFTER the last observation const mostRecentSummary = recentSummaries[0]; - if (mostRecentSummary && (mostRecentSummary.investigated || mostRecentSummary.learned || mostRecentSummary.completed || mostRecentSummary.next_steps)) { + const mostRecentObservation = observations[0]; // observations are DESC by created_at_epoch + + const shouldShowSummary = mostRecentSummary && + (mostRecentSummary.investigated || mostRecentSummary.learned || mostRecentSummary.completed || mostRecentSummary.next_steps) && + (!mostRecentObservation || mostRecentSummary.created_at_epoch > mostRecentObservation.created_at_epoch); + + if (shouldShowSummary) { output.push(...renderSummaryField('Investigated', mostRecentSummary.investigated, colors.blue, useColors)); output.push(...renderSummaryField('Learned', mostRecentSummary.learned, colors.yellow, useColors)); output.push(...renderSummaryField('Completed', mostRecentSummary.completed, colors.green, useColors));