11 Commits

Author SHA1 Message Date
jxtan
b2285e870a docs: Add Skills Directory link to zh-CN and zh-TW README (#206)
* Update links and add skills directory in README

* Add skills directory link to README in Chinese
2026-02-12 00:03:20 -08:00
zdoc.app
daff6c7445 Revert "Revert "fix: correct markdown code block syntax in go-test.md"" (#201) 2026-02-12 00:03:17 -08:00
Francis Behnen
c95ac2c7c3 Refer in README to install.sh for installing rules instead of instructutions for manual (#196) 2026-02-12 00:02:38 -08:00
Affaan Mustafa
75ab8e6194 fix: eliminate child process spawns during session startup (#162)
getAvailablePackageManagers() spawned where.exe/which for each package
manager (npm, pnpm, yarn, bun). During SessionStart hooks, these 4+
child processes combined with Bun's own initialization exceeded the
spawn limit on Windows, freezing the terminal.

Fix: Remove process spawning from the hot path. Steps 1-5 of detection
(env var, project config, package.json, lock file, global config) already
cover all file-based detection. If none match, default to npm without
spawning. Also fix getSelectionPrompt() to list supported PMs without
checking availability.
2026-02-12 00:01:23 -08:00
Affaan Mustafa
0f1597dccf ci: trigger AgentShield action with fail-on-findings fix 2026-02-11 23:59:41 -08:00
Affaan Mustafa
422467dbe0 fix(sessions): also fix require() paths in Cursor and zh-CN sessions commands
Same fix as the main sessions.md — use CLAUDE_PLUGIN_ROOT with
~/.claude/ fallback instead of relative paths.
2026-02-11 23:57:50 -08:00
Affaan Mustafa
87d19f97a6 fix(sessions): make session hooks actually persist and load context (#187)
session-end.js: Extract meaningful summaries from CLAUDE_TRANSCRIPT_PATH
instead of writing blank template files. Pulls user messages, tools used,
and files modified from the session transcript JSONL.

session-start.js: Output the latest session summary to stdout (via the
output() helper) so it gets injected into Claude's conversation context,
instead of only logging to stderr which just shows briefly in the terminal.
2026-02-11 23:56:41 -08:00
Affaan Mustafa
5febfc84f5 fix(sessions): resolve require() paths for plugin installations (#200)
Replace relative require('./scripts/lib/...') with dynamic path resolution
using CLAUDE_PLUGIN_ROOT env var (set by Claude Code for plugins) with
fallback to ~/.claude/ for manual installations. This fixes /sessions
command failing when ECC is installed as a plugin.
2026-02-11 23:54:38 -08:00
Affaan Mustafa
9406ffbfc9 ci: trigger action with all dist files 2026-02-11 23:52:13 -08:00
Affaan Mustafa
fb449eae0d ci: trigger AgentShield action with updated v1 tag 2026-02-11 23:50:55 -08:00
Affaan Mustafa
e41ee0c858 fix: resolve multiple reported issues (#205, #182, #188, #172, #173) (#207)
* fix: resolve multiple reported issues (#205, #182, #188, #172, #173)

- fix(observe.sh): replace triple-quote JSON parsing with stdin pipe to
  prevent ~49% parse failures on payloads with quotes/backslashes/unicode
- fix(hooks.json): correct matcher syntax to use simple tool name regexes
  instead of unsupported logical expressions; move command/path filtering
  into hook scripts; use exit code 2 for blocking hooks
- fix(skills): quote YAML descriptions containing colons in 3 skill files
  and add missing frontmatter to 2 skill files for Codex CLI compatibility
- feat(rules): add paths: filters to all 15 language-specific rule files
  so they only load when working on matching file types
- fix(agents): align model fields with CONTRIBUTING.md recommendations
  (opus for planner/architect, sonnet for reviewers/workers, haiku for
  doc-updater)

* ci: use AgentShield GitHub Action instead of npx

Switch from npx ecc-agentshield to uses: affaan-m/agentshield@v1
for proper GitHub Action demo and marketplace visibility.
2026-02-11 23:48:45 -08:00
44 changed files with 361 additions and 158 deletions

View File

@@ -23,8 +23,8 @@ Display all sessions with metadata, filtering, and pagination.
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const result = sm.getAllSessions({ limit: 20 });
const aliases = aa.listAliases();
@@ -62,8 +62,8 @@ Load and display a session's content (by ID or alias).
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const id = process.argv[1];
// First try to resolve as alias
@@ -123,8 +123,8 @@ Create a memorable alias for a session.
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const sessionId = process.argv[1];
const aliasName = process.argv[2];
@@ -163,7 +163,7 @@ Delete an existing alias.
**Script:**
```bash
node -e "
const aa = require('./scripts/lib/session-aliases');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const aliasName = process.argv[1];
if (!aliasName) {
@@ -192,8 +192,8 @@ Show detailed information about a session.
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const id = process.argv[1];
const resolved = aa.resolveAlias(id);
@@ -239,7 +239,7 @@ Show all session aliases.
**Script:**
```bash
node -e "
const aa = require('./scripts/lib/session-aliases');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const aliases = aa.listAliases();
console.log('Session Aliases (' + aliases.length + '):');

View File

@@ -25,11 +25,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Run AgentShield Security Scan
uses: affaan-m/agentshield@v1
with:
node-version: '20.x'
- name: Run AgentShield security scan
run: npx ecc-agentshield scan --path . --min-severity medium --format terminal
continue-on-error: true # Informational only — ECC contains intentional config examples
path: '.'
min-severity: 'medium'
format: 'terminal'
fail-on-findings: 'false'

View File

@@ -117,19 +117,22 @@ Get up and running in under 2 minutes:
> ⚠️ **Important:** Claude Code plugins cannot distribute `rules` automatically. Install them manually:
```bash
# Clone the repo first
git clone https://github.com/affaan-m/everything-claude-code.git
cd everything-claude-code
# Install common rules (required)
cp -r everything-claude-code/rules/common/* ~/.claude/rules/
# Install language-specific rules (pick your stack)
cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/
cp -r everything-claude-code/rules/python/* ~/.claude/rules/
cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
# Recommended: use the installer (handles common + language rules safely)
./install.sh typescript # or python or golang
# You can pass multiple languages:
# ./install.sh typescript python golang
# or target cursor:
# ./install.sh --target cursor typescript
```
For manual install instructions see the README in the `rules/` folder.
### Step 3: Start Using
```bash

View File

@@ -512,6 +512,7 @@ node tests/hooks/hooks.test.js
- **详细指南(高级):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
- **关注:** [@affaanmustafa](https://x.com/affaanmustafa)
- **zenith.chat:** [zenith.chat](https://zenith.chat)
- **技能目录:** [awesome-agent-skills](https://github.com/JackyST0/awesome-agent-skills)
---

View File

@@ -2,7 +2,7 @@
name: build-error-resolver
description: Build and TypeScript error resolution specialist. Use PROACTIVELY when build fails or type errors occur. Fixes build/type errors only with minimal diffs, no architectural edits. Focuses on getting the build green quickly.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: sonnet
---
# Build Error Resolver

View File

@@ -2,7 +2,7 @@
name: code-reviewer
description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code. MUST BE USED for all code changes.
tools: ["Read", "Grep", "Glob", "Bash"]
model: opus
model: sonnet
---
You are a senior code reviewer ensuring high standards of code quality and security.

View File

@@ -2,7 +2,7 @@
name: database-reviewer
description: PostgreSQL database specialist for query optimization, schema design, security, and performance. Use PROACTIVELY when writing SQL, creating migrations, designing schemas, or troubleshooting database performance. Incorporates Supabase best practices.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: sonnet
---
# Database Reviewer

View File

@@ -2,7 +2,7 @@
name: doc-updater
description: Documentation and codemap specialist. Use PROACTIVELY for updating codemaps and documentation. Runs /update-codemaps and /update-docs, generates docs/CODEMAPS/*, updates READMEs and guides.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: haiku
---
# Documentation & Codemap Specialist

View File

@@ -2,7 +2,7 @@
name: e2e-runner
description: End-to-end testing specialist using Vercel Agent Browser (preferred) with Playwright fallback. Use PROACTIVELY for generating, maintaining, and running E2E tests. Manages test journeys, quarantines flaky tests, uploads artifacts (screenshots, videos, traces), and ensures critical user flows work.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: sonnet
---
# E2E Test Runner

View File

@@ -2,7 +2,7 @@
name: go-build-resolver
description: Go build, vet, and compilation error resolution specialist. Fixes build errors, go vet issues, and linter warnings with minimal changes. Use when Go builds fail.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: sonnet
---
# Go Build Error Resolver

View File

@@ -2,7 +2,7 @@
name: go-reviewer
description: Expert Go code reviewer specializing in idiomatic Go, concurrency patterns, error handling, and performance. Use for all Go code changes. MUST BE USED for Go projects.
tools: ["Read", "Grep", "Glob", "Bash"]
model: opus
model: sonnet
---
You are a senior Go code reviewer ensuring high standards of idiomatic Go and best practices.

View File

@@ -2,7 +2,7 @@
name: python-reviewer
description: Expert Python code reviewer specializing in PEP 8 compliance, Pythonic idioms, type hints, security, and performance. Use for all Python code changes. MUST BE USED for Python projects.
tools: ["Read", "Grep", "Glob", "Bash"]
model: opus
model: sonnet
---
You are a senior Python code reviewer ensuring high standards of Pythonic code and best practices.

View File

@@ -2,7 +2,7 @@
name: refactor-cleaner
description: Dead code cleanup and consolidation specialist. Use PROACTIVELY for removing unused code, duplicates, and refactoring. Runs analysis tools (knip, depcheck, ts-prune) to identify dead code and safely removes it.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: sonnet
---
# Refactor & Dead Code Cleaner

View File

@@ -2,7 +2,7 @@
name: security-reviewer
description: Security vulnerability detection and remediation specialist. Use PROACTIVELY after writing code that handles user input, authentication, API endpoints, or sensitive data. Flags secrets, SSRF, injection, unsafe crypto, and OWASP Top 10 vulnerabilities.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
model: sonnet
---
# Security Reviewer

View File

@@ -2,7 +2,7 @@
name: tdd-guide
description: Test-Driven Development specialist enforcing write-tests-first methodology. Use PROACTIVELY when writing new features, fixing bugs, or refactoring code. Ensures 80%+ test coverage.
tools: ["Read", "Write", "Edit", "Bash", "Grep"]
model: opus
model: sonnet
---
You are a Test-Driven Development (TDD) specialist who ensures all code is developed test-first with comprehensive coverage.

View File

@@ -35,7 +35,7 @@ REPEAT → Next test case
## Example Session
```text
````
User: /go-test I need a function to validate email addresses
Agent:
@@ -167,7 +167,7 @@ ok project/validator 0.003s
✓ Coverage: 100%
## TDD Complete!
```
````
## Test Patterns

View File

@@ -23,8 +23,8 @@ Display all sessions with metadata, filtering, and pagination.
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const result = sm.getAllSessions({ limit: 20 });
const aliases = aa.listAliases();
@@ -62,8 +62,8 @@ Load and display a session's content (by ID or alias).
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const id = process.argv[1];
// First try to resolve as alias
@@ -123,8 +123,8 @@ Create a memorable alias for a session.
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const sessionId = process.argv[1];
const aliasName = process.argv[2];
@@ -163,7 +163,7 @@ Delete an existing alias.
**Script:**
```bash
node -e "
const aa = require('./scripts/lib/session-aliases');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const aliasName = process.argv[1];
if (!aliasName) {
@@ -192,8 +192,8 @@ Show detailed information about a session.
**Script:**
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const id = process.argv[1];
const resolved = aa.resolveAlias(id);
@@ -239,7 +239,7 @@ Show all session aliases.
**Script:**
```bash
node -e "
const aa = require('./scripts/lib/session-aliases');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const aliases = aa.listAliases();
console.log('Session Aliases (' + aliases.length + '):');

View File

@@ -24,8 +24,8 @@
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const result = sm.getAllSessions({ limit: 20 });
const aliases = aa.listAliases();
@@ -64,8 +64,8 @@ for (const s of result.sessions) {
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const id = process.argv[1];
// First try to resolve as alias
@@ -126,8 +126,8 @@ if (session.metadata.lastUpdated) {
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const sessionId = process.argv[1];
const aliasName = process.argv[2];
@@ -167,7 +167,7 @@ if (result.success) {
```bash
node -e "
const aa = require('./scripts/lib/session-aliases');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const aliasName = process.argv[1];
if (!aliasName) {
@@ -197,8 +197,8 @@ if (result.success) {
```bash
node -e "
const sm = require('./scripts/lib/session-manager');
const aa = require('./scripts/lib/session-aliases');
const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const id = process.argv[1];
const resolved = aa.resolveAlias(id);
@@ -245,7 +245,7 @@ if (aliases.length > 0) {
```bash
node -e "
const aa = require('./scripts/lib/session-aliases');
const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases');
const aliases = aa.listAliases();
console.log('Session Aliases (' + aliases.length + '):');

View File

@@ -464,6 +464,7 @@ node tests/hooks/hooks.test.js
- **完整指南(進階):** [Everything Claude Code 完整指南](https://x.com/affaanmustafa/status/2014040193557471352)
- **追蹤:** [@affaanmustafa](https://x.com/affaanmustafa)
- **zenith.chat** [zenith.chat](https://zenith.chat)
- **技能目錄:** [awesome-agent-skills](https://github.com/JackyST0/awesome-agent-skills)
---

View File

@@ -3,47 +3,47 @@
"hooks": {
"PreToolUse": [
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"(npm run dev|pnpm( run)? dev|yarn dev|bun run dev)\"",
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node -e \"console.error('[Hook] BLOCKED: Dev server must run in tmux for log access');console.error('[Hook] Use: tmux new-session -d -s dev \\\"npm run dev\\\"');console.error('[Hook] Then: tmux attach -t dev');process.exit(1)\""
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/(npm run dev|pnpm( run)? dev|yarn dev|bun run dev)/.test(cmd)){console.error('[Hook] BLOCKED: Dev server must run in tmux for log access');console.error('[Hook] Use: tmux new-session -d -s dev \\\"npm run dev\\\"');console.error('[Hook] Then: tmux attach -t dev');process.exit(2)}console.log(d)})\""
}
],
"description": "Block dev servers outside tmux - ensures you can access logs"
},
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make|docker|pytest|vitest|playwright)\"",
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node -e \"if(!process.env.TMUX){console.error('[Hook] Consider running in tmux for session persistence');console.error('[Hook] tmux new -s dev | tmux attach -t dev')}\""
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(!process.env.TMUX&&/(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make\\b|docker\\b|pytest|vitest|playwright)/.test(cmd)){console.error('[Hook] Consider running in tmux for session persistence');console.error('[Hook] tmux new -s dev | tmux attach -t dev')}console.log(d)})\""
}
],
"description": "Reminder to use tmux for long-running commands"
},
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"git push\"",
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node -e \"console.error('[Hook] Review changes before push...');console.error('[Hook] Continuing with push (remove this hook to add interactive review)')\""
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/git push/.test(cmd)){console.error('[Hook] Review changes before push...');console.error('[Hook] Continuing with push (remove this hook to add interactive review)')}console.log(d)})\""
}
],
"description": "Reminder before git push to review changes"
},
{
"matcher": "tool == \"Write\" && tool_input.file_path matches \"\\\\.(md|txt)$\" && !(tool_input.file_path matches \"README\\\\.md|CLAUDE\\\\.md|AGENTS\\\\.md|CONTRIBUTING\\\\.md\")",
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.(md|txt)$/.test(p)&&!/(README|CLAUDE|AGENTS|CONTRIBUTING)\\.md$/.test(p)){console.error('[Hook] BLOCKED: Unnecessary documentation file creation');console.error('[Hook] File: '+p);console.error('[Hook] Use README.md for documentation instead');process.exit(1)}console.log(d)})\""
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.(md|txt)$/.test(p)&&!/(README|CLAUDE|AGENTS|CONTRIBUTING)\\.md$/.test(p)){console.error('[Hook] BLOCKED: Unnecessary documentation file creation');console.error('[Hook] File: '+p);console.error('[Hook] Use README.md for documentation instead');process.exit(2)}console.log(d)})\""
}
],
"description": "Block creation of random .md files - keeps docs consolidated"
},
{
"matcher": "tool == \"Edit\" || tool == \"Write\"",
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
@@ -79,7 +79,7 @@
],
"PostToolUse": [
{
"matcher": "tool == \"Bash\"",
"matcher": "Bash",
"hooks": [
{
"type": "command",
@@ -89,11 +89,11 @@
"description": "Log PR URL and provide review command after PR creation"
},
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"(npm run build|pnpm build|yarn build)\"",
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{console.error('[Hook] Build completed - async analysis running in background');console.log(d)})\"",
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/(npm run build|pnpm build|yarn build)/.test(cmd)){console.error('[Hook] Build completed - async analysis running in background')}console.log(d)})\"",
"async": true,
"timeout": 30
}
@@ -101,31 +101,31 @@
"description": "Example: async hook for build analysis (runs in background without blocking)"
},
{
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.(ts|tsx|js|jsx)$\"",
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "node -e \"const{execFileSync}=require('child_process');const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&fs.existsSync(p)){try{execFileSync('npx',['prettier','--write',p],{stdio:['pipe','pipe','pipe']})}catch(e){}}console.log(d)})\""
"command": "node -e \"const{execFileSync}=require('child_process');const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&/\\.(ts|tsx|js|jsx)$/.test(p)&&fs.existsSync(p)){try{execFileSync('npx',['prettier','--write',p],{stdio:['pipe','pipe','pipe']})}catch(e){}}console.log(d)})\""
}
],
"description": "Auto-format JS/TS files with Prettier after edits"
},
{
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.(ts|tsx)$\"",
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "node -e \"const{execSync}=require('child_process');const fs=require('fs');const path=require('path');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&fs.existsSync(p)){let dir=path.dirname(p);while(dir!==path.dirname(dir)&&!fs.existsSync(path.join(dir,'tsconfig.json'))){dir=path.dirname(dir)}if(fs.existsSync(path.join(dir,'tsconfig.json'))){try{const r=execSync('npx tsc --noEmit --pretty false 2>&1',{cwd:dir,encoding:'utf8',stdio:['pipe','pipe','pipe']});const lines=r.split('\\n').filter(l=>l.includes(p)).slice(0,10);if(lines.length)console.error(lines.join('\\n'))}catch(e){const lines=(e.stdout||'').split('\\n').filter(l=>l.includes(p)).slice(0,10);if(lines.length)console.error(lines.join('\\n'))}}}console.log(d)})\""
"command": "node -e \"const{execFileSync}=require('child_process');const fs=require('fs');const path=require('path');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&/\\.(ts|tsx)$/.test(p)&&fs.existsSync(p)){let dir=path.dirname(p);while(dir!==path.dirname(dir)&&!fs.existsSync(path.join(dir,'tsconfig.json'))){dir=path.dirname(dir)}if(fs.existsSync(path.join(dir,'tsconfig.json'))){try{const r=execFileSync('npx',['tsc','--noEmit','--pretty','false'],{cwd:dir,encoding:'utf8',stdio:['pipe','pipe','pipe']});const lines=r.split('\\n').filter(l=>l.includes(p)).slice(0,10);if(lines.length)console.error(lines.join('\\n'))}catch(e){const lines=(e.stdout||'').split('\\n').filter(l=>l.includes(p)).slice(0,10);if(lines.length)console.error(lines.join('\\n'))}}}console.log(d)})\""
}
],
"description": "TypeScript check after editing .ts/.tsx files"
},
{
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.(ts|tsx|js|jsx)$\"",
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&fs.existsSync(p)){const c=fs.readFileSync(p,'utf8');const lines=c.split('\\n');const matches=[];lines.forEach((l,idx)=>{if(/console\\.log/.test(l))matches.push((idx+1)+': '+l.trim())});if(matches.length){console.error('[Hook] WARNING: console.log found in '+p);matches.slice(0,5).forEach(m=>console.error(m));console.error('[Hook] Remove console.log before committing')}}console.log(d)})\""
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&/\\.(ts|tsx|js|jsx)$/.test(p)&&fs.existsSync(p)){const c=fs.readFileSync(p,'utf8');const lines=c.split('\\n');const matches=[];lines.forEach((l,idx)=>{if(/console\\.log/.test(l))matches.push((idx+1)+': '+l.trim())});if(matches.length){console.error('[Hook] WARNING: console.log found in '+p);matches.slice(0,5).forEach(m=>console.error(m));console.error('[Hook] Remove console.log before committing')}}console.log(d)})\""
}
],
"description": "Warn about console.log statements after edits"

View File

@@ -1,3 +1,9 @@
---
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
---
# Go Coding Style
> This file extends [common/coding-style.md](../common/coding-style.md) with Go specific content.

View File

@@ -1,3 +1,9 @@
---
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
---
# Go Hooks
> This file extends [common/hooks.md](../common/hooks.md) with Go specific content.

View File

@@ -1,3 +1,9 @@
---
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
---
# Go Patterns
> This file extends [common/patterns.md](../common/patterns.md) with Go specific content.

View File

@@ -1,3 +1,9 @@
---
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
---
# Go Security
> This file extends [common/security.md](../common/security.md) with Go specific content.

View File

@@ -1,3 +1,9 @@
---
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
---
# Go Testing
> This file extends [common/testing.md](../common/testing.md) with Go specific content.

View File

@@ -1,3 +1,8 @@
---
paths:
- "**/*.py"
- "**/*.pyi"
---
# Python Coding Style
> This file extends [common/coding-style.md](../common/coding-style.md) with Python specific content.

View File

@@ -1,3 +1,8 @@
---
paths:
- "**/*.py"
- "**/*.pyi"
---
# Python Hooks
> This file extends [common/hooks.md](../common/hooks.md) with Python specific content.

View File

@@ -1,3 +1,8 @@
---
paths:
- "**/*.py"
- "**/*.pyi"
---
# Python Patterns
> This file extends [common/patterns.md](../common/patterns.md) with Python specific content.

View File

@@ -1,3 +1,8 @@
---
paths:
- "**/*.py"
- "**/*.pyi"
---
# Python Security
> This file extends [common/security.md](../common/security.md) with Python specific content.

View File

@@ -1,3 +1,8 @@
---
paths:
- "**/*.py"
- "**/*.pyi"
---
# Python Testing
> This file extends [common/testing.md](../common/testing.md) with Python specific content.

View File

@@ -1,3 +1,10 @@
---
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
---
# TypeScript/JavaScript Coding Style
> This file extends [common/coding-style.md](../common/coding-style.md) with TypeScript/JavaScript specific content.

View File

@@ -1,3 +1,10 @@
---
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
---
# TypeScript/JavaScript Hooks
> This file extends [common/hooks.md](../common/hooks.md) with TypeScript/JavaScript specific content.

View File

@@ -1,3 +1,10 @@
---
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
---
# TypeScript/JavaScript Patterns
> This file extends [common/patterns.md](../common/patterns.md) with TypeScript/JavaScript specific content.

View File

@@ -1,3 +1,10 @@
---
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
---
# TypeScript/JavaScript Security
> This file extends [common/security.md](../common/security.md) with TypeScript/JavaScript specific content.

View File

@@ -1,3 +1,10 @@
---
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
---
# TypeScript/JavaScript Testing
> This file extends [common/testing.md](../common/testing.md) with TypeScript/JavaScript specific content.

View File

@@ -4,8 +4,9 @@
*
* Cross-platform (Windows, macOS, Linux)
*
* Runs when Claude session ends. Creates/updates session log file
* with timestamp for continuity tracking.
* Runs when Claude session ends. Extracts a meaningful summary from
* the session transcript (via CLAUDE_TRANSCRIPT_PATH) and saves it
* to a session file for cross-session continuity.
*/
const path = require('path');
@@ -16,35 +17,114 @@ const {
getTimeString,
getSessionIdShort,
ensureDir,
readFile,
writeFile,
replaceInFile,
log
} = require('../lib/utils');
/**
* Extract a meaningful summary from the session transcript.
* Reads the JSONL transcript and pulls out key information:
* - User messages (tasks requested)
* - Tools used
* - Files modified
*/
function extractSessionSummary(transcriptPath) {
const content = readFile(transcriptPath);
if (!content) return null;
const lines = content.split('\n').filter(Boolean);
const userMessages = [];
const toolsUsed = new Set();
const filesModified = new Set();
for (const line of lines) {
try {
const entry = JSON.parse(line);
// Collect user messages (first 200 chars each)
if (entry.type === 'user' || entry.role === 'user') {
const text = typeof entry.content === 'string'
? entry.content
: Array.isArray(entry.content)
? entry.content.map(c => c.text || '').join(' ')
: '';
if (text.trim()) {
userMessages.push(text.trim().slice(0, 200));
}
}
// Collect tool names and modified files
if (entry.type === 'tool_use' || entry.tool_name) {
const toolName = entry.tool_name || entry.name || '';
if (toolName) toolsUsed.add(toolName);
const filePath = entry.tool_input?.file_path || entry.input?.file_path || '';
if (filePath && (toolName === 'Edit' || toolName === 'Write')) {
filesModified.add(filePath);
}
}
} catch {
// Skip unparseable lines
}
}
if (userMessages.length === 0) return null;
return {
userMessages: userMessages.slice(-10), // Last 10 user messages
toolsUsed: Array.from(toolsUsed).slice(0, 20),
filesModified: Array.from(filesModified).slice(0, 30),
totalMessages: userMessages.length
};
}
async function main() {
const sessionsDir = getSessionsDir();
const today = getDateString();
const shortId = getSessionIdShort();
// Include session ID in filename for unique per-session tracking
const sessionFile = path.join(sessionsDir, `${today}-${shortId}-session.tmp`);
ensureDir(sessionsDir);
const currentTime = getTimeString();
// If session file exists for today, update the end time
// Try to extract summary from transcript
const transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
let summary = null;
if (transcriptPath && fs.existsSync(transcriptPath)) {
summary = extractSessionSummary(transcriptPath);
}
if (fs.existsSync(sessionFile)) {
const success = replaceInFile(
// Update existing session file
replaceInFile(
sessionFile,
/\*\*Last Updated:\*\*.*/,
`**Last Updated:** ${currentTime}`
);
if (success) {
log(`[SessionEnd] Updated session file: ${sessionFile}`);
// If we have a new summary and the file still has the blank template, replace it
if (summary) {
const existing = readFile(sessionFile);
if (existing && existing.includes('[Session context goes here]')) {
const updatedContent = existing.replace(
/## Current State\n\n\[Session context goes here\]\n\n### Completed\n- \[ \]\n\n### In Progress\n- \[ \]\n\n### Notes for Next Session\n-\n\n### Context to Load\n```\n\[relevant files\]\n```/,
buildSummarySection(summary)
);
writeFile(sessionFile, updatedContent);
}
}
log(`[SessionEnd] Updated session file: ${sessionFile}`);
} else {
// Create new session file with template
// Create new session file
const summarySection = summary
? buildSummarySection(summary)
: `## Current State\n\n[Session context goes here]\n\n### Completed\n- [ ]\n\n### In Progress\n- [ ]\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``;
const template = `# Session: ${today}
**Date:** ${today}
**Started:** ${currentTime}
@@ -52,23 +132,7 @@ async function main() {
---
## Current State
[Session context goes here]
### Completed
- [ ]
### In Progress
- [ ]
### Notes for Next Session
-
### Context to Load
\`\`\`
[relevant files]
\`\`\`
${summarySection}
`;
writeFile(sessionFile, template);
@@ -78,6 +142,35 @@ async function main() {
process.exit(0);
}
function buildSummarySection(summary) {
let section = '## Session Summary\n\n';
// Tasks (from user messages)
section += '### Tasks\n';
for (const msg of summary.userMessages) {
section += `- ${msg}\n`;
}
section += '\n';
// Files modified
if (summary.filesModified.length > 0) {
section += '### Files Modified\n';
for (const f of summary.filesModified) {
section += `- ${f}\n`;
}
section += '\n';
}
// Tools used
if (summary.toolsUsed.length > 0) {
section += `### Tools Used\n${summary.toolsUsed.join(', ')}\n\n`;
}
section += `### Stats\n- Total user messages: ${summary.totalMessages}\n`;
return section;
}
main().catch(err => {
console.error('[SessionEnd] Error:', err.message);
process.exit(0);

View File

@@ -4,16 +4,20 @@
*
* Cross-platform (Windows, macOS, Linux)
*
* Runs when a new Claude session starts. Checks for recent session
* files and notifies Claude of available context to load.
* Runs when a new Claude session starts. Loads the most recent session
* summary into Claude's context via stdout, and reports available
* sessions and learned skills.
*/
const fs = require('fs');
const {
getSessionsDir,
getLearnedSkillsDir,
findFiles,
ensureDir,
log
readFile,
log,
output
} = require('../lib/utils');
const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager');
const { listAliases } = require('../lib/session-aliases');
@@ -27,13 +31,19 @@ async function main() {
ensureDir(learnedDir);
// Check for recent session files (last 7 days)
// Match both old format (YYYY-MM-DD-session.tmp) and new format (YYYY-MM-DD-shortid-session.tmp)
const recentSessions = findFiles(sessionsDir, '*-session.tmp', { maxAge: 7 });
if (recentSessions.length > 0) {
const latest = recentSessions[0];
log(`[SessionStart] Found ${recentSessions.length} recent session(s)`);
log(`[SessionStart] Latest: ${latest.path}`);
// Read and inject the latest session content into Claude's context
const content = readFile(latest.path);
if (content && !content.includes('[Session context goes here]')) {
// Only inject if the session has actual content (not the blank template)
output(`Previous session summary:\n${content}`);
}
}
// Check for learned skills
@@ -56,8 +66,8 @@ async function main() {
const pm = getPackageManager();
log(`[SessionStart] Package manager: ${pm.name} (${pm.source})`);
// If package manager was detected via fallback, show selection prompt
if (pm.source === 'fallback' || pm.source === 'default') {
// If no explicit package manager config was found, show selection prompt
if (pm.source === 'default') {
log('[SessionStart] No package manager preference found.');
log(getSelectionPrompt());
}

View File

@@ -127,6 +127,11 @@ function detectFromPackageJson(projectDir = process.cwd()) {
/**
* Get available package managers (installed on system)
*
* WARNING: This spawns child processes (where.exe on Windows, which on Unix)
* for each package manager. Do NOT call this during session startup hooks —
* it can exceed Bun's spawn limit on Windows and freeze the plugin.
* Use detectFromLockFile() or detectFromPackageJson() for hot paths.
*/
function getAvailablePackageManagers() {
const available = [];
@@ -149,7 +154,7 @@ function getAvailablePackageManagers() {
* 3. package.json packageManager field
* 4. Lock file detection
* 5. Global user preference (in ~/.claude/package-manager.json)
* 6. First available package manager (by priority)
* 6. Default to npm (no child processes spawned)
*
* @param {object} options - { projectDir, fallbackOrder }
* @returns {object} - { name, config, source }
@@ -215,19 +220,13 @@ function getPackageManager(options = {}) {
};
}
// 6. Use first available package manager
const available = getAvailablePackageManagers();
for (const pmName of fallbackOrder) {
if (available.includes(pmName)) {
return {
name: pmName,
config: PACKAGE_MANAGERS[pmName],
source: 'fallback'
};
}
}
// Default to npm (always available with Node.js)
// 6. Default to npm (always available with Node.js)
// NOTE: Previously this called getAvailablePackageManagers() which spawns
// child processes (where.exe/which) for each PM. This caused plugin freezes
// on Windows (see #162) because session-start hooks run during Bun init,
// and the spawned processes exceed Bun's spawn limit.
// Steps 1-5 already cover all config-based and file-based detection.
// If none matched, npm is the safe default.
return {
name: 'npm',
config: PACKAGE_MANAGERS.npm,
@@ -306,22 +305,18 @@ function getExecCommand(binary, args = '', options = {}) {
/**
* Interactive prompt for package manager selection
* Returns a message for Claude to show to user
*
* NOTE: Does NOT spawn child processes to check availability.
* Lists all supported PMs and shows how to configure preference.
*/
function getSelectionPrompt() {
const available = getAvailablePackageManagers();
const current = getPackageManager();
let message = '[PackageManager] Available package managers:\n';
for (const pmName of available) {
const indicator = pmName === current.name ? ' (current)' : '';
message += ` - ${pmName}${indicator}\n`;
}
let message = '[PackageManager] No package manager preference detected.\n';
message += 'Supported package managers: ' + Object.keys(PACKAGE_MANAGERS).join(', ') + '\n';
message += '\nTo set your preferred package manager:\n';
message += ' - Global: Set CLAUDE_PACKAGE_MANAGER environment variable\n';
message += ' - Or add to ~/.claude/package-manager.json: {"packageManager": "pnpm"}\n';
message += ' - Or add to package.json: {"packageManager": "pnpm@8"}\n';
message += ' - Or add a lock file to your project (e.g., pnpm-lock.yaml)\n';
return message;
}

View File

@@ -56,20 +56,20 @@ if [ -z "$INPUT_JSON" ]; then
exit 0
fi
# Parse using python (more reliable than jq for complex JSON)
PARSED=$(python3 << EOF
# Parse using python via stdin pipe (safe for all JSON payloads)
PARSED=$(echo "$INPUT_JSON" | python3 -c '
import json
import sys
try:
data = json.loads('''$INPUT_JSON''')
data = json.load(sys.stdin)
# Extract fields - Claude Code hook format
hook_type = data.get('hook_type', 'unknown') # PreToolUse or PostToolUse
tool_name = data.get('tool_name', data.get('tool', 'unknown'))
tool_input = data.get('tool_input', data.get('input', {}))
tool_output = data.get('tool_output', data.get('output', ''))
session_id = data.get('session_id', 'unknown')
hook_type = data.get("hook_type", "unknown") # PreToolUse or PostToolUse
tool_name = data.get("tool_name", data.get("tool", "unknown"))
tool_input = data.get("tool_input", data.get("input", {}))
tool_output = data.get("tool_output", data.get("output", ""))
session_id = data.get("session_id", "unknown")
# Truncate large inputs/outputs
if isinstance(tool_input, dict):
@@ -83,20 +83,19 @@ try:
tool_output_str = str(tool_output)[:5000]
# Determine event type
event = 'tool_start' if 'Pre' in hook_type else 'tool_complete'
event = "tool_start" if "Pre" in hook_type else "tool_complete"
print(json.dumps({
'parsed': True,
'event': event,
'tool': tool_name,
'input': tool_input_str if event == 'tool_start' else None,
'output': tool_output_str if event == 'tool_complete' else None,
'session': session_id
"parsed": True,
"event": event,
"tool": tool_name,
"input": tool_input_str if event == "tool_start" else None,
"output": tool_output_str if event == "tool_complete" else None,
"session": session_id
}))
except Exception as e:
print(json.dumps({'parsed': False, 'error': str(e)}))
EOF
)
print(json.dumps({"parsed": False, "error": str(e)}))
')
# Check if parsing succeeded
PARSED_OK=$(echo "$PARSED" | python3 -c "import json,sys; print(json.load(sys.stdin).get('parsed', False))")
@@ -104,7 +103,11 @@ PARSED_OK=$(echo "$PARSED" | python3 -c "import json,sys; print(json.load(sys.st
if [ "$PARSED_OK" != "True" ]; then
# Fallback: log raw input for debugging
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "{\"timestamp\":\"$timestamp\",\"event\":\"parse_error\",\"raw\":$(echo "$INPUT_JSON" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()[:1000]))')}" >> "$OBSERVATIONS_FILE"
echo "$INPUT_JSON" | python3 -c "
import json, sys
raw = sys.stdin.read()[:2000]
print(json.dumps({'timestamp': '$timestamp', 'event': 'parse_error', 'raw': raw}))
" >> "$OBSERVATIONS_FILE"
exit 0
fi
@@ -121,10 +124,10 @@ fi
# Build and write observation
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
python3 << EOF
import json
echo "$PARSED" | python3 -c "
import json, sys
parsed = json.loads('''$PARSED''')
parsed = json.load(sys.stdin)
observation = {
'timestamp': '$timestamp',
'event': parsed['event'],
@@ -139,7 +142,7 @@ if parsed['output']:
with open('$OBSERVATIONS_FILE', 'a') as f:
f.write(json.dumps(observation) + '\n')
EOF
"
# Signal observer if running
OBSERVER_PID_FILE="${CONFIG_DIR}/.observer.pid"

View File

@@ -1,6 +1,6 @@
---
name: django-verification
description: Verification loop for Django projects: migrations, linting, tests with coverage, security scans, and deployment readiness checks before release or PR.
description: "Verification loop for Django projects: migrations, linting, tests with coverage, security scans, and deployment readiness checks before release or PR."
---
# Django Verification Loop

View File

@@ -1,6 +1,6 @@
---
name: java-coding-standards
description: Java coding standards for Spring Boot services: naming, immutability, Optional usage, streams, exceptions, generics, and project layout.
description: "Java coding standards for Spring Boot services: naming, immutability, Optional usage, streams, exceptions, generics, and project layout."
---
# Java Coding Standards

View File

@@ -1,11 +1,14 @@
---
name: project-guidelines-example
description: "Example project-specific skill template based on a real production application."
---
# Project Guidelines Skill (Example)
This is an example of a project-specific skill. Use this as a template for your own projects.
Based on a real production application: [Zenith](https://zenith.chat) - AI-powered customer discovery platform.
---
## When to Use
Reference this skill when working on the specific project it's designed for. Project skills contain:

View File

@@ -1,6 +1,6 @@
---
name: springboot-verification
description: Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR.
description: "Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR."
---
# Spring Boot Verification Loop

View File

@@ -1,3 +1,8 @@
---
name: verification-loop
description: "A comprehensive verification system for Claude Code sessions."
---
# Verification Loop Skill
A comprehensive verification system for Claude Code sessions.