Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .claude/commands/add-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Add a new adapter

Create a new tool adapter for the specified package and tool. Follow these steps exactly.

## 1. Determine the target

Ask which **package** (hooks, mcp, agents, skills, or rules) and which **AI tool** to add.

## 2. Study the reference adapter

Read the Claude Code adapter for the target package — it's always the most complete:
- `packages/{package}/src/adapters/claude-code.ts`
- `packages/{package}/src/adapters/claude-code.test.ts`

Also read `packages/{package}/src/adapters/base.ts` for the base class interface.

## 3. Create the adapter file

Create `packages/{package}/src/adapters/{tool-name}.ts`:

**For hooks adapters** (complex — event mapping required):
- Define `EVENT_MAP: Record<string, string[]>` mapping all 15 universal events to tool-native events (empty array for unsupported)
- Define `REVERSE_MAP: Record<string, HookEventType[]>` as the inverse
- Extend `BaseAdapter`, implement: `id`, `name`, `version`, `capabilities`, `detect()`, `generate()`, `mapEvent()`, `mapNativeEvent()`, `uninstall()`
- Use `this.commandExists()` and `this.existsSync()` from base class in `detect()`

**For mcp/agents/skills/rules adapters** (simpler — no event mapping):
- Extend the package's `BaseAdapter`
- Implement: `id`, `name`, `version`, `detect()`, `generate()`, `install()`
- For agents: consider using `createMarkdownAdapter()` factory from `markdown-adapter.ts`

End the file with self-registration:
```typescript
const adapter = new MyToolAdapter();
registry.register(adapter);
export { MyToolAdapter };
export default adapter;
```

## 4. Register in all.ts

Add `import "./{tool-name}.js";` to `packages/{package}/src/adapters/all.ts`.

## 5. Write tests

Create `packages/{package}/src/adapters/{tool-name}.test.ts` covering:
- Metadata: `id`, `name`, `version`
- Capabilities (hooks only): all flags, `supportedEvents`, `blockableEvents`
- `mapEvent()` / `mapNativeEvent()` (hooks only): every map entry + unknown events returning `[]`
- `generate()`: file paths, content format, deduplication, empty input, multi-event
- `detect()`: both found and not-found paths

Target 100% coverage. Use the Claude Code adapter test as template.

## 6. Verify

Run `npm run check` — must pass with 0 errors and 0 warnings.
21 changes: 21 additions & 0 deletions .claude/commands/release-prep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Prepare for release

Verify the codebase is ready for semantic-release on merge to master.

## Steps

1. Run `npm run check` — all four stages must pass (lint, format, typecheck, test)

2. Check recent commits follow conventional commit format (Angular preset):
- `feat:` → minor release
- `fix:`, `perf:`, `refactor:`, `revert:` → patch release
- `docs:`, `chore:`, `style:`, `test:`, `ci:` → no release
- `BREAKING CHANGE:` in footer → major release

3. Run `npm run release:publish:dry` to preview what would be published

4. Verify all workspace package.json files have consistent versions

5. Check that `scripts/sync-versions.js` and `scripts/publish-workspaces.js` are intact and unmodified

Report the results: which packages will be published, what version bump is expected, and any issues found.
11 changes: 11 additions & 0 deletions .claude/commands/run-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Run full check pipeline

Run `npm run check` which executes lint, format check, typecheck, and test in sequence.

If any stage fails:
1. Read the error output carefully
2. Fix the issue
3. Re-run only the failing stage to verify the fix
4. Run the full `npm run check` again to confirm everything passes

Do not stop until all four stages pass with zero errors and zero warnings.
61 changes: 61 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(npm test*)",
"Bash(npx vitest *)",
"Bash(npx turbo *)",
"Bash(npx oxlint *)",
"Bash(npx oxfmt *)",
"Bash(npx tsc *)",
"Bash(git status*)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(node scripts/*)"
],
"deny": [
"Bash(npm publish*)",
"Bash(npx semantic-release*)",
"Bash(git push --force*)"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "file_path=$(echo \"$TOOL_INPUT\" | node -e \"process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).file_path||'')\" 2>/dev/null); [[ \"$file_path\" == *.ts ]] && [[ -f \"$file_path\" ]] && npx oxfmt \"$file_path\" 2>/dev/null; exit 0"
}
]
},
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/ai-hooks-runner.js",
"timeout": 10,
"description": "ai-hooks: PostToolUse"
}
]
}
],
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/ai-hooks-runner.js",
"timeout": 10,
"description": "ai-hooks: PreToolUse"
}
]
}
]
},
"env": {
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1"
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist/
!.env.example
coverage/
.DS_Store
.claude/settings.local.json
2 changes: 1 addition & 1 deletion .releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@semantic-release/npm",
{
"npmPublish": true,
"pkgRoot": "packages/core",
"pkgRoot": "packages/hooks",
"access": "public"
}
],
Expand Down
93 changes: 93 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Universal hook engine and configuration management for AI coding tools. Monorepo (`@premierstudio/*`) providing a unified event/hook system that translates to 9+ AI tool-specific formats (Claude Code, Codex, Cursor, Gemini CLI, Kiro, OpenCode, Cline, Factory Droid, Amp).

## Commands

```bash
npm run check # Full verification: lint + format + typecheck + test (run before PRs)
npm run build # Build all packages (turbo)
npm test # Run all tests (vitest)
npx vitest run packages/hooks # Run tests for a single package
npx vitest run packages/hooks/src/adapters/claude-code.test.ts # Single test file
npm run test:watch # Watch mode
npm run lint # oxlint
npm run lint:fix # oxlint --fix
npm run fmt # oxfmt (auto-format)
npm run fmt:check # Check formatting
npm run typecheck # tsc across all packages (turbo)
```

## Architecture

### Monorepo Structure

Six packages managed by npm workspaces + Turborepo:

| Package | npm name | Purpose |
|---------|----------|---------|
| `packages/hooks` | `@premierstudio/ai-hooks` | Core engine: hook engine, adapters, config loader, built-in hooks |
| `packages/mcp` | `@premierstudio/ai-mcp` | MCP server configuration management |
| `packages/agents` | `@premierstudio/ai-agents` | Agent configuration for AI tools |
| `packages/skills` | `@premierstudio/ai-skills` | Skills/prompts configuration |
| `packages/rules` | `@premierstudio/ai-rules` | Project rules configuration |
| `packages/cli` | `@premierstudio/ai-tools` | Unified CLI routing to all engines |

The `cli` package depends on all others. The other five packages are independent of each other.

### Repeated Package Pattern

Each engine package (hooks, mcp, agents, skills, rules) follows the same internal layout:

- `src/adapters/` — Tool-specific implementations (one per AI tool) + `base.ts` base class + `registry.ts` global registry
- `src/config/` — `defineConfig()` helper and `loadConfig()` dynamic importer
- `src/cli/` — Package-specific CLI commands with `bin.ts` entry point
- `src/types/` — TypeScript type definitions
- `src/index.ts` — Public API barrel export

### Hook Engine (packages/hooks) — the most complex package

Express.js-style middleware chain. For runtime internals, event mapping, and adapter implementation details, see `packages/hooks/CLAUDE.md`.

- **HookEngine** (`runtime/engine.ts`): Central orchestrator. Registers hooks, dispatches events via `emit()`.
- **Chain execution** (`runtime/chain.ts`): Hooks sorted by priority (lower = first), `next()` middleware pattern. Stops on block. Enforces per-hook timeouts.
- **15 universal events** (`types/events.ts`): session:start/end, prompt:submit/response, tool:before/after, file:read/write/edit/delete, shell:before/after, mcp:before/after, notification. Before events are blockable; after events are observe-only.
- **Built-in hooks** (`hooks/builtin.ts`): block-dangerous-commands, scan-secrets, protect-sensitive-files, audit-shell.

### Adapter System (shared across all engine packages)

Each adapter extends its package's `BaseAdapter` and self-registers into a global registry on import. Importing `adapters/all.ts` registers all adapters for that engine. The hooks package has the most complex adapters (event mapping, blocking); other packages have simpler adapters (config file generation only).

### Config System

Each engine has a `defineConfig()` helper (exception: rules uses `defineRulesConfig()`). The hooks package additionally has a `hook()` fluent builder, `loadConfig()` dynamic importer, and preset composition via `extends`. Other engines have trivial config helpers that pass through the object.

### Simpler Engine Packages (mcp, agents, skills, rules)

All follow the same adapter-registry-CLI pattern as hooks but without the runtime engine. Each package's CLAUDE.md documents what's unique:

- **mcp**: Transport types (stdio/SSE), server definitions, import/sync across tools
- **agents**: Reusable `createMarkdownAdapter()` factory, YAML frontmatter, model/tools fields
- **skills**: Simplest — just name + content as markdown, no metadata
- **rules**: Scoping system (always/glob/manual/agent), priority, YAML frontmatter

## Code Conventions

- **Strict TypeScript**: `noUnusedLocals`, `noUnusedParameters`, `noUncheckedIndexedAccess` all enabled
- **No `any`**: `typescript/no-explicit-any` is an error in oxlint
- **No `_variable` prefix** for unused params — use `void variable` instead
- **ESM only**: All packages are `"type": "module"`, target ES2023/Node 22
- **Formatting**: oxfmt — double quotes, trailing commas, 100 char width, 2-space indent
- **Tests**: vitest with globals, co-located as `*.test.ts` next to source files, target 100% coverage

## Build & Release

- **tsup** builds each package to ESM with declaration maps
- **Turborepo** orchestrates build order (packages build in dependency order)
- **semantic-release** on master: analyzes conventional commits, bumps versions, publishes to npm
- `scripts/sync-versions.js` keeps all workspace package versions in lockstep
- `scripts/publish-workspaces.js` publishes non-primary packages after the core package
Loading
Loading