diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 862511f..d526ac5 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,11 +1,8 @@ - # @shellicar/claude-cli — Repo Memory - - ## Why This Harness Exists Each session starts with a blank slate. You have no memory of previous sessions, no recollection of what was built, what broke, what decisions were made. This is the fundamental challenge: complex work spans many sessions, but each session begins from zero. @@ -25,11 +22,9 @@ The harness and session logs exist to solve this. They are your memory across se **Why verification matters**: Code changes that look correct may not work end-to-end. Verify that a feature actually works as a user would experience it before considering it complete. Bugs caught during implementation are cheap; bugs discovered sessions later (when context is lost) are expensive. The harness is deliberately structured. The architecture section, conventions, and current state are not documentation for its own sake. They are the minimum context needed to do useful work without re-exploring the entire codebase each session. - - ## Never Guess If you do not have enough information to do something, stop and ask. Do not guess. Do not infer. Do not fill in blanks with what seems reasonable. @@ -41,11 +36,9 @@ This applies to everything: requirements, API behavior, architectural decisions, Guessing is poison. A guessed assumption becomes a code decision. Other code builds on that decision. Future sessions read that code and treat it as intentional. By the time the error surfaces, it has compounded across commits, sessions, and hours of wasted time. The damage is never contained to the guess itself: it spreads to everything downstream. A question costs one message. A look costs one tool call. A guess costs everything built on top of it. - - ## Session Protocol Every session has three phases: start, work, end. @@ -72,16 +65,12 @@ Every session has three phases: start, work, end. - ## Current State - Branch: `feature/monorepo-workspace` -In-progress: Vite+ toolchain cleanup complete. PR shellicar/claude-cli#172 updated, auto-merge enabled. Awaiting CI. - +In-progress: Phase 2 complete. PR shellicar/claude-cli#172 created, auto-merge enabled. Awaiting CI. - ## Architecture **Stack**: TypeScript, esbuild (bundler), `@anthropic-ai/claude-agent-sdk`. pnpm monorepo workspace with turbo. CLI package lives at `packages/claude-cli/`. @@ -90,31 +79,29 @@ In-progress: Vite+ toolchain cleanup complete. PR shellicar/claude-cli#172 updat **Key source files** (all under `packages/claude-cli/`): -| File | Role | -| ---------------------------------- | --------------------------------------------------------------------- | -| `src/ClaudeCli.ts` | Orchestrator, startup sequence, event loop, query cycle | -| `src/session.ts` | `QuerySession`, SDK wrapper, session/resume lifecycle | -| `src/AppState.ts` | Phase state machine (`idle`, `sending`, `thinking`, `idle`) | -| `src/terminal.ts` | ANSI terminal rendering, three-zone layout | -| `src/renderer.ts` | Pure editor content preparation (cursor math) | -| `src/StatusLineBuilder.ts` | Fluent builder for width-accurate ANSI status lines | -| `src/SessionManager.ts` | Session file I/O (`.claude/cli-session`) | -| `src/AuditWriter.ts` | JSONL event logger (`~/.claude/audit/.jsonl`) | -| `src/files.ts` | `initFiles()` creates `.claude/` dir, returns `CliPaths` | -| `src/cli-config/` | Config subsystem, schema, loading, diffing, hot reload | -| `src/providers/` | `GitProvider`, `UsageProvider`, system prompt data sources | -| `src/PermissionManager.ts` | Tool approval queue and permission prompt UI | -| `src/PromptManager.ts` | `AskUserQuestion` dialog, single/multi-select + free text | -| `src/CommandMode.ts` | Ctrl+/ state machine for attachment and session operations | -| `src/SdkResult.ts` | Parses `SDKResultSuccess`, extracts errors, rate limits, token counts | -| `src/UsageTracker.ts` | Context usage and session cost tracking interface | -| `src/mcp/shellicar/autoApprove.ts` | Glob-based auto-approve for exec commands (`execAutoApprove` config) | -| `docs/sdk-findings.md` | SDK behaviour discoveries (session semantics, tool options, etc.) | - +| File | Role | +|------|------| +| `src/ClaudeCli.ts` | Orchestrator, startup sequence, event loop, query cycle | +| `src/session.ts` | `QuerySession`, SDK wrapper, session/resume lifecycle | +| `src/AppState.ts` | Phase state machine (`idle`, `sending`, `thinking`, `idle`) | +| `src/terminal.ts` | ANSI terminal rendering, three-zone layout | +| `src/renderer.ts` | Pure editor content preparation (cursor math) | +| `src/StatusLineBuilder.ts` | Fluent builder for width-accurate ANSI status lines | +| `src/SessionManager.ts` | Session file I/O (`.claude/cli-session`) | +| `src/AuditWriter.ts` | JSONL event logger (`~/.claude/audit/.jsonl`) | +| `src/files.ts` | `initFiles()` creates `.claude/` dir, returns `CliPaths` | +| `src/cli-config/` | Config subsystem, schema, loading, diffing, hot reload | +| `src/providers/` | `GitProvider`, `UsageProvider`, system prompt data sources | +| `src/PermissionManager.ts` | Tool approval queue and permission prompt UI | +| `src/PromptManager.ts` | `AskUserQuestion` dialog, single/multi-select + free text | +| `src/CommandMode.ts` | Ctrl+/ state machine for attachment and session operations | +| `src/SdkResult.ts` | Parses `SDKResultSuccess`, extracts errors, rate limits, token counts | +| `src/UsageTracker.ts` | Context usage and session cost tracking interface | +| `src/mcp/shellicar/autoApprove.ts` | Glob-based auto-approve for exec commands (`execAutoApprove` config) | +| `docs/sdk-findings.md` | SDK behaviour discoveries (session semantics, tool options, etc.) | - ## Conventions - **TypeScript** throughout — `pnpm type-check` to verify @@ -126,7 +113,6 @@ In-progress: Vite+ toolchain cleanup complete. PR shellicar/claude-cli#172 updat - ## Linting & Formatting - **Formatter/linter**: `biome` @@ -139,7 +125,6 @@ In-progress: Vite+ toolchain cleanup complete. PR shellicar/claude-cli#172 updat - ## Key Patterns ### Keypress-Driven Event Loop @@ -173,28 +158,26 @@ Opt-in via `shellicarMcp: true` config. Registers an in-process MCP server (`she - ## Known Debt / Gotchas 1. **AuditWriter is fatal-on-error** — any write failure calls `process.exit(1)`. No graceful degradation. -2. **SessionManager has no error handling on write** — `save()` and `clear()` use bare `writeFileSync`. File permission errors crash the process mid-interaction. +3. **SessionManager has no error handling on write** — `save()` and `clear()` use bare `writeFileSync`. File permission errors crash the process mid-interaction. -3. **thinking/thinkingEffort not tracked by diffConfig** — changes to these fields produce no user notification. Same for `compactModel`. Must restart or use `/config` to verify. +4. **thinking/thinkingEffort not tracked by diffConfig** — changes to these fields produce no user notification. Same for `compactModel`. Must restart or use `/config` to verify. -4. **Slash commands are string-matched in `submit()`** — no command registry. Adding commands requires editing the submit dispatch block. +5. **Slash commands are string-matched in `submit()`** — no command registry. Adding commands requires editing the submit dispatch block. -5. **Context thresholds hardcoded** — 85%/90% tool disable thresholds are not configurable. +6. **Context thresholds hardcoded** — 85%/90% tool disable thresholds are not configurable. -6. **Cursor positioning via Viewport**: `Viewport.scrollOffset` tracks visible window into layout rows. Off-by-1 errors at screen boundaries are possible but not yet observed post-Stage 5 rewrite. +7. **Cursor positioning via Viewport**: `Viewport.scrollOffset` tracks visible window into layout rows. Off-by-1 errors at screen boundaries are possible but not yet observed post-Stage 5 rewrite. -7. **Null unsets in config merge are subtle** — `"model": null` in local config means "use home config's model", not "set to null". Easy to confuse. +8. **Null unsets in config merge are subtle** — `"model": null` in local config means "use home config's model", not "set to null". Easy to confuse. -8. **No atomic session file writes** — `writeFileSync` is not atomic. Crash during write corrupts `.claude/cli-session`. +9. **No atomic session file writes** — `writeFileSync` is not atomic. Crash during write corrupts `.claude/cli-session`. - ## Recent Decisions - **Structured command execution via in-process MCP** (#99) — replaced freeform Bash with a structured Exec tool served by an in-process MCP server. Glob-based auto-approve (`execAutoApprove`) with custom zero-dep glob matcher (no minimatch dependency). diff --git a/.claude/sessions/2026-03-25.md b/.claude/sessions/2026-03-25.md index 0aac104..938961c 100644 --- a/.claude/sessions/2026-03-25.md +++ b/.claude/sessions/2026-03-25.md @@ -46,7 +46,6 @@ **Why late abort works (Escape after output has started)**: When aborting after output has started, the SDK is in a different state: - - The model is generating text/thinking, not requesting tool permissions - There are no in-flight `handleControlRequest` calls (no pending `control_request` messages) - The abort causes `readMessages`'s `for await` loop to exit (stdout closes when child is killed) @@ -86,7 +85,6 @@ public cancel(): void { ``` Key details for the implementer: - - The handler checks `reason.message === 'Operation aborted'` (the exact string from `AbortError`) - 5-second timeout covers the SDK's 2s SIGTERM timeout plus margin for pipe buffer draining - `.unref()` ensures the timer doesn't keep the process alive on exit @@ -95,7 +93,6 @@ Key details for the implementer: - `AbortError` is exported from the SDK (`@anthropic-ai/claude-agent-sdk`), so the implementer could use `instanceof AbortError` instead of string matching if preferred **SDK references (minified names)**: - - `Q0` = `ProcessTransport`, `write()` at sdk.mjs line 19, throws on aborted signal - `G0` = `Query`, `handleControlRequest()` at sdk.mjs line 19-20, fire-and-forget in `readMessages()` - `u$` = `AbortError` (exported as `AbortError` in sdk.d.ts line 17), `class u$ extends Error{}` diff --git a/.claude/sessions/2026-03-30.md b/.claude/sessions/2026-03-30.md index ec203d3..52f26df 100644 --- a/.claude/sessions/2026-03-30.md +++ b/.claude/sessions/2026-03-30.md @@ -90,7 +90,7 @@ MockScreen maintains one cell grid at a time. On buffer switch, save/restore the ```typescript export class MockScreen implements Screen { - public cells: string[][]; // active buffer's cells (no longer readonly) + public cells: string[][]; // active buffer's cells (no longer readonly) public cursorRow = 0; public cursorCol = 0; public scrollbackViolations = 0; @@ -107,12 +107,14 @@ export class MockScreen implements Screen { public enterAltBuffer(): void { if (this.activeBuffer === 'alt') return; this.savedMainState = { - cells: this.cells.map((row) => [...row]), + cells: this.cells.map(row => [...row]), cursorRow: this.cursorRow, cursorCol: this.cursorCol, }; this.activeBuffer = 'alt'; - this.cells = Array.from({ length: this.rows }, () => new Array(this.columns).fill('')); + this.cells = Array.from({ length: this.rows }, () => + new Array(this.columns).fill('') + ); this.cursorRow = 0; this.cursorCol = 0; this.pendingWrap = false; @@ -145,16 +147,15 @@ export class MockScreen implements Screen { // Read main buffer content while in alt buffer public getMainRow(r: number): string { - const cells = this.activeBuffer === 'main' ? this.cells : this.savedMainState?.cells; + const cells = this.activeBuffer === 'main' + ? this.cells + : this.savedMainState?.cells; if (!cells) return ''; const row = cells[r]; if (!row) return ''; let lastNonEmpty = -1; for (let c = row.length - 1; c >= 0; c--) { - if (row[c] !== '') { - lastNonEmpty = c; - break; - } + if (row[c] !== '') { lastNonEmpty = c; break; } } return row.slice(0, lastNonEmpty + 1).join(''); } @@ -164,14 +165,12 @@ export class MockScreen implements Screen { ### 3. Renderer Changes **Removed fields:** - - `lastVisibleCursorRow` (position tracking, no longer needed) - `lastFrame` (stale frame re-render for writeHistory, no longer needed) - `pendingResize` (resize heuristics, no longer needed) - `zoneHeight` (used only for resize narrow/widen heuristic) **Removed methods:** - - `notifyResize()` (no resize heuristics in alt buffer) - `writeHistoryLine()` (history no longer written to screen by Renderer) - `writeHistory()` (deprecated, also removed) @@ -219,7 +218,6 @@ public render(frame: ViewportResult): void { ``` Key changes from current render(): - - Always starts with `cursorAt(1, 1)` instead of `cursorUp(lastVisibleCursorRow)` or resize heuristics - Final cursor positioned via `cursorAt(row, col)` instead of `cursorUp + cursorTo` - No state tracked between renders (stateless render) @@ -283,7 +281,7 @@ this.redraw(); // New: this.showSkills(); -this.term.flushHistory(); // exit alt, write history, re-enter alt +this.term.flushHistory(); // exit alt, write history, re-enter alt this.redraw(); ``` @@ -347,7 +345,7 @@ process.stdout.on('resize', () => { clearTimeout(this.resizeTimer); this.resizeTimer = setTimeout(() => { this.term.paused = false; - this.redraw(); // Just redraw, no notifyResize needed + this.redraw(); // Just redraw, no notifyResize needed }, 300); }); ``` @@ -373,7 +371,6 @@ this.redraw(); // Initial render in alt buffer ### 3. Existing Renderer tests need replacement Many Renderer tests verify main-buffer behavior: `cursorUp` tracking, `writeHistory` pushing zone down, resize heuristics (narrow vs widen). These tests become invalid with alt buffer rendering. They should be replaced with tests that verify: - - `render()` always starts with `cursorAt(1,1)` - `clearDown` clears leftover from taller previous frames - Cursor positioned correctly via `cursorAt` @@ -502,7 +499,10 @@ export class HistoryViewport { this.scrollOffset = Math.max(0, buffer.length - rows); } else { // Cap to valid range (buffer may have grown since last scroll) - this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, buffer.length - rows)); + this.scrollOffset = Math.max( + 0, + Math.min(this.scrollOffset, buffer.length - rows), + ); } const slice = buffer.slice(this.scrollOffset, this.scrollOffset + rows); @@ -510,7 +510,9 @@ export class HistoryViewport { // Top-pad: empty rows first, content at bottom of region. // This keeps the most recent history adjacent to the zone. const padding = rows - slice.length; - const result = padding > 0 ? [...Array(padding).fill(''), ...slice] : slice; + const result = padding > 0 + ? [...Array(padding).fill(''), ...slice] + : slice; return { rows: result, @@ -618,7 +620,6 @@ Transitions: ``` **Queryable via `mode` getter.** ClaudeCli reads this for: - 1. Escape key routing: if `term.isHistoryMode`, Esc returns to live instead of aborting query 2. Status line: Terminal reads `historyViewport.mode` to show position indicator @@ -816,7 +817,6 @@ public render(historyRows: string[], zoneFrame: ViewportResult): void { ``` **Key points:** - - History rows written first (top of screen), zone rows below - Cursor absolute position = history row count + zone-relative cursor row (1-based) - Trim applies to the combined buffer. Since the zone always has content (at least 1 editor line), trailing empties from zone padding are trimmed but history padding in the middle is preserved (this is correct: it creates the visual gap between sparse history and the zone) @@ -855,7 +855,6 @@ private writeHistory(line: string): void { ``` **Consumers:** - - `flushHistory()`: reads and clears `historyBuffer` (writes to main buffer). Unchanged. - `renderZone()`: wraps `displayBuffer` and feeds to `historyViewport.resolve()`. Persists across flushes. @@ -996,7 +995,6 @@ private writeHistory(line: string): void { ### 1. Startup messages appear in both buffers With the fix, startup messages appear in: - - Main buffer (via `screen.write()`) visible in terminal scrollback - Alt buffer history region (via `displayBuffer`) visible while using the app @@ -1017,7 +1015,6 @@ One existing test needs updating: - **`empty displayBuffer: zone gets full screen`** (terminal-integration.spec.ts line 213-243): Currently tests the short-circuit path where `historyFrame.rows = []` and zone gets `screenRows`. After the fix, empty displayBuffer resolves through the viewport, producing padding rows. The zone gets `zoneHeight` rows (not `screenRows`), anchored at the bottom. Test should be rewritten to verify bottom-anchoring: history region filled with padding, zone at bottom of screen. New tests needed: - - Zone anchored to bottom with empty displayBuffer (padding rows at top, zone content at bottom) - Startup messages visible in displayBuffer after pre-alt-buffer writes (simulate `writeHistory` calls before `inAltBuffer = true`, verify they appear in displayBuffer) - Existing behavior preserved when history has content (already tested, should pass without changes) diff --git a/.claude/sessions/2026-03-31.md b/.claude/sessions/2026-03-31.md index 8e9c33f..c3e098f 100644 --- a/.claude/sessions/2026-03-31.md +++ b/.claude/sessions/2026-03-31.md @@ -1,12 +1,10 @@ ### 10:05 - investigation/input-lag (#166) - - Did: Traced full keystroke-to-screen render path, quantified per-render work at 192x62, identified 5 optimization options (cache wrapping, row diffing, editor-only fast path, time throttle, incremental wrapping), analyzed interactions with existing features and future work (#158, #159) - Files: none modified in claude-cli repo (research-only session) - Decisions: investigation report written to fleet repo at `projects/claude-cli/investigations/input-lag.md` - Next: stakeholder reviews report, decides which option(s) to implement ### 11:05 - profiling/input-lag (#166) - - Did: Profiled render path with hrtime.bigint() at 100/1K/5K/10K history lines. History wrapping is 99.9% of render cost at scale. Within wrapping: Segmenter slow path 70%, stringWidth 23%, sanitiseZwj 3%. Render scales linearly at ~0.96us/line (9.6ms at 10K lines). - Files: profiling/render-profile.ts, profiling/wrapline-breakdown.ts, profiling/stdout-profile.ts (instrumentation scripts), profiling/results.md, profiling/wrapline-breakdown.md (raw data) - Decisions: Used standalone profiling scripts importing real functions rather than instrumenting the CLI directly. Report written to fleet repo at `projects/claude-cli/investigations/input-lag-profiling.md` diff --git a/.claude/sessions/2026-04-01.md b/.claude/sessions/2026-04-01.md index e03f775..9dcd22e 100644 --- a/.claude/sessions/2026-04-01.md +++ b/.claude/sessions/2026-04-01.md @@ -6,21 +6,18 @@ - Next: Run `pnpm type-check` and `pnpm test` to verify. Then commit, push, create PR for Phase 2. ### 04:19 - fix/keystroke-input-lag (#166) Phase 2 - - Did: Committed and pushed Phase 1 work, created PR shellicar/claude-cli#169 with auto-merge enabled - Files: `.claude/sessions/2026-03-31.md`, `.claude/sessions/2026-04-01.md`, `src/terminal.ts`, `test/terminal-perf.spec.ts` - Decisions: Session logs from earlier session were already staged, included in the commit - Next: Awaiting CodeQL and Node.js CI checks to pass, then auto-merge ### 04:46 - fix/keystroke-input-lag (#166) Phase 3 - - Did: Added functional tests for `renderZone()` correctness (`test/terminal-functional.spec.ts`: 6 tests covering history visibility, multi-line append, line wrapping at column boundary, and cache invalidation on resize). Added append-then-render performance test to `test/terminal-perf.spec.ts` (primes 10K cache then measures 1-line append render). Added optional `screen?: Screen` parameter to `Terminal` constructor to enable MockScreen injection. - Files: `src/terminal.ts`, `test/terminal-functional.spec.ts` (new), `test/terminal-perf.spec.ts`, `.claude/sessions/2026-04-01.md` - Decisions: Used captureScreen helper (mutable columns, write capture) for wrapping and resize tests instead of MockScreen, since MockScreen has fixed dimensions. MockScreen used for tests that need cell-level content inspection. All 238 tests pass. - Next: PR #169 already open on `fix/keystroke-input-lag`. Phase 3 commit pushed to same branch. ### 22:15 - profiling/post-cache (#166) - - Did: Wrote `profiling/post-cache-profile.ts` measuring render path across three scenarios (keystroke cache hit, history append at 1/10/100 lines, resize cache invalidation) at 192x62 with 10K history lines. Keystroke render dropped from 9,559 us (pre-cache) to 1.5 us (sub-step) / 11.4 us p95 (Terminal class). Append scales linearly at ~0.9 us/line. Resize re-wrap costs ~10ms as expected. - Files: `profiling/post-cache-profile.ts` (new), `profiling/post-cache-results.md` (generated) - Decisions: Research-only session, no commits. Used two measurement approaches: Terminal class for total time validation, replicated renderZone logic for sub-step breakdown. Report written to fleet repo at `projects/claude-cli/investigations/post-cache-profiling.md`. diff --git a/.claude/sessions/2026-04-02.md b/.claude/sessions/2026-04-02.md index ad6ab38..87ffd7f 100644 --- a/.claude/sessions/2026-04-02.md +++ b/.claude/sessions/2026-04-02.md @@ -7,10 +7,10 @@ ### 02:08 - enhance/monorepo-workspace Phase 1 -- Did: Converted single-package repo to pnpm monorepo workspace. Moved all source (src/, test/, inject/, schema/, docs/, build.ts, vitest.config.ts, tsconfig.json, tsconfig.check.json, package.json) to packages/claude-cli/ via git mv. Created private workspace root package.json with turbo scripts and devDeps (@biomejs/biome, lefthook, npm-check-updates, syncpack, turbo). Removed packageManager, @biomejs/biome, lefthook, scripts.ci, scripts.ci:fix from packages/claude-cli/package.json. Created turbo.json, .syncpackrc, .packagename, root vitest.config.ts. Updated pnpm-workspace.yaml (packages/\*, lefthook in onlyBuiltDependencies), biome.json (schema path glob), lefthook.yml (verify-version.sh pre-push script), knip.json (workspace path), .gitignore (.turbo). Copied .lefthook/pre-push/ scripts from build-version reference. Updated scripts/verify-version.sh and scripts/tag-latest.sh to read package.json from packages//. Updated .github/workflows/node.js.yml and npm-publish.yml with packages/claude-cli/ paths. +- Did: Converted single-package repo to pnpm monorepo workspace. Moved all source (src/, test/, inject/, schema/, docs/, build.ts, vitest.config.ts, tsconfig.json, tsconfig.check.json, package.json) to packages/claude-cli/ via git mv. Created private workspace root package.json with turbo scripts and devDeps (@biomejs/biome, lefthook, npm-check-updates, syncpack, turbo). Removed packageManager, @biomejs/biome, lefthook, scripts.ci, scripts.ci:fix from packages/claude-cli/package.json. Created turbo.json, .syncpackrc, .packagename, root vitest.config.ts. Updated pnpm-workspace.yaml (packages/*, lefthook in onlyBuiltDependencies), biome.json (schema path glob), lefthook.yml (verify-version.sh pre-push script), knip.json (workspace path), .gitignore (.turbo). Copied .lefthook/pre-push/ scripts from build-version reference. Updated scripts/verify-version.sh and scripts/tag-latest.sh to read package.json from packages//. Updated .github/workflows/node.js.yml and npm-publish.yml with packages/claude-cli/ paths. - Verification: pnpm install, build, test (239 passed), type-check, ci (86 files clean), node packages/claude-cli/dist/main.js --version all pass. - Files: packages/claude-cli/ (all source), package.json (new root), turbo.json, .syncpackrc, .packagename, vitest.config.ts, pnpm-workspace.yaml, biome.json, lefthook.yml, .lefthook/pre-push/, knip.json, .gitignore, scripts/verify-version.sh, scripts/tag-latest.sh, .github/workflows/node.js.yml, .github/workflows/npm-publish.yml -- Decisions: daemon:false in turbo.json per prompt spec (turbo 2.9.3 shows deprecation warning, harmless). Root vitest.config.ts uses projects pattern delegating to packages/\*. +- Decisions: daemon:false in turbo.json per prompt spec (turbo 2.9.3 shows deprecation warning, harmless). Root vitest.config.ts uses projects pattern delegating to packages/*. - Next: Phase 2. Commit, push, create PR. ### 02:28 - feature/monorepo-workspace Phase 2 @@ -19,10 +19,3 @@ - Files: .lefthook/pre-push/verify-version-functions.sh - Decisions: verify-version fix not in ecosystem repo yet (ecosystem files were identical to the broken version). Fix sourced from mcp-exec .lefthook/pre-push/verify-version-functions.sh. - Next: Awaiting CI checks, then auto-merge. - -### 03:50 - feature/monorepo-workspace Vite+ toolchain cleanup - -- Did: Removed biome, turbo, lefthook, npm-check-updates from root package.json (all replaced by vp). Added vp config (prepare script) for git hooks via .vite-hooks/. Created .vite-hooks/pre-commit (vp staged) and .vite-hooks/pre-push (scripts/verify-version.sh). Moved verify-version scripts from .lefthook/pre-push/ to scripts/. Added staged block to root vite.config.ts. Removed type-check scripts from root and packages/claude-cli/package.json (vp check with typeCheck: true covers this). Removed vp run type-check from CI workflows. Added vp install step and reordered vp check before vp test in both CI workflows. Added node-version: '24' to node.js.yml setup-vp step. Updated root scripts to use vp equivalents (vp lint, vp fmt, vp check, vp outdated, vp run -r). -- Files: vite.config.ts, .vite-hooks/pre-commit, .vite-hooks/pre-push, scripts/verify-version.sh, scripts/verify-version-functions.sh, package.json, packages/claude-cli/package.json, .github/workflows/node.js.yml, .github/workflows/npm-publish.yml -- Decisions: syncpack kept (no vp equivalent for cross-workspace version consistency). build.ts kept on esbuild (vite build.lib missing: inject, dropLabels, @shellicar plugins). vp check with typeCheck: true replaces tsc --noEmit via tsgolint (TypeScript Go toolchain). lefthook scripts lived in .lefthook/pre-push/ not scripts/; moved to scripts/ when lefthook was removed. -- Next: Awaiting CI on PR #172, then auto-merge. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5910ad9..76bfe37 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,13 +9,13 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: 'CodeQL Advanced' +name: "CodeQL Advanced" on: push: - branches: ['main'] + branches: [ "main" ] pull_request: - branches: ['main'] + branches: [ "main" ] schedule: - cron: '33 13 * * 3' @@ -53,39 +53,39 @@ jobs: # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: '/language:${{matrix.language}}' + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 5a11585..bc93413 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -2,29 +2,37 @@ name: Node.js CI on: push: - branches: ['main'] + branches: [ "main" ] pull_request: - branches: ['main'] + branches: [ "main" ] jobs: build: + runs-on: ubuntu-24.04 + strategy: + matrix: + node-version: [24.x] + steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install GitVersion - run: | - curl -sL https://github.com/GitTools/GitVersion/releases/download/6.6.0/gitversion-linux-x64-6.6.0.tar.gz -o /tmp/gitversion.tar.gz - tar xzf /tmp/gitversion.tar.gz -C /usr/local/bin gitversion - chmod +x /usr/local/bin/gitversion - - uses: voidzero-dev/setup-vp@v1 - with: - node-version: '24' - cache: true - - run: vp install - - run: vp pm audit - - run: vp check - - run: vp test - - run: vp run build + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install GitVersion + run: | + curl -sL https://github.com/GitTools/GitVersion/releases/download/6.6.0/gitversion-linux-x64-6.6.0.tar.gz -o /tmp/gitversion.tar.gz + tar xzf /tmp/gitversion.tar.gz -C /usr/local/bin gitversion + chmod +x /usr/local/bin/gitversion + - uses: pnpm/action-setup@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + - run: pnpm audit + - run: pnpm i --frozen-lockfile + - run: pnpm run --if-present build --only + - run: pnpm run --if-present type-check --only + - run: pnpm run --if-present test --only + - run: pnpm run ci diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 5946fc7..bff8c92 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,6 +1,3 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages - name: Node.js Package on: @@ -19,16 +16,30 @@ jobs: curl -sL https://github.com/GitTools/GitVersion/releases/download/6.6.0/gitversion-linux-x64-6.6.0.tar.gz -o /tmp/gitversion.tar.gz tar xzf /tmp/gitversion.tar.gz -C /usr/local/bin gitversion chmod +x /usr/local/bin/gitversion - - uses: voidzero-dev/setup-vp@v1 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - node-version: '24' - registry-url: 'https://registry.npmjs.org/' - cache: true - - run: vp install - - run: vp pm audit - - run: vp check - - run: vp test - - run: vp run build + node-version: 24 + registry-url: https://registry.npmjs.org/ + cache: 'pnpm' + - run: pnpm audit + - run: pnpm i --frozen-lockfile + - run: pnpm run --if-present build --only + - run: pnpm run --if-present type-check --only + - run: pnpm run --if-present test --only + - run: pnpm run ci + - name: Verify release tag matches package.json + run: | + cd "packages/$(cat .packagename)" + VERSION=$(node -p "require('./package.json').version") + RELEASE_TAG="${{ github.event.release.tag_name }}" + echo "package.json version: $VERSION" + echo "Release tag: $RELEASE_TAG" + if [ "$VERSION" != "$RELEASE_TAG" ]; then + echo "❌ Version mismatch! Release tag does not match package.json" + exit 1 + fi + echo "✅ Versions match" - run: | . ./scripts/get-npm-tag.sh cd "packages/$(cat .packagename)" @@ -42,6 +53,6 @@ jobs: fi echo "Publishing with tag: $TAG" - vp pm publish --ignore-scripts --no-git-checks --tag "$TAG" + pnpm publish --ignore-scripts --no-git-checks --tag "$TAG" env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/scripts/verify-version-functions.sh b/.lefthook/pre-push/verify-version-functions.sh similarity index 100% rename from scripts/verify-version-functions.sh rename to .lefthook/pre-push/verify-version-functions.sh diff --git a/.lefthook/pre-push/verify-version.sh b/.lefthook/pre-push/verify-version.sh new file mode 100755 index 0000000..c686546 --- /dev/null +++ b/.lefthook/pre-push/verify-version.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +SCRIPT_DIR=$(dirname "$0") +. "$SCRIPT_DIR/verify-version-functions.sh" + +main() { + local package_name=$(get_package_name) || exit $? + local full_version=$(get_full_version "$package_name") || exit $? + local changelog=$(get_changelog) || exit $? + local base_version=$(get_base_version "$full_version") + + check_version_header "$changelog" "$base_version" "$full_version" + check_version_links "$changelog" "$package_name" +} + +main diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit deleted file mode 100644 index 85fb65b..0000000 --- a/.vite-hooks/pre-commit +++ /dev/null @@ -1 +0,0 @@ -vp staged diff --git a/.vite-hooks/pre-push b/.vite-hooks/pre-push deleted file mode 100644 index 477706b..0000000 --- a/.vite-hooks/pre-push +++ /dev/null @@ -1 +0,0 @@ -sh scripts/verify-version.sh diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index adfdf83..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,88 +0,0 @@ - - -# Using Vite+, the Unified Toolchain for the Web - -This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, but it invokes Vite through `vp dev` and `vp build`. - -## Vite+ Workflow - -`vp` is a global binary that handles the full development lifecycle. Run `vp help` to print a list of commands and `vp --help` for information about a specific command. - -### Start - -- create - Create a new project from a template -- migrate - Migrate an existing project to Vite+ -- config - Configure hooks and agent integration -- staged - Run linters on staged files -- install (`i`) - Install dependencies -- env - Manage Node.js versions - -### Develop - -- dev - Run the development server -- check - Run format, lint, and TypeScript type checks -- lint - Lint code -- fmt - Format code -- test - Run tests - -### Execute - -- run - Run monorepo tasks -- exec - Execute a command from local `node_modules/.bin` -- dlx - Execute a package binary without installing it as a dependency -- cache - Manage the task cache - -### Build - -- build - Build for production -- pack - Build libraries -- preview - Preview production build - -### Manage Dependencies - -Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the `packageManager` field in `package.json` or package manager-specific lockfiles. - -- add - Add packages to dependencies -- remove (`rm`, `un`, `uninstall`) - Remove packages from dependencies -- update (`up`) - Update packages to latest versions -- dedupe - Deduplicate dependencies -- outdated - Check for outdated packages -- list (`ls`) - List installed packages -- why (`explain`) - Show why a package is installed -- info (`view`, `show`) - View package information from the registry -- link (`ln`) / unlink - Manage local package links -- pm - Forward a command to the package manager - -### Maintain - -- upgrade - Update `vp` itself to the latest version - -These commands map to their corresponding tools. For example, `vp dev --port 3000` runs Vite's dev server and works the same as Vite. `vp test` runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using `vp --version`. This is useful when researching documentation, features, and bugs. - -## Common Pitfalls - -- **Using the package manager directly:** Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations. -- **Always use Vite commands to run tools:** Don't attempt to run `vp vitest` or `vp oxlint`. They do not exist. Use `vp test` and `vp lint` instead. -- **Running scripts:** Vite+ built-in commands (`vp dev`, `vp build`, `vp test`, etc.) always run the Vite+ built-in tool, not any `package.json` script of the same name. To run a custom script that shares a name with a built-in command, use `vp run