From 985b24aabee20a7f51e4b2dac597df103937425e Mon Sep 17 00:00:00 2001 From: Logan Lindquist Land Date: Wed, 3 Jun 2026 08:58:04 -0500 Subject: [PATCH] chore: remove beads integration and legacy files Delete the Beads backend (the .beads directory, hooks, backups, and metadata) and prune associated exports to simplify the repository. Update CLI help and tests to reflect removal, adding coverage-path and force flags and aligning tests accordingly. Bump dependencies (Biome to 2.4.16 and bun types to 1.3.14) and refresh lockfiles and package.json to ensure reproducible builds. Improve vitest config handling by adding a stripCommentsAndStrings utility to reliably ignore coverage directives inside comments or string literals. --- .beads/.gitignore | 52 -------------- .beads/README.md | 115 ------------------------------- .beads/backup/backup_state.json | 13 ---- .beads/backup/comments.jsonl | 0 .beads/backup/config.jsonl | 11 --- .beads/backup/dependencies.jsonl | 32 --------- .beads/backup/events.jsonl | 47 ------------- .beads/backup/issues.jsonl | 47 ------------- .beads/backup/labels.jsonl | 0 .beads/config.yaml | 62 ----------------- .beads/hooks/post-checkout | 9 --- .beads/hooks/post-merge | 9 --- .beads/hooks/pre-commit | 9 --- .beads/hooks/pre-push | 9 --- .beads/hooks/prepare-commit-msg | 9 --- .beads/interactions.jsonl | 0 .beads/issues.jsonl | 47 ------------- .beads/metadata.json | 6 -- .gitattributes | 3 - .gitignore | 28 +++++--- bun.lock | 26 +++---- package.json | 4 +- src/cli.ts | 18 ++--- src/commands/index.ts | 7 -- src/lib/config.ts | 21 +++++- src/lib/coverage.ts | 9 ++- src/lib/detect.ts | 26 ++++--- src/lib/index.ts | 9 --- src/lib/vitest-config.ts | 36 +++++++++- src/utils/index.ts | 8 --- test/cli.test.ts | 2 + test/lib/config.test.ts | 92 ++++++++++++++++++++++++- test/lib/coverage.test.ts | 34 +++++++++ test/lib/detect.test.ts | 90 +++++++++++++++++++++--- test/lib/vitest-config.test.ts | 97 ++++++++++++++++++++++++++ 35 files changed, 421 insertions(+), 566 deletions(-) delete mode 100644 .beads/.gitignore delete mode 100644 .beads/README.md delete mode 100644 .beads/backup/backup_state.json delete mode 100644 .beads/backup/comments.jsonl delete mode 100644 .beads/backup/config.jsonl delete mode 100644 .beads/backup/dependencies.jsonl delete mode 100644 .beads/backup/events.jsonl delete mode 100644 .beads/backup/issues.jsonl delete mode 100644 .beads/backup/labels.jsonl delete mode 100644 .beads/config.yaml delete mode 100755 .beads/hooks/post-checkout delete mode 100755 .beads/hooks/post-merge delete mode 100755 .beads/hooks/pre-commit delete mode 100755 .beads/hooks/pre-push delete mode 100755 .beads/hooks/prepare-commit-msg delete mode 100644 .beads/interactions.jsonl delete mode 100644 .beads/issues.jsonl delete mode 100644 .beads/metadata.json delete mode 100644 .gitattributes delete mode 100644 src/commands/index.ts delete mode 100644 src/lib/index.ts delete mode 100644 src/utils/index.ts diff --git a/.beads/.gitignore b/.beads/.gitignore deleted file mode 100644 index ffb2317..0000000 --- a/.beads/.gitignore +++ /dev/null @@ -1,52 +0,0 @@ -# Dolt database (managed by Dolt, not git) -dolt/ -dolt-access.lock - -# Runtime files -bd.sock -bd.sock.startlock -sync-state.json -last-touched - -# Local version tracking (prevents upgrade notification spam after git ops) -.local_version - -# Worktree redirect file (contains relative path to main repo's .beads/) -# Must not be committed as paths would be wrong in other clones -redirect - -# Sync state (local-only, per-machine) -# These files are machine-specific and should not be shared across clones -.sync.lock -export-state/ - -# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) -ephemeral.sqlite3 -ephemeral.sqlite3-journal -ephemeral.sqlite3-wal -ephemeral.sqlite3-shm - -# Dolt server management (auto-started by bd) -dolt-server.pid -dolt-server.log -dolt-server.lock -dolt-server.port -dolt-server.activity -dolt-monitor.pid - -# Legacy files (from pre-Dolt versions) -*.db -*.db?* -*.db-journal -*.db-wal -*.db-shm -db.sqlite -bd.db -daemon.lock -daemon.log -daemon-*.log.gz -daemon.pid -# NOTE: Do NOT add negation patterns here. -# They would override fork protection in .git/info/exclude. -# Config files (metadata.json, config.yaml) are tracked by git by default -# since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md deleted file mode 100644 index 2424304..0000000 --- a/.beads/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# Beads - AI-Native Issue Tracking - -Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. - -## What is Beads? - -Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. - -**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) - -## Quick Start - -### Essential Commands - -```bash -# Create new issues -bd create "Add user authentication" - -# View all issues -bd list - -# View issue details -bd show - -# Update issue status -bd update --status in_progress -bd update --status done - -# Sync with git remote -bd sync -``` - -### Working with Issues - -Issues in Beads are: -- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code -- **AI-friendly**: CLI-first design works perfectly with AI coding agents -- **Branch-aware**: Issues can follow your branch workflow -- **Always in sync**: Auto-syncs with your commits - -## Why Beads? - -✨ **AI-Native Design** -- Built specifically for AI-assisted development workflows -- CLI-first interface works seamlessly with AI coding agents -- No context switching to web UIs - -šŸš€ **Developer Focused** -- Issues live in your repo, right next to your code -- Works offline, syncs when you push -- Fast, lightweight, and stays out of your way - -šŸ”§ **Git Integration** -- Automatic sync with git commits -- Branch-aware issue tracking -- Intelligent JSONL merge resolution - -## Get Started with Beads - -Try Beads in your own projects: - -```bash -# Install Beads -curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash - -# Initialize in your repo -bd init - -# Create your first issue -bd create "Try out Beads" -``` - -## Dolt Backend - -Beads uses [Dolt](https://www.dolthub.com/) as its database engine. Dolt must be installed for beads to work. - -```bash -# Install Dolt -brew install dolt -``` - -The `.beads/` directory contains: -- **Dolt database** (the `dolt/` directory) -- **`issues.jsonl`** — JSONL backup of all issues (portable, used for recovery) -- **`dolt-server.lock`** — PID file for the Dolt SQL server daemon - -### Recovery / Re-importing Issues - -If beads stops working (e.g., "no beads database found" after a fresh clone, Dolt reinstall, or corrupted state), re-import from the JSONL backup: - -```bash -# Re-initialize and import all issues from JSONL backup -bd init --force --prefix --from-jsonl - -# For this project: -bd init --force --prefix uncov --from-jsonl -``` - -This reads `.beads/issues.jsonl` and rebuilds the Dolt database. The `--force` flag is required when `.beads/` already exists. - -### Stale Daemon - -If you see connection errors, the Dolt server daemon may have a stale PID. Check and kill it: - -```bash -cat .beads/dolt-server.lock # Shows the PID -kill # Kill stale process -bd list # Will restart the daemon automatically -``` - -## Learn More - -- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) -- **Quick Start Guide**: Run `bd quickstart` -- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) diff --git a/.beads/backup/backup_state.json b/.beads/backup/backup_state.json deleted file mode 100644 index b4c3f07..0000000 --- a/.beads/backup/backup_state.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "last_dolt_commit": "ce2k10ue6864cd9gc024iqh08r99nhv6", - "last_event_id": 0, - "timestamp": "2026-03-11T20:20:20.327917Z", - "counts": { - "issues": 47, - "events": 47, - "comments": 0, - "dependencies": 32, - "labels": 0, - "config": 11 - } -} \ No newline at end of file diff --git a/.beads/backup/comments.jsonl b/.beads/backup/comments.jsonl deleted file mode 100644 index e69de29..0000000 diff --git a/.beads/backup/config.jsonl b/.beads/backup/config.jsonl deleted file mode 100644 index d105a1b..0000000 --- a/.beads/backup/config.jsonl +++ /dev/null @@ -1,11 +0,0 @@ -{"key":"auto_compact_enabled","value":"false"} -{"key":"compact_batch_size","value":"50"} -{"key":"compact_parallel_workers","value":"5"} -{"key":"compact_tier1_days","value":"30"} -{"key":"compact_tier1_dep_levels","value":"2"} -{"key":"compact_tier2_commits","value":"100"} -{"key":"compact_tier2_days","value":"90"} -{"key":"compact_tier2_dep_levels","value":"5"} -{"key":"compaction_enabled","value":"false"} -{"key":"issue_prefix","value":"uncov"} -{"key":"schema_version","value":"6"} diff --git a/.beads/backup/dependencies.jsonl b/.beads/backup/dependencies.jsonl deleted file mode 100644 index a2f8911..0000000 --- a/.beads/backup/dependencies.jsonl +++ /dev/null @@ -1,32 +0,0 @@ -{"created_at":"2026-02-03T02:56:18Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-kah","issue_id":"uncov-0lq","type":"blocks"} -{"created_at":"2026-02-03T02:56:18Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-kah","issue_id":"uncov-0ou","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-fn0","issue_id":"uncov-2bo","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-mv8","issue_id":"uncov-2bo","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ocy","issue_id":"uncov-2bo","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-s2m","issue_id":"uncov-2bo","type":"blocks"} -{"created_at":"2026-02-03T02:56:16Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-brm","issue_id":"uncov-38c","type":"blocks"} -{"created_at":"2026-02-03T02:56:16Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ocy","issue_id":"uncov-38c","type":"blocks"} -{"created_at":"2026-02-03T02:56:16Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-p0r","issue_id":"uncov-38c","type":"blocks"} -{"created_at":"2026-02-03T02:56:16Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-q77","issue_id":"uncov-38c","type":"blocks"} -{"created_at":"2026-02-03T02:56:18Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-38c","issue_id":"uncov-b90","type":"blocks"} -{"created_at":"2026-02-03T02:56:08Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-brm","type":"blocks"} -{"created_at":"2026-02-03T02:56:08Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ed6","issue_id":"uncov-brm","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-brm","issue_id":"uncov-e27","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-mv8","issue_id":"uncov-e27","type":"blocks"} -{"created_at":"2026-02-03T02:56:18Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ocy","issue_id":"uncov-e27","type":"blocks"} -{"created_at":"2026-02-03T02:56:17Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-p0r","issue_id":"uncov-e27","type":"blocks"} -{"created_at":"2026-02-03T02:56:07Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-ed6","type":"blocks"} -{"created_at":"2026-02-03T02:56:09Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-fn0","type":"blocks"} -{"created_at":"2026-02-03T02:56:09Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ed6","issue_id":"uncov-fn0","type":"blocks"} -{"created_at":"2026-02-03T02:56:18Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-b90","issue_id":"uncov-kah","type":"blocks"} -{"created_at":"2026-02-03T02:56:08Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-mv8","type":"blocks"} -{"created_at":"2026-02-03T02:56:09Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ed6","issue_id":"uncov-mv8","type":"blocks"} -{"created_at":"2026-02-03T02:56:07Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-ocy","type":"blocks"} -{"created_at":"2026-02-03T02:56:08Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-p0r","type":"blocks"} -{"created_at":"2026-02-03T02:56:08Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ed6","issue_id":"uncov-p0r","type":"blocks"} -{"created_at":"2026-02-03T02:56:08Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-qu3","issue_id":"uncov-p0r","type":"blocks"} -{"created_at":"2026-02-03T02:56:18Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-38c","issue_id":"uncov-p2k","type":"blocks"} -{"created_at":"2026-02-03T02:56:07Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-q77","type":"blocks"} -{"created_at":"2026-02-03T02:56:07Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-qu3","type":"blocks"} -{"created_at":"2026-02-03T02:56:09Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-3ky","issue_id":"uncov-s2m","type":"blocks"} -{"created_at":"2026-02-03T02:56:09Z","created_by":"Logan Lindquist Land","depends_on_id":"uncov-ed6","issue_id":"uncov-s2m","type":"blocks"} diff --git a/.beads/backup/events.jsonl b/.beads/backup/events.jsonl deleted file mode 100644 index 7d4a50c..0000000 --- a/.beads/backup/events.jsonl +++ /dev/null @@ -1,47 +0,0 @@ -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":1,"issue_id":"uncov-0lq","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":2,"issue_id":"uncov-0ou","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":3,"issue_id":"uncov-0xe","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":4,"issue_id":"uncov-17j","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":5,"issue_id":"uncov-19b","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":6,"issue_id":"uncov-1e5","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":7,"issue_id":"uncov-1yi","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":8,"issue_id":"uncov-2bo","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":9,"issue_id":"uncov-2ur","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":10,"issue_id":"uncov-38c","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":11,"issue_id":"uncov-38p","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":12,"issue_id":"uncov-3ky","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":13,"issue_id":"uncov-3te","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":14,"issue_id":"uncov-4bm","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":15,"issue_id":"uncov-4du","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":16,"issue_id":"uncov-54t","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":17,"issue_id":"uncov-5yk","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":18,"issue_id":"uncov-67r","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":19,"issue_id":"uncov-6no","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":20,"issue_id":"uncov-77g","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":21,"issue_id":"uncov-a4f","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":22,"issue_id":"uncov-b90","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":23,"issue_id":"uncov-bq5","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":24,"issue_id":"uncov-brm","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":25,"issue_id":"uncov-cgy","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":26,"issue_id":"uncov-e27","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":27,"issue_id":"uncov-ed6","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":28,"issue_id":"uncov-efk","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":29,"issue_id":"uncov-ejy","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":30,"issue_id":"uncov-eqy","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":31,"issue_id":"uncov-fn0","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":32,"issue_id":"uncov-iz5","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":33,"issue_id":"uncov-kah","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":34,"issue_id":"uncov-l2y","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":35,"issue_id":"uncov-mc5","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":36,"issue_id":"uncov-mv8","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":37,"issue_id":"uncov-o9j","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":38,"issue_id":"uncov-ocy","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":39,"issue_id":"uncov-p0r","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":40,"issue_id":"uncov-p2k","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":41,"issue_id":"uncov-q77","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":42,"issue_id":"uncov-qu3","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":43,"issue_id":"uncov-r6p","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":44,"issue_id":"uncov-s2m","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":45,"issue_id":"uncov-tr8","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":46,"issue_id":"uncov-uar","new_value":"","old_value":""} -{"actor":"Logan Lindquist Land","comment":null,"created_at":"2026-03-11T15:20:13Z","event_type":"created","id":47,"issue_id":"uncov-xg6","new_value":"","old_value":""} diff --git a/.beads/backup/issues.jsonl b/.beads/backup/issues.jsonl deleted file mode 100644 index 6cbb955..0000000 --- a/.beads/backup/issues.jsonl +++ /dev/null @@ -1,47 +0,0 @@ -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"install.sh implemented with all security fixes, passed code review","closed_at":"2026-02-03T14:19:50Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d503440106ec0d45d8976ef97277d42098ef578ab4fa302ba48b85d6d7bd7d2d","created_at":"2026-02-03T02:55:05Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create install.sh for curl-based installation.\n\n**Usage:**\n```bash\ncurl -fsSL https://raw.githubusercontent.com/llbbl/uncov/main/install.sh | bash\n```\n\n**Script Behavior:**\n1. Detect OS and architecture\n2. Download appropriate binary from GitHub releases\n3. Install to ~/.local/bin or /usr/local/bin\n4. Verify binary works (uncov --version)\n5. Print success message with PATH instructions if needed\n\n**Platform Detection:**\n```bash\nOS=$(uname -s | tr '[:upper:]' '[:lower:]')\nARCH=$(uname -m)\n# Map to our naming: darwin-arm64, darwin-x64, linux-x64\n```\n\n**Installation Locations:**\n1. If ~/.local/bin exists and is in PATH → install there\n2. Else if user has write access to /usr/local/bin → install there\n3. Else → install to ~/.local/bin and warn about PATH\n\n**Error Handling:**\n- Unsupported platform → clear error message\n- Download failed → retry once, then fail with URL\n- Checksum mismatch → fail with warning\n\n**Success Output:**\n```\nDetected: darwin-arm64\nDownloading uncov v0.1.0...\nInstalling to /usr/local/bin/uncov...\nāœ“ uncov v0.1.0 installed successfully\\!\n\nRun 'uncov --help' to get started.\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-0lq","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create install.sh script for easy binary installation","updated_at":"2026-02-03T14:19:50Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Homebrew tap scaffolded at github.com/llbbl/homebrew-uncov","closed_at":"2026-02-03T14:37:58Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"eab4fbc946ac3545887a7d383a741f6df835048042506f37bdceb4c90a62d25e","created_at":"2026-02-03T02:55:28Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create Homebrew tap repository for easy macOS installation (v0.3.0 goal).\n\n**Repository:** homebrew-uncov (separate repo)\n\n**Formula: uncov.rb**\n```ruby\nclass Uncov \u003c Formula\n desc \"CLI tool to report files with low test coverage\"\n homepage \"https://github.com/llbbl/uncov\"\n version \"0.1.0\"\n\n on_macos do\n if Hardware::CPU.arm?\n url \"https://github.com/llbbl/uncov/releases/download/v0.1.0/uncov-darwin-arm64\"\n sha256 \"\u003csha256\u003e\"\n else\n url \"https://github.com/llbbl/uncov/releases/download/v0.1.0/uncov-darwin-x64\"\n sha256 \"\u003csha256\u003e\"\n end\n end\n\n on_linux do\n url \"https://github.com/llbbl/uncov/releases/download/v0.1.0/uncov-linux-x64\"\n sha256 \"\u003csha256\u003e\"\n end\n\n def install\n bin.install \"uncov-darwin-arm64\" =\u003e \"uncov\" if OS.mac? \u0026\u0026 Hardware::CPU.arm?\n bin.install \"uncov-darwin-x64\" =\u003e \"uncov\" if OS.mac? \u0026\u0026 !Hardware::CPU.arm?\n bin.install \"uncov-linux-x64\" =\u003e \"uncov\" if OS.linux?\n end\n\n test do\n assert_match \"uncov\", shell_output(\"#{bin}/uncov --version\")\n end\nend\n```\n\n**Installation:**\n```bash\nbrew tap llbbl/uncov\nbrew install uncov\n```\n\n**Automation:**\n- Update formula SHA256 on each release\n- Can be automated via GitHub Actions","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-0ou","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":4,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create Homebrew tap for uncov distribution","updated_at":"2026-02-03T14:37:58Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:47:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7055923bf40de0094c6dd765c75abf498126369fbcaf876103cc02ce100f7f5e","created_at":"2026-02-03T03:43:40Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Low\n**Location:** src/cli.ts line 55-56\n\n**Issue:** Default coverage path 'coverage/coverage-summary.json' is a magic string.\n\n**The Fix:**\n```typescript\n// At top of file or in constants\nconst DEFAULT_COVERAGE_PATH = 'coverage/coverage-summary.json';\n\n// In parseArgs options:\n'coverage-path': {\n type: 'string',\n short: 'c',\n default: DEFAULT_COVERAGE_PATH,\n},\n```\n\n**Acceptance Criteria:**\n- Default path is a named constant\n- Constant is exported for use by other modules if needed","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-0xe","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Extract default coverage path to constant","updated_at":"2026-02-03T03:47:20Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:21:14Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e9dfb1333198798d9b718a11a2670f84eeff777b213dce387f2a2651547117f9","created_at":"2026-02-03T03:16:17Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** The build script uses @ts-expect-error to suppress a type issue with Bun.build compile option. Should use CLI approach instead.\n\n**Location:** scripts/build.ts lines 39-40\n\n**The Fix:**\nUse CLI via Bun.spawn instead of Bun.build API:\n```typescript\nasync function build(): Promise\u003cvoid\u003e {\n const distDir = './dist';\n await mkdir(distDir, { recursive: true });\n\n console.log('Building uncov for all platforms...\\n');\n\n for (const { target, outfile } of TARGETS) {\n console.log(`Building ${target}...`);\n \n const proc = Bun.spawn([\n 'bun', 'build', '--compile',\n '--target', target,\n '--outfile', `${distDir}/${outfile}`,\n './src/cli.ts'\n ], {\n stdout: 'inherit',\n stderr: 'inherit',\n });\n \n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n console.error(`Failed to build ${target}`);\n process.exit(1);\n }\n \n console.log(` -\u003e ${distDir}/${outfile}`);\n }\n\n console.log('\\nBuild complete!');\n}\n```\n\n**Acceptance Criteria:**\n- No @ts-expect-error in build script\n- `bun run scripts/build.ts` works correctly\n- Build produces binaries in dist/","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-17j","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Build script uses @ts-expect-error for compile option","updated_at":"2026-02-03T03:21:14Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"44dcfb462552b226c5fc9f7494b731e877a3de185dd7d4a83aa2c87715673d2c","created_at":"2026-02-03T13:02:41Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Low\n**Location:** src/lib/config.ts (validatePartialConfig function)\n\n**Issue:** Validates threshold is a number but not within 0-100 range. Could accept -5 or 500.\n\n**The Fix:**\n```typescript\nif (typeof obj.threshold === 'number' \u0026\u0026 obj.threshold \u003e= 0 \u0026\u0026 obj.threshold \u003c= 100) {\n result.threshold = obj.threshold;\n}\n```\n\n**Acceptance Criteria:**\n- Threshold validated to 0-100 range\n- Invalid thresholds are ignored (use default)\n- Add tests for out-of-range values","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-19b","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Add threshold range validation in config","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:46:28Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"81a2bb5ec8eafde8a6bb347d7a259c161158e76e0f3e9e2b72d0802f2d326eeb","created_at":"2026-02-03T03:43:39Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Critical\n**Location:** src/utils/fs.ts (all file operations)\n\n**Issue:** File utilities accept arbitrary paths without validation. Could allow reading/writing files outside project directory via path traversal (e.g., `../../../etc/passwd`).\n\n**The Fix:**\n```typescript\nimport { resolve, relative, isAbsolute } from 'node:path';\n\nexport function validatePath(targetPath: string, baseDir: string = process.cwd()): string {\n const resolved = resolve(baseDir, targetPath);\n const relativePath = relative(baseDir, resolved);\n \n if (relativePath.startsWith('..') || isAbsolute(relativePath)) {\n throw new Error(`Path \"${targetPath}\" is outside allowed directory`);\n }\n \n return resolved;\n}\n```\n\nApply validation to readJson, writeJson, readText, writeText, appendLine.\n\n**Acceptance Criteria:**\n- validatePath function exists and is tested\n- File operations validate paths when baseDir is provided\n- Attempting to access `../../../etc/passwd` throws error\n- Tests cover path traversal attempts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-1e5","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Critical: Add path validation to prevent directory traversal","updated_at":"2026-02-03T03:46:28Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"bc3545fa7726c3c5bff639059c45bfe43e0037b33f48e671d6069ade6f5a2c9d","created_at":"2026-02-03T13:32:32Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Add --verbose flag to show detailed information for troubleshooting.\n\n**Usage:**\n```bash\nuncov --verbose\nuncov init --verbose\nuncov check --verbose\n```\n\n**Verbose Output Examples:**\n\nFor report command:\n```\n[verbose] Loading config from /project\n[verbose] Config sources: CLI overrides, package.json\n[verbose] Final config: { threshold: 10, coveragePath: 'coverage/coverage-summary.json' }\n[verbose] Reading coverage from: /project/coverage/coverage-summary.json\n[verbose] Found 15 files in coverage data\n[verbose] Filtered to 3 files at or below 10% threshold\n\nFiles at or below 10% line coverage: 3\n...\n```\n\nFor init command:\n```\n[verbose] Working directory: /project\n[verbose] Checking for lockfiles...\n[verbose] Found: pnpm-lock.yaml\n[verbose] Detected package manager: pnpm\n[verbose] Reading package.json from: /project/package.json\n[verbose] Existing scripts: dev, build, test\n...\n```\n\n**Implementation:**\n1. Add 'verbose' to CLI parseArgs options (boolean, short: 'V')\n2. Create src/utils/logger.ts with verbose logging utility\n3. Pass verbose flag through to commands\n4. Add [verbose] prefixed logs at key decision points\n\n**Logger Utility:**\n```typescript\nexport function createLogger(verbose: boolean) {\n return {\n verbose: (msg: string) =\u003e verbose \u0026\u0026 console.error(`[verbose] ${msg}`),\n info: (msg: string) =\u003e console.log(msg),\n error: (msg: string) =\u003e console.error(msg),\n };\n}\n```\n\n**Note:** Verbose output goes to stderr so it doesn't interfere with --json output.\n\n**Tests:** Verify verbose flag produces expected output","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-1yi","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add --verbose flag for debug output","updated_at":"2026-02-03T13:43:02Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:18:54Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"afabe8016c4fabd68623c2559eef2a534588adf027d5210e686ef35aedfdd152","created_at":"2026-02-03T02:53:10Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/commands/init.ts - the `uncov init` command for v0.2.0.\n\n**CLI Flags:**\n- `--force` - Overwrite existing configuration\n\n**Interactive Flow:**\n1. Detect package manager (pnpm \u003e bun \u003e npm \u003e yarn)\n2. Check for existing vitest config\n3. If exists and no --force: prompt to add coverage config\n4. If doesn't exist: create new vitest.config.ts\n5. Add scripts to package.json\n6. Add coverage/ to .gitignore\n\n**Actions Performed:**\n1. **Detect environment**\n - Package manager from lockfiles\n - Existing vitest/vite config\n\n2. **Update package.json**\n - Add `test:coverage`: 'vitest run --coverage'\n - Add `coverage:low`: 'uncov'\n - Skip existing scripts (report which)\n\n3. **Configure vitest**\n - Create or update vitest.config.ts with:\n ```typescript\n coverage: {\n provider: 'v8',\n reporter: ['text', 'json-summary'],\n reportsDirectory: './coverage',\n }\n ```\n\n4. **Update .gitignore**\n - Add 'coverage/' if not present\n\n**Output:**\n```\nāœ“ Detected package manager: pnpm\nāœ“ Added script: test:coverage\nāœ“ Added script: coverage:low\nāœ“ Updated vitest.config.ts with coverage settings\nāœ“ Added coverage/ to .gitignore\n\nRun 'pnpm test:coverage' to generate coverage, then 'uncov' to see low coverage files.\n```\n\n**Error Handling:**\n- No package.json → error with instructions\n- Existing coverage config without --force → warn and skip\n\n**Tests Required:** test/commands/init.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-2bo","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement init command: bootstrap coverage configuration","updated_at":"2026-02-03T13:18:54Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:46:28Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5869cf4d2b7383959d8b60be29af89da6ffbb0af166c3aed38ef2e1ce01f3f13","created_at":"2026-02-03T03:43:39Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Critical\n**Location:** src/utils/fs.ts line 29, src/lib/coverage.ts\n\n**Issue:** `readJson\u003cT\u003e` uses `as T` type assertion without runtime validation. Malformed coverage-summary.json could cause runtime errors or security issues.\n\n**The Fix:**\nAdd validation functions in src/lib/coverage.ts:\n```typescript\nfunction isValidCoverageMetric(obj: unknown): obj is CoverageMetric {\n if (typeof obj \\!== 'object' || obj === null) return false;\n const metric = obj as Record\u003cstring, unknown\u003e;\n return (\n typeof metric.total === 'number' \u0026\u0026\n typeof metric.covered === 'number' \u0026\u0026\n typeof metric.pct === 'number'\n );\n}\n\nfunction isValidFileCoverage(obj: unknown): obj is FileCoverage {\n if (typeof obj \\!== 'object' || obj === null) return false;\n const coverage = obj as Record\u003cstring, unknown\u003e;\n return isValidCoverageMetric(coverage.lines);\n}\n\nexport function parseCoverageSummary(data: unknown): CoverageSummary {\n if (typeof data \\!== 'object' || data === null) {\n throw new Error('Coverage summary must be an object');\n }\n \n const summary = data as Record\u003cstring, unknown\u003e;\n \n if (\\!isValidFileCoverage(summary.total)) {\n throw new Error('Coverage summary missing valid \"total\" field');\n }\n \n return summary as CoverageSummary;\n}\n```\n\n**Acceptance Criteria:**\n- parseCoverageSummary validates structure at runtime\n- Invalid coverage data throws descriptive errors\n- coverage-summary-missing-fields.json fixture is used in tests\n- Tests verify error messages for various invalid inputs","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-2ur","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Critical: Add runtime validation for parsed JSON coverage data","updated_at":"2026-02-03T03:46:28Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:18:54Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"2bc216af54f4b6318bec86fc5de838c87dedaf084ce3dce7d37ca7a633ae8fd8","created_at":"2026-02-03T02:52:30Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/commands/report.ts - the default command when running `uncov`.\n\n**CLI Flags:**\n- `--threshold \u003cn\u003e` (default: 10) - Show files at or below this line coverage %\n- `--fail` - Exit code 1 if any files below threshold\n- `--json` - Output JSON instead of table\n- `--coverage-path \u003cpath\u003e` (default: coverage/coverage-summary.json)\n\n**Implementation:**\n1. Parse CLI args using util.parseArgs\n2. Read coverage-summary.json via coverage.ts\n3. Filter files at or below threshold\n4. Sort by percentage ascending\n5. Format and output results\n6. Exit with appropriate code\n\n**Exit Codes:**\n- 0: Success (or low coverage found but --fail not set)\n- 1: Low coverage found AND --fail set\n- 2: Missing coverage file or config error\n\n**Output Formats:**\nTable (default):\n```\nFiles at or below 10% line coverage: 3\n\n 0.00% LH 0/LF 120 src/renderer.ts\n 5.26% LH 2/LF 38 src/bootstrap.ts\n 10.00% LH 10/LF 100 src/engine.ts\n```\n\nJSON (--json):\n```json\n[{\"path\": \"src/renderer.ts\", \"percentage\": 0, \"linesHit\": 0, \"linesFound\": 120}, ...]\n```\n\n**Tests Required:** test/commands/report.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-38c","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement report command: default CLI command for coverage reporting","updated_at":"2026-02-03T13:18:54Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"67bca8223ec4e1d49f8abda9c9df3150f573a537699fd4dc7a416df84e431f28","created_at":"2026-02-03T13:02:40Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium\n**Location:** src/lib/detect.ts (lines 58-67) and src/lib/vitest-config.ts (lines 11-20)\n\n**Issue:** Both files define the same list of config files to check (vitest.config.ts, vite.config.ts, etc.). Violates DRY.\n\n**The Fix:**\nCreate src/lib/constants.ts:\n```typescript\nexport const VITEST_CONFIG_FILES = [\n { file: 'vitest.config.ts', type: 'ts' },\n { file: 'vitest.config.js', type: 'js' },\n { file: 'vitest.config.mts', type: 'mts' },\n { file: 'vitest.config.mjs', type: 'mjs' },\n { file: 'vite.config.ts', type: 'ts' },\n { file: 'vite.config.js', type: 'js' },\n { file: 'vite.config.mts', type: 'mts' },\n { file: 'vite.config.mjs', type: 'mjs' },\n] as const;\n\nexport type VitestConfigType = (typeof VITEST_CONFIG_FILES)[number]['type'];\n```\n\nUpdate detect.ts and vitest-config.ts to import from constants.ts.\n\n**Acceptance Criteria:**\n- Constants extracted to shared module\n- Both files import from constants\n- No duplicate config lists\n- Tests still pass","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-38p","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Extract shared vitest config file list to constants","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:21:14Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"40dd97d0a3dce83fa90f2457641f783026c411c4b1a40c16ca83e2adc8e2a42f","created_at":"2026-02-03T02:52:05Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create the initial project structure as defined in the spec:\n\n**Directory Structure:**\n- src/cli.ts - Entry point with arg parsing\n- src/commands/ - Command implementations directory\n- src/lib/ - Core logic directory\n- src/utils/ - Helper utilities directory\n- test/ - Test directory\n- test/fixtures/ - Sample coverage files for testing\n- scripts/ - Build scripts directory\n\n**Base Files to Create:**\n- src/cli.ts with basic structure (parseArgs setup, command routing skeleton)\n- Export barrel files where needed\n\n**Acceptance Criteria:**\n- All directories exist\n- cli.ts has basic structure with parseArgs from util\n- Project compiles with `bun build`","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-3ky","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Project scaffolding: create src directory structure and base files","updated_at":"2026-02-03T03:21:14Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"19a34f92d4d2285007904eaabba0e4d850dcbfc8508bdaf4419bc03848551ed3","created_at":"2026-02-03T13:02:40Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium\n**Location:** src/lib/vitest-config.ts (lines 43-65)\n\n**Issue:** Regex-based detection is overly broad. Matches any 'coverage' string including comments, imports, or unrelated variables. Can produce false positives.\n\n**The Fix:**\nImprove detection to look for actual coverage config blocks:\n```typescript\nexport function hasCoverageConfig(configPath: string): boolean {\n if (!fileExists(configPath)) {\n return false;\n }\n\n try {\n const content = readText(configPath);\n \n // Primary check: explicit coverage configuration block\n const hasCoverageBlock = /coverage\\s*:\\s*\\{/.test(content);\n if (hasCoverageBlock) {\n return true;\n }\n \n // Secondary check: coverage property assignment\n const hasCoverageProperty = /coverage\\s*:\\s*[a-zA-Z]/.test(content);\n return hasCoverageProperty;\n } catch {\n return false;\n }\n}\n```\n\nAdd JSDoc explaining heuristic nature.\n\n**Acceptance Criteria:**\n- More accurate detection of coverage config\n- JSDoc documents limitations\n- Tests updated for new behavior","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-3te","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Improve hasCoverageConfig detection accuracy","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a2014c300473d0fb273ac4f604ac9d23cc5ecf382e5b69830debe72f465e0584","created_at":"2026-02-03T13:32:29Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Add ANSI color codes to improve CLI readability. Zero dependencies - use escape codes directly.\n\n**Colors to Add:**\n- Red: Low coverage files, errors, failed checks\n- Green: Success messages, passing checks, files above threshold\n- Yellow: Warnings, skipped items, threshold boundary\n- Cyan: File paths, informational headers\n- Bold: Section headers, important values\n\n**Implementation:**\n\nCreate src/utils/colors.ts:\n```typescript\nconst isColorSupported = process.stdout.isTTY \u0026\u0026 !process.env.NO_COLOR;\n\nexport const colors = {\n red: (s: string) =\u003e isColorSupported ? `\\x1b[31m${s}\\x1b[0m` : s,\n green: (s: string) =\u003e isColorSupported ? `\\x1b[32m${s}\\x1b[0m` : s,\n yellow: (s: string) =\u003e isColorSupported ? `\\x1b[33m${s}\\x1b[0m` : s,\n cyan: (s: string) =\u003e isColorSupported ? `\\x1b[36m${s}\\x1b[0m` : s,\n bold: (s: string) =\u003e isColorSupported ? `\\x1b[1m${s}\\x1b[0m` : s,\n};\n```\n\n**Apply to:**\n- report command: Red for 0% files, yellow for low coverage, green header when no issues\n- init command: Green checkmarks, yellow for skipped, red for errors\n- check command: Green āœ“ for pass, red āœ— for fail\n\n**Standards:**\n- Respect NO_COLOR env var (https://no-color.org/)\n- Detect TTY to disable colors when piped\n- Add --no-color flag as override\n\n**Tests:** test/utils/colors.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-4bm","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add colorized terminal output with TTY detection","updated_at":"2026-02-03T13:43:01Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:56:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"07c5b47122ef28ffad9c7b2088382d16213c297eb07e4f0b9294bf2cebfbb22e","created_at":"2026-02-03T13:53:36Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium (Performance)\n\nAdd Bun dependency caching to speed up CI:\n\n```yaml\n- name: Cache Bun dependencies\n uses: actions/cache@v4\n with:\n path: ~/.bun/install/cache\n key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}\n restore-keys: |\n ${{ runner.os }}-bun-\n```\n\nAdd this before the 'Install dependencies' step in both test and build jobs.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-4du","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Add dependency caching to CI workflow","updated_at":"2026-02-03T13:56:51Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"85d082d98a04e1b15a6289b29bed524a6199c021bceb1775d782766e6eae026a","created_at":"2026-02-03T13:02:41Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Low\n**Location:** src/lib/detect.ts (detectGitignore function)\n\n**Issue:** Only matches exact patterns. Misses valid gitignore patterns like 'coverage*', '**/coverage', 'coverage/**'.\n\n**The Fix:**\nExpand pattern matching:\n```typescript\nconst hasCoverage = lines.some((line) =\u003e {\n if (line.startsWith('#') || line === '') {\n return false;\n }\n // Match common coverage patterns\n return /^[/*]*coverage[/*]*$/.test(line) || \n line.startsWith('coverage') ||\n line.endsWith('/coverage') ||\n line.endsWith('/coverage/');\n});\n```\n\n**Acceptance Criteria:**\n- Matches coverage*, **/coverage, coverage/** patterns\n- Add tests for glob patterns","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-54t","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Improve gitignore pattern matching","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"9ebd1e73797b0bbf2c3d59405d125ccdfab9485b67a5ec6542735a10d1410b5f","created_at":"2026-02-03T13:41:48Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium\n\nAdd test coverage for --verbose flag in check and report commands.\n\nFor test/commands/check.test.ts:\n```typescript\ndescribe('with --verbose flag', () =\u003e {\n it('should output debug information to stderr', async () =\u003e {\n // Setup and call checkCommand({ verbose: true })\n // Verify stderr contains '[verbose]'\n });\n});\n```\n\nFor test/commands/report.test.ts:\n```typescript\ndescribe('with --verbose flag', () =\u003e {\n it('should output debug information to stderr', async () =\u003e {\n // Setup and call reportCommand({ verbose: true })\n // Verify stderr contains '[verbose]'\n });\n});\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-5yk","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Add verbose flag tests for check and report commands","updated_at":"2026-02-03T13:43:01Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"61992ecc1ec13df9687e23bb7022bdd55f027a9a9656883be294bb7799e4d8cb","created_at":"2026-02-03T13:41:47Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Low\n\nRemove the unused default `colors` instance from src/utils/colors.ts (line 64).\n\nAll commands correctly use `createColors(options.noColor)`. The default export is never used and could mislead developers.\n\nRemove:\n```typescript\nexport const colors = createColors();\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-67r","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Remove unused colors default export","updated_at":"2026-02-03T13:43:01Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"2e6ee5226491c2727a93506d607e0f8b3711cb7538d7bf486c261057f82e47e6","created_at":"2026-02-03T13:41:46Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Low\n\nRemove unused `Colors` and `Logger` type imports from:\n- src/commands/check.ts (lines 9, 11)\n- src/commands/init.ts (lines 8, 10)\n- src/commands/report.ts (lines 12, 15)\n\nChange from:\n```typescript\nimport { type Colors, createColors } from '../utils/colors';\nimport { type Logger, createLogger } from '../utils/logger';\n```\n\nTo:\n```typescript\nimport { createColors } from '../utils/colors';\nimport { createLogger } from '../utils/logger';\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-6no","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Remove unused type imports from commands","updated_at":"2026-02-03T13:43:01Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:21:13Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"2fc50b4a2196997bd2d6867e597524b5e0b4e39977b97c59ca1aad242b901198","created_at":"2026-02-03T03:16:16Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** The formatting utilities (formatPercent, formatLines, formatFileLine, formatReport) have implementations but no unit tests. These pure functions are ideal for unit testing.\n\n**Location:** Create test/format.test.ts\n\n**Tests to Add:**\n```typescript\nimport { describe, expect, it } from 'bun:test';\nimport { formatPercent, formatLines, formatFileLine, formatReport } from '../src/utils/format';\n\ndescribe('formatPercent', () =\u003e {\n it('should format 0% correctly', () =\u003e {\n expect(formatPercent(0)).toBe(' 0.00%');\n });\n \n it('should format 100% correctly', () =\u003e {\n expect(formatPercent(100)).toBe('100.00%');\n });\n \n it('should format decimal percentages', () =\u003e {\n expect(formatPercent(5.26)).toBe(' 5.26%');\n });\n});\n\ndescribe('formatLines', () =\u003e {\n it('should format line counts with padding', () =\u003e {\n expect(formatLines(10, 100)).toBe('LH 10/LF 100');\n });\n});\n\ndescribe('formatReport', () =\u003e {\n it('should show message when no files below threshold', () =\u003e {\n expect(formatReport([], 10)).toBe('No files at or below 10% line coverage.');\n });\n});\n```\n\n**Acceptance Criteria:**\n- test/format.test.ts exists with comprehensive tests\n- All format functions have test coverage\n- Tests pass with `bun test`","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-77g","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add: Unit tests for format utilities","updated_at":"2026-02-03T03:21:13Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:19:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"609144f5c0f7b3f36c92c1ca0c7d2c9185f8c9cdbe20128d6f1b8c2fe9591974","created_at":"2026-02-03T03:14:50Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** Version string '0.1.0' is duplicated in package.json and cli.ts (line 11). Creates maintenance burden and version mismatch risk.\n\n**Location:** src/cli.ts line 11\n\n**The Fix:**\n```typescript\n// src/cli.ts - Import version from package.json\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\n```\n\n**Acceptance Criteria:**\n- VERSION constant reads from package.json\n- `bun run dev -- --version` shows correct version\n- No duplicate version strings in codebase","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-a4f","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Import version from package.json instead of hardcoding","updated_at":"2026-02-03T03:19:02Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5fb4d310d207eeab487deed3d91e56234aa7045cfc5917a55cf5fc8a806d40fe","created_at":"2026-02-03T02:54:54Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create scripts/build.ts for building cross-platform binaries.\n\n**Target Platforms:**\n- darwin-arm64 (macOS Apple Silicon)\n- darwin-x64 (macOS Intel)\n- linux-x64 (Linux)\n- windows-x64 (Windows)\n\n**Build Command:**\n```bash\nbun build --compile --target=bun-{platform}-{arch} --outfile=dist/uncov-{platform}-{arch}\n```\n\n**Script Features:**\n1. Clean dist/ directory before build\n2. Build for all platforms in parallel\n3. Report success/failure for each target\n4. Generate checksums (SHA256) for each binary\n\n**Output Structure:**\n```\ndist/\nā”œā”€ā”€ uncov-darwin-arm64\nā”œā”€ā”€ uncov-darwin-arm64.sha256\nā”œā”€ā”€ uncov-darwin-x64\nā”œā”€ā”€ uncov-darwin-x64.sha256\nā”œā”€ā”€ uncov-linux-x64\nā”œā”€ā”€ uncov-linux-x64.sha256\nā”œā”€ā”€ uncov-windows-x64.exe\n└── uncov-windows-x64.exe.sha256\n```\n\n**Package.json Scripts:**\n```json\n{\n \"build:local\": \"bun build --compile --outfile=dist/uncov ./src/cli.ts\",\n \"build:all\": \"bun run scripts/build.ts\"\n}\n```\n\n**Tests:** Manual verification (build and run on local platform)","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-b90","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement build script for cross-platform binaries","updated_at":"2026-02-03T13:43:02Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T14:19:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"12b6cd8321b223884480f69c94b19011af6f547e32dec045cc3bbb7f31dd1f0b","created_at":"2026-02-03T14:19:05Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Lines 266-267: Warn user before attempting sudo. Add: warn \"Permission denied. Attempting install with sudo...\" before the sudo cp command.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-bq5","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Warn user before sudo escalation in install.sh","updated_at":"2026-02-03T14:19:21Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e18f9b77492ecf34dab415d3935ab504585e78fc42ed499c4e882095f1a029a4","created_at":"2026-02-03T02:52:31Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/lib/config.ts to handle configuration loading.\n\n**Config Sources (priority order):**\n1. CLI flags (highest priority)\n2. `uncov.config.json` in project root\n3. `uncov` field in `package.json`\n4. Defaults (lowest priority)\n\n**Config Schema:**\n```typescript\ninterface UncovConfig {\n threshold: number; // default: 10\n exclude: string[]; // glob patterns to exclude\n failOnLow: boolean; // default: false\n coveragePath: string; // default: 'coverage/coverage-summary.json'\n}\n```\n\n**Functions to Implement:**\n- `loadConfig(cliOverrides?: Partial\u003cUncovConfig\u003e): UncovConfig`\n- `findConfigFile(): string | null` - Look for uncov.config.json\n- `readPackageJsonConfig(): Partial\u003cUncovConfig\u003e | null`\n- `mergeConfigs(...configs: Partial\u003cUncovConfig\u003e[]): UncovConfig`\n\n**Config File Format:**\n```json\n{\n \"threshold\": 20,\n \"exclude\": [\"**/*.test.ts\", \"**/index.ts\"],\n \"failOnLow\": false\n}\n```\n\n**Tests Required:** test/lib/config.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-brm","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement config.ts: read uncov configuration","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:21:13Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"86e8a9b4c89b013d12a486247edcc52ab77e53c10ec24e9c7d6a9f5749bdce54","created_at":"2026-02-03T03:16:16Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** Tests spawn processes and check stdout but don't verify exit codes. Need to assert on exit codes for proper testing.\n\n**Location:** test/cli.test.ts (all test cases)\n\n**The Fix:**\n```typescript\nit('should show version with --version flag', async () =\u003e {\n const proc = Bun.spawn(['bun', 'run', './src/cli.ts', '--version'], {\n cwd: `${import.meta.dir}/..`,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n \n const exitCode = await proc.exited;\n const output = await new Response(proc.stdout).text();\n \n expect(exitCode).toBe(0);\n expect(output.trim()).toBe('0.1.0');\n});\n```\n\n**Acceptance Criteria:**\n- All existing tests assert exit code 0\n- Add test for exit code 2 on invalid input\n- Tests check both stdout and exit codes","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-cgy","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Improve: Add exit code assertions to CLI tests","updated_at":"2026-02-03T03:21:13Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:18:54Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7a07a3c0e5b00a734f101675eaaa3d3c5706c3f06863c18bc55ced7c868929cd","created_at":"2026-02-03T02:53:11Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/commands/check.ts - the `uncov check` command for v0.3.0.\n\n**Purpose:** Verify coverage setup is correct (useful for CI).\n\n**Checks Performed:**\n1. **Vitest config exists and has coverage settings**\n - Look for vitest.config.* or vite.config.* with test section\n - Verify coverage.reporter includes 'json-summary'\n - Verify coverage.reportsDirectory is set\n\n2. **coverage-summary.json exists**\n - Check default path: coverage/coverage-summary.json\n - Or configured path from uncov config\n\n3. **Scripts are configured**\n - Check for test:coverage or similar script\n - Check for uncov script (optional)\n\n**Output (success):**\n```\nāœ“ Vitest config found: vitest.config.ts\nāœ“ Coverage reporter configured: json-summary\nāœ“ Coverage summary exists: coverage/coverage-summary.json\nāœ“ Scripts configured: test:coverage\n\nAll checks passed!\n```\n\n**Output (failure):**\n```\nāœ— Coverage summary not found: coverage/coverage-summary.json\n Run 'pnpm test:coverage' to generate coverage data\n\n1 check failed\n```\n\n**Exit Codes:**\n- 0: All checks pass\n- 1: One or more checks failed\n- 2: Configuration error\n\n**Tests Required:** test/commands/check.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-e27","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement check command: verify coverage setup","updated_at":"2026-02-03T13:18:54Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:47:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"eacfb380f20946d670ad699058c704503ba758d9c64ca663dc445ad7c0bc2f89","created_at":"2026-02-03T02:54:33Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/utils/fs.ts for common file operations.\n\n**Functions to Implement:**\n\n`fileExists(path: string): boolean`\n- Check if file exists (sync)\n\n`readJson\u003cT\u003e(path: string): T`\n- Read and parse JSON file\n- Throw descriptive error on parse failure\n\n`writeJson(path: string, data: unknown): void`\n- Write JSON with 2-space indent\n- Ensure directory exists\n\n`readText(path: string): string`\n- Read text file contents\n\n`writeText(path: string, content: string): void`\n- Write text file\n- Ensure directory exists\n\n`ensureDir(path: string): void`\n- Create directory if it doesn't exist (recursive)\n\n`appendLine(path: string, line: string): void`\n- Append line to file (create if doesn't exist)\n- Handle trailing newline properly\n\n**Error Handling:**\n- All functions should throw descriptive errors\n- Include file path in error messages\n- Distinguish between 'not found' and 'permission denied'\n\n**Tests Required:** test/utils/fs.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-ed6","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement fs.ts: file system helper utilities","updated_at":"2026-02-03T03:47:20Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"6fafc91c936dfdb8e2a64d1a31e5d71c53f3a98ab5de4629ded269ad7914c2cf","created_at":"2026-02-03T13:41:48Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium\n\nAfter build succeeds, verify the binary file actually exists before generating checksum.\n\nIn scripts/build.ts, add check after successful build:\n```typescript\nif (exitCode === 0) {\n // Verify the binary was actually created\n if (!existsSync(outPath)) {\n return {\n target,\n outfile,\n success: false,\n error: 'Binary file was not created despite successful build',\n };\n }\n // Then generate checksum...\n}\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-efk","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Add binary existence check in build script","updated_at":"2026-02-03T13:43:01Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:19:03Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f6abbe748a5180d7399780331582f0bb62341c9f18a8dd9edd663f0a07cc2d06","created_at":"2026-02-03T03:15:52Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** The threshold is parsed with Number.parseInt() but there's no validation for non-numeric input, negative values, or values over 100.\n\n**Location:** src/cli.ts line 84\n\n**The Fix:**\n```typescript\nfunction parseCliArgs(args: string[]): ParsedArgs {\n // ... existing parseArgs code ...\n\n const thresholdRaw = values.threshold ?? '10';\n const threshold = Number.parseInt(thresholdRaw, 10);\n \n if (Number.isNaN(threshold)) {\n console.error(`Error: Invalid threshold value \"${thresholdRaw}\". Must be a number.`);\n process.exit(2);\n }\n \n if (threshold \u003c 0 || threshold \u003e 100) {\n console.error(`Error: Threshold must be between 0 and 100, got ${threshold}.`);\n process.exit(2);\n }\n\n return {\n // ... rest\n threshold,\n };\n}\n```\n\n**Acceptance Criteria:**\n- `uncov --threshold abc` shows error and exits 2\n- `uncov --threshold -10` shows error and exits 2\n- `uncov --threshold 150` shows error and exits 2\n- `uncov --threshold 50` works normally","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-ejy","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Add threshold argument validation","updated_at":"2026-02-03T03:19:03Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:56:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c529dcac4af3a104afcce875c57f1c36f034b8cfedcd4ca5bfb5fbb9e68702eb","created_at":"2026-02-03T13:53:30Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** High (Security)\n\nMove `contents: write` permission from workflow level to only the release job.\n\n**Current (insecure):**\n```yaml\npermissions:\n contents: write # Applied to ALL jobs\n```\n\n**Fix:**\n```yaml\njobs:\n test:\n permissions:\n contents: read\n \n build:\n permissions:\n contents: read\n \n release:\n permissions:\n contents: write # Only release needs write\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-eqy","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Move contents:write permission to release job only","updated_at":"2026-02-03T13:56:51Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3f8d06ef7f6eb2a2bc8977d4fa15547978763e5a19c4436bfaee6eb433f4b021","created_at":"2026-02-03T02:52:50Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/lib/package-json.ts for safely modifying package.json.\n\n**Functions to Implement:**\n\n`readPackageJson(path?: string): PackageJson`\n- Read and parse package.json\n- Handle missing file gracefully\n\n`writePackageJson(pkg: PackageJson, path?: string): void`\n- Write with consistent formatting (2-space indent)\n- Preserve existing formatting where possible\n\n`addScripts(scripts: Record\u003cstring, string\u003e): { added: string[]; skipped: string[] }`\n- Add scripts only if they don't exist\n- Return which were added vs skipped (already exist)\n\n`hasScript(name: string): boolean`\n- Check if a script exists\n\n**Scripts to Add (via init command):**\n```json\n{\n \"test:coverage\": \"vitest run --coverage\",\n \"coverage:low\": \"uncov\"\n}\n```\n\n**Safety:**\n- Never overwrite existing scripts without --force\n- Backup original before modification (optional)\n- Validate JSON before writing\n\n**Tests Required:** test/lib/package-json.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-fn0","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement package-json.ts: safe package.json modification","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T14:19:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d1d9c4e5103cac1d67d584c0d75780de84b1cdb90d0285638212f3eefe7b6f10","created_at":"2026-02-03T14:19:05Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Line 186: Add error handling for mktemp failure. Change: TMP_DIR=$(mktemp -d) to: TMP_DIR=$(mktemp -d) || error \"Failed to create temporary directory\"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-iz5","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add error handling for mktemp in install.sh","updated_at":"2026-02-03T14:19:21Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:56:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b70ad44c3490700436b2ff2ba8e0653a22c1ea1c1b045c76f09b8bb710de89e9","created_at":"2026-02-03T02:54:54Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create .github/workflows/ci.yml for automated testing and releases.\n\n**Workflow Triggers:**\n- Push to main\n- Pull requests to main\n- Tags matching v*.*.*\n\n**Jobs:**\n\n**1. Test Job**\n- Runs on: ubuntu-latest\n- Steps:\n - Checkout\n - Setup Bun\n - Install dependencies (bun install)\n - Run linter (bun run lint)\n - Run tests (bun test)\n\n**2. Build Job** (on tag push only)\n- Runs on: matrix of [ubuntu-latest, macos-latest, windows-latest]\n- Steps:\n - Checkout\n - Setup Bun\n - Build binary for platform\n - Upload artifact\n\n**3. Release Job** (on tag push, after build)\n- Create GitHub release\n- Upload all binary artifacts\n- Generate release notes from commits\n\n**Release Artifact Names:**\n- uncov-darwin-arm64\n- uncov-darwin-x64\n- uncov-linux-x64\n- uncov-windows-x64.exe\n\n**Matrix Strategy:**\n```yaml\nstrategy:\n matrix:\n include:\n - os: macos-latest\n target: darwin-arm64\n - os: macos-13\n target: darwin-x64\n - os: ubuntu-latest\n target: linux-x64\n - os: windows-latest\n target: windows-x64\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-kah","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create GitHub Actions CI workflow for testing and releases","updated_at":"2026-02-03T13:56:51Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"67793c04a5dd6a4788b5d012cd2210e55eaaad65ebfcf2edead357b4724156f6","created_at":"2026-02-03T13:32:29Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Add --dry-run flag to show what init would change without modifying files.\n\n**Usage:**\n```bash\nuncov init --dry-run\n```\n\n**Output Format:**\n```\nDry run - no files will be modified\n\nWould detect package manager: pnpm\nWould add script: test:coverage -\u003e 'vitest run --coverage'\nWould add script: coverage:low -\u003e 'uncov'\nWould create: vitest.config.ts\nWould append to .gitignore: coverage/\n\nRun without --dry-run to apply these changes.\n```\n\n**Implementation:**\n1. Add 'dry-run' to CLI parseArgs options (boolean flag)\n2. Pass dryRun option to initCommand()\n3. In initCommand, check dryRun before each write operation\n4. Use 'Would X' prefix for dry-run output vs 'āœ“ X' for real execution\n5. Skip all writeText, appendLine, writePackageJson calls when dry-run\n\n**CLI Addition:**\n```typescript\n'dry-run': {\n type: 'boolean',\n default: false,\n},\n```\n\n**Tests:** Add dry-run tests to test/commands/init.test.ts\n- Verify no files are created/modified\n- Verify output shows 'Would' prefixes\n- Verify exit code is 0","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-l2y","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add --dry-run flag to init command","updated_at":"2026-02-03T13:43:01Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T14:19:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"97142953a926f34ea362241a1ff8f6ec3301f26f7ac24ef38c739210b4645767","created_at":"2026-02-03T14:18:59Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Replace cat | awk with awk directly in install.sh line 202. Change: EXPECTED_CHECKSUM=$(cat \"$TMP_CHECKSUM\" | awk '{print $1}') to: EXPECTED_CHECKSUM=$(awk '{print $1}' \"$TMP_CHECKSUM\")","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-mc5","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix UUOC in install.sh line 202","updated_at":"2026-02-03T14:19:21Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"49b11fe77e202a1830dd551ba68ac36fc3e8017c7045974ec2d6a6cfd172201c","created_at":"2026-02-03T02:52:49Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/lib/detect.ts for detecting project environment.\n\n**Functions to Implement:**\n\n`detectPackageManager(): 'pnpm' | 'bun' | 'npm' | 'yarn'`\n- Check for lockfiles in priority order:\n 1. pnpm-lock.yaml → 'pnpm'\n 2. bun.lockb → 'bun'\n 3. package-lock.json → 'npm'\n 4. yarn.lock → 'yarn'\n- Default to 'npm' if none found\n\n`detectVitestConfig(): { path: string; type: 'ts' | 'js' | 'mts' | 'mjs' } | null`\n- Look for vitest.config.{ts,js,mts,mjs}\n- Also check vite.config.* with test section\n\n`detectExistingCoverageConfig(): boolean`\n- Check if vitest config already has coverage settings\n\n`detectGitignore(): { exists: boolean; hasCoverage: boolean }`\n- Check if .gitignore exists\n- Check if 'coverage/' or 'coverage' is already listed\n\n**Tests Required:** test/lib/detect.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-mv8","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement detect.ts: package manager and config detection","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:19:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"95280ed87eab9bedf30825c260540b37d3e2305a325d8d726f8000f216ccf342","created_at":"2026-02-03T03:14:50Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** There are two fileExists functions - one stub in detect.ts (line 28) and one implemented in utils/fs.ts (line 12). This violates DRY and will cause confusion.\n\n**Locations:**\n- src/lib/detect.ts line 28 (stub - REMOVE)\n- src/utils/fs.ts line 12 (implementation - KEEP)\n\n**The Fix:**\nRemove the stub from detect.ts and import from utils:\n```typescript\n// src/lib/detect.ts\nimport { fileExists } from '../utils/fs';\n\n// Then use fileExists() in detectPackageManager implementation\n```\n\n**Acceptance Criteria:**\n- Only one fileExists function exists (in utils/fs.ts)\n- detect.ts imports from utils/fs\n- No barrel export conflicts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-o9j","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Remove duplicate fileExists function from detect.ts","updated_at":"2026-02-03T03:19:02Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:47:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"1845c10a5afab4cab53b1e0d42cff3091baf4270bfb5dce7d47379eafd02a047","created_at":"2026-02-03T02:52:31Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Complete src/cli.ts as the main entry point.\n\n**Responsibilities:**\n- Parse top-level args to determine command\n- Route to appropriate command handler\n- Handle --help and --version flags\n- Graceful error handling with user-friendly messages\n\n**Commands to Route:**\n- (default/no command) → report command\n- `init` → init command\n- `check` → check command\n\n**Global Flags:**\n- `--help, -h` - Show help text\n- `--version, -v` - Show version from package.json\n\n**Help Output:**\n```\nuncov - Report files with low test coverage\n\nUsage: uncov [command] [options]\n\nCommands:\n (default) Report files below coverage threshold\n init Bootstrap coverage configuration\n check Verify coverage setup\n\nOptions:\n --threshold \u003cn\u003e Coverage threshold percentage (default: 10)\n --fail Exit 1 if files below threshold\n --json Output as JSON\n --help, -h Show this help\n --version, -v Show version\n```\n\n**Error Handling:**\n- Unknown command → suggest similar, show help\n- Missing required args → show specific error + help\n\n**Tests Required:** test/cli.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-ocy","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement CLI entry point with command routing","updated_at":"2026-02-03T03:47:20Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"99042d85d841efa02b8dee21fc16ffe62d2a0f36e9f67c0c0f95ec172b8c5a96","created_at":"2026-02-03T02:52:11Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/lib/coverage.ts to parse Vitest/Istanbul coverage output.\n\n**Input Format:** coverage/coverage-summary.json (Istanbul standard)\n```json\n{\n \"total\": { \"lines\": { \"total\": 1000, \"covered\": 500, \"pct\": 50 }, ... },\n \"src/file.ts\": { \"lines\": { \"total\": 100, \"covered\": 10, \"pct\": 10 }, ... }\n}\n```\n\n**Functions to Implement:**\n- `parseCoverageSummary(path: string): CoverageData` - Read and parse JSON\n- `filterBelowThreshold(data: CoverageData, threshold: number): FileEntry[]` - Filter files at or below threshold\n- `sortByPercentage(files: FileEntry[]): FileEntry[]` - Sort ascending by coverage %\n\n**Types to Define:**\n- `CoverageData` - Parsed coverage structure\n- `FileEntry` - Single file with path, lines hit/found, percentage\n\n**Error Handling:**\n- File not found → throw descriptive error\n- Invalid JSON → throw descriptive error\n- Missing required fields → throw descriptive error\n\n**Tests Required:** test/lib/coverage.test.ts with fixture files","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-p0r","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement coverage.ts: parse Istanbul coverage-summary.json","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:43:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"36af82fb5679a99ba2892c474a8dc44e6d36fb10441af00741ccb9dc9606bb64","created_at":"2026-02-03T02:55:29Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create README.md with full documentation.\n\n**Sections:**\n\n**1. Header**\n- Project name and tagline\n- Badges (CI status, version, license)\n\n**2. Features**\n- Zero dependencies\n- Single binary\n- Cross-platform\n- No config required\n\n**3. Installation**\n- curl install script\n- Homebrew (when available)\n- Download from releases\n- Build from source\n\n**4. Quick Start**\n```bash\n# Generate coverage with vitest\npnpm test:coverage\n\n# Report low coverage files\nuncov\n\n# Files below 50% coverage\nuncov --threshold 50\n\n# Fail CI if low coverage\nuncov --fail\n```\n\n**5. Commands**\n- uncov (default) - detailed usage\n- uncov init - detailed usage\n- uncov check - detailed usage\n\n**6. Configuration**\n- uncov.config.json format\n- package.json format\n- CLI flags reference\n\n**7. Exit Codes**\n- Table of codes and meanings\n\n**8. Development**\n- Prerequisites (Bun)\n- Setup instructions\n- Running tests\n- Building locally\n\n**9. License**\n- MIT\n\n**Keep concise** - link to docs/spec.md for full details","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-p2k","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Write comprehensive README.md documentation","updated_at":"2026-02-03T13:43:02Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:47:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"949bdb60533301b53c3085cddf47e97b06a40dc2351daf735c7eb13bef9d904a","created_at":"2026-02-03T02:52:11Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/utils/format.ts for consistent output formatting.\n\n**Functions to Implement:**\n\n`formatCoverageTable(files: FileEntry[]): string`\n- Format output like:\n ```\n Files at or below 10% line coverage: 3\n\n 0.00% LH 0/LF 120 src/renderer.ts\n 5.26% LH 2/LF 38 src/bootstrap.ts\n 10.00% LH 10/LF 100 src/engine.ts\n ```\n- Right-align percentage column\n- LH = lines hit, LF = lines found\n\n`formatJson(files: FileEntry[]): string`\n- JSON array output for --json flag\n- Include: path, percentage, linesHit, linesFound\n\n`formatSummary(count: number, threshold: number): string`\n- Header line: 'Files at or below X% line coverage: N'\n\n**Tests Required:** test/utils/format.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-q77","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement format.ts: output formatting utilities","updated_at":"2026-02-03T03:47:20Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:47:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ad229066e98b75852ed87028e7df208e658c71ec37783658eeee882e276d4201","created_at":"2026-02-03T02:55:28Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create test/fixtures/ with sample Istanbul coverage data for testing.\n\n**Fixtures to Create:**\n\n**test/fixtures/coverage-summary-valid.json**\n- Standard valid coverage output\n- Multiple files with varying coverage (0%, 10%, 50%, 100%)\n- Include 'total' summary\n\n**test/fixtures/coverage-summary-empty.json**\n- Valid JSON but only 'total' with no individual files\n\n**test/fixtures/coverage-summary-all-covered.json**\n- All files at 100% coverage\n\n**test/fixtures/coverage-summary-all-zero.json**\n- All files at 0% coverage\n\n**test/fixtures/coverage-summary-invalid.json**\n- Malformed JSON for error testing\n\n**test/fixtures/coverage-summary-missing-fields.json**\n- Valid JSON but missing required fields\n\n**Sample Valid Structure:**\n```json\n{\n \"total\": {\n \"lines\": { \"total\": 1000, \"covered\": 450, \"skipped\": 0, \"pct\": 45 },\n \"statements\": { \"total\": 1000, \"covered\": 450, \"skipped\": 0, \"pct\": 45 },\n \"functions\": { \"total\": 100, \"covered\": 50, \"skipped\": 0, \"pct\": 50 },\n \"branches\": { \"total\": 200, \"covered\": 80, \"skipped\": 0, \"pct\": 40 }\n },\n \"src/renderer.ts\": {\n \"lines\": { \"total\": 120, \"covered\": 0, \"skipped\": 0, \"pct\": 0 },\n ...\n },\n \"src/engine.ts\": {\n \"lines\": { \"total\": 100, \"covered\": 10, \"skipped\": 0, \"pct\": 10 },\n ...\n }\n}\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-qu3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create test fixtures: sample coverage-summary.json files","updated_at":"2026-02-03T03:47:20Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:56:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"9cc9a3087476acc2781fcc09ea89d6655f40c4519ba255713e7b99b125fe2b34","created_at":"2026-02-03T13:53:31Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium (Reproducibility)\n\n1. Pin Bun version instead of using 'latest':\n```yaml\n- uses: oven-sh/setup-bun@v1\n with:\n bun-version: '1.1.42'\n```\n\n2. Commit bun.lockb lockfile\n\n3. Use --frozen-lockfile in CI:\n```yaml\n- name: Install dependencies\n run: bun install --frozen-lockfile\n```","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-r6p","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Pin Bun version and add lockfile","updated_at":"2026-02-03T13:56:51Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T13:04:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"87d847e7171dd1c8b773e8750adf22bf3cd9cf169cacdfc52fb2b676f85cd25a","created_at":"2026-02-03T02:52:50Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"Create src/lib/vitest-config.ts for vitest config management.\n\n**Functions to Implement:**\n\n`hasVitestConfig(): boolean`\n- Check for vitest.config.* or vite.config.* with test section\n\n`hasCoverageConfig(): boolean`\n- Check if coverage is already configured in vitest config\n\n`createVitestConfig(): string`\n- Generate new vitest.config.ts content:\n```typescript\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n coverage: {\n provider: 'v8',\n reporter: ['text', 'json-summary'],\n reportsDirectory: './coverage',\n },\n },\n});\n```\n\n`patchVitestConfig(existingContent: string): string`\n- Add coverage config to existing vitest.config.ts\n- Use AST manipulation or careful string manipulation\n- Preserve existing config, only add coverage section\n\n**Edge Cases:**\n- vite.config.ts with test section (common pattern)\n- vitest.workspace.ts (workspace setups)\n- Already has partial coverage config\n\n**Tests Required:** test/lib/vitest-config.test.ts","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-s2m","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement vitest-config.ts: detect and modify vitest configuration","updated_at":"2026-02-03T13:04:56Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:47:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"69060be03596a0b90019519b7545d12b16f882ecc832b91e918e385865142ee4","created_at":"2026-02-03T03:43:40Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Severity:** Medium\n**Location:** src/utils/fs.ts lines 127-145\n\n**Issue:** Deprecated async wrappers (readJsonFile, writeJsonFile) are misleading:\n- Not truly async (no await, just wrapped in Promise)\n- Silently swallow errors by returning null\n- Have no tests\n\n**The Fix:**\nRemove the deprecated functions entirely (lines 126-145):\n```typescript\n// Delete these deprecated functions:\n// - readJsonFile\n// - writeJsonFile\n```\n\nAlso remove any exports of these functions from index.ts.\n\n**Acceptance Criteria:**\n- Deprecated async wrappers are removed\n- No references to removed functions\n- Lint passes","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-tr8","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Remove deprecated async wrappers from fs.ts","updated_at":"2026-02-03T03:47:20Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T14:19:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d50fc246416ac7835f203a4c1320af26dd74b0607a5b7462b267a340d72f400e","created_at":"2026-02-03T14:19:05Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"In get_latest_version function, validate VERSION format after extraction. Add case statement to check for v[0-9]* or [0-9]* pattern, error on invalid format.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-uar","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add version format validation in install.sh","updated_at":"2026-02-03T14:19:21Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-03T03:21:13Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"fcc1cd7140b59d0b57d2060b2e72d77ea5ba91f499662397a6722953b6794544","created_at":"2026-02-03T03:16:14Z","created_by":"loganlindquist","crystallizes":0,"defer_until":null,"description":"**Issue:** Running `uncov foobar` silently defaults to the report command. This confuses users who misspell commands.\n\n**Location:** src/cli.ts lines 71-78\n\n**The Fix:**\n```typescript\nconst command = positionals[0];\nlet parsedCommand: 'report' | 'init' | 'check' = 'report';\n\nif (command === 'init') {\n parsedCommand = 'init';\n} else if (command === 'check') {\n parsedCommand = 'check';\n} else if (command \u0026\u0026 command \\!== 'report') {\n console.error(`Unknown command: ${command}`);\n console.error(`Run 'uncov --help' for usage information.`);\n process.exit(2);\n}\n```\n\n**Acceptance Criteria:**\n- `uncov foobar` shows 'Unknown command: foobar' and exits 2\n- `uncov report` works normally\n- `uncov` (no command) defaults to report","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"uncov-xg6","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix: Add unrecognized command handling","updated_at":"2026-02-03T03:21:13Z","waiters":"","wisp_type":"","work_type":""} diff --git a/.beads/backup/labels.jsonl b/.beads/backup/labels.jsonl deleted file mode 100644 index e69de29..0000000 diff --git a/.beads/config.yaml b/.beads/config.yaml deleted file mode 100644 index f242785..0000000 --- a/.beads/config.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# Beads Configuration File -# This file configures default behavior for all bd commands in this repository -# All settings can also be set via environment variables (BD_* prefix) -# or overridden with command-line flags - -# Issue prefix for this repository (used by bd init) -# If not set, bd init will auto-detect from directory name -# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. -# issue-prefix: "" - -# Use no-db mode: load from JSONL, no SQLite, write back after each command -# When true, bd will use .beads/issues.jsonl as the source of truth -# instead of SQLite database -# no-db: false - -# Disable daemon for RPC communication (forces direct database access) -# no-daemon: false - -# Disable auto-flush of database to JSONL after mutations -# no-auto-flush: false - -# Disable auto-import from JSONL when it's newer than database -# no-auto-import: false - -# Enable JSON output by default -# json: false - -# Default actor for audit trails (overridden by BD_ACTOR or --actor) -# actor: "" - -# Path to database (overridden by BEADS_DB or --db) -# db: "" - -# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) -# auto-start-daemon: true - -# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) -# flush-debounce: "5s" - -# Git branch for beads commits (bd sync will commit to this branch) -# IMPORTANT: Set this for team projects so all clones use the same sync branch. -# This setting persists across clones (unlike database config which is gitignored). -# Can also use BEADS_SYNC_BRANCH env var for local override. -# If not set, bd sync will require you to run 'bd config set sync.branch '. -# sync-branch: "beads-sync" - -# Multi-repo configuration (experimental - bd-307) -# Allows hydrating from multiple repositories and routing writes to the correct JSONL -# repos: -# primary: "." # Primary repo (where this database lives) -# additional: # Additional repos to hydrate from (read-only) -# - ~/beads-planning # Personal planning repo -# - ~/work-planning # Work planning repo - -# Integration settings (access with 'bd config get/set') -# These are stored in the database, not in this file: -# - jira.url -# - jira.project -# - linear.url -# - linear.api-key -# - github.org -# - github.repo diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout deleted file mode 100755 index 3640ea6..0000000 --- a/.beads/hooks/post-checkout +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.58.0 --- -# This section is managed by beads. Do not remove these markers. -if command -v bd >/dev/null 2>&1; then - export BD_GIT_HOOK=1 - bd hooks run post-checkout "$@" - _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi -fi -# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge deleted file mode 100755 index b4f4998..0000000 --- a/.beads/hooks/post-merge +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.58.0 --- -# This section is managed by beads. Do not remove these markers. -if command -v bd >/dev/null 2>&1; then - export BD_GIT_HOOK=1 - bd hooks run post-merge "$@" - _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi -fi -# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit deleted file mode 100755 index 5410562..0000000 --- a/.beads/hooks/pre-commit +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.58.0 --- -# This section is managed by beads. Do not remove these markers. -if command -v bd >/dev/null 2>&1; then - export BD_GIT_HOOK=1 - bd hooks run pre-commit "$@" - _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi -fi -# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push deleted file mode 100755 index 1a3292d..0000000 --- a/.beads/hooks/pre-push +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.58.0 --- -# This section is managed by beads. Do not remove these markers. -if command -v bd >/dev/null 2>&1; then - export BD_GIT_HOOK=1 - bd hooks run pre-push "$@" - _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi -fi -# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg deleted file mode 100755 index afd628c..0000000 --- a/.beads/hooks/prepare-commit-msg +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.58.0 --- -# This section is managed by beads. Do not remove these markers. -if command -v bd >/dev/null 2>&1; then - export BD_GIT_HOOK=1 - bd hooks run prepare-commit-msg "$@" - _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi -fi -# --- END BEADS INTEGRATION --- diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl deleted file mode 100644 index e69de29..0000000 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl deleted file mode 100644 index d51a5db..0000000 --- a/.beads/issues.jsonl +++ /dev/null @@ -1,47 +0,0 @@ -{"id":"uncov-0lq","title":"Create install.sh script for easy binary installation","description":"Create install.sh for curl-based installation.\n\n**Usage:**\n```bash\ncurl -fsSL https://raw.githubusercontent.com/llbbl/uncov/main/install.sh | bash\n```\n\n**Script Behavior:**\n1. Detect OS and architecture\n2. Download appropriate binary from GitHub releases\n3. Install to ~/.local/bin or /usr/local/bin\n4. Verify binary works (uncov --version)\n5. Print success message with PATH instructions if needed\n\n**Platform Detection:**\n```bash\nOS=$(uname -s | tr '[:upper:]' '[:lower:]')\nARCH=$(uname -m)\n# Map to our naming: darwin-arm64, darwin-x64, linux-x64\n```\n\n**Installation Locations:**\n1. If ~/.local/bin exists and is in PATH → install there\n2. Else if user has write access to /usr/local/bin → install there\n3. Else → install to ~/.local/bin and warn about PATH\n\n**Error Handling:**\n- Unsupported platform → clear error message\n- Download failed → retry once, then fail with URL\n- Checksum mismatch → fail with warning\n\n**Success Output:**\n```\nDetected: darwin-arm64\nDownloading uncov v0.1.0...\nInstalling to /usr/local/bin/uncov...\nāœ“ uncov v0.1.0 installed successfully\\!\n\nRun 'uncov --help' to get started.\n```","status":"closed","priority":3,"issue_type":"task","created_at":"2026-02-02T20:55:05.235861-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T08:19:49.876382-06:00","closed_at":"2026-02-03T08:19:49.876382-06:00","close_reason":"install.sh implemented with all security fixes, passed code review","dependencies":[{"issue_id":"uncov-0lq","depends_on_id":"uncov-kah","type":"blocks","created_at":"2026-02-02T20:56:17.955806-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-0ou","title":"Create Homebrew tap for uncov distribution","description":"Create Homebrew tap repository for easy macOS installation (v0.3.0 goal).\n\n**Repository:** homebrew-uncov (separate repo)\n\n**Formula: uncov.rb**\n```ruby\nclass Uncov \u003c Formula\n desc \"CLI tool to report files with low test coverage\"\n homepage \"https://github.com/llbbl/uncov\"\n version \"0.1.0\"\n\n on_macos do\n if Hardware::CPU.arm?\n url \"https://github.com/llbbl/uncov/releases/download/v0.1.0/uncov-darwin-arm64\"\n sha256 \"\u003csha256\u003e\"\n else\n url \"https://github.com/llbbl/uncov/releases/download/v0.1.0/uncov-darwin-x64\"\n sha256 \"\u003csha256\u003e\"\n end\n end\n\n on_linux do\n url \"https://github.com/llbbl/uncov/releases/download/v0.1.0/uncov-linux-x64\"\n sha256 \"\u003csha256\u003e\"\n end\n\n def install\n bin.install \"uncov-darwin-arm64\" =\u003e \"uncov\" if OS.mac? \u0026\u0026 Hardware::CPU.arm?\n bin.install \"uncov-darwin-x64\" =\u003e \"uncov\" if OS.mac? \u0026\u0026 !Hardware::CPU.arm?\n bin.install \"uncov-linux-x64\" =\u003e \"uncov\" if OS.linux?\n end\n\n test do\n assert_match \"uncov\", shell_output(\"#{bin}/uncov --version\")\n end\nend\n```\n\n**Installation:**\n```bash\nbrew tap llbbl/uncov\nbrew install uncov\n```\n\n**Automation:**\n- Update formula SHA256 on each release\n- Can be automated via GitHub Actions","status":"closed","priority":4,"issue_type":"task","created_at":"2026-02-02T20:55:28.308201-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T08:37:57.685535-06:00","closed_at":"2026-02-03T08:37:57.685535-06:00","close_reason":"Homebrew tap scaffolded at github.com/llbbl/homebrew-uncov","dependencies":[{"issue_id":"uncov-0ou","depends_on_id":"uncov-kah","type":"blocks","created_at":"2026-02-02T20:56:18.068068-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-0xe","title":"Fix: Extract default coverage path to constant","description":"**Severity:** Low\n**Location:** src/cli.ts line 55-56\n\n**Issue:** Default coverage path 'coverage/coverage-summary.json' is a magic string.\n\n**The Fix:**\n```typescript\n// At top of file or in constants\nconst DEFAULT_COVERAGE_PATH = 'coverage/coverage-summary.json';\n\n// In parseArgs options:\n'coverage-path': {\n type: 'string',\n short: 'c',\n default: DEFAULT_COVERAGE_PATH,\n},\n```\n\n**Acceptance Criteria:**\n- Default path is a named constant\n- Constant is exported for use by other modules if needed","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-02-02T21:43:40.151488-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:47:19.675593-06:00","closed_at":"2026-02-02T21:47:19.675593-06:00","close_reason":"Closed"} -{"id":"uncov-17j","title":"Fix: Build script uses @ts-expect-error for compile option","description":"**Issue:** The build script uses @ts-expect-error to suppress a type issue with Bun.build compile option. Should use CLI approach instead.\n\n**Location:** scripts/build.ts lines 39-40\n\n**The Fix:**\nUse CLI via Bun.spawn instead of Bun.build API:\n```typescript\nasync function build(): Promise\u003cvoid\u003e {\n const distDir = './dist';\n await mkdir(distDir, { recursive: true });\n\n console.log('Building uncov for all platforms...\\n');\n\n for (const { target, outfile } of TARGETS) {\n console.log(`Building ${target}...`);\n \n const proc = Bun.spawn([\n 'bun', 'build', '--compile',\n '--target', target,\n '--outfile', `${distDir}/${outfile}`,\n './src/cli.ts'\n ], {\n stdout: 'inherit',\n stderr: 'inherit',\n });\n \n const exitCode = await proc.exited;\n if (exitCode !== 0) {\n console.error(`Failed to build ${target}`);\n process.exit(1);\n }\n \n console.log(` -\u003e ${distDir}/${outfile}`);\n }\n\n console.log('\\nBuild complete!');\n}\n```\n\n**Acceptance Criteria:**\n- No @ts-expect-error in build script\n- `bun run scripts/build.ts` works correctly\n- Build produces binaries in dist/","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-02T21:16:17.371737-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:21:13.517263-06:00","closed_at":"2026-02-02T21:21:13.517263-06:00","close_reason":"Closed"} -{"id":"uncov-19b","title":"Fix: Add threshold range validation in config","description":"**Severity:** Low\n**Location:** src/lib/config.ts (validatePartialConfig function)\n\n**Issue:** Validates threshold is a number but not within 0-100 range. Could accept -5 or 500.\n\n**The Fix:**\n```typescript\nif (typeof obj.threshold === 'number' \u0026\u0026 obj.threshold \u003e= 0 \u0026\u0026 obj.threshold \u003c= 100) {\n result.threshold = obj.threshold;\n}\n```\n\n**Acceptance Criteria:**\n- Threshold validated to 0-100 range\n- Invalid thresholds are ignored (use default)\n- Add tests for out-of-range values","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-02-03T07:02:40.874431-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.242748-06:00","closed_at":"2026-02-03T07:04:56.242748-06:00","close_reason":"Closed"} -{"id":"uncov-1e5","title":"Critical: Add path validation to prevent directory traversal","description":"**Severity:** Critical\n**Location:** src/utils/fs.ts (all file operations)\n\n**Issue:** File utilities accept arbitrary paths without validation. Could allow reading/writing files outside project directory via path traversal (e.g., `../../../etc/passwd`).\n\n**The Fix:**\n```typescript\nimport { resolve, relative, isAbsolute } from 'node:path';\n\nexport function validatePath(targetPath: string, baseDir: string = process.cwd()): string {\n const resolved = resolve(baseDir, targetPath);\n const relativePath = relative(baseDir, resolved);\n \n if (relativePath.startsWith('..') || isAbsolute(relativePath)) {\n throw new Error(`Path \"${targetPath}\" is outside allowed directory`);\n }\n \n return resolved;\n}\n```\n\nApply validation to readJson, writeJson, readText, writeText, appendLine.\n\n**Acceptance Criteria:**\n- validatePath function exists and is tested\n- File operations validate paths when baseDir is provided\n- Attempting to access `../../../etc/passwd` throws error\n- Tests cover path traversal attempts","status":"closed","priority":0,"issue_type":"bug","created_at":"2026-02-02T21:43:39.006322-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:46:27.659632-06:00","closed_at":"2026-02-02T21:46:27.659632-06:00","close_reason":"Closed"} -{"id":"uncov-1yi","title":"Add --verbose flag for debug output","description":"Add --verbose flag to show detailed information for troubleshooting.\n\n**Usage:**\n```bash\nuncov --verbose\nuncov init --verbose\nuncov check --verbose\n```\n\n**Verbose Output Examples:**\n\nFor report command:\n```\n[verbose] Loading config from /project\n[verbose] Config sources: CLI overrides, package.json\n[verbose] Final config: { threshold: 10, coveragePath: 'coverage/coverage-summary.json' }\n[verbose] Reading coverage from: /project/coverage/coverage-summary.json\n[verbose] Found 15 files in coverage data\n[verbose] Filtered to 3 files at or below 10% threshold\n\nFiles at or below 10% line coverage: 3\n...\n```\n\nFor init command:\n```\n[verbose] Working directory: /project\n[verbose] Checking for lockfiles...\n[verbose] Found: pnpm-lock.yaml\n[verbose] Detected package manager: pnpm\n[verbose] Reading package.json from: /project/package.json\n[verbose] Existing scripts: dev, build, test\n...\n```\n\n**Implementation:**\n1. Add 'verbose' to CLI parseArgs options (boolean, short: 'V')\n2. Create src/utils/logger.ts with verbose logging utility\n3. Pass verbose flag through to commands\n4. Add [verbose] prefixed logs at key decision points\n\n**Logger Utility:**\n```typescript\nexport function createLogger(verbose: boolean) {\n return {\n verbose: (msg: string) =\u003e verbose \u0026\u0026 console.error(`[verbose] ${msg}`),\n info: (msg: string) =\u003e console.log(msg),\n error: (msg: string) =\u003e console.error(msg),\n };\n}\n```\n\n**Note:** Verbose output goes to stderr so it doesn't interfere with --json output.\n\n**Tests:** Verify verbose flag produces expected output","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-02-03T07:32:32.146084-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.500145-06:00","closed_at":"2026-02-03T07:43:01.500145-06:00","close_reason":"Closed"} -{"id":"uncov-2bo","title":"Implement init command: bootstrap coverage configuration","description":"Create src/commands/init.ts - the `uncov init` command for v0.2.0.\n\n**CLI Flags:**\n- `--force` - Overwrite existing configuration\n\n**Interactive Flow:**\n1. Detect package manager (pnpm \u003e bun \u003e npm \u003e yarn)\n2. Check for existing vitest config\n3. If exists and no --force: prompt to add coverage config\n4. If doesn't exist: create new vitest.config.ts\n5. Add scripts to package.json\n6. Add coverage/ to .gitignore\n\n**Actions Performed:**\n1. **Detect environment**\n - Package manager from lockfiles\n - Existing vitest/vite config\n\n2. **Update package.json**\n - Add `test:coverage`: 'vitest run --coverage'\n - Add `coverage:low`: 'uncov'\n - Skip existing scripts (report which)\n\n3. **Configure vitest**\n - Create or update vitest.config.ts with:\n ```typescript\n coverage: {\n provider: 'v8',\n reporter: ['text', 'json-summary'],\n reportsDirectory: './coverage',\n }\n ```\n\n4. **Update .gitignore**\n - Add 'coverage/' if not present\n\n**Output:**\n```\nāœ“ Detected package manager: pnpm\nāœ“ Added script: test:coverage\nāœ“ Added script: coverage:low\nāœ“ Updated vitest.config.ts with coverage settings\nāœ“ Added coverage/ to .gitignore\n\nRun 'pnpm test:coverage' to generate coverage, then 'uncov' to see low coverage files.\n```\n\n**Error Handling:**\n- No package.json → error with instructions\n- Existing coverage config without --force → warn and skip\n\n**Tests Required:** test/commands/init.test.ts","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-02-02T20:53:10.323176-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:18:53.569118-06:00","closed_at":"2026-02-03T07:18:53.569118-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-2bo","depends_on_id":"uncov-mv8","type":"blocks","created_at":"2026-02-02T20:56:16.57644-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-2bo","depends_on_id":"uncov-fn0","type":"blocks","created_at":"2026-02-02T20:56:16.688394-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-2bo","depends_on_id":"uncov-s2m","type":"blocks","created_at":"2026-02-02T20:56:16.802375-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-2bo","depends_on_id":"uncov-ocy","type":"blocks","created_at":"2026-02-02T20:56:16.914277-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-2ur","title":"Critical: Add runtime validation for parsed JSON coverage data","description":"**Severity:** Critical\n**Location:** src/utils/fs.ts line 29, src/lib/coverage.ts\n\n**Issue:** `readJson\u003cT\u003e` uses `as T` type assertion without runtime validation. Malformed coverage-summary.json could cause runtime errors or security issues.\n\n**The Fix:**\nAdd validation functions in src/lib/coverage.ts:\n```typescript\nfunction isValidCoverageMetric(obj: unknown): obj is CoverageMetric {\n if (typeof obj \\!== 'object' || obj === null) return false;\n const metric = obj as Record\u003cstring, unknown\u003e;\n return (\n typeof metric.total === 'number' \u0026\u0026\n typeof metric.covered === 'number' \u0026\u0026\n typeof metric.pct === 'number'\n );\n}\n\nfunction isValidFileCoverage(obj: unknown): obj is FileCoverage {\n if (typeof obj \\!== 'object' || obj === null) return false;\n const coverage = obj as Record\u003cstring, unknown\u003e;\n return isValidCoverageMetric(coverage.lines);\n}\n\nexport function parseCoverageSummary(data: unknown): CoverageSummary {\n if (typeof data \\!== 'object' || data === null) {\n throw new Error('Coverage summary must be an object');\n }\n \n const summary = data as Record\u003cstring, unknown\u003e;\n \n if (\\!isValidFileCoverage(summary.total)) {\n throw new Error('Coverage summary missing valid \"total\" field');\n }\n \n return summary as CoverageSummary;\n}\n```\n\n**Acceptance Criteria:**\n- parseCoverageSummary validates structure at runtime\n- Invalid coverage data throws descriptive errors\n- coverage-summary-missing-fields.json fixture is used in tests\n- Tests verify error messages for various invalid inputs","status":"closed","priority":0,"issue_type":"bug","created_at":"2026-02-02T21:43:39.39318-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:46:27.689365-06:00","closed_at":"2026-02-02T21:46:27.689365-06:00","close_reason":"Closed"} -{"id":"uncov-38c","title":"Implement report command: default CLI command for coverage reporting","description":"Create src/commands/report.ts - the default command when running `uncov`.\n\n**CLI Flags:**\n- `--threshold \u003cn\u003e` (default: 10) - Show files at or below this line coverage %\n- `--fail` - Exit code 1 if any files below threshold\n- `--json` - Output JSON instead of table\n- `--coverage-path \u003cpath\u003e` (default: coverage/coverage-summary.json)\n\n**Implementation:**\n1. Parse CLI args using util.parseArgs\n2. Read coverage-summary.json via coverage.ts\n3. Filter files at or below threshold\n4. Sort by percentage ascending\n5. Format and output results\n6. Exit with appropriate code\n\n**Exit Codes:**\n- 0: Success (or low coverage found but --fail not set)\n- 1: Low coverage found AND --fail set\n- 2: Missing coverage file or config error\n\n**Output Formats:**\nTable (default):\n```\nFiles at or below 10% line coverage: 3\n\n 0.00% LH 0/LF 120 src/renderer.ts\n 5.26% LH 2/LF 38 src/bootstrap.ts\n 10.00% LH 10/LF 100 src/engine.ts\n```\n\nJSON (--json):\n```json\n[{\"path\": \"src/renderer.ts\", \"percentage\": 0, \"linesHit\": 0, \"linesFound\": 120}, ...]\n```\n\n**Tests Required:** test/commands/report.test.ts","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-02-02T20:52:30.298274-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:18:53.533875-06:00","closed_at":"2026-02-03T07:18:53.533875-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-38c","depends_on_id":"uncov-p0r","type":"blocks","created_at":"2026-02-02T20:56:15.969029-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-38c","depends_on_id":"uncov-q77","type":"blocks","created_at":"2026-02-02T20:56:16.083298-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-38c","depends_on_id":"uncov-brm","type":"blocks","created_at":"2026-02-02T20:56:16.1985-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-38c","depends_on_id":"uncov-ocy","type":"blocks","created_at":"2026-02-02T20:56:16.310572-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-38p","title":"Fix: Extract shared vitest config file list to constants","description":"**Severity:** Medium\n**Location:** src/lib/detect.ts (lines 58-67) and src/lib/vitest-config.ts (lines 11-20)\n\n**Issue:** Both files define the same list of config files to check (vitest.config.ts, vite.config.ts, etc.). Violates DRY.\n\n**The Fix:**\nCreate src/lib/constants.ts:\n```typescript\nexport const VITEST_CONFIG_FILES = [\n { file: 'vitest.config.ts', type: 'ts' },\n { file: 'vitest.config.js', type: 'js' },\n { file: 'vitest.config.mts', type: 'mts' },\n { file: 'vitest.config.mjs', type: 'mjs' },\n { file: 'vite.config.ts', type: 'ts' },\n { file: 'vite.config.js', type: 'js' },\n { file: 'vite.config.mts', type: 'mts' },\n { file: 'vite.config.mjs', type: 'mjs' },\n] as const;\n\nexport type VitestConfigType = (typeof VITEST_CONFIG_FILES)[number]['type'];\n```\n\nUpdate detect.ts and vitest-config.ts to import from constants.ts.\n\n**Acceptance Criteria:**\n- Constants extracted to shared module\n- Both files import from constants\n- No duplicate config lists\n- Tests still pass","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T07:02:40.096625-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.180074-06:00","closed_at":"2026-02-03T07:04:56.180074-06:00","close_reason":"Closed"} -{"id":"uncov-3ky","title":"Project scaffolding: create src directory structure and base files","description":"Create the initial project structure as defined in the spec:\n\n**Directory Structure:**\n- src/cli.ts - Entry point with arg parsing\n- src/commands/ - Command implementations directory\n- src/lib/ - Core logic directory\n- src/utils/ - Helper utilities directory\n- test/ - Test directory\n- test/fixtures/ - Sample coverage files for testing\n- scripts/ - Build scripts directory\n\n**Base Files to Create:**\n- src/cli.ts with basic structure (parseArgs setup, command routing skeleton)\n- Export barrel files where needed\n\n**Acceptance Criteria:**\n- All directories exist\n- cli.ts has basic structure with parseArgs from util\n- Project compiles with `bun build`","status":"closed","priority":0,"issue_type":"task","created_at":"2026-02-02T20:52:04.777657-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:21:13.825666-06:00","closed_at":"2026-02-02T21:21:13.825666-06:00","close_reason":"Closed"} -{"id":"uncov-3te","title":"Fix: Improve hasCoverageConfig detection accuracy","description":"**Severity:** Medium\n**Location:** src/lib/vitest-config.ts (lines 43-65)\n\n**Issue:** Regex-based detection is overly broad. Matches any 'coverage' string including comments, imports, or unrelated variables. Can produce false positives.\n\n**The Fix:**\nImprove detection to look for actual coverage config blocks:\n```typescript\nexport function hasCoverageConfig(configPath: string): boolean {\n if (!fileExists(configPath)) {\n return false;\n }\n\n try {\n const content = readText(configPath);\n \n // Primary check: explicit coverage configuration block\n const hasCoverageBlock = /coverage\\s*:\\s*\\{/.test(content);\n if (hasCoverageBlock) {\n return true;\n }\n \n // Secondary check: coverage property assignment\n const hasCoverageProperty = /coverage\\s*:\\s*[a-zA-Z]/.test(content);\n return hasCoverageProperty;\n } catch {\n return false;\n }\n}\n```\n\nAdd JSDoc explaining heuristic nature.\n\n**Acceptance Criteria:**\n- More accurate detection of coverage config\n- JSDoc documents limitations\n- Tests updated for new behavior","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T07:02:40.491341-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.210031-06:00","closed_at":"2026-02-03T07:04:56.210031-06:00","close_reason":"Closed"} -{"id":"uncov-4bm","title":"Add colorized terminal output with TTY detection","description":"Add ANSI color codes to improve CLI readability. Zero dependencies - use escape codes directly.\n\n**Colors to Add:**\n- Red: Low coverage files, errors, failed checks\n- Green: Success messages, passing checks, files above threshold\n- Yellow: Warnings, skipped items, threshold boundary\n- Cyan: File paths, informational headers\n- Bold: Section headers, important values\n\n**Implementation:**\n\nCreate src/utils/colors.ts:\n```typescript\nconst isColorSupported = process.stdout.isTTY \u0026\u0026 !process.env.NO_COLOR;\n\nexport const colors = {\n red: (s: string) =\u003e isColorSupported ? `\\x1b[31m${s}\\x1b[0m` : s,\n green: (s: string) =\u003e isColorSupported ? `\\x1b[32m${s}\\x1b[0m` : s,\n yellow: (s: string) =\u003e isColorSupported ? `\\x1b[33m${s}\\x1b[0m` : s,\n cyan: (s: string) =\u003e isColorSupported ? `\\x1b[36m${s}\\x1b[0m` : s,\n bold: (s: string) =\u003e isColorSupported ? `\\x1b[1m${s}\\x1b[0m` : s,\n};\n```\n\n**Apply to:**\n- report command: Red for 0% files, yellow for low coverage, green header when no issues\n- init command: Green checkmarks, yellow for skipped, red for errors\n- check command: Green āœ“ for pass, red āœ— for fail\n\n**Standards:**\n- Respect NO_COLOR env var (https://no-color.org/)\n- Detect TTY to disable colors when piped\n- Add --no-color flag as override\n\n**Tests:** test/utils/colors.test.ts","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-02-03T07:32:28.729153-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.440711-06:00","closed_at":"2026-02-03T07:43:01.440711-06:00","close_reason":"Closed"} -{"id":"uncov-4du","title":"Fix: Add dependency caching to CI workflow","description":"**Severity:** Medium (Performance)\n\nAdd Bun dependency caching to speed up CI:\n\n```yaml\n- name: Cache Bun dependencies\n uses: actions/cache@v4\n with:\n path: ~/.bun/install/cache\n key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}\n restore-keys: |\n ${{ runner.os }}-bun-\n```\n\nAdd this before the 'Install dependencies' step in both test and build jobs.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-02-03T07:53:35.976126-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:56:51.105161-06:00","closed_at":"2026-02-03T07:56:51.105161-06:00","close_reason":"Closed"} -{"id":"uncov-54t","title":"Fix: Improve gitignore pattern matching","description":"**Severity:** Low\n**Location:** src/lib/detect.ts (detectGitignore function)\n\n**Issue:** Only matches exact patterns. Misses valid gitignore patterns like 'coverage*', '**/coverage', 'coverage/**'.\n\n**The Fix:**\nExpand pattern matching:\n```typescript\nconst hasCoverage = lines.some((line) =\u003e {\n if (line.startsWith('#') || line === '') {\n return false;\n }\n // Match common coverage patterns\n return /^[/*]*coverage[/*]*$/.test(line) || \n line.startsWith('coverage') ||\n line.endsWith('/coverage') ||\n line.endsWith('/coverage/');\n});\n```\n\n**Acceptance Criteria:**\n- Matches coverage*, **/coverage, coverage/** patterns\n- Add tests for glob patterns","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-02-03T07:02:41.280236-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.274651-06:00","closed_at":"2026-02-03T07:04:56.274651-06:00","close_reason":"Closed"} -{"id":"uncov-5yk","title":"Fix: Add verbose flag tests for check and report commands","description":"**Severity:** Medium\n\nAdd test coverage for --verbose flag in check and report commands.\n\nFor test/commands/check.test.ts:\n```typescript\ndescribe('with --verbose flag', () =\u003e {\n it('should output debug information to stderr', async () =\u003e {\n // Setup and call checkCommand({ verbose: true })\n // Verify stderr contains '[verbose]'\n });\n});\n```\n\nFor test/commands/report.test.ts:\n```typescript\ndescribe('with --verbose flag', () =\u003e {\n it('should output debug information to stderr', async () =\u003e {\n // Setup and call reportCommand({ verbose: true })\n // Verify stderr contains '[verbose]'\n });\n});\n```","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T07:41:47.566326-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.380734-06:00","closed_at":"2026-02-03T07:43:01.380734-06:00","close_reason":"Closed"} -{"id":"uncov-67r","title":"Fix: Remove unused colors default export","description":"**Severity:** Low\n\nRemove the unused default `colors` instance from src/utils/colors.ts (line 64).\n\nAll commands correctly use `createColors(options.noColor)`. The default export is never used and could mislead developers.\n\nRemove:\n```typescript\nexport const colors = createColors();\n```","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-02-03T07:41:47.029394-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.352029-06:00","closed_at":"2026-02-03T07:43:01.352029-06:00","close_reason":"Closed"} -{"id":"uncov-6no","title":"Fix: Remove unused type imports from commands","description":"**Severity:** Low\n\nRemove unused `Colors` and `Logger` type imports from:\n- src/commands/check.ts (lines 9, 11)\n- src/commands/init.ts (lines 8, 10)\n- src/commands/report.ts (lines 12, 15)\n\nChange from:\n```typescript\nimport { type Colors, createColors } from '../utils/colors';\nimport { type Logger, createLogger } from '../utils/logger';\n```\n\nTo:\n```typescript\nimport { createColors } from '../utils/colors';\nimport { createLogger } from '../utils/logger';\n```","status":"closed","priority":3,"issue_type":"bug","created_at":"2026-02-03T07:41:46.488283-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.323105-06:00","closed_at":"2026-02-03T07:43:01.323105-06:00","close_reason":"Closed"} -{"id":"uncov-77g","title":"Add: Unit tests for format utilities","description":"**Issue:** The formatting utilities (formatPercent, formatLines, formatFileLine, formatReport) have implementations but no unit tests. These pure functions are ideal for unit testing.\n\n**Location:** Create test/format.test.ts\n\n**Tests to Add:**\n```typescript\nimport { describe, expect, it } from 'bun:test';\nimport { formatPercent, formatLines, formatFileLine, formatReport } from '../src/utils/format';\n\ndescribe('formatPercent', () =\u003e {\n it('should format 0% correctly', () =\u003e {\n expect(formatPercent(0)).toBe(' 0.00%');\n });\n \n it('should format 100% correctly', () =\u003e {\n expect(formatPercent(100)).toBe('100.00%');\n });\n \n it('should format decimal percentages', () =\u003e {\n expect(formatPercent(5.26)).toBe(' 5.26%');\n });\n});\n\ndescribe('formatLines', () =\u003e {\n it('should format line counts with padding', () =\u003e {\n expect(formatLines(10, 100)).toBe('LH 10/LF 100');\n });\n});\n\ndescribe('formatReport', () =\u003e {\n it('should show message when no files below threshold', () =\u003e {\n expect(formatReport([], 10)).toBe('No files at or below 10% line coverage.');\n });\n});\n```\n\n**Acceptance Criteria:**\n- test/format.test.ts exists with comprehensive tests\n- All format functions have test coverage\n- Tests pass with `bun test`","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T21:16:16.073855-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:21:13.486121-06:00","closed_at":"2026-02-02T21:21:13.486121-06:00","close_reason":"Closed"} -{"id":"uncov-a4f","title":"Fix: Import version from package.json instead of hardcoding","description":"**Issue:** Version string '0.1.0' is duplicated in package.json and cli.ts (line 11). Creates maintenance burden and version mismatch risk.\n\n**Location:** src/cli.ts line 11\n\n**The Fix:**\n```typescript\n// src/cli.ts - Import version from package.json\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\n```\n\n**Acceptance Criteria:**\n- VERSION constant reads from package.json\n- `bun run dev -- --version` shows correct version\n- No duplicate version strings in codebase","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-02-02T21:14:49.603492-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:19:02.454726-06:00","closed_at":"2026-02-02T21:19:02.454726-06:00","close_reason":"Closed"} -{"id":"uncov-b90","title":"Implement build script for cross-platform binaries","description":"Create scripts/build.ts for building cross-platform binaries.\n\n**Target Platforms:**\n- darwin-arm64 (macOS Apple Silicon)\n- darwin-x64 (macOS Intel)\n- linux-x64 (Linux)\n- windows-x64 (Windows)\n\n**Build Command:**\n```bash\nbun build --compile --target=bun-{platform}-{arch} --outfile=dist/uncov-{platform}-{arch}\n```\n\n**Script Features:**\n1. Clean dist/ directory before build\n2. Build for all platforms in parallel\n3. Report success/failure for each target\n4. Generate checksums (SHA256) for each binary\n\n**Output Structure:**\n```\ndist/\nā”œā”€ā”€ uncov-darwin-arm64\nā”œā”€ā”€ uncov-darwin-arm64.sha256\nā”œā”€ā”€ uncov-darwin-x64\nā”œā”€ā”€ uncov-darwin-x64.sha256\nā”œā”€ā”€ uncov-linux-x64\nā”œā”€ā”€ uncov-linux-x64.sha256\nā”œā”€ā”€ uncov-windows-x64.exe\n└── uncov-windows-x64.exe.sha256\n```\n\n**Package.json Scripts:**\n```json\n{\n \"build:local\": \"bun build --compile --outfile=dist/uncov ./src/cli.ts\",\n \"build:all\": \"bun run scripts/build.ts\"\n}\n```\n\n**Tests:** Manual verification (build and run on local platform)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:54:53.709579-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.531039-06:00","closed_at":"2026-02-03T07:43:01.531039-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-b90","depends_on_id":"uncov-38c","type":"blocks","created_at":"2026-02-02T20:56:17.734343-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-bq5","title":"Warn user before sudo escalation in install.sh","description":"Lines 266-267: Warn user before attempting sudo. Add: warn \"Permission denied. Attempting install with sudo...\" before the sudo cp command.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T08:19:05.108831-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T08:19:20.963331-06:00","closed_at":"2026-02-03T08:19:20.963331-06:00","close_reason":"Closed"} -{"id":"uncov-brm","title":"Implement config.ts: read uncov configuration","description":"Create src/lib/config.ts to handle configuration loading.\n\n**Config Sources (priority order):**\n1. CLI flags (highest priority)\n2. `uncov.config.json` in project root\n3. `uncov` field in `package.json`\n4. Defaults (lowest priority)\n\n**Config Schema:**\n```typescript\ninterface UncovConfig {\n threshold: number; // default: 10\n exclude: string[]; // glob patterns to exclude\n failOnLow: boolean; // default: false\n coveragePath: string; // default: 'coverage/coverage-summary.json'\n}\n```\n\n**Functions to Implement:**\n- `loadConfig(cliOverrides?: Partial\u003cUncovConfig\u003e): UncovConfig`\n- `findConfigFile(): string | null` - Look for uncov.config.json\n- `readPackageJsonConfig(): Partial\u003cUncovConfig\u003e | null`\n- `mergeConfigs(...configs: Partial\u003cUncovConfig\u003e[]): UncovConfig`\n\n**Config File Format:**\n```json\n{\n \"threshold\": 20,\n \"exclude\": [\"**/*.test.ts\", \"**/index.ts\"],\n \"failOnLow\": false\n}\n```\n\n**Tests Required:** test/lib/config.test.ts","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:52:30.772821-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.339701-06:00","closed_at":"2026-02-03T07:04:56.339701-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-brm","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:08.061163-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-brm","depends_on_id":"uncov-ed6","type":"blocks","created_at":"2026-02-02T20:56:08.174383-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-cgy","title":"Improve: Add exit code assertions to CLI tests","description":"**Issue:** Tests spawn processes and check stdout but don't verify exit codes. Need to assert on exit codes for proper testing.\n\n**Location:** test/cli.test.ts (all test cases)\n\n**The Fix:**\n```typescript\nit('should show version with --version flag', async () =\u003e {\n const proc = Bun.spawn(['bun', 'run', './src/cli.ts', '--version'], {\n cwd: `${import.meta.dir}/..`,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n \n const exitCode = await proc.exited;\n const output = await new Response(proc.stdout).text();\n \n expect(exitCode).toBe(0);\n expect(output.trim()).toBe('0.1.0');\n});\n```\n\n**Acceptance Criteria:**\n- All existing tests assert exit code 0\n- Add test for exit code 2 on invalid input\n- Tests check both stdout and exit codes","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T21:16:15.769078-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:21:13.456059-06:00","closed_at":"2026-02-02T21:21:13.456059-06:00","close_reason":"Closed"} -{"id":"uncov-e27","title":"Implement check command: verify coverage setup","description":"Create src/commands/check.ts - the `uncov check` command for v0.3.0.\n\n**Purpose:** Verify coverage setup is correct (useful for CI).\n\n**Checks Performed:**\n1. **Vitest config exists and has coverage settings**\n - Look for vitest.config.* or vite.config.* with test section\n - Verify coverage.reporter includes 'json-summary'\n - Verify coverage.reportsDirectory is set\n\n2. **coverage-summary.json exists**\n - Check default path: coverage/coverage-summary.json\n - Or configured path from uncov config\n\n3. **Scripts are configured**\n - Check for test:coverage or similar script\n - Check for uncov script (optional)\n\n**Output (success):**\n```\nāœ“ Vitest config found: vitest.config.ts\nāœ“ Coverage reporter configured: json-summary\nāœ“ Coverage summary exists: coverage/coverage-summary.json\nāœ“ Scripts configured: test:coverage\n\nAll checks passed!\n```\n\n**Output (failure):**\n```\nāœ— Coverage summary not found: coverage/coverage-summary.json\n Run 'pnpm test:coverage' to generate coverage data\n\n1 check failed\n```\n\n**Exit Codes:**\n- 0: All checks pass\n- 1: One or more checks failed\n- 2: Configuration error\n\n**Tests Required:** test/commands/check.test.ts","status":"closed","priority":3,"issue_type":"feature","created_at":"2026-02-02T20:53:10.561065-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:18:53.606267-06:00","closed_at":"2026-02-03T07:18:53.606267-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-e27","depends_on_id":"uncov-mv8","type":"blocks","created_at":"2026-02-02T20:56:17.164737-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-e27","depends_on_id":"uncov-brm","type":"blocks","created_at":"2026-02-02T20:56:17.277445-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-e27","depends_on_id":"uncov-p0r","type":"blocks","created_at":"2026-02-02T20:56:17.391548-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-e27","depends_on_id":"uncov-ocy","type":"blocks","created_at":"2026-02-02T20:56:17.501771-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-ed6","title":"Implement fs.ts: file system helper utilities","description":"Create src/utils/fs.ts for common file operations.\n\n**Functions to Implement:**\n\n`fileExists(path: string): boolean`\n- Check if file exists (sync)\n\n`readJson\u003cT\u003e(path: string): T`\n- Read and parse JSON file\n- Throw descriptive error on parse failure\n\n`writeJson(path: string, data: unknown): void`\n- Write JSON with 2-space indent\n- Ensure directory exists\n\n`readText(path: string): string`\n- Read text file contents\n\n`writeText(path: string, content: string): void`\n- Write text file\n- Ensure directory exists\n\n`ensureDir(path: string): void`\n- Create directory if it doesn't exist (recursive)\n\n`appendLine(path: string, line: string): void`\n- Append line to file (create if doesn't exist)\n- Handle trailing newline properly\n\n**Error Handling:**\n- All functions should throw descriptive errors\n- Include file path in error messages\n- Distinguish between 'not found' and 'permission denied'\n\n**Tests Required:** test/utils/fs.test.ts","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-02T20:54:32.981618-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:47:19.710033-06:00","closed_at":"2026-02-02T21:47:19.710033-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-ed6","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:07.004481-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-efk","title":"Fix: Add binary existence check in build script","description":"**Severity:** Medium\n\nAfter build succeeds, verify the binary file actually exists before generating checksum.\n\nIn scripts/build.ts, add check after successful build:\n```typescript\nif (exitCode === 0) {\n // Verify the binary was actually created\n if (!existsSync(outPath)) {\n return {\n target,\n outfile,\n success: false,\n error: 'Binary file was not created despite successful build',\n };\n }\n // Then generate checksum...\n}\n```","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T07:41:48.086096-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.411604-06:00","closed_at":"2026-02-03T07:43:01.411604-06:00","close_reason":"Closed"} -{"id":"uncov-ejy","title":"Fix: Add threshold argument validation","description":"**Issue:** The threshold is parsed with Number.parseInt() but there's no validation for non-numeric input, negative values, or values over 100.\n\n**Location:** src/cli.ts line 84\n\n**The Fix:**\n```typescript\nfunction parseCliArgs(args: string[]): ParsedArgs {\n // ... existing parseArgs code ...\n\n const thresholdRaw = values.threshold ?? '10';\n const threshold = Number.parseInt(thresholdRaw, 10);\n \n if (Number.isNaN(threshold)) {\n console.error(`Error: Invalid threshold value \"${thresholdRaw}\". Must be a number.`);\n process.exit(2);\n }\n \n if (threshold \u003c 0 || threshold \u003e 100) {\n console.error(`Error: Threshold must be between 0 and 100, got ${threshold}.`);\n process.exit(2);\n }\n\n return {\n // ... rest\n threshold,\n };\n}\n```\n\n**Acceptance Criteria:**\n- `uncov --threshold abc` shows error and exits 2\n- `uncov --threshold -10` shows error and exits 2\n- `uncov --threshold 150` shows error and exits 2\n- `uncov --threshold 50` works normally","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-02-02T21:15:52.495852-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:19:02.514569-06:00","closed_at":"2026-02-02T21:19:02.514569-06:00","close_reason":"Closed"} -{"id":"uncov-eqy","title":"Fix: Move contents:write permission to release job only","description":"**Severity:** High (Security)\n\nMove `contents: write` permission from workflow level to only the release job.\n\n**Current (insecure):**\n```yaml\npermissions:\n contents: write # Applied to ALL jobs\n```\n\n**Fix:**\n```yaml\njobs:\n test:\n permissions:\n contents: read\n \n build:\n permissions:\n contents: read\n \n release:\n permissions:\n contents: write # Only release needs write\n```","status":"closed","priority":0,"issue_type":"bug","created_at":"2026-02-03T07:53:30.492316-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:56:51.046473-06:00","closed_at":"2026-02-03T07:56:51.046473-06:00","close_reason":"Closed"} -{"id":"uncov-fn0","title":"Implement package-json.ts: safe package.json modification","description":"Create src/lib/package-json.ts for safely modifying package.json.\n\n**Functions to Implement:**\n\n`readPackageJson(path?: string): PackageJson`\n- Read and parse package.json\n- Handle missing file gracefully\n\n`writePackageJson(pkg: PackageJson, path?: string): void`\n- Write with consistent formatting (2-space indent)\n- Preserve existing formatting where possible\n\n`addScripts(scripts: Record\u003cstring, string\u003e): { added: string[]; skipped: string[] }`\n- Add scripts only if they don't exist\n- Return which were added vs skipped (already exist)\n\n`hasScript(name: string): boolean`\n- Check if a script exists\n\n**Scripts to Add (via init command):**\n```json\n{\n \"test:coverage\": \"vitest run --coverage\",\n \"coverage:low\": \"uncov\"\n}\n```\n\n**Safety:**\n- Never overwrite existing scripts without --force\n- Backup original before modification (optional)\n- Validate JSON before writing\n\n**Tests Required:** test/lib/package-json.test.ts","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:52:49.587304-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.40143-06:00","closed_at":"2026-02-03T07:04:56.40143-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-fn0","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:08.757612-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-fn0","depends_on_id":"uncov-ed6","type":"blocks","created_at":"2026-02-02T20:56:08.871325-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-iz5","title":"Add error handling for mktemp in install.sh","description":"Line 186: Add error handling for mktemp failure. Change: TMP_DIR=$(mktemp -d) to: TMP_DIR=$(mktemp -d) || error \"Failed to create temporary directory\"","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T08:19:04.72882-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T08:19:20.93282-06:00","closed_at":"2026-02-03T08:19:20.93282-06:00","close_reason":"Closed"} -{"id":"uncov-kah","title":"Create GitHub Actions CI workflow for testing and releases","description":"Create .github/workflows/ci.yml for automated testing and releases.\n\n**Workflow Triggers:**\n- Push to main\n- Pull requests to main\n- Tags matching v*.*.*\n\n**Jobs:**\n\n**1. Test Job**\n- Runs on: ubuntu-latest\n- Steps:\n - Checkout\n - Setup Bun\n - Install dependencies (bun install)\n - Run linter (bun run lint)\n - Run tests (bun test)\n\n**2. Build Job** (on tag push only)\n- Runs on: matrix of [ubuntu-latest, macos-latest, windows-latest]\n- Steps:\n - Checkout\n - Setup Bun\n - Build binary for platform\n - Upload artifact\n\n**3. Release Job** (on tag push, after build)\n- Create GitHub release\n- Upload all binary artifacts\n- Generate release notes from commits\n\n**Release Artifact Names:**\n- uncov-darwin-arm64\n- uncov-darwin-x64\n- uncov-linux-x64\n- uncov-windows-x64.exe\n\n**Matrix Strategy:**\n```yaml\nstrategy:\n matrix:\n include:\n - os: macos-latest\n target: darwin-arm64\n - os: macos-13\n target: darwin-x64\n - os: ubuntu-latest\n target: linux-x64\n - os: windows-latest\n target: windows-x64\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:54:53.956377-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:56:51.136697-06:00","closed_at":"2026-02-03T07:56:51.136697-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-kah","depends_on_id":"uncov-b90","type":"blocks","created_at":"2026-02-02T20:56:17.845049-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-l2y","title":"Add --dry-run flag to init command","description":"Add --dry-run flag to show what init would change without modifying files.\n\n**Usage:**\n```bash\nuncov init --dry-run\n```\n\n**Output Format:**\n```\nDry run - no files will be modified\n\nWould detect package manager: pnpm\nWould add script: test:coverage -\u003e 'vitest run --coverage'\nWould add script: coverage:low -\u003e 'uncov'\nWould create: vitest.config.ts\nWould append to .gitignore: coverage/\n\nRun without --dry-run to apply these changes.\n```\n\n**Implementation:**\n1. Add 'dry-run' to CLI parseArgs options (boolean flag)\n2. Pass dryRun option to initCommand()\n3. In initCommand, check dryRun before each write operation\n4. Use 'Would X' prefix for dry-run output vs 'āœ“ X' for real execution\n5. Skip all writeText, appendLine, writePackageJson calls when dry-run\n\n**CLI Addition:**\n```typescript\n'dry-run': {\n type: 'boolean',\n default: false,\n},\n```\n\n**Tests:** Add dry-run tests to test/commands/init.test.ts\n- Verify no files are created/modified\n- Verify output shows 'Would' prefixes\n- Verify exit code is 0","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-02-03T07:32:29.25789-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.470313-06:00","closed_at":"2026-02-03T07:43:01.470313-06:00","close_reason":"Closed"} -{"id":"uncov-mc5","title":"Fix UUOC in install.sh line 202","description":"Replace cat | awk with awk directly in install.sh line 202. Change: EXPECTED_CHECKSUM=$(cat \"$TMP_CHECKSUM\" | awk '{print $1}') to: EXPECTED_CHECKSUM=$(awk '{print $1}' \"$TMP_CHECKSUM\")","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T08:18:59.445294-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T08:19:20.90235-06:00","closed_at":"2026-02-03T08:19:20.90235-06:00","close_reason":"Closed"} -{"id":"uncov-mv8","title":"Implement detect.ts: package manager and config detection","description":"Create src/lib/detect.ts for detecting project environment.\n\n**Functions to Implement:**\n\n`detectPackageManager(): 'pnpm' | 'bun' | 'npm' | 'yarn'`\n- Check for lockfiles in priority order:\n 1. pnpm-lock.yaml → 'pnpm'\n 2. bun.lockb → 'bun'\n 3. package-lock.json → 'npm'\n 4. yarn.lock → 'yarn'\n- Default to 'npm' if none found\n\n`detectVitestConfig(): { path: string; type: 'ts' | 'js' | 'mts' | 'mjs' } | null`\n- Look for vitest.config.{ts,js,mts,mjs}\n- Also check vite.config.* with test section\n\n`detectExistingCoverageConfig(): boolean`\n- Check if vitest config already has coverage settings\n\n`detectGitignore(): { exists: boolean; hasCoverage: boolean }`\n- Check if .gitignore exists\n- Check if 'coverage/' or 'coverage' is already listed\n\n**Tests Required:** test/lib/detect.test.ts","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:52:49.344921-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.371429-06:00","closed_at":"2026-02-03T07:04:56.371429-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-mv8","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:08.40956-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-mv8","depends_on_id":"uncov-ed6","type":"blocks","created_at":"2026-02-02T20:56:08.52271-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-o9j","title":"Fix: Remove duplicate fileExists function from detect.ts","description":"**Issue:** There are two fileExists functions - one stub in detect.ts (line 28) and one implemented in utils/fs.ts (line 12). This violates DRY and will cause confusion.\n\n**Locations:**\n- src/lib/detect.ts line 28 (stub - REMOVE)\n- src/utils/fs.ts line 12 (implementation - KEEP)\n\n**The Fix:**\nRemove the stub from detect.ts and import from utils:\n```typescript\n// src/lib/detect.ts\nimport { fileExists } from '../utils/fs';\n\n// Then use fileExists() in detectPackageManager implementation\n```\n\n**Acceptance Criteria:**\n- Only one fileExists function exists (in utils/fs.ts)\n- detect.ts imports from utils/fs\n- No barrel export conflicts","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-02-02T21:14:49.894382-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:19:02.484338-06:00","closed_at":"2026-02-02T21:19:02.484338-06:00","close_reason":"Closed"} -{"id":"uncov-ocy","title":"Implement CLI entry point with command routing","description":"Complete src/cli.ts as the main entry point.\n\n**Responsibilities:**\n- Parse top-level args to determine command\n- Route to appropriate command handler\n- Handle --help and --version flags\n- Graceful error handling with user-friendly messages\n\n**Commands to Route:**\n- (default/no command) → report command\n- `init` → init command\n- `check` → check command\n\n**Global Flags:**\n- `--help, -h` - Show help text\n- `--version, -v` - Show version from package.json\n\n**Help Output:**\n```\nuncov - Report files with low test coverage\n\nUsage: uncov [command] [options]\n\nCommands:\n (default) Report files below coverage threshold\n init Bootstrap coverage configuration\n check Verify coverage setup\n\nOptions:\n --threshold \u003cn\u003e Coverage threshold percentage (default: 10)\n --fail Exit 1 if files below threshold\n --json Output as JSON\n --help, -h Show this help\n --version, -v Show version\n```\n\n**Error Handling:**\n- Unknown command → suggest similar, show help\n- Missing required args → show specific error + help\n\n**Tests Required:** test/cli.test.ts","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-02T20:52:30.532892-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:47:19.802583-06:00","closed_at":"2026-02-02T21:47:19.802583-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-ocy","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:07.346017-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-p0r","title":"Implement coverage.ts: parse Istanbul coverage-summary.json","description":"Create src/lib/coverage.ts to parse Vitest/Istanbul coverage output.\n\n**Input Format:** coverage/coverage-summary.json (Istanbul standard)\n```json\n{\n \"total\": { \"lines\": { \"total\": 1000, \"covered\": 500, \"pct\": 50 }, ... },\n \"src/file.ts\": { \"lines\": { \"total\": 100, \"covered\": 10, \"pct\": 10 }, ... }\n}\n```\n\n**Functions to Implement:**\n- `parseCoverageSummary(path: string): CoverageData` - Read and parse JSON\n- `filterBelowThreshold(data: CoverageData, threshold: number): FileEntry[]` - Filter files at or below threshold\n- `sortByPercentage(files: FileEntry[]): FileEntry[]` - Sort ascending by coverage %\n\n**Types to Define:**\n- `CoverageData` - Parsed coverage structure\n- `FileEntry` - Single file with path, lines hit/found, percentage\n\n**Error Handling:**\n- File not found → throw descriptive error\n- Invalid JSON → throw descriptive error\n- Missing required fields → throw descriptive error\n\n**Tests Required:** test/lib/coverage.test.ts with fixture files","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-02T20:52:10.97738-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.307154-06:00","closed_at":"2026-02-03T07:04:56.307154-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-p0r","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:07.591341-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-p0r","depends_on_id":"uncov-ed6","type":"blocks","created_at":"2026-02-02T20:56:07.702419-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-p0r","depends_on_id":"uncov-qu3","type":"blocks","created_at":"2026-02-02T20:56:07.816536-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-p2k","title":"Write comprehensive README.md documentation","description":"Create README.md with full documentation.\n\n**Sections:**\n\n**1. Header**\n- Project name and tagline\n- Badges (CI status, version, license)\n\n**2. Features**\n- Zero dependencies\n- Single binary\n- Cross-platform\n- No config required\n\n**3. Installation**\n- curl install script\n- Homebrew (when available)\n- Download from releases\n- Build from source\n\n**4. Quick Start**\n```bash\n# Generate coverage with vitest\npnpm test:coverage\n\n# Report low coverage files\nuncov\n\n# Files below 50% coverage\nuncov --threshold 50\n\n# Fail CI if low coverage\nuncov --fail\n```\n\n**5. Commands**\n- uncov (default) - detailed usage\n- uncov init - detailed usage\n- uncov check - detailed usage\n\n**6. Configuration**\n- uncov.config.json format\n- package.json format\n- CLI flags reference\n\n**7. Exit Codes**\n- Table of codes and meanings\n\n**8. Development**\n- Prerequisites (Bun)\n- Setup instructions\n- Running tests\n- Building locally\n\n**9. License**\n- MIT\n\n**Keep concise** - link to docs/spec.md for full details","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:55:28.558758-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:43:01.560213-06:00","closed_at":"2026-02-03T07:43:01.560213-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-p2k","depends_on_id":"uncov-38c","type":"blocks","created_at":"2026-02-02T20:56:18.313896-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-q77","title":"Implement format.ts: output formatting utilities","description":"Create src/utils/format.ts for consistent output formatting.\n\n**Functions to Implement:**\n\n`formatCoverageTable(files: FileEntry[]): string`\n- Format output like:\n ```\n Files at or below 10% line coverage: 3\n\n 0.00% LH 0/LF 120 src/renderer.ts\n 5.26% LH 2/LF 38 src/bootstrap.ts\n 10.00% LH 10/LF 100 src/engine.ts\n ```\n- Right-align percentage column\n- LH = lines hit, LF = lines found\n\n`formatJson(files: FileEntry[]): string`\n- JSON array output for --json flag\n- Include: path, percentage, linesHit, linesFound\n\n`formatSummary(count: number, threshold: number): string`\n- Header line: 'Files at or below X% line coverage: N'\n\n**Tests Required:** test/utils/format.test.ts","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-02T20:52:11.201581-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:47:19.771775-06:00","closed_at":"2026-02-02T21:47:19.771775-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-q77","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:07.23294-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-qu3","title":"Create test fixtures: sample coverage-summary.json files","description":"Create test/fixtures/ with sample Istanbul coverage data for testing.\n\n**Fixtures to Create:**\n\n**test/fixtures/coverage-summary-valid.json**\n- Standard valid coverage output\n- Multiple files with varying coverage (0%, 10%, 50%, 100%)\n- Include 'total' summary\n\n**test/fixtures/coverage-summary-empty.json**\n- Valid JSON but only 'total' with no individual files\n\n**test/fixtures/coverage-summary-all-covered.json**\n- All files at 100% coverage\n\n**test/fixtures/coverage-summary-all-zero.json**\n- All files at 0% coverage\n\n**test/fixtures/coverage-summary-invalid.json**\n- Malformed JSON for error testing\n\n**test/fixtures/coverage-summary-missing-fields.json**\n- Valid JSON but missing required fields\n\n**Sample Valid Structure:**\n```json\n{\n \"total\": {\n \"lines\": { \"total\": 1000, \"covered\": 450, \"skipped\": 0, \"pct\": 45 },\n \"statements\": { \"total\": 1000, \"covered\": 450, \"skipped\": 0, \"pct\": 45 },\n \"functions\": { \"total\": 100, \"covered\": 50, \"skipped\": 0, \"pct\": 50 },\n \"branches\": { \"total\": 200, \"covered\": 80, \"skipped\": 0, \"pct\": 40 }\n },\n \"src/renderer.ts\": {\n \"lines\": { \"total\": 120, \"covered\": 0, \"skipped\": 0, \"pct\": 0 },\n ...\n },\n \"src/engine.ts\": {\n \"lines\": { \"total\": 100, \"covered\": 10, \"skipped\": 0, \"pct\": 10 },\n ...\n }\n}\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-02T20:55:28.053333-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:47:19.742065-06:00","closed_at":"2026-02-02T21:47:19.742065-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-qu3","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:07.118652-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-r6p","title":"Fix: Pin Bun version and add lockfile","description":"**Severity:** Medium (Reproducibility)\n\n1. Pin Bun version instead of using 'latest':\n```yaml\n- uses: oven-sh/setup-bun@v1\n with:\n bun-version: '1.1.42'\n```\n\n2. Commit bun.lockb lockfile\n\n3. Use --frozen-lockfile in CI:\n```yaml\n- name: Install dependencies\n run: bun install --frozen-lockfile\n```","status":"closed","priority":0,"issue_type":"bug","created_at":"2026-02-03T07:53:31.082652-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:56:51.074748-06:00","closed_at":"2026-02-03T07:56:51.074748-06:00","close_reason":"Closed"} -{"id":"uncov-s2m","title":"Implement vitest-config.ts: detect and modify vitest configuration","description":"Create src/lib/vitest-config.ts for vitest config management.\n\n**Functions to Implement:**\n\n`hasVitestConfig(): boolean`\n- Check for vitest.config.* or vite.config.* with test section\n\n`hasCoverageConfig(): boolean`\n- Check if coverage is already configured in vitest config\n\n`createVitestConfig(): string`\n- Generate new vitest.config.ts content:\n```typescript\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n coverage: {\n provider: 'v8',\n reporter: ['text', 'json-summary'],\n reportsDirectory: './coverage',\n },\n },\n});\n```\n\n`patchVitestConfig(existingContent: string): string`\n- Add coverage config to existing vitest.config.ts\n- Use AST manipulation or careful string manipulation\n- Preserve existing config, only add coverage section\n\n**Edge Cases:**\n- vite.config.ts with test section (common pattern)\n- vitest.workspace.ts (workspace setups)\n- Already has partial coverage config\n\n**Tests Required:** test/lib/vitest-config.test.ts","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T20:52:49.816751-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T07:04:56.433681-06:00","closed_at":"2026-02-03T07:04:56.433681-06:00","close_reason":"Closed","dependencies":[{"issue_id":"uncov-s2m","depends_on_id":"uncov-3ky","type":"blocks","created_at":"2026-02-02T20:56:09.109874-06:00","created_by":"loganlindquist"},{"issue_id":"uncov-s2m","depends_on_id":"uncov-ed6","type":"blocks","created_at":"2026-02-02T20:56:09.222726-06:00","created_by":"loganlindquist"}]} -{"id":"uncov-tr8","title":"Fix: Remove deprecated async wrappers from fs.ts","description":"**Severity:** Medium\n**Location:** src/utils/fs.ts lines 127-145\n\n**Issue:** Deprecated async wrappers (readJsonFile, writeJsonFile) are misleading:\n- Not truly async (no await, just wrapped in Promise)\n- Silently swallow errors by returning null\n- Have no tests\n\n**The Fix:**\nRemove the deprecated functions entirely (lines 126-145):\n```typescript\n// Delete these deprecated functions:\n// - readJsonFile\n// - writeJsonFile\n```\n\nAlso remove any exports of these functions from index.ts.\n\n**Acceptance Criteria:**\n- Deprecated async wrappers are removed\n- No references to removed functions\n- Lint passes","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-02T21:43:39.770187-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:47:19.641811-06:00","closed_at":"2026-02-02T21:47:19.641811-06:00","close_reason":"Closed"} -{"id":"uncov-uar","title":"Add version format validation in install.sh","description":"In get_latest_version function, validate VERSION format after extraction. Add case statement to check for v[0-9]* or [0-9]* pattern, error on invalid format.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-03T08:19:05.478505-06:00","created_by":"loganlindquist","updated_at":"2026-02-03T08:19:20.993268-06:00","closed_at":"2026-02-03T08:19:20.993268-06:00","close_reason":"Closed"} -{"id":"uncov-xg6","title":"Fix: Add unrecognized command handling","description":"**Issue:** Running `uncov foobar` silently defaults to the report command. This confuses users who misspell commands.\n\n**Location:** src/cli.ts lines 71-78\n\n**The Fix:**\n```typescript\nconst command = positionals[0];\nlet parsedCommand: 'report' | 'init' | 'check' = 'report';\n\nif (command === 'init') {\n parsedCommand = 'init';\n} else if (command === 'check') {\n parsedCommand = 'check';\n} else if (command \u0026\u0026 command \\!== 'report') {\n console.error(`Unknown command: ${command}`);\n console.error(`Run 'uncov --help' for usage information.`);\n process.exit(2);\n}\n```\n\n**Acceptance Criteria:**\n- `uncov foobar` shows 'Unknown command: foobar' and exits 2\n- `uncov report` works normally\n- `uncov` (no command) defaults to report","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-02-02T21:16:14.226737-06:00","created_by":"loganlindquist","updated_at":"2026-02-02T21:21:13.425374-06:00","closed_at":"2026-02-02T21:21:13.425374-06:00","close_reason":"Closed"} diff --git a/.beads/metadata.json b/.beads/metadata.json deleted file mode 100644 index 31f5ec3..0000000 --- a/.beads/metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "database": "dolt", - "backend": "dolt", - "dolt_mode": "server", - "dolt_database": "uncov" -} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 807d598..0000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ - -# Use bd merge for beads JSONL files -.beads/issues.jsonl merge=beads diff --git a/.gitignore b/.gitignore index a237e42..d93e89c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,6 @@ # Dependencies node_modules/ -docs/ -.claude/ -.gemini/ -.cursor/ -.agent/ -GEMINI.md -AGENTS.md -CLAUDE.md - - # Build output dist/ uncov @@ -19,6 +9,24 @@ uncov.exe # Coverage coverage/ +# Docs (local only) +docs/ + +# Agent / IDE context (per-tool, not shared) +.claude/ +.gemini/ +.cursor/ +.agent/ +.amp/ +.codex/ +.antigravity/ +CLAUDE.md +AGENTS.md +GEMINI.md + +# Beads (issue tracker, local state) +.beads/ + # IDE .idea/ .vscode/ diff --git a/bun.lock b/bun.lock index e360089..c91ce9a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,35 +5,35 @@ "": { "name": "uncov", "devDependencies": { - "@biomejs/biome": "^2.4.6", - "@types/bun": "^1.3.10", + "@biomejs/biome": "2.4.16", + "@types/bun": "1.3.14", }, }, }, "packages": { - "@biomejs/biome": ["@biomejs/biome@2.4.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.6", "@biomejs/cli-darwin-x64": "2.4.6", "@biomejs/cli-linux-arm64": "2.4.6", "@biomejs/cli-linux-arm64-musl": "2.4.6", "@biomejs/cli-linux-x64": "2.4.6", "@biomejs/cli-linux-x64-musl": "2.4.6", "@biomejs/cli-win32-arm64": "2.4.6", "@biomejs/cli-win32-x64": "2.4.6" }, "bin": { "biome": "bin/biome" } }, "sha512-QnHe81PMslpy3mnpL8DnO2M4S4ZnYPkjlGCLWBZT/3R9M6b5daArWMMtEfP52/n174RKnwRIf3oT8+wc9ihSfQ=="], + "@biomejs/biome": ["@biomejs/biome@2.4.16", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.16", "@biomejs/cli-darwin-x64": "2.4.16", "@biomejs/cli-linux-arm64": "2.4.16", "@biomejs/cli-linux-arm64-musl": "2.4.16", "@biomejs/cli-linux-x64": "2.4.16", "@biomejs/cli-linux-x64-musl": "2.4.16", "@biomejs/cli-win32-arm64": "2.4.16", "@biomejs/cli-win32-x64": "2.4.16" }, "bin": { "biome": "bin/biome" } }, "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NW18GSyxr+8sJIqgoGwVp5Zqm4SALH4b4gftIA0n62PTuBs6G2tHlwNAOj0Vq0KKSs7Sf88VjjmHh0O36EnzrQ=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-4uiE/9tuI7cnjtY9b07RgS7gGyYOAfIAGeVJWEfeCnAarOAS7qVmuRyX6d7JTKw28/mt+rUzMasYeZ+0R/U1Mw=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-kMLaI7OF5GN1Q8Doymjro1P8rVEoy7BKQALNz6fiR8IC1WKduoNyteBtJlHT7ASIL0Cx2jR6VUOBIbcB1B8pew=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-F/JdB7eN22txiTqHM5KhIVt0jVkzZwVYrdTR1O3Y4auBOQcXxHK4dxULf4z43QyZI5tsnQJrRBHZy7wwtL+B3A=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oHXmUFEoH8Lql1xfc3QkFLiC1hGR7qedv5eKNlC185or+o4/4HiaU7vYODAH3peRCfsuLr1g6v2fK9dFFOYdyw=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-C9s98IPDu7DYarjlZNuzJKTjVHN03RUnmHV5htvqsx6vEUXCDSJ59DNwjKVD5XYoSS4N+BYhq3RTBAL8X6svEg=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-xzThn87Pf3YrOGTEODFGONmqXpTwUNxovQb72iaUOdcw8sBSY3+3WD8Hm9IhMYLnPi0n32s3L3NWU6+eSjfqFg=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.6", "", { "os": "win32", "cpu": "x64" }, "sha512-7++XhnsPlr1HDbor5amovPjOH6vsrFOCdp93iKXhFn6bcMUI6soodj3WWKfgEO6JosKU1W5n3uky3WW9RlRjTg=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="], - "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="], - "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], } diff --git a/package.json b/package.json index f03da3d..5c3740f 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "coverage:low": "uncov" }, "devDependencies": { - "@biomejs/biome": "^2.4.6", - "@types/bun": "^1.3.10" + "@biomejs/biome": "^2.4.16", + "@types/bun": "^1.3.14" }, "keywords": [ "coverage", diff --git a/src/cli.ts b/src/cli.ts index 5637a44..7d3bbef 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -23,14 +23,16 @@ Commands: check Verify coverage setup Options: - --threshold Coverage threshold percentage (default: 10) - --fail Exit 1 if files below threshold - --json Output as JSON - --no-color Disable colorized output - --verbose, -V Show debug information - --dry-run Show what would happen without making changes (init only) - --help, -h Show this help - --version, -v Show version + --threshold Coverage threshold percentage (default: 10) + --fail Exit 1 if files below threshold + --json Output as JSON + --coverage-path, -c

Path to coverage JSON file (report only) + --force Overwrite existing vitest config (init only) + --no-color Disable colorized output + --verbose, -V Show debug information + --dry-run Show what would happen without making changes (init only) + --help, -h Show this help + --version, -v Show version `.trim(); interface ParsedArgs { diff --git a/src/commands/index.ts b/src/commands/index.ts deleted file mode 100644 index 5ad413b..0000000 --- a/src/commands/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Command exports - */ - -export { checkCommand } from "./check"; -export { type InitOptions, initCommand } from "./init"; -export { type ReportOptions, reportCommand } from "./report"; diff --git a/src/lib/config.ts b/src/lib/config.ts index 5761447..c7453e0 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -60,11 +60,26 @@ export function readPackageJsonConfig(cwd?: string): Partial | null return validatePartialConfig(uncovConfig as Record); } return null; - } catch { + } catch (error) { + warnConfigError(packagePath, error); return null; } } +/** + * Write a warning to stderr about a config load failure. + * Skips warnings for missing files (ENOENT) since that's the common case. + */ +function warnConfigError(path: string, error: unknown): void { + // Don't warn on file-not-found; defaults silently apply + const code = (error as NodeJS.ErrnoException | null)?.code; + if (code === "ENOENT") { + return; + } + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`[uncov] warning: failed to parse ${path}: ${message}. Using defaults.\n`); +} + /** * Validate and extract valid config fields from an object */ @@ -142,8 +157,8 @@ export function loadConfig(cliOverrides?: Partial, cwd?: string): U try { const fileConfig = readJson>(configFilePath); configs.push(validatePartialConfig(fileConfig)); - } catch { - // Ignore invalid config file, use defaults + } catch (error) { + warnConfigError(configFilePath, error); } } diff --git a/src/lib/coverage.ts b/src/lib/coverage.ts index 5b46212..b397b91 100644 --- a/src/lib/coverage.ts +++ b/src/lib/coverage.ts @@ -8,7 +8,7 @@ import { readJson } from "../utils/fs"; /** * Coverage metrics for a single file */ -export interface FileCoverage { +interface FileCoverage { lines: CoverageMetric; statements: CoverageMetric; functions: CoverageMetric; @@ -18,7 +18,7 @@ export interface FileCoverage { /** * Individual coverage metric (lines, statements, etc.) */ -export interface CoverageMetric { +interface CoverageMetric { total: number; covered: number; skipped: number; @@ -115,6 +115,11 @@ export function filterBelowThreshold( // Skip the 'total' entry if (path === "total") continue; + // Skip files with no executable lines (e.g., type-only .d.ts re-exports, + // empty barrel files). Istanbul reports pct: 0 for these, but they have + // nothing to test - including them would be misleading noise. + if (coverage.lines.total === 0) continue; + const linesPct = coverage.lines.pct; if (linesPct <= threshold) { result.push({ diff --git a/src/lib/detect.ts b/src/lib/detect.ts index b2e3083..9eaf804 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -32,6 +32,7 @@ export interface GitignoreResult { */ const LOCKFILE_MAP: Array<{ file: string; manager: PackageManager }> = [ { file: "pnpm-lock.yaml", manager: "pnpm" }, + { file: "bun.lock", manager: "bun" }, { file: "bun.lockb", manager: "bun" }, { file: "package-lock.json", manager: "npm" }, { file: "yarn.lock", manager: "yarn" }, @@ -85,21 +86,26 @@ export function detectGitignore(cwd?: string): GitignoreResult { const content = readText(gitignorePath); const lines = content.split("\n").map((line) => line.trim()); - // Check if any line matches coverage patterns + // Check if any line actually ignores the `coverage/` directory. + // Only accept exact entries (after stripping leading `!` for negation): + // coverage, coverage/, /coverage, /coverage/, **/coverage, **/coverage/ + // Per gitignore semantics, `**/` matches zero or more path components, + // so `**/coverage` and `**/coverage/` also match the root `coverage/` dir. + // Reject coverage*, coverage-final.json, coverage-reports/, etc. const hasCoverage = lines.some((line) => { // Skip comments and empty lines - if (line.startsWith("#") || line === "") { + if (line === "" || line.startsWith("#")) { return false; } - // Match common coverage patterns: - // - coverage, coverage/, /coverage, /coverage/ - // - coverage*, **/coverage - // - paths ending with /coverage or /coverage/ + // Strip leading `!` (negation marker) so we examine the underlying pattern + const pattern = line.startsWith("!") ? line.slice(1) : line; return ( - /^[/*]*coverage[/*]*$/.test(line) || - line.startsWith("coverage") || - line.endsWith("/coverage") || - line.endsWith("/coverage/") + pattern === "coverage" || + pattern === "coverage/" || + pattern === "/coverage" || + pattern === "/coverage/" || + pattern === "**/coverage" || + pattern === "**/coverage/" ); }); diff --git a/src/lib/index.ts b/src/lib/index.ts deleted file mode 100644 index 5e7f269..0000000 --- a/src/lib/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Library exports - */ - -export * from "./config"; -export * from "./coverage"; -export * from "./detect"; -export * from "./package-json"; -export * from "./vitest-config"; diff --git a/src/lib/vitest-config.ts b/src/lib/vitest-config.ts index b01d6ee..5e3f882 100644 --- a/src/lib/vitest-config.ts +++ b/src/lib/vitest-config.ts @@ -46,23 +46,53 @@ export function hasCoverageConfig(configPath: string): boolean { try { const content = readText(configPath); + const stripped = stripCommentsAndStrings(content); // Primary check: explicit coverage configuration block // Matches: coverage: { or coverage : { - const hasCoverageBlock = /coverage\s*:\s*\{/.test(content); + const hasCoverageBlock = /coverage\s*:\s*\{/.test(stripped); if (hasCoverageBlock) { return true; } // Secondary check: coverage property assignment - // Matches: coverage: someVariable or coverage: true - const hasCoverageProperty = /coverage\s*:\s*[a-zA-Z]/.test(content); + // Matches: coverage: someVariable, coverage: true, coverage: 'all' + // (string literals are reduced to empty quote pairs by the stripper, + // so we look for an identifier or a quote char after the colon). + const hasCoverageProperty = /coverage\s*:\s*[a-zA-Z'"`]/.test(stripped); return hasCoverageProperty; } catch { return false; } } +/** + * Strip line comments, block comments, and string literals (single, double, + * backtick) from TypeScript/JavaScript source. Used to avoid false positives + * when text-matching for coverage configuration. + * + * Not a full parser - does not handle regex literals containing comment-like + * sequences or nested template literal expressions, but is sufficient for + * realistic vitest config shapes. + */ +function stripCommentsAndStrings(source: string): string { + return source.replace( + /(\/\*[\s\S]*?\*\/)|(\/\/[^\n]*)|("(?:\\.|[^"\\])*")|('(?:\\.|[^'\\])*')|(`(?:\\.|[^`\\])*`)/g, + (_match, block, line, dq, sq, tpl) => { + // Comments are removed entirely. + if (block || line) return ""; + // String literals are replaced with an empty literal of the same + // quote style. This preserves the surrounding syntax (e.g. + // `coverage: ''` still looks like a property assignment to a + // string value) while erasing any matchable content inside. + if (dq) return '""'; + if (sq) return "''"; + if (tpl) return "``"; + return ""; + }, + ); +} + /** * Generate a complete vitest.config.ts file with coverage configuration * @returns Complete vitest.config.ts content diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 6eaeb81..0000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Utility exports - */ - -export * from "./colors"; -export * from "./format"; -export * from "./fs"; -export * from "./logger"; diff --git a/test/cli.test.ts b/test/cli.test.ts index c700da3..29a5402 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -52,6 +52,8 @@ describe("CLI", () => { expect(output).toContain("uncov - Report files with low test coverage"); expect(output).toContain("--threshold"); expect(output).toContain("--fail"); + expect(output).toContain("--coverage-path"); + expect(output).toContain("--force"); }); it("should run report command with coverage file", async () => { diff --git a/test/lib/config.test.ts b/test/lib/config.test.ts index 8d5e2ab..1ce8f51 100644 --- a/test/lib/config.test.ts +++ b/test/lib/config.test.ts @@ -2,7 +2,7 @@ * Unit tests for configuration management */ -import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -294,8 +294,94 @@ describe("config utilities", () => { const configPath = join(testDir, "uncov.config.json"); writeFileSync(configPath, "not valid json"); - const result = loadConfig(undefined, testDir); - expect(result).toEqual(DEFAULT_CONFIG); + const stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true); + try { + const result = loadConfig(undefined, testDir); + expect(result).toEqual(DEFAULT_CONFIG); + expect(stderrSpy).toHaveBeenCalled(); + const warning = String(stderrSpy.mock.calls[0]?.[0] ?? ""); + expect(warning).toContain("[uncov] warning"); + expect(warning).toContain("uncov.config.json"); + } finally { + stderrSpy.mockRestore(); + } + }); + + it("should warn when uncov.config.json is malformed JSON", () => { + const configPath = join(testDir, "uncov.config.json"); + writeFileSync(configPath, "{ broken: json,, }"); + + const stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true); + try { + const result = loadConfig(undefined, testDir); + expect(result).toEqual(DEFAULT_CONFIG); + expect(stderrSpy).toHaveBeenCalledTimes(1); + const warning = String(stderrSpy.mock.calls[0]?.[0] ?? ""); + expect(warning).toContain("[uncov] warning"); + expect(warning).toContain(configPath); + expect(warning).toContain("Using defaults"); + } finally { + stderrSpy.mockRestore(); + } + }); + + it("should warn when package.json is malformed JSON", () => { + const packagePath = join(testDir, "package.json"); + writeFileSync(packagePath, "{ not valid json"); + + const stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true); + try { + const result = loadConfig(undefined, testDir); + expect(result).toEqual(DEFAULT_CONFIG); + expect(stderrSpy).toHaveBeenCalledTimes(1); + const warning = String(stderrSpy.mock.calls[0]?.[0] ?? ""); + expect(warning).toContain("[uncov] warning"); + expect(warning).toContain("package.json"); + } finally { + stderrSpy.mockRestore(); + } + }); + + it("should NOT warn when config files are simply missing", () => { + const stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true); + try { + const result = loadConfig(undefined, testDir); + expect(result).toEqual(DEFAULT_CONFIG); + expect(stderrSpy).not.toHaveBeenCalled(); + } finally { + stderrSpy.mockRestore(); + } + }); + }); + + describe("readPackageJsonConfig error handling", () => { + it("should warn and return null on malformed package.json", () => { + const packagePath = join(testDir, "package.json"); + writeFileSync(packagePath, "{ invalid json"); + + const stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true); + try { + const result = readPackageJsonConfig(testDir); + expect(result).toBeNull(); + expect(stderrSpy).toHaveBeenCalledTimes(1); + const warning = String(stderrSpy.mock.calls[0]?.[0] ?? ""); + expect(warning).toContain("[uncov] warning"); + expect(warning).toContain(packagePath); + expect(warning).toContain("Using defaults"); + } finally { + stderrSpy.mockRestore(); + } + }); + + it("should NOT warn when package.json is missing", () => { + const stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true); + try { + const result = readPackageJsonConfig(testDir); + expect(result).toBeNull(); + expect(stderrSpy).not.toHaveBeenCalled(); + } finally { + stderrSpy.mockRestore(); + } }); }); }); diff --git a/test/lib/coverage.test.ts b/test/lib/coverage.test.ts index 843441d..1330802 100644 --- a/test/lib/coverage.test.ts +++ b/test/lib/coverage.test.ts @@ -237,6 +237,40 @@ describe("coverage utilities", () => { expect(result).toHaveLength(0); }); + it("should exclude files with zero executable lines (type-only/barrel files)", () => { + const summary = validateCoverageSummary({ + total: { + lines: { total: 10, covered: 0, skipped: 0, pct: 0 }, + statements: { total: 10, covered: 0, skipped: 0, pct: 0 }, + functions: { total: 0, covered: 0, skipped: 0, pct: 0 }, + branches: { total: 0, covered: 0, skipped: 0, pct: 0 }, + }, + "/project/src/types.d.ts": { + lines: { total: 0, covered: 0, skipped: 0, pct: 0 }, + statements: { total: 0, covered: 0, skipped: 0, pct: 0 }, + functions: { total: 0, covered: 0, skipped: 0, pct: 0 }, + branches: { total: 0, covered: 0, skipped: 0, pct: 0 }, + }, + "/project/src/untested.ts": { + lines: { total: 10, covered: 0, skipped: 0, pct: 0 }, + statements: { total: 10, covered: 0, skipped: 0, pct: 0 }, + functions: { total: 2, covered: 0, skipped: 0, pct: 0 }, + branches: { total: 0, covered: 0, skipped: 0, pct: 0 }, + }, + }); + + const result = filterBelowThreshold(summary, 10); + + // Type-only file (lines.total === 0) is excluded + expect(result.find((f) => f.path === "/project/src/types.d.ts")).toBeUndefined(); + // Real 0% file (lines.total === 10) is still included + const untested = result.find((f) => f.path === "/project/src/untested.ts"); + expect(untested).toBeDefined(); + expect(untested?.linesPct).toBe(0); + expect(untested?.linesTotal).toBe(10); + expect(result).toHaveLength(1); + }); + it("should include correct coverage data in result", () => { const summary = parseCoverageSummary(join(FIXTURES_DIR, "coverage-summary-valid.json")); const result = filterBelowThreshold(summary, 0); diff --git a/test/lib/detect.test.ts b/test/lib/detect.test.ts index 2d25935..860c37e 100644 --- a/test/lib/detect.test.ts +++ b/test/lib/detect.test.ts @@ -40,6 +40,13 @@ describe("detect utilities", () => { expect(result).toBe("bun"); }); + it("should detect bun from bun.lock (Bun 1.2+ text format)", () => { + writeFileSync(join(testDir, "bun.lock"), '{ "lockfileVersion": 1 }'); + + const result = detectPackageManager(testDir); + expect(result).toBe("bun"); + }); + it("should detect npm from package-lock.json", () => { writeFileSync(join(testDir, "package-lock.json"), "{}"); @@ -235,15 +242,15 @@ describe("detect utilities", () => { expect(result.hasCoverage).toBe(true); }); - it("should detect coverage* glob pattern", () => { + it("should NOT match coverage* glob pattern (does not ignore coverage/ dir reliably)", () => { writeFileSync(join(testDir, ".gitignore"), "node_modules\ncoverage*\n"); const result = detectGitignore(testDir); expect(result.exists).toBe(true); - expect(result.hasCoverage).toBe(true); + expect(result.hasCoverage).toBe(false); }); - it("should detect **/coverage glob pattern", () => { + it("should match **/coverage glob pattern (matches root coverage/)", () => { writeFileSync(join(testDir, ".gitignore"), "**/coverage\n"); const result = detectGitignore(testDir); @@ -251,7 +258,7 @@ describe("detect utilities", () => { expect(result.hasCoverage).toBe(true); }); - it("should detect **/coverage/ glob pattern", () => { + it("should match **/coverage/ glob pattern (matches root coverage/)", () => { writeFileSync(join(testDir, ".gitignore"), "**/coverage/\n"); const result = detectGitignore(testDir); @@ -259,20 +266,87 @@ describe("detect utilities", () => { expect(result.hasCoverage).toBe(true); }); - it("should detect path/to/coverage pattern", () => { + it("should NOT match nested path/to/coverage pattern", () => { writeFileSync(join(testDir, ".gitignore"), "some/path/to/coverage\n"); const result = detectGitignore(testDir); expect(result.exists).toBe(true); - expect(result.hasCoverage).toBe(true); + expect(result.hasCoverage).toBe(false); }); - it("should detect path/to/coverage/ pattern with trailing slash", () => { + it("should NOT match nested path/coverage/ pattern", () => { writeFileSync(join(testDir, ".gitignore"), "some/path/coverage/\n"); const result = detectGitignore(testDir); expect(result.exists).toBe(true); - expect(result.hasCoverage).toBe(true); + expect(result.hasCoverage).toBe(false); + }); + + describe("strict matching (uncov-q2s)", () => { + const accepted = ["coverage", "coverage/", "/coverage", "/coverage/"]; + for (const entry of accepted) { + it(`matches accepted form: ${JSON.stringify(entry)}`, () => { + writeFileSync(join(testDir, ".gitignore"), `node_modules\n${entry}\n.env\n`); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(true); + }); + } + + const rejected = [ + "coverage-final.json", + "coverage-reports/", + "coverage_old/", + "coveragerc", + "coverage.xml", + ]; + for (const entry of rejected) { + it(`does not match: ${JSON.stringify(entry)}`, () => { + writeFileSync(join(testDir, ".gitignore"), `${entry}\n`); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(false); + }); + } + + it("does not match a commented '# coverage' line", () => { + writeFileSync(join(testDir, ".gitignore"), "# coverage\nnode_modules\n"); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(false); + }); + + it("does not match an inline-commented 'coverage # ...' line", () => { + // gitignore doesn't support inline comments — the trailing text + // becomes part of the pattern, so this should NOT match. + writeFileSync(join(testDir, ".gitignore"), "coverage # ignored\n"); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(false); + }); + + it("does not match an empty string entry", () => { + writeFileSync(join(testDir, ".gitignore"), "\n\n\n"); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(false); + }); + + it("matches coverage/ with tab indentation", () => { + writeFileSync(join(testDir, ".gitignore"), "node_modules\n\tcoverage/\n"); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(true); + }); + + it("matches coverage with mixed tab+space indentation", () => { + writeFileSync(join(testDir, ".gitignore"), " \t coverage \t \n"); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(true); + }); + + it("matches when other lines have comments above", () => { + writeFileSync( + join(testDir, ".gitignore"), + "# build artifacts\nnode_modules\n\n# test output\ncoverage/\n", + ); + const result = detectGitignore(testDir); + expect(result.hasCoverage).toBe(true); + }); }); }); }); diff --git a/test/lib/vitest-config.test.ts b/test/lib/vitest-config.test.ts index 788d779..a98f6b7 100644 --- a/test/lib/vitest-config.test.ts +++ b/test/lib/vitest-config.test.ts @@ -211,6 +211,103 @@ export default defineConfig({ expect(result).toBe(false); }); + it("should ignore coverage: { ... } inside a line comment", () => { + const configPath = join(testDir, "vitest.config.ts"); + writeFileSync( + configPath, + `import { defineConfig } from 'vitest/config'; + +// coverage: { provider: 'v8' } +export default defineConfig({ + test: { + globals: true, + }, +}); +`, + ); + + const result = hasCoverageConfig(configPath); + expect(result).toBe(false); + }); + + it("should ignore coverage: mentioned inside a string literal", () => { + const configPath = join(testDir, "vitest.config.ts"); + writeFileSync( + configPath, + `import { defineConfig } from 'vitest/config'; + +const docsUrl = "docs https://example.com/coverage: thing"; + +export default defineConfig({ + test: { + globals: true, + }, +}); +`, + ); + + const result = hasCoverageConfig(configPath); + expect(result).toBe(false); + }); + + it("should ignore coverage: { ... } inside a block comment", () => { + const configPath = join(testDir, "vitest.config.ts"); + writeFileSync( + configPath, + `import { defineConfig } from 'vitest/config'; + +/* coverage: { provider: 'v8' } */ +export default defineConfig({ + test: { + globals: true, + }, +}); +`, + ); + + const result = hasCoverageConfig(configPath); + expect(result).toBe(false); + }); + + it("should detect real coverage block alongside coverage in a comment", () => { + const configPath = join(testDir, "vitest.config.ts"); + writeFileSync( + configPath, + `import { defineConfig } from 'vitest/config'; + +// configures coverage: as below +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + }, + }, +}); +`, + ); + + const result = hasCoverageConfig(configPath); + expect(result).toBe(true); + }); + + it("should detect coverage: 'all' string value", () => { + const configPath = join(testDir, "vitest.config.ts"); + writeFileSync( + configPath, + `import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: 'all', + }, +}); +`, + ); + + const result = hasCoverageConfig(configPath); + expect(result).toBe(true); + }); + it("should handle empty config file", () => { const configPath = join(testDir, "vitest.config.ts"); writeFileSync(configPath, "");