From 65d8aadcf951fbb3c66a75dabc14459f3c07cec8 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Fri, 15 May 2026 19:30:59 +0300 Subject: [PATCH 1/6] docs(readme): add full root readme (AC 5) --- README.md | 116 ++++++++++++++++++++++++++- plans/AC5-root-readme.md | 167 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 plans/AC5-root-readme.md diff --git a/README.md b/README.md index 6fea290..e0bd5ec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,116 @@ # WorkTrace -Browser extension + web dashboard that captures developer -session context and generates AI-powered reports. +Browser extension + web dashboard that captures developer session context and generates AI-powered session reports. -See [docs/Task.md](docs/Task.md) for full scope and acceptance criteria. +## What it is + +A Chrome extension (Manifest V3) silently records the pages a developer opens during a coding session — URLs, titles, page metadata, tags, optionally the music they listen to — and ships them to a Next.js dashboard. The dashboard renders a per-session timeline and asks an LLM to summarize each session into a structured Markdown report. + +See [docs/Task.md](docs/Task.md) for the full specification, acceptance criteria, and four-week roadmap. + +## Repo layout + +``` +worktrace/ +├─ dashboard/ Next.js 16 web app (App Router) + API routes + Prisma +├─ extension/ Chrome Manifest V3 extension (Vite + @crxjs/vite-plugin) +├─ docs/ Project specification (Task.md) +├─ plans/ Per-AC implementation plans (Plan → Review → Implement) +├─ .claude/skills/ Task recipes for the AI assistant +├─ CLAUDE.md AI agent rules (architecture invariants) +└─ docker-compose.yml Local PostgreSQL for development +``` + +The two apps are isolated — `extension/` never imports from `dashboard/` and vice versa. They communicate over HTTP only. + +## Tech stack + +- **Dashboard** — Next.js 16 (App Router, Server Components), React 19, Tailwind CSS v4, TanStack Query v5, Framer Motion, D3.js +- **Backend** — Next.js Route Handlers, Prisma 7 (with `@prisma/adapter-pg`), Zod, `google-auth-library`, `jsonwebtoken` +- **Extension** — Manifest V3, Vite 7 + `@crxjs/vite-plugin`, TypeScript (strict) +- **Database** — PostgreSQL 16 (Docker for local, Neon for production) +- **AI** — LLM provider TBD (Week 3): OpenAI / Groq / Gemini / OpenRouter +- **Deploy** — Vercel (dashboard), `.zip` for local extension install + +Versions in `dashboard/package.json` and `extension/package.json` are authoritative. + +## Prerequisites + +- **Node.js** 20 or newer +- **Docker Desktop** (or Docker Engine + Compose plugin) +- **Chrome** 120+ (for loading the unpacked extension) + +## Setup + +### 1. Clone and configure env + +```bash +git clone https://github.com/BODMAT/worktrace.git +cd worktrace +cp dashboard/.env.example dashboard/.env +cp extension/.env.example extension/.env +``` + +The dashboard `.env` contains `DATABASE_URL` (already pointing at the docker-compose Postgres) plus `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `JWT_SECRET`. The Google/JWT values are **not required for Week 1** — leave them blank until the auth flow lands in Week 2. + +### 2. Start PostgreSQL + +```bash +docker-compose up -d +``` + +This boots a `worktrace-postgres` container on host port **5433** (mapped to container 5432, so it won't collide with a local Postgres on 5432). The `DATABASE_URL` in `dashboard/.env.example` already uses this port. + +### 3. Run the dashboard + +```bash +cd dashboard +npm install +npx prisma migrate dev +npm run dev +``` + +The dashboard is now at `http://localhost:3000`. The `npm run dev` step also implicitly runs `prisma generate` on first build, materializing the Prisma client and Zod schemas into `dashboard/generated/` (both gitignored). + +### 4. Build and load the extension + +```bash +cd extension +npm install +npm run build +``` + +Then in Chrome: + +1. Open `chrome://extensions` +2. Toggle **Developer mode** (top right) +3. Click **Load unpacked** +4. Select `extension/dist/` + +The extension card should appear with no errors. For HMR-rebuild during active development use `npm run dev` instead of `npm run build`. + +## Verifying it works + +1. **Dashboard** — `http://localhost:3000` loads (Next.js placeholder page is fine for Week 1). +2. **Extension** — the card on `chrome://extensions` shows no errors; clicking the toolbar icon opens an empty popup; the service worker logs `[worktrace] installed` (visible via the extension's **Inspect** link). +3. **API** — a POST to the events endpoint round-trips through Postgres: + + ```bash + curl -X POST http://localhost:3000/api/v1/events \ + -H "Content-Type: application/json" \ + -d '{"sessionId":"ctestsess0000000000000001","url":"https://example.com","title":"Hello","tags":["test"],"timestamp":"2026-05-15T12:00:00Z"}' + ``` + + A 201 response with the created event means the dashboard, Prisma, and Postgres are wired up correctly. (The `sessionId` must reference an existing `Session` row — create one via `npx prisma studio` or seed it manually.) + +## Project docs + +- [docs/Task.md](docs/Task.md) — full acceptance criteria, weekly roadmap, git workflow +- [CLAUDE.md](CLAUDE.md) — AI agent rules: architecture invariants, type-safety constraints, import boundaries +- [.claude/skills/](.claude/skills/) — task recipes (`api-route.md`, `prisma-model.md`, `extension-message.md`) +- [plans/](plans/) — per-AC implementation plans authored before each commit (the Plan → Review → Implement loop) + +## Deploy + +- **Dashboard** — Vercel with **Root Directory: `dashboard`**. The build command `prisma generate && next build` (declared in `dashboard/package.json`) regenerates the Prisma client at build time since `dashboard/generated/` is gitignored. Production database is Neon. +- **Extension** — distributed as a `.zip` of `extension/dist/` for local install. Chrome Web Store packaging is scheduled for Week 4 AC 2. diff --git a/plans/AC5-root-readme.md b/plans/AC5-root-readme.md new file mode 100644 index 0000000..75443a5 --- /dev/null +++ b/plans/AC5-root-readme.md @@ -0,0 +1,167 @@ +# Plan: AC 5 — Root `README.md` + +**Branch:** `chore/repo-setup` (first commit; a second commit on the same branch will add GitHub branch-protection notes + minimal CI) +**Closes:** Week 1 AC 5 ([docs/Task.md L59](../docs/Task.md#L59)) +**Status:** plan, awaiting approval + +## Scope + +Replace the current 7-line placeholder `README.md` with a complete, ready root README that lets a fresh contributor (or reviewer) go from `git clone` to a running dashboard + loadable extension in one read. + +Per AC 5 in TZ: +> Dashboard і Extension живуть в одному репо у папках `/dashboard` і `/extension`. У Vercel налаштовано `Root Directory: dashboard`. Є кореневий `README.md` з інструкцією `docker-compose up` і як запустити extension локально. + +The first two clauses are already true. Only the README is missing — that's the entire scope of this commit. + +## What's in / out + +User-confirmed scope (from review questions): + +**In** — language: English. Sections to include beyond the two TZ-mandated commands: +- Tech stack overview + link to full TZ +- Prisma migrate + `.env` setup steps + +**Out** — explicitly skipped: +- Workflow guide (Plan → Review → Implement, branch strategy, conventional commits) — already in `docs/Task.md`; linked, not duplicated +- Vercel deployment guide — only the **fact** of `Root Directory: dashboard` is mentioned (it's literally in AC 5's wording); no env-var setup walkthrough +- Screenshots / demo GIFs — no real UX yet; defer to Week 4 AC 3 (Loom demo) +- Sub-package READMEs (`dashboard/README.md`, `extension/README.md`) — single root file is enough at this stage + +## Files + +| Path | Action | Notes | +|---|---|---| +| `README.md` | rewrite | Replace 7-line placeholder with full README | +| `plans/AC5-root-readme.md` | add (this file) | | + +Nothing else touched. The second commit on this branch (GitHub branch-protection + minimal CI) will be planned separately. + +## Design decisions + +### 1. One file, not three + +User picked "тільки кореневий". A single root README: +- Avoids duplication between `dashboard/README.md` and `extension/README.md`. +- Keeps the contributor's mental cost low (one entry point). +- The AC 4 plan said it would add `extension/README.md`, but that file was never created — the relevant content (Vite build, load unpacked) goes into the root README instead. + +### 2. Audience: a reviewer who just cloned the repo + +Not a marketing readme. The reader is someone (TA, future-me, a hiring reviewer) who wants to: +- Understand what the project is in 30 seconds. +- Get it running locally in ~5 minutes. +- Know where to look for deeper context (TZ, CLAUDE.md, `/plans`). + +Optimize for "can I run this?" over "is this impressive?". Tone: terse, command-first, no marketing copy. + +### 3. Structure (proposed) + +``` +# WorkTrace + ← one-line tagline (browser extension + dashboard for AI-generated session reports) + +## What it is + ← 2-3 sentence summary. Link → docs/Task.md for full spec. + +## Repo layout + ← tree showing /dashboard, /extension, /docs, /plans, plus a one-line description each. + +## Tech stack + ← compact bulleted list grouped by area (dashboard / extension / db / ai). + Not a full version dump — package.json is the source of truth. + +## Prerequisites + ← Node 20+, Docker Desktop (or Docker Engine + Compose), Chrome 120+. + +## Setup + ### 1. Clone & env + ← `git clone`, `cp dashboard/.env.example dashboard/.env`, + `cp extension/.env.example extension/.env`, + note that GOOGLE_CLIENT_ID/SECRET/JWT_SECRET stay blank until Week 2. + ### 2. Start Postgres + ← `docker-compose up -d`. Note port 5433 (not 5432). + ### 3. Dashboard + ← `cd dashboard && npm install && npx prisma migrate dev && npm run dev` + → http://localhost:3000 + ### 4. Extension + ← `cd extension && npm install && npm run build` + → `chrome://extensions` → Developer mode → Load unpacked → select `extension/dist/` + ← optional: `npm run dev` for HMR-rebuild during development. + +## Verifying it works + ← three quick checks: + 1. dashboard loads at localhost:3000 (Next.js placeholder page is fine for Week 1) + 2. extension card appears in chrome://extensions with no errors + 3. POST to /api/v1/events via Postman/curl returns 201 (one-line example) + +## Project docs + ← bulleted links: + - docs/Task.md — full acceptance criteria & roadmap + - CLAUDE.md — AI agent rules (architecture invariants) + - .claude/skills/ — task recipes + - plans/ — per-AC implementation plans (Plan → Review → Implement workflow) + +## Deploy + ← two lines: + - Dashboard: Vercel with **Root Directory: `dashboard`** (Build Command default; Prisma client is generated by `npm run build`). + - Extension: `.zip` of `extension/dist/` for local install (Chrome Web Store packaging is Week 4 AC 2). +``` + +### 4. What goes in "Tech stack" + +Compact, grouped, no versions (versions live in `package.json`): + +- **Dashboard** — Next.js 16 (App Router), React 19, Tailwind v4, TanStack Query, Framer Motion, D3.js +- **Backend** — Next.js Route Handlers, Prisma 7 (with `@prisma/adapter-pg`), Zod, `google-auth-library`, `jsonwebtoken` +- **Extension** — Manifest V3, Vite + `@crxjs/vite-plugin`, TypeScript +- **Database** — PostgreSQL 16 (Docker local / Neon prod) +- **AI** — TBD (planned Week 3): OpenAI / Groq / Gemini + +Marking AI as TBD is honest — no key picked yet, no code touching the LLM exists. Don't promise what's not built. + +### 5. `.env` handling + +The two `.env.example` files already exist: +- `dashboard/.env.example` — DATABASE_URL (matches docker-compose), GOOGLE_CLIENT_ID/SECRET, JWT_SECRET, NEXT_PUBLIC_APP_URL +- `extension/.env.example` — VITE_API_BASE_URL + +README will: +- Tell the user to copy each `.env.example` → `.env`. +- Note that Google/JWT vars are **not required** for Week 1 (the API works without them; auth lands in Week 2). +- Not duplicate the variable list — the example files are authoritative. + +### 6. Postgres port + +`docker-compose.yml` maps host **5433** → container 5432 (avoids clashing with a local Postgres on 5432). README will state this explicitly because the next thing a contributor does after `docker-compose up` is connect to the DB, and the off-by-one port is the #1 trip wire. + +### 7. Style rules + +- Code blocks fenced with language hint (` ```bash ` / ` ```ts `) so GitHub highlights them. +- All commands assume the user is in the repo root unless the block starts with `cd`. +- Use relative links (`[docs/Task.md](docs/Task.md)`) so they work both on GitHub and in editors. +- No emojis (CLAUDE.md rule). +- Keep total length under ~150 lines — anything longer signals scope creep. + +## Verification + +- [ ] `README.md` renders cleanly on GitHub (preview before merge) +- [ ] All relative links resolve (no 404s) +- [ ] A fresh-clone walkthrough on a clean machine succeeds end to end: + - [ ] `docker-compose up -d` brings Postgres up + - [ ] `cd dashboard && npm i && npx prisma migrate dev` succeeds + - [ ] `npm run dev` → localhost:3000 loads + - [ ] `cd extension && npm i && npm run build` produces `dist/` + - [ ] Chrome loads `extension/dist/` unpacked without errors +- [ ] No mention of features that don't exist yet (AI report, auth flow, etc.) framed as if they work — those are explicitly labeled as upcoming weeks + +## Out of scope (for this commit) + +- GitHub branch-protection rules (`main` and `development`) — second commit on this branch. +- Minimal CI workflow (`.github/workflows/ci.yml` — lint + typecheck) — second commit on this branch. +- PR template, issue templates, CODEOWNERS — not asked for. +- Contributing guide — TZ doesn't request, and Plan→Review→Implement workflow is already in `docs/Task.md`. +- License file — TZ doesn't request. + +## Lesson for the skill + +No skill update needed — README authoring isn't a recurring task that warrants a recipe. The `init` skill (built-in) handles CLAUDE.md generation, which is the analogous task for AI guidance. From 7c997a17b80298982779d1be6c2f8f3a4b9d1f44 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Fri, 15 May 2026 19:45:38 +0300 Subject: [PATCH 2/6] chore(repo): add license, contributing guide, and PR template --- .github/PULL_REQUEST_TEMPLATE.md | 44 ++++ CONTRIBUTING.md | 94 +++++++++ LICENSE | 21 ++ plans/{ => week1}/AC3-cors.md | 2 +- plans/{ => week1}/AC4-extension-init.md | 2 +- plans/{ => week1}/AC5-root-readme.md | 2 +- plans/{ => week1}/AC7-events-api-route.md | 2 +- plans/week1/repo-governance-and-ci.md | 235 ++++++++++++++++++++++ 8 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE rename plans/{ => week1}/AC3-cors.md (99%) rename plans/{ => week1}/AC4-extension-init.md (99%) rename plans/{ => week1}/AC5-root-readme.md (99%) rename plans/{ => week1}/AC7-events-api-route.md (99%) create mode 100644 plans/week1/repo-governance-and-ci.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..05368cf --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,44 @@ +## What + + + +## Changes + +### Code + + +- + +### Tooling / config + + +- + +### Docs / plan + + +- `plans/weekN/.md` — design rationale +- + +## Design decisions + + +- + +## Verification + + +- [ ] `npx tsc --noEmit` (dashboard) — clean +- [ ] `npm run lint` (dashboard) — clean +- [ ] `npm run typecheck` (extension) — clean +- [ ] Manual test: + +## Out of scope (future PRs) + + +- + +## Closes + + +- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7fd89bd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,94 @@ +# Contributing + +Short reference for working in this repo. Full context, weekly roadmap, and acceptance criteria live in [docs/Task.md](docs/Task.md). + +## Workflow: Plan → Review → Implement + +Never start a non-trivial change by writing code. The loop is: + +1. **Plan** — write a markdown file at `plans/weekN/.md` describing scope, files touched, design decisions, and a verification checklist. The plan goes on the feature branch as its first commit (or alongside the implementation commit when small). +2. **Review** — the user reads the plan and approves (or asks for adjustments) before any code is written. +3. **Implement** — only after approval. The implementation should follow the plan; if reality diverges, update the plan in the same PR. + +See [docs/Task.md § "Головне правило воркфлоу"](docs/Task.md#L180) for the rationale. + +## Branches + +Two protected branches: + +- `main` — production. Direct pushes blocked. Only merges from `development` after final review. +- `development` — integration branch. Direct pushes blocked. All feature work merges here via PR. + +Each task gets its own branch off `development`: + +``` +feature/ new functionality +fix/ bug fix +chore/ deps, config, repo meta — no logic changes +refactor/ behavior-preserving refactor +docs/ docs-only changes +``` + +PR → `development`. When a week (or milestone) is done: `development` → PR → `main`. + +See [docs/Task.md § "Git воркфлоу"](docs/Task.md#L104) for full naming conventions and examples. + +## Commit messages + +[Conventional Commits](https://www.conventionalcommits.org/). Format: + +``` +(): +``` + +Types: `feat`, `fix`, `chore`, `refactor`, `style`, `test`, `docs`. + +- Description in English, lowercase, no trailing period. +- First line ≤ 72 characters. +- Body (after a blank line) only if extra context is needed. + +Examples: + +``` +feat(extension): add content script for page metadata parsing +fix(popup): reset timer on service worker restart +docs(readme): add full root readme (AC 5) +chore(deps): update prisma to 7.9 +``` + +Full guidance: [docs/Task.md § "Commit messages"](docs/Task.md#L144). + +## Branch protection (one-time GitHub UI setup — repo owner) + +Settings → Branches → add rule for each of `main` and `development`: + +- [x] Require a pull request before merging +- [x] Require status checks to pass before merging + - Required checks (once CI lands): `CI / dashboard`, `CI / extension` +- [x] Do not allow bypassing the above settings + +This step is GitHub UI only — it isn't expressible in a commit. Enable after the CI workflow has run at least once on `development` so the status check names become selectable. + +## Where things live + +Architectural invariants enforced in [CLAUDE.md](CLAUDE.md). Quick map: + +| Concern | Location | +|---|---| +| Business logic (dashboard) | `dashboard/server/` — never inside React components or Route Handlers | +| Route Handlers | `dashboard/app/api/**/route.ts` — thin: parse → validate (Zod) → call `server/` → respond | +| Auth (Google token, JWT) | `dashboard/app/api/auth/**/route.ts` + `dashboard/middleware.ts` — nowhere else | +| Zod schemas for API input | `dashboard/server/schemas/.ts` — derived from `@/generated/zod/...`, never hand-written from scratch | +| Prisma schema + migrations | `dashboard/prisma/` | +| Extension service worker | `extension/src/background/` — only place that talks to the API or `chrome.storage.local` | +| Extension content/popup | `extension/src/content/`, `extension/src/popup/` — message the background, never `fetch` directly | +| Task recipes (for the AI) | `.claude/skills/` | +| Plans (per-AC) | `plans/weekN/` | + +## Type safety + +TypeScript strict. `any` is forbidden — use `unknown` + type guards, or define proper types. Enforced by `tsc --noEmit` in CI. + +## `extension/` ↔ `dashboard/` isolation + +Never import from `dashboard/` inside `extension/` or vice versa. They communicate only via HTTP (the API routes). Separate `package.json`, `tsconfig.json`, and `node_modules` per app. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ebfcc52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 BODMAT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plans/AC3-cors.md b/plans/week1/AC3-cors.md similarity index 99% rename from plans/AC3-cors.md rename to plans/week1/AC3-cors.md index 9c9e82b..70ec389 100644 --- a/plans/AC3-cors.md +++ b/plans/week1/AC3-cors.md @@ -1,7 +1,7 @@ # Plan: AC 3 — CORS for Chrome Extension **Branch:** `feature/api-events-and-cors` (second commit, same branch as AC 7) -**Closes:** Week 1 AC 3 ([docs/Task.md L55](../docs/Task.md#L55)) +**Closes:** Week 1 AC 3 ([docs/Task.md L55](../../docs/Task.md#L55)) **Status:** plan, awaiting approval ## Scope diff --git a/plans/AC4-extension-init.md b/plans/week1/AC4-extension-init.md similarity index 99% rename from plans/AC4-extension-init.md rename to plans/week1/AC4-extension-init.md index b81c57c..b9a78c1 100644 --- a/plans/AC4-extension-init.md +++ b/plans/week1/AC4-extension-init.md @@ -1,7 +1,7 @@ # Plan: AC 4 — Chrome Extension initialization **Branch:** `feature/extension-init` -**Closes:** Week 1 AC 4 ([docs/Task.md L58](../docs/Task.md#L58)) +**Closes:** Week 1 AC 4 ([docs/Task.md L58](../../docs/Task.md#L58)) **Status:** plan, awaiting approval ## Scope diff --git a/plans/AC5-root-readme.md b/plans/week1/AC5-root-readme.md similarity index 99% rename from plans/AC5-root-readme.md rename to plans/week1/AC5-root-readme.md index 75443a5..b3a3426 100644 --- a/plans/AC5-root-readme.md +++ b/plans/week1/AC5-root-readme.md @@ -1,7 +1,7 @@ # Plan: AC 5 — Root `README.md` **Branch:** `chore/repo-setup` (first commit; a second commit on the same branch will add GitHub branch-protection notes + minimal CI) -**Closes:** Week 1 AC 5 ([docs/Task.md L59](../docs/Task.md#L59)) +**Closes:** Week 1 AC 5 ([docs/Task.md L59](../../docs/Task.md#L59)) **Status:** plan, awaiting approval ## Scope diff --git a/plans/AC7-events-api-route.md b/plans/week1/AC7-events-api-route.md similarity index 99% rename from plans/AC7-events-api-route.md rename to plans/week1/AC7-events-api-route.md index 987935d..d16426d 100644 --- a/plans/AC7-events-api-route.md +++ b/plans/week1/AC7-events-api-route.md @@ -1,7 +1,7 @@ # Plan: AC 7 — POST /api/v1/events Route Handler **Branch:** `feature/api-events-and-cors` (combined PR with AC 3) -**Closes:** Week 1 AC 7 ([docs/Task.md L63](../docs/Task.md#L63)) +**Closes:** Week 1 AC 7 ([docs/Task.md L63](../../docs/Task.md#L63)) **Status:** implemented, awaiting smoke test This plan is written post-hoc after several mid-stream corrections from the user. It documents the final decisions and the changes that landed, including all the adjustments made during implementation. diff --git a/plans/week1/repo-governance-and-ci.md b/plans/week1/repo-governance-and-ci.md new file mode 100644 index 0000000..424c97d --- /dev/null +++ b/plans/week1/repo-governance-and-ci.md @@ -0,0 +1,235 @@ +# Plan: Repository governance + minimal CI + +**Branch:** `chore/repo-setup` (commits 2 and 3, continuing after `65d8aad` — root README) +**Closes:** +- No Week 1 AC directly (governance files aren't in TZ) +- Week 4 AC 1 partially ([docs/Task.md L97](../../docs/Task.md#L97)) — CI runs lint+typecheck on PRs to `development` and `main`, branch protection prevents merging with failing checks +**Status:** plan, awaiting approval + +## Scope + +Two commits, in order: + +### Commit 2 — `chore: add license, contributing guide, and PR template` + +GitHub community-health files + documented branch-protection rules. No code, only repo-meta. + +### Commit 3 — `ci: add lint and typecheck workflow` + +Minimal GitHub Actions workflow that runs ESLint on the dashboard and `tsc --noEmit` on both packages for every PR to `development` and `main`. This is the technical half of Week 4 AC 1; UI-side branch-protection (toggling "Require status checks") is a one-time GitHub Settings click that the repo owner does after the workflow lands and runs once successfully. + +## Files + +### Commit 2 + +| Path | Action | Notes | +|---|---|---| +| `LICENSE` | new | MIT, copyright 2026 BODMAT | +| `CONTRIBUTING.md` | new | Plan→Review→Implement, branch naming, commit conventions, branch protection summary | +| `.github/PULL_REQUEST_TEMPLATE.md` | new | Auto-populated PR body — closes AC link, plan link, changes summary, verification checklist | +| `plans/repo-governance-and-ci.md` | add (this file) | committed with commit 2 | + +### Commit 3 + +| Path | Action | Notes | +|---|---|---| +| `.github/workflows/ci.yml` | new | Two jobs: `dashboard` (lint + typecheck), `extension` (typecheck) | + +Nothing else touched. No changes to `dashboard/` or `extension/` source. + +## Design decisions + +### 1. LICENSE — MIT, single copyright holder + +User picked MIT in the previous question. Standard form, single copyright line: + +``` +Copyright (c) 2026 BODMAT +``` + +Use the canonical OSI MIT text verbatim (no project-specific clauses). Adding `` is optional — `BODMAT` matches the GitHub handle. + +### 2. CONTRIBUTING.md — reference, not full duplication + +The Plan→Review→Implement workflow, branch strategy, and commit conventions are already in [docs/Task.md](../../docs/Task.md) (§ "Git воркфлоу", "Commit messages", "Головне правило воркфлоу"). CONTRIBUTING.md will: + +- Summarize each in 3-5 lines (so a drive-by contributor doesn't need to read the full TZ). +- Link out to `docs/Task.md` for full context. +- Add one section that's **not** in `docs/Task.md`: branch-protection rules that need to be toggled in GitHub Settings UI by the owner (`main` and `development` — require PR, require status checks, do not allow bypass). +- Add a short "Where to put new files" map (server logic → `dashboard/server/`, extension messaging → `extension/src/background/`, etc.) — pulled from CLAUDE.md. + +Tone: imperative, terse. Audience: someone who just cloned the repo and wants to open their first PR. + +Section sketch: + +``` +# Contributing + +## Workflow: Plan → Review → Implement + ← 3 bullet description, link to docs/Task.md § "Головне правило воркфлоу" + +## Branches + ← naming (feature/, fix/, chore/), branched from development, + PR back to development. Link to docs/Task.md § "Git воркфлоу". + +## Commit messages + ← Conventional Commits, 1 example line, link to docs/Task.md. + +## Branch protection (repo owner — one-time GitHub UI setup) + ← bulleted list: Settings → Branches → for main and development: + [x] Require a pull request before merging + [x] Require status checks to pass before merging + [x] Do not allow bypassing the above settings + Status check to require: "CI / dashboard" and "CI / extension" (added in next commit). + +## Where things live + ← short map from CLAUDE.md (server/ for business logic, etc.) +``` + +### 3. Pull request template — reinforces Plan→Review→Implement + +GitHub renders `.github/PULL_REQUEST_TEMPLATE.md` as the default PR body. The template should make it impossible to open a PR without saying which plan and AC it closes — the loop's enforcement mechanism. + +Template: + +```markdown +## Closes +- AC: +- Plan: + +## Changes +- +- + +## Verification +- [ ] `npm run lint` (dashboard) — clean +- [ ] `npm run typecheck` (extension) — clean +- [ ] `npx tsc --noEmit` (dashboard) — clean +- [ ] Manual test: + +## Notes + +``` + +The verification commands match what CI runs, so a green CI box ≈ verification done. + +### 4. CI workflow shape + +`.github/workflows/ci.yml`: + +```yaml +name: CI + +on: + pull_request: + branches: [development, main] + push: + branches: [development, main] # also runs on direct pushes for status badges + +jobs: + dashboard: + runs-on: ubuntu-latest + defaults: + run: + working-directory: dashboard + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: dashboard/package-lock.json + - run: npm ci + - run: npx prisma generate + - run: npm run lint + - run: npx tsc --noEmit + + extension: + runs-on: ubuntu-latest + defaults: + run: + working-directory: extension + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: extension/package-lock.json + - run: npm ci + - run: npm run typecheck +``` + +Decisions inside this: + +#### 4a. Two jobs, not a matrix + +Matrix would be tempting (`strategy.matrix.package: [dashboard, extension]`) but the two packages have different scripts (`lint` exists in dashboard, not extension; `prisma generate` only matters in dashboard). Splitting into named jobs makes the GitHub status-check names readable (`CI / dashboard`, `CI / extension`) and lets us configure required checks per branch protection cleanly. + +#### 4b. `prisma generate` before typecheck + +The dashboard codebase imports from `@/generated/prisma/client` and `@/generated/zod/...`, both gitignored. Without `prisma generate`, `tsc` would error with "Cannot find module". The dashboard's `npm run build` already chains this (`"build": "prisma generate && next build"`), but `tsc --noEmit` alone doesn't — we run it explicitly. Extension doesn't have generated artifacts so no equivalent step needed. + +#### 4c. No ESLint in extension + +`extension/package.json` doesn't declare a lint script, and there's no ESLint config there. Out of scope for this commit (the AC 4 plan noted ESLint for extension is a future task). When extension lint is added later, append `- run: npm run lint` to the extension job. + +#### 4d. `actions/checkout@v4`, `actions/setup-node@v4`, Node 20 + +- `@v4` are current as of 2026-05. Pin majors; let Dependabot bump. +- Node 20 LTS matches the README prerequisite. Next.js 16 needs Node ≥ 18.18, so 20 is comfortable. +- No `cache: 'npm'` matrix wizardry — `cache-dependency-path` pins each job's cache to its own `package-lock.json` so they don't trash each other. + +#### 4e. Triggers — PR + push + +- `pull_request`: required for branch protection's "require status checks" to apply. +- `push` to `development`/`main`: gives the README a green-build badge and catches direct pushes (shouldn't happen once branch protection is on, but defensive). +- Not running on every push to feature branches — saves Actions minutes; the PR trigger covers it. + +#### 4f. No matrix on Node versions + +YAGNI. We support exactly Node 20 (per README). Add a 22/23 column when there's a reason. + +#### 4g. No caching of `~/.npm` manually + +`actions/setup-node@v4` with `cache: 'npm'` handles it. Don't reinvent. + +### 5. What CI does NOT do (yet) + +- **Build** (`next build`, `vite build`) — slower, and lint+typecheck already catch type/syntax errors. Add when we have build-time failures worth catching (e.g., when AC 4 Vercel deploys started failing). +- **Tests** — there are no tests yet; nothing to run. +- **Migrations check** (`prisma migrate diff` against committed schema) — useful, but Week 1 has exactly one migration. Add if drift becomes a problem. +- **Lint extension code** — see 4c. Future. +- **Auto-deploy to Vercel** — Vercel has its own GitHub integration; we don't need to duplicate it in Actions. (Week 4 AC 1 says "При мерджі в main — автодеплой на Vercel" — that's the Vercel integration, not a workflow step here.) + +### 6. Branch protection — documented, not automated + +GitHub branch protection is a UI setting, not a file in the repo (Rulesets API exists but is overkill for this). CONTRIBUTING.md will explicitly list the toggles to enable. The repo owner does this once after commit 3 lands and the workflow has run successfully on `development` (otherwise "Require status checks" can't find the check names to require). + +## Verification + +### Commit 2 + +- [ ] `LICENSE` exists, MIT text, copyright 2026 BODMAT +- [ ] `CONTRIBUTING.md` renders on GitHub, all internal links resolve +- [ ] `.github/PULL_REQUEST_TEMPLATE.md` exists — verify by opening a draft PR after push, body is pre-filled + +### Commit 3 + +- [ ] `.github/workflows/ci.yml` validates as YAML (`yamllint` or just GitHub Actions UI showing no parse errors) +- [ ] After push, **Actions** tab shows two jobs running: `dashboard` and `extension` +- [ ] Both jobs go green on the existing `chore/repo-setup` branch state +- [ ] If we deliberately break a file (add `const x: number = "string"` somewhere), CI fails on `dashboard` job — confirms typecheck is wired +- [ ] Branch-protection UI on GitHub now lists `dashboard` and `extension` as status checks the user can require + +## Out of scope (for this branch) + +- ESLint for extension code (no config exists; add when needed). +- Build step in CI (deferred until something breaks). +- Auto-merge / Dependabot / CodeQL / release workflows. +- CHANGELOG.md. +- Issue templates (skipped per earlier answer). + +## Lesson for the skill + +After commit 3, the `.claude/skills/` recipes already say "use Plan → Review → Implement"; they don't need an update for CI. If a future Route Handler skill adds a test command, mention it in the PR template's Verification section. From 14deffe6256c002978bd48364a3d79d61ac9a5b0 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Fri, 15 May 2026 20:20:08 +0300 Subject: [PATCH 3/6] ci: add lint and typecheck workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions workflow that runs on every pull request and push to development/main. Two jobs: - dashboard: prisma generate (required before tsc — generated client is gitignored), npm run lint, tsc --noEmit - extension: tsc --noEmit --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1354136 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + pull_request: + branches: [development, main] + push: + branches: [development, main] + +jobs: + dashboard: + runs-on: ubuntu-latest + defaults: + run: + working-directory: dashboard + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: dashboard/package-lock.json + - run: npm ci + - run: npx prisma generate + - run: npm run lint + - run: npx tsc --noEmit + + extension: + runs-on: ubuntu-latest + defaults: + run: + working-directory: extension + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: extension/package-lock.json + - run: npm ci + - run: npm run typecheck From 4e09c87a84f0045a91b0dccb82063f6ae5a721e4 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Fri, 15 May 2026 20:22:03 +0300 Subject: [PATCH 4/6] chore(lint): tighten typescript rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stricter eslint rules in dashboard to enforce CLAUDE.md type-safety guarantees at PR time: - @typescript-eslint/no-explicit-any: error - @typescript-eslint/ban-ts-comment: error - @typescript-eslint/consistent-type-assertions: forbid object-literal casts ({ foo } as T) - no-restricted-syntax: forbid casting to unknown (incl. as unknown as T) Also ignore generated/** (output of prisma generate — autogenerated) and remove the now-redundant eslint-disable-next-line in server/db.ts. --- dashboard/eslint.config.mjs | 24 ++++++++++++++++++++++++ dashboard/server/db.ts | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/dashboard/eslint.config.mjs b/dashboard/eslint.config.mjs index 05e726d..e355fe6 100644 --- a/dashboard/eslint.config.mjs +++ b/dashboard/eslint.config.mjs @@ -12,7 +12,31 @@ const eslintConfig = defineConfig([ "out/**", "build/**", "next-env.d.ts", + // Generated by `prisma generate` — both `prisma-client` and `prisma-zod-generator`. + "generated/**", ]), + { + rules: { + // CLAUDE.md: `any` is forbidden. Use `unknown` + type guards or a proper type. + "@typescript-eslint/no-explicit-any": "error", + // Block `@ts-ignore`, `@ts-nocheck`, `@ts-expect-error` (the latter without a description). + "@typescript-eslint/ban-ts-comment": "error", + // Forbid `{ foo: bar } as SomeType` — a common source of silent bugs. Allow `value as SomeType`. + "@typescript-eslint/consistent-type-assertions": [ + "error", + { assertionStyle: "as", objectLiteralTypeAssertions: "never" }, + ], + // Forbid casting to `unknown` (incl. the `as unknown as T` escape hatch). + "no-restricted-syntax": [ + "error", + { + selector: "TSAsExpression[typeAnnotation.type='TSUnknownKeyword']", + message: + "Casting to `unknown` (including `as unknown as T`) is forbidden. Use a proper type guard or define a real type.", + }, + ], + }, + }, ]); export default eslintConfig; diff --git a/dashboard/server/db.ts b/dashboard/server/db.ts index c8c4511..1144249 100644 --- a/dashboard/server/db.ts +++ b/dashboard/server/db.ts @@ -2,7 +2,6 @@ import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "@/generated/prisma/client"; declare global { - // eslint-disable-next-line no-var var prismaGlobal: PrismaClient | undefined; } From 3308ac5f71944cb5c21161bc683c2a47fd91e27d Mon Sep 17 00:00:00 2001 From: Bohdan Date: Fri, 15 May 2026 20:49:14 +0300 Subject: [PATCH 5/6] chore(tooling): add husky pre-commit, gitattributes, nvmrc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit hook runs eslint --fix on staged dashboard files via lint-staged. Catches issues locally before CI; typecheck stays in CI because it needs the whole project and is too slow per-commit. - husky + lint-staged installed in a new root package.json (the repo is a monorepo without a workspace tool) - lint-staged.config.mjs uses npm --prefix dashboard to run eslint from the dashboard cwd so the flat config resolves naturally - .gitattributes normalizes line endings to LF — stops the "LF will be replaced by CRLF" warnings on every commit - .nvmrc pins Node 20 for nvm use / IDE auto-switch --- .gitattributes | 14 + .husky/pre-commit | 1 + .nvmrc | 1 + lint-staged.config.mjs | 14 + package-lock.json | 786 ++++++++++++++++++++++++++++++ package.json | 13 + plans/week1/repo-hygiene-husky.md | 154 ++++++ 7 files changed, 983 insertions(+) create mode 100644 .gitattributes create mode 100644 .husky/pre-commit create mode 100644 .nvmrc create mode 100644 lint-staged.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 plans/week1/repo-hygiene-husky.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e8f7a24 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Normalize line endings — LF in repo, LF in working tree on all platforms. +* text=auto eol=lf + +# Binary file types — never touch line endings. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.zip binary +*.pdf binary +*.woff binary +*.woff2 binary diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs new file mode 100644 index 0000000..184ead9 --- /dev/null +++ b/lint-staged.config.mjs @@ -0,0 +1,14 @@ +import path from "node:path"; + +const dashboardLint = (files) => { + const dashboardDir = path.resolve("dashboard"); + const relative = files + .map((f) => path.relative(dashboardDir, f).replace(/\\/g, "/")) + .map((f) => `"${f}"`) + .join(" "); + return `npm --prefix dashboard run lint -- --fix ${relative}`; +}; + +export default { + "dashboard/**/*.{ts,tsx,mjs,cjs,js,jsx}": dashboardLint, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dbc47b9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,786 @@ +{ + "name": "worktrace", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "worktrace", + "version": "0.1.0", + "devDependencies": { + "husky": "^9", + "lint-staged": "^15" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d702cef --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "worktrace", + "version": "0.1.0", + "private": true, + "description": "WorkTrace monorepo — Chrome extension + Next.js dashboard for AI-generated dev session reports.", + "scripts": { + "prepare": "husky" + }, + "devDependencies": { + "husky": "^9", + "lint-staged": "^15" + } +} diff --git a/plans/week1/repo-hygiene-husky.md b/plans/week1/repo-hygiene-husky.md new file mode 100644 index 0000000..4283d52 --- /dev/null +++ b/plans/week1/repo-hygiene-husky.md @@ -0,0 +1,154 @@ +# Plan: Repo hygiene + husky pre-commit + +**Branch:** `chore/repo-setup` (5th commit, continuing after `4e09c87` — lint tightening) +**Closes:** no AC directly — local DX improvements complementing the CI workflow. +**Status:** plan, awaiting approval + +## Scope + +Single commit `chore(repo): add husky pre-commit, gitattributes, nvmrc`. Three additions, all small, none of which conflict with future weekly work: + +- **Husky + lint-staged pre-commit hook** — runs ESLint with `--fix` on staged dashboard files. Catches errors locally before CI. Requires a root `package.json` (the repo has no workspace tool today). +- **`.gitattributes`** — normalize line endings to LF in git (stops the `LF will be replaced by CRLF` warnings on every commit, keeps the repo cross-platform sane). +- **`.nvmrc`** — pin Node 20 for `nvm use` / IDE auto-switch. + +## Files + +| Path | Action | Notes | +|---|---|---| +| `package.json` | new | root — `husky` + `lint-staged` as devDeps, `prepare` script | +| `package-lock.json` | new (auto) | generated by `npm install` | +| `.husky/pre-commit` | new | one-line shell: `npx lint-staged` | +| `lint-staged.config.mjs` | new | JS config — handles dashboard subdir paths cross-platform | +| `.gitattributes` | new | `* text=auto eol=lf` + binary markers | +| `.nvmrc` | new | one line: `20` | +| `plans/week1/repo-hygiene-husky.md` | add (this file) | | + +Root `.gitignore` already covers `node_modules/` — no change needed. + +## Design decisions + +### 1. Why a root `package.json` + +The repo is a monorepo without a workspace tool (`dashboard/` and `extension/` each have their own `package.json` and `node_modules`). Husky must install git hooks via a `prepare` script that runs at install time — that script needs to live in a `package.json` that the contributor will `npm install`. + +Three options considered: + +- **Root `package.json` just for husky/lint-staged.** Standard pattern. Contributor runs `npm install` at repo root once to install the hook. Mild cost: one more `node_modules/` directory (small — only husky + lint-staged + their deps). +- **Put husky inside `dashboard/`.** Awkward: `npm install` in dashboard would install hooks pointing to commands that assume cwd=repo-root. Also doesn't catch commits made when editing extension code if the contributor never installed dashboard deps. +- **No husky, rely on CI only.** What we have today. Loses the fast-feedback DX win. + +**Decision:** root `package.json`. Minimal — just husky + lint-staged. No build, no scripts beyond `prepare`. + +### 2. lint-staged config — JS, not JSON + +`lint-staged` config can live in `package.json` (JSON) or a separate `lint-staged.config.{mjs,js,cjs}` file (JS). For this monorepo we need JS because: + +- Staged files come in as absolute paths. +- ESLint resolves its flat config from `cwd`, not from the file's directory. +- So we need to run ESLint with `cwd=dashboard/`, passing dashboard-relative paths. +- That path arithmetic needs JS. + +Shape: + +```js +// lint-staged.config.mjs +import path from "node:path"; + +const dashboardLint = (files) => { + const dashboardDir = path.resolve("dashboard"); + const relative = files + .map((f) => path.relative(dashboardDir, f).replace(/\\/g, "/")) + .map((f) => `"${f}"`) + .join(" "); + // `npm --prefix` is cross-platform (no `cd && …` shell chains). + // `-- --fix ` forwards args to the dashboard `lint` script (which is `eslint`). + return `npm --prefix dashboard run lint -- --fix ${relative}`; +}; + +export default { + "dashboard/**/*.{ts,tsx,mjs,cjs,js,jsx}": dashboardLint, +}; +``` + +### 3. What runs in pre-commit + +- **`eslint --fix` on staged dashboard files** — auto-fixes formatting + flags real errors before commit. +- **NOT `tsc --noEmit`** — typecheck needs the whole project; running it per-commit adds ~5-10s for a small dashboard, more later. Stays in CI. +- **NOT extension lint** — extension has no ESLint config yet (noted in earlier plan). Add to lint-staged once it does. +- **NOT extension typecheck** — same reason as dashboard typecheck (speed). + +The pre-commit hook is a fast smoke test, not a full quality gate. The full gate is CI. + +### 4. `.gitattributes` content + +``` +# Treat as text and normalize line endings to LF. +* text=auto eol=lf + +# Binary — never modified. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.zip binary +*.pdf binary +*.woff binary +*.woff2 binary +``` + +On Windows, git will still write CRLF to the working tree (controlled by `core.autocrlf`); `.gitattributes` only controls what's stored in the repo. Net effect: no more `LF will be replaced by CRLF` warnings on commit because git knows the file is text and the normalization is intentional. + +Existing files committed with CRLF in the repo won't be retroactively renormalized — git only converts on next touch. That's fine; no need to mass-rewrite history. + +### 5. `.nvmrc` content + +``` +20 +``` + +One line, no `v` prefix (`nvm` accepts both, but plain numbers are the convention). + +### 6. Installing without breaking existing flows + +After `npm install` at the new root `package.json`: +- A root `node_modules/` appears. +- `.husky/_/` directory gets created by husky's install script (handles the actual git hook wiring). +- `git commit` now invokes `.husky/pre-commit`. + +Existing flows (`cd dashboard && npm run dev`, `cd extension && npm run build`) are unaffected — they read their own `package.json` and `node_modules` as before. + +### 7. Skipping the hook when needed + +`git commit --no-verify` skips husky hooks. This should be rare (per CLAUDE.md, you investigate root causes rather than bypass); documenting it in CONTRIBUTING.md isn't necessary — most devs already know `--no-verify`. + +### 8. Versions to pin + +- `husky@^9` — latest major. v9 removed the `husky install` command; replaced by `husky` directly. Our `prepare` script reflects that. +- `lint-staged@^15` — latest stable, supports ESM config. + +Exact versions resolved at install time. + +## Verification + +- [ ] `npm install` at repo root succeeds; `node_modules/` and `package-lock.json` appear. +- [ ] `.husky/pre-commit` exists and is wired (husky uses `core.hooksPath`, so `ls .git/hooks/` should NOT show `pre-commit`). +- [ ] `cat .nvmrc` → `20`. +- [ ] Stage a deliberately-bad dashboard file (e.g. add `const x: any = 1` to a TS file) → `git commit` fails with the ESLint error. +- [ ] Stage a clean dashboard file → `git commit` succeeds, `eslint --fix` runs (no visible output if nothing to fix). +- [ ] Stage a file outside `dashboard/**` (e.g. a markdown file) → commit succeeds without running lint. +- [ ] After `.gitattributes` is in place, a subsequent commit of a TS file no longer prints `LF will be replaced by CRLF`. + +## Out of scope (for this branch) + +- Workspaces (`npm workspaces` / `pnpm`) — would unify `node_modules` but disrupts the current isolated layout. Defer until there's a real reason. +- Husky pre-push hook (run full typecheck) — heavier; add later if devs land bad commits often. +- ESLint config for `extension/` — already noted in the CI plan; separate task. +- Conventional-commits validation (`commitlint`) — useful but not asked for. +- `.editorconfig` — declined. + +## Lesson for the skill + +No skill file needs an update — pre-commit hooks aren't a recurring code-authoring task. If a contributor sees the hook fail and wants to bypass it, the rule "diagnose, don't bypass" is already in CLAUDE.md. From 2761c5f3a9364b151e3fe3914ef53339035fac54 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Fri, 15 May 2026 21:10:52 +0300 Subject: [PATCH 6/6] fix(ci): provide dummy DATABASE_URL for prisma generate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prisma.config.ts uses prisma/config's strict env() helper, which throws PrismaConfigEnvError if DATABASE_URL is missing at load time. The CI runner has no .env file (gitignored), so prisma generate failed before reading the schema. prisma generate does not connect to the database — it only reads the schema and writes the generated client + zod schemas. A dummy URL satisfies the config loader without touching any real database. Set DATABASE_URL only on the prisma generate step, not on the whole job, so an accidental query elsewhere still fails loudly. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1354136..351dfb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,12 @@ jobs: cache-dependency-path: dashboard/package-lock.json - run: npm ci - run: npx prisma generate + env: + # prisma.config.ts uses prisma/config's strict env() helper, which + # throws if DATABASE_URL is unset. `prisma generate` does not connect + # to the database — it only reads the schema and writes the client — + # so a dummy URL satisfies the config loader. + DATABASE_URL: "postgresql://prisma:prisma@localhost:5432/prisma" - run: npm run lint - run: npx tsc --noEmit