diff --git a/.fallowrc.json b/.fallowrc.json new file mode 100644 index 0000000..ea9432e --- /dev/null +++ b/.fallowrc.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://docs.fallow.tools/schema/fallow.schema.json", + "entry": [ + "app/**/__tests__/**/*.{ts,tsx}", + "app/**/*.test.{ts,tsx}", + "lib/**/__tests__/**/*.{ts,tsx}", + "lib/**/*.test.{ts,tsx}" + ] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..155641e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + dev-dependencies: + dependency-type: development + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f7ff2a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run lint + + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run typecheck + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm test + + # Advisory security scan — surfaces findings without blocking the merge. + semgrep: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + # setuptools provides pkg_resources, which semgrep's deps need on + # Python 3.12 (where it is no longer bundled). + - run: pip install 'setuptools<81' 'semgrep==1.95.0' + - run: > + semgrep scan + --config=p/typescript --config=p/react --config=p/nextjs --config=p/secrets + --severity=ERROR --metrics=off || true diff --git a/.github/workflows/fallow.yml b/.github/workflows/fallow.yml new file mode 100644 index 0000000..46ef8e4 --- /dev/null +++ b/.github/workflows/fallow.yml @@ -0,0 +1,17 @@ +name: fallow + +on: + pull_request: + +jobs: + audit: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: fallow-rs/fallow@v2 + with: + fail-on-issues: false + format: json diff --git a/.gitignore b/.gitignore index fe6cf0d..f415c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ out/ # Debug npm-debug.log* +firebase-debug.log* +*.log # IDE .idea/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..70e6d98 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +npm run typecheck && npm test diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..3d064b8 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "*.{ts,tsx}": "eslint --fix" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d32b9dd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,129 @@ +# Worklog — Development Guide + +AI-powered time logging for consultants. Aggregates your day hour-by-hour from +Google, Slack, Trello, GitHub, Jira, and HubSpot, then uses AI to generate +ready-to-submit time entries mapped to your projects in Milient/Moment. + +This file is the canonical guide for both humans and AI agents. `CLAUDE.md` +imports it. For deeper reference, see: + +- [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) — system design, data flow, auth, caching +- [docs/PATTERNS.md](./docs/PATTERNS.md) — code patterns & conventions +- [docs/DOMAIN-LOGIC.md](./docs/DOMAIN-LOGIC.md) — business rules +- [docs/adr/](./docs/adr/) — architecture decision records + +## Tech Stack + +- **Framework**: Next.js 14 (App Router) +- **Language**: TypeScript 5 (`strict`) +- **Auth**: NextAuth.js v5 (JWT-only, no database) +- **UI**: shadcn/ui + Tailwind CSS v4 + Lucide icons +- **AI**: Google Gemini 2.5 Flash Lite +- **Time tracking**: Milient / Moment +- **Hosting**: Vercel + +## Commands + +```bash +npm run dev # Start dev server (http://localhost:3000) +npm run build # Production build +npm run lint # ESLint (flat config) +npm run lint:fix # ESLint with autofix +npm run typecheck # tsc --noEmit +npm test # Vitest (run once) +npm run test:watch # Vitest (watch mode) +``` + +`npm install` sets up git hooks automatically via husky. + +## Quality Gates + +Three gates protect `main`. They run locally via git hooks and again in CI: + +| Gate | Local | CI | +|------|-------|----| +| Lint (`npm run lint`) | pre-commit (lint-staged, changed files) | `ci.yml` | +| Typecheck (`npm run typecheck`) | pre-push | `ci.yml` | +| Tests (`npm test`) | pre-push | `ci.yml` | + +Advisory (non-blocking) checks on PRs: Semgrep security scan (`ci.yml`) and a +`fallow` dead-code/reachability audit (`fallow.yml`). Dependencies are kept +fresh by Dependabot. + +Lint blocks only on **errors**. Pre-existing `any` at third-party API +boundaries and `react-hooks/exhaustive-deps` are intentionally **warnings** — +see [Known tech-debt](#known-tech-debt). + +## Development Workflow + +1. Branch off `main` (`feat/…`, `fix/…`, `chore/…`). +2. Commit after each logical change with a short, descriptive message. +3. **Bump the version** in `package.json` → `"version"` before pushing. This is + the single source of truth; `app/lib/version.ts`, `i18n.tsx`, and the about + page all read from it. Use semver: patch = bug fix, minor = feature, + major = breaking change. **Never push without bumping.** +4. Push and open a PR. CI must be green before merge. + +## Testing + +- Unit tests live next to the code in `__tests__/` folders and use Vitest. +- Focus on the pure, high-value logic: `app/lib/aggregator.ts`, + `app/lib/ai/parse.ts`, `app/lib/ai/preprocess.ts`. +- The test script pins `TZ=Europe/Oslo`. Anything that reads local time + (`getHours()`) is timezone-sensitive — write fixtures accordingly (pass an + explicit `timezone` arg, or use timestamps without a `Z` suffix so they parse + as local time). + +## Coding Conventions + +See [docs/PATTERNS.md](./docs/PATTERNS.md) for detail. The essentials: + +- **Adapter pattern** for pluggable integrations: `lib/ai/` and `lib/pm/` each + expose an interface + factory + implementation. New provider = one new file + + a factory line. +- **i18n**: all UI strings live in `app/lib/i18n.tsx` as a flat dotted-key + dictionary. Use `const { t, lang } = useTranslation()`. +- **Components**: shadcn/ui base components, Lucide icons, Tailwind only — no + CSS modules or styled-components. +- **API routes** authenticate first, then handle: + ```ts + const session = await auth() + if (!session?.accessToken) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + } + ``` +- **Responsive**: when conditional rendering is needed, branch on + `matchMedia("(min-width: 1024px)")` in JS — never mount two instances of a + stateful component and hide one with CSS. + +## Scope Discipline + +- Only make changes that are directly requested or clearly necessary. +- Don't add features, refactor, or "improve" beyond what was asked. +- Don't add comments, docstrings, or type annotations to code you didn't change. +- Don't create new files unless necessary — prefer editing existing ones. +- Don't create documentation files unless explicitly requested. + +## Git Protocol + +- Commit after each logical change, not in large batches. +- Never force-push, amend published commits, or skip hooks. +- Never commit `.env.local` or files containing secrets. + +## Human Approval Gates + +Always ask before: + +- Destructive operations (deleting files/branches, dropping data) +- Actions visible to others (pushing, creating PRs/issues, sending messages) +- Architectural decisions with multiple valid approaches + +## Known tech-debt + +Tracked as lint warnings to burn down incrementally rather than blocking CI: + +- Type the `any` at third-party API boundaries (`google.ts`, `slack.ts`, + `jira.ts`, `hubspot.ts`, `trello.ts`, `milient.ts`). +- Resolve `react-hooks/exhaustive-deps` warnings in `app/page.tsx`. +- Consider promoting Semgrep from advisory to blocking once the baseline is + clean, and adding bundle-size budgets (`size-limit`). diff --git a/CLAUDE.md b/CLAUDE.md index de25a56..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,34 +1 @@ -# Agent Rules - -See also: [ARCHITECTURE.md](./ARCHITECTURE.md) | [PATTERNS.md](./PATTERNS.md) | [DOMAIN-LOGIC.md](./DOMAIN-LOGIC.md) - -## Workflow - -After each code change: -1. Commit with a short descriptive message. -2. Push it - -**Before pushing** (every `git push`), bump the version in `package.json` → `"version"`. That is the single source of truth — all other places (`i18n.tsx`, about page) read from it automatically via `app/lib/version.ts`. - -Use semver: patch for bug fixes, minor for features, major for breaking changes. This is mandatory — never push without checking the version. - -## Scope Discipline - -- Only make changes that are directly requested or clearly necessary. -- Don't add features, refactor, or "improve" beyond what was asked. -- Don't add comments, docstrings, or type annotations to code you didn't change. -- Don't create new files unless absolutely required — prefer editing existing ones. -- Don't create documentation files unless explicitly requested. - -## Git Protocol - -- Commit after each logical change, not in large batches. -- Never force-push, amend published commits, or skip hooks. -- Never commit `.env.local` or files containing secrets. - -## Human Approval Gates - -Always ask before: -- Destructive operations (deleting files/branches, dropping data) -- Actions visible to others (pushing, creating PRs/issues, sending messages) -- Architectural decisions with multiple valid approaches +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5b4e1fa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing to Worklog + +Thanks for contributing. This is an internal Frontkom project; the conventions +below keep it pleasant to work in. + +## Prerequisites + +- Node.js 22 (matches CI) +- npm + +## Setup + +```bash +npm install # also installs git hooks (husky) +cp .env.example .env.local # fill in credentials — see README.md +npm run dev # http://localhost:3000 +``` + +## Workflow + +1. Branch off `main`: `feat/…`, `fix/…`, or `chore/…`. +2. Make focused commits with short, descriptive messages. +3. Run the checks before pushing (the git hooks run these too): + ```bash + npm run lint + npm run typecheck + npm test + ``` +4. **Bump `version` in `package.json`** (semver) before pushing — it is the + single source of truth for the version shown in the app. +5. Open a PR. CI (lint + typecheck + test) must be green to merge. + +## Quality gates + +- **pre-commit** runs ESLint (with autofix) on staged files. +- **pre-push** runs the typecheck and the test suite. +- **CI** re-runs all three on every PR, plus advisory Semgrep and `fallow` + audits. + +Lint blocks on errors only; warnings are tracked tech-debt (see `AGENTS.md`). + +## Tests + +Unit tests use Vitest and live in `__tests__/` folders next to the code under +test. Prioritise the pure logic in `app/lib/`. The suite pins +`TZ=Europe/Oslo`; keep timezone-sensitive fixtures deterministic. + +## Conventions + +See [`AGENTS.md`](./AGENTS.md) for the full development guide and +[`docs/`](./docs) for architecture, patterns, and domain rules. diff --git a/README.md b/README.md index 2bf13ad..9dbc5e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Worklog +[](https://github.com/perandre/worklog/actions/workflows/ci.yml) + AI-powered time logging for consultants. Aggregates your day hour-by-hour from Google, Slack, Trello, GitHub, Jira, and HubSpot — then uses AI to generate ready-to-submit time entries mapped to your projects in Milient/Moment. Built with Next.js 14, shadcn/ui, Tailwind CSS, and Google Gemini. No database. Supports dark mode. @@ -18,11 +20,25 @@ cp .env.example .env.local # Fill in your credentials npm run dev # Open http://localhost:3000 ``` +## Development + +```bash +npm run dev # Dev server +npm run lint # ESLint +npm run typecheck # tsc --noEmit +npm test # Vitest +``` + +Git hooks (installed by `npm install`) lint staged files on commit and run the +typecheck + tests on push. CI runs the same checks on every PR. See +[CONTRIBUTING.md](./CONTRIBUTING.md) for the workflow and [AGENTS.md](./AGENTS.md) +for the full development guide. + ## Environment Variables ``` # NextAuth -NEXTAUTH_URL=https://your-app.vercel.app +NEXTAUTH_URL=https://worklog.frontkom.com NEXTAUTH_SECRET=generate-a-random-secret # Google OAuth (Calendar, Gmail, Drive Activity) @@ -121,9 +137,11 @@ Milient is a time management platform used by Norwegian consulting firms. The ap 1. Push to GitHub 2. Import the project at [vercel.com](https://vercel.com) -3. Add all environment variables (set `NEXTAUTH_URL` to your Vercel domain) +3. Add all environment variables (set `NEXTAUTH_URL` to your production domain) 4. Deploy -5. Update all OAuth redirect URIs to your Vercel domain +5. Add your custom domain in Vercel → Settings → Domains, then point its DNS at + Vercel (see [docs/CALLBACK-URLS.md](./docs/CALLBACK-URLS.md)) +6. Update all OAuth redirect URIs to your production domain ## Tech Stack diff --git a/app/about/page.tsx b/app/about/page.tsx index 2781464..ad856a5 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -111,7 +111,7 @@ export default function AboutPage() { You had a productive day. Back-to-back meetings, a long email thread, three code reviews, half a dozen Slack threads, and two Jira tickets closed. Then 4:55 PM arrives and your time-tracking system is just… waiting. Blinking cursor, empty rows.
- Worklog is the missing piece. It collects everything you did, lays it out hour by hour, and uses AI to translate it into billable time entries — mapped to the right projects and activity types, with descriptions you'd actually write yourself. + Worklog is the missing piece. It collects everything you did, lays it out hour by hour, and uses AI to translate it into billable time entries — mapped to the right projects and activity types, with descriptions you'd actually write yourself.
@@ -146,7 +146,7 @@ export default function AboutPage() {- Google Calendar events are primaries — the anchor points of each hour. Everything else (emails, messages, commits) is a communication, shown alongside but capped at six per hour so the view doesn't turn into a wall of noise. Emails are also deduplicated by thread, so a long back-and-forth shows up as one item. + Google Calendar events are primaries — the anchor points of each hour. Everything else (emails, messages, commits) is a communication, shown alongside but capped at six per hour so the view doesn't turn into a wall of noise. Emails are also deduplicated by thread, so a long back-and-forth shows up as one item.
@@ -159,7 +159,7 @@ export default function AboutPage() { How it works- This is where the interesting stuff happens. Here's the full journey from "you pick a date" to "here's your timelog." + This is where the interesting stuff happens. Here's the full journey from "you pick a date" to "here's your timelog."
- All seven sources are queried simultaneously. Each has its own OAuth token stored as a secure HTTP-only cookie. Google uses NextAuth's JWT; Jira stores only the refresh token (access tokens are too big for cookies) and fetches a fresh one per request. + All seven sources are queried simultaneously. Each has its own OAuth token stored as a secure HTTP-only cookie. Google uses NextAuth's JWT; Jira stores only the refresh token (access tokens are too big for cookies) and fetches a fresh one per request.
- When you click Generate, the app pulls your project context from the time-tracking system: the 20 projects you've used most in the last 14 days, the top 3 activity types per project, your hour allocations for the day, any existing time records, and the time-lock date (logged hours before that date are untouchable). + When you click Generate, the app pulls your project context from the time-tracking system: the 20 projects you've used most in the last 14 days, the top 3 activity types per project, your hour allocations for the day, any existing time records, and the time-lock date (logged hours before that date are untouchable).
- You see the suggestions, edit anything that looks off, and hit Submit. Each entry is validated server-side against the time-lock before being written to your time-tracking system. The suggestions are cached in localStorage per date so a page refresh doesn't throw away your work. + You see the suggestions, edit anything that looks off, and hit Submit. Each entry is validated server-side against the time-lock before being written to your time-tracking system. The suggestions are cached in localStorage per date so a page refresh doesn't throw away your work.
- Milient (also known as Moment) is a time management platform widely used by Norwegian consulting firms. Worklog uses it as the source of truth for projects, activity types, hour allocations, and as the destination for submitted time records. Your user account is resolved dynamically from your Google sign-in email, so there's no separate login. + Milient (also known as Moment) is a time management platform widely used by Norwegian consulting firms. Worklog uses it as the source of truth for projects, activity types, hour allocations, and as the destination for submitted time records. Your user account is resolved dynamically from your Google sign-in email, so there's no separate login.