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
14 changes: 14 additions & 0 deletions .claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ Edit `skill-rules.json` to:
- Adjust confidence scoring
- Update directory mappings

## Knowledge Skills (.claude/skills/knowledge/*.skill.md)

Domain-specific knowledge files loaded on keyword match. See `skills/knowledge/` for available skills.

```
[LOAD]on keyword match from frontmatter `activates_on` → read matching .skill.md
[FALLBACK]if `expires` date passed → use Context7 `context7` field or fetch `sources[]`
[CACHE]loaded skills persist for session|don't re-read same skill
[FORMAT]frontmatter: name,version,domain,expires,activates_on[],sources[],context7
```

Available: `esbuild-esm`, `vitest`, `better-sqlite3`, `mcp-protocol`, `commander-cli`
Plus global skills at `~/.claude/skills/knowledge/`: `stripe-api`, `railway-deploy`, `anthropic-sdk`

## StackMemory-Specific Patterns

Key patterns enforced by hooks and agents:
Expand Down
60 changes: 60 additions & 0 deletions .claude/skills/knowledge/better-sqlite3.skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: better-sqlite3
version: 2025.12.1
domain: database
expires: 2026-12-01
activates_on: [sqlite, better-sqlite3, database, db, query, fts, fts5, pragma, wal, transaction]
sources:
- https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md
context7: WiseLibs/better-sqlite3
---

# better-sqlite3

## Basics
- Open: `new Database(path)` or `new Database(':memory:')`
- **Synchronous API** — no async/await, no callbacks
- Prepare + run: `db.prepare('SELECT * FROM t WHERE id = ?').get(id)`
- All rows: `.all(params)` | Single row: `.get(params)` | Execute: `.run(params)`
- Params: positional `?` or named `$name` / `:name` / `@name`

## Transactions
```js
const insert = db.prepare('INSERT INTO t (a, b) VALUES (?, ?)');
const insertMany = db.transaction((items) => {
for (const item of items) insert.run(item.a, item.b);
});
insertMany(items); // atomic, auto-rollback on error
```
- `db.transaction()` returns a reusable function — best pattern for batch ops
- Nested transactions: use `.deferred()`, `.immediate()`, `.exclusive()`

## FTS5 (Full-Text Search)
- Create: `CREATE VIRTUAL TABLE t_fts USING fts5(content, tokenize='porter unicode61')`
- Search: `SELECT * FROM t_fts WHERE t_fts MATCH 'query'`
- Rank: `SELECT *, rank FROM t_fts WHERE t_fts MATCH 'query' ORDER BY rank`
- BM25: `SELECT *, bm25(t_fts) as score FROM t_fts WHERE t_fts MATCH ?`
- Highlight: `highlight(t_fts, 0, '<b>', '</b>')`

## WAL Mode
- Enable: `db.pragma('journal_mode = WAL')` — concurrent reads, single writer
- Always enable for production — significant performance improvement
- `db.pragma('busy_timeout = 5000')` — wait up to 5s for write lock

## Performance
- `db.pragma('cache_size = -64000')` — 64MB cache
- `db.pragma('synchronous = NORMAL')` — faster writes (WAL mode safe)
- `db.prepare()` caches the statement plan — reuse prepared statements
- Batch inserts: always wrap in `db.transaction()` — 100x faster than individual inserts

## ESM Import
- CJS native addon: `import Database from 'better-sqlite3'`
- Mark as `external` in esbuild — can't bundle native `.node` files
- Prebuilt binaries: `npm install` downloads correct platform binary

## Gotchas
- Synchronous — blocks event loop on large queries; use worker_threads for heavy ops
- `.get()` returns `undefined` if no row (not `null`)
- `.run()` returns `{ changes, lastInsertRowid }` — not the row itself
- Column names are case-sensitive in result objects
- VACUUM: locks entire DB — run during maintenance windows only
65 changes: 65 additions & 0 deletions .claude/skills/knowledge/commander-cli.skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: commander-cli
version: 2025.12.1
domain: cli
expires: 2026-12-01
activates_on: [commander, cli, command, option, argument, subcommand, parse, program, action]
sources:
- https://github.com/tj/commander.js#readme
context7: tj/commander.js
---

# Commander.js CLI Framework

## Basic Setup
```ts
import { Command } from 'commander';
const program = new Command();
program
.name('mycli')
.version('1.0.0')
.description('description');
```

## Commands
```ts
program
.command('serve')
.description('Start server')
.option('-p, --port <number>', 'port', '3000')
.option('-v, --verbose', 'verbose output')
.argument('<dir>', 'directory to serve')
.action((dir, options) => {
console.log(dir, options.port, options.verbose);
});
```

## Subcommands
```ts
const parent = program.command('db');
parent.command('migrate').action(() => { ... });
parent.command('seed').action(() => { ... });
// Usage: mycli db migrate
```

## Options
- Required value: `-p, --port <number>` (angle brackets)
- Optional value: `-p, --port [number]` (square brackets)
- Boolean flag: `-v, --verbose` (no value)
- Variadic: `-f, --files <items...>` (collects into array)
- Default: `.option('-p, --port <n>', 'desc', '3000')`
- Choices: `.addOption(new Option('--env <e>').choices(['dev', 'prod']))`
- Negatable: `--no-color` (sets `options.color = false`)

## Patterns for This Project
- 70+ commands — use subcommand groups (`skill`, `frame`, `session`, `linear`, etc.)
- Action handlers: async functions with try/catch + `process.exit(1)` on error
- Global options: define on `program` before subcommands
- Help: auto-generated — add `.addHelpText('after', text)` for examples

## Gotchas
- Option values are strings by default — parse with `.argParser(parseInt)` or coerce in action
- `program.parse()` must be called last (or `program.parseAsync()` for async actions)
- `.command('*')` for catch-all unknown commands
- Negative options: `--no-foo` creates `options.foo = false` — conflicts if `--foo` also defined
- ESM: Commander works fine, but ensure `#!/usr/bin/env node` in bin entry
43 changes: 43 additions & 0 deletions .claude/skills/knowledge/esbuild-esm.skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: esbuild-esm
version: 2025.12.1
domain: build
expires: 2026-12-01
activates_on: [esbuild, build, bundle, esm, import, export, module, cjs, commonjs, dist, entry]
sources:
- https://esbuild.github.io/api/
- https://nodejs.org/api/esm.html
context7: evanw/esbuild
---

# esbuild + ESM

## esbuild Config
- Entry: `esbuild.build({ entryPoints, bundle, platform, format, outdir })`
- Platform: `'node'` for CLI tools (excludes node builtins from bundle)
- Format: `'esm'` — this project is ESM-first (`"type": "module"` in package.json)
- External: mark `better-sqlite3`, native addons as external (can't bundle .node files)
- Sourcemaps: `sourcemap: true` for debugging

## ESM Rules (Node.js)
- **Always** add `.js` extension to relative imports: `import { foo } from './bar.js'`
- `__dirname` / `__filename` not available — use `import.meta.url` + `fileURLToPath()`
- `require()` not available — use `createRequire(import.meta.url)` for CJS interop
- Top-level `await` works in ESM
- JSON imports: `import data from './file.json' with { type: 'json' }` (or createRequire)

## CJS Interop
- Import CJS from ESM: default import works (`import pkg from 'cjs-pkg'`)
- Named exports: may need `import pkg from 'pkg'; const { named } = pkg;`
- `better-sqlite3`: CJS native addon — import as default, mark external in esbuild

## Package.json
- `"type": "module"` — all .js files are ESM
- `"exports"` field for package entry points (not just "main")
- `"bin"` field for CLI executables — ensure shebang `#!/usr/bin/env node`

## Gotchas
- Missing `.js` extension → `ERR_MODULE_NOT_FOUND` (most common error)
- esbuild `bundle: true` inlines deps — use `external` for native modules
- `--packages=external` excludes all node_modules (useful for dev builds)
- Watch mode: `esbuild.context().then(ctx => ctx.watch())` — not `--watch` flag in API
69 changes: 69 additions & 0 deletions .claude/skills/knowledge/mcp-protocol.skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: mcp-protocol
version: 2025.12.1
domain: protocol
expires: 2026-06-01
activates_on: [mcp, model context protocol, tool, resource, prompt, server, transport, stdio, sse, streamable]
sources:
- https://modelcontextprotocol.io/docs
- https://github.com/modelcontextprotocol/typescript-sdk
context7: modelcontextprotocol/typescript-sdk
---

# Model Context Protocol (MCP)

## SDK (@modelcontextprotocol/sdk)
```ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new McpServer({ name: 'my-server', version: '1.0.0' });
```

## Tools
```ts
server.tool('tool_name', 'description', {
param: z.string().describe('param description'), // Zod schema
}, async ({ param }) => {
return { content: [{ type: 'text', text: result }] };
});
```
- Input schema: Zod objects auto-converted to JSON Schema
- Return: `{ content: [{ type: 'text' | 'image' | 'resource', ... }] }`
- Error: `{ isError: true, content: [{ type: 'text', text: errorMsg }] }`

## Resources
```ts
server.resource('resource://uri', 'description', async (uri) => {
return { contents: [{ uri, mimeType: 'text/plain', text: data }] };
});
```
- Static URI or template: `resource://users/{id}`
- `list_changed` notification when resources update

## Transports
- **Stdio**: `StdioServerTransport` — for CLI-launched servers (Claude Code default)
- **Streamable HTTP**: `StreamableHTTPServerTransport` — for networked servers
- **SSE** (deprecated): use Streamable HTTP instead for new servers

## Prompts
```ts
server.prompt('prompt_name', 'description', { arg: z.string() }, ({ arg }) => {
return { messages: [{ role: 'user', content: { type: 'text', text: `...${arg}...` } }] };
});
```

## Client Side
```ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const client = new Client({ name: 'my-client', version: '1.0.0' });
await client.connect(transport);
const result = await client.callTool({ name: 'tool_name', arguments: { param: 'value' } });
```

## Gotchas
- Tool names: snake_case convention (not camelCase)
- Stdio transport: server MUST NOT write to stdout except MCP messages (use stderr for logs)
- Zod schemas: `.describe()` on each field — LLMs use descriptions for tool calling
- Error handling: return error content, don't throw (throws crash the server)
- Transport cleanup: `await server.close()` on shutdown
57 changes: 57 additions & 0 deletions .claude/skills/knowledge/vitest.skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: vitest
version: 2025.12.1
domain: testing
expires: 2026-12-01
activates_on: [vitest, test, spec, mock, spy, describe, it, expect, vi, beforeEach, coverage]
sources:
- https://vitest.dev/api/
- https://vitest.dev/guide/mocking
context7: vitest-dev/vitest
---

# Vitest

## Config (this project)
- Projects: unit, integration, live (API), bench
- Coverage: v8 provider, thresholds: 25% statements, 20% branches, 30% functions
- ESM native — no transform needed (unlike Jest + SWC)

## API (differs from Jest)
- Mock function: `vi.fn()` (not `jest.fn()`)
- Mock module: `vi.mock('./module')` — hoisted like Jest
- Spy: `vi.spyOn(obj, 'method')`
- Timers: `vi.useFakeTimers()` / `vi.advanceTimersByTime(ms)`
- Clear: `vi.clearAllMocks()` / `vi.resetAllMocks()` / `vi.restoreAllMocks()`
- Snapshot: `expect(val).toMatchSnapshot()` — same as Jest

## Mock Patterns
```ts
vi.mock('./dep.js', () => ({
myFn: vi.fn().mockReturnValue('mocked'),
}));

// Reset per test
beforeEach(() => {
vi.clearAllMocks();
// re-set implementations after clear
});
```

## Inline vs Config Mocks
- `vi.mock()` in test file — hoisted, file-scoped
- `__mocks__/` directory — auto-mock (same as Jest convention)
- `vi.hoisted()` — declare variables used in `vi.mock()` factory

## Key Differences from Jest
- `vi` namespace instead of `jest` global
- Native ESM — no `.js` extension issues in tests
- `vi.stubEnv('KEY', 'val')` for env vars (cleaner than `process.env` mutation)
- `--reporter=verbose` for detailed output
- `vitest bench` for benchmarks (built-in, not separate tool)

## Gotchas
- `vi.mock()` factory can't reference outer variables unless via `vi.hoisted()`
- `vi.clearAllMocks()` resets calls + implementations (same gotcha as Jest)
- `--pool=forks` vs `--pool=threads` — forks for better isolation, threads for speed
- SQLite tests: use `:memory:` or temp file, not shared DB (parallel execution)
46 changes: 46 additions & 0 deletions .github/workflows/pr-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: PR CI

on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]

concurrency:
group: pr-ci-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm run lint

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm run test:run
env:
CI: true

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm run build
Loading
Loading