From 11f7feeb3488af1f4a70cd31606c430658395844 Mon Sep 17 00:00:00 2001 From: esnoko Date: Tue, 13 Oct 2026 14:45:59 +0200 Subject: [PATCH 1/3] Add verified agent development workflow and security checks --- .github/dependabot.yml | 22 ++ .github/pull_request_template.md | 15 + .github/workflows/ci.yml | 70 +++++ AGENTS.md | 53 ++++ README.md | 10 + backend/package-lock.json | 52 ++-- backend/package.json | 4 + backend/server.js | 14 +- backend/test/api.test.js | 60 ++++ backend/test/cache.test.js | 20 ++ backend/test/github.test.js | 75 +++++ backend/test/scoring.test.js | 47 +++ docs/development-workflow.md | 75 +++++ frontend/package-lock.json | 485 ++++++++++++++++++++++++------- package-lock.json | 14 +- package.json | 3 + 16 files changed, 864 insertions(+), 155 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yml create mode 100644 AGENTS.md create mode 100644 backend/test/api.test.js create mode 100644 backend/test/cache.test.js create mode 100644 backend/test/github.test.js create mode 100644 backend/test/scoring.test.js create mode 100644 docs/development-workflow.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0d659f7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 3 + - package-ecosystem: npm + directory: /backend + schedule: + interval: weekly + open-pull-requests-limit: 3 + - package-ecosystem: npm + directory: /frontend + schedule: + interval: weekly + open-pull-requests-limit: 3 + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 3 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b6bfb40 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## Change + +Describe the problem and resulting behavior. + +## Validation + +- [ ] `npm run check` passed (or explain failures below). +- [ ] Relevant behavior tests cover the change. +- [ ] Dependency audit and secret scan passed in CI. + +Include commands/results and any manual checks that matter. + +## Risks or remaining work + +Mention API changes, deployment settings, or checks that could not run. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0ff93fd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + name: Tests and frontend build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 + with: + node-version: '22' + cache: npm + cache-dependency-path: | + package-lock.json + backend/package-lock.json + frontend/package-lock.json + - run: npm ci + - run: npm --prefix backend ci + - run: npm --prefix frontend ci + - run: npm run check + + dependencies: + name: Dependency audit + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 + with: + node-version: '22' + - run: npm audit --audit-level=high + - run: npm --prefix backend audit --audit-level=high + - run: npm --prefix frontend audit --audit-level=high + + secrets: + name: Secret scan + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + - name: Scan Git history with Gitleaks + run: >- + docker run --rm --network none + -e GIT_CONFIG_COUNT=1 + -e GIT_CONFIG_KEY_0=safe.directory + -e GIT_CONFIG_VALUE_0=/repo + -v "$GITHUB_WORKSPACE:/repo:ro" + ghcr.io/gitleaks/gitleaks:v8.24.2 + git /repo --redact --log-opts="--all" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8551704 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,53 @@ +# Working on DevTrack + +## Project boundaries + +DevTrack is a React/Vite dashboard backed by an Express API. Analytics are +deterministic: keep scoring, GitHub ingestion, builders, insights, and caching +in `backend/services`; keep presentation and interaction in `frontend/src`. +Preserve the response contract documented in README.md unless the task explicitly +changes it. The MVP has no persistent database. + +## Commands + +Use Node 22. Install locked dependencies with `npm ci` at the root and +`npm --prefix backend ci` / `npm --prefix frontend ci`. + +- `npm test`: offline backend tests using Node's built-in runner. +- `npm run build`: production frontend build. +- `npm run check`: tests followed by the frontend build. +- `npm --prefix backend run dev`: API development server. +- `npm --prefix frontend run dev`: frontend development server. + +On Windows PowerShell, use `npm.cmd` if execution policy blocks `npm.ps1`. + +## Agent workflow + +1. Inspect the relevant code and current Git changes before editing. Preserve + unrelated user work. Define the task's expected behavior and acceptance criteria. +2. Make focused changes. Add meaningful behavior tests for scoring, caching, API + contracts, and upstream failure handling when those behaviors change. Mock + GitHub requests; tests must not depend on live accounts or credentials. +3. Run `npm run check` before handing off code changes. Report failures and + unavailable checks honestly; do not weaken checks to obtain a passing result. +4. Report the resulting behavior, validation, and any remaining limitations. + PRs should include evidence of checks and require human review before merging. + +## Access boundaries + +Use the coding tool's actual sandbox controls to limit writes to this checkout +and restrict network access to what the task needs. This file is guidance, not +an enforcement mechanism. Do not read or print real `.env` files, production +credentials, or deployment secrets. Use `.env.example` and mocked data. +Do not embed tokens in source, logs, prompts, or test fixtures. + +Production deployments, credential changes, and automatic merges require explicit +user authorization. Keep CI read-only and free of production credentials; use +`pull_request`, never `pull_request_target` to execute contributed code. + +## Parallel work + +Use multiple agents only when the user explicitly requests them. Assign each +independent task a separate branch and worktree, define ownership, and agree on +any shared API contract before implementation. Worktrees isolate changes, not +credentials or processes. Validate the combined result before merging. diff --git a/README.md b/README.md index cf30b9a..236c5ac 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,16 @@ npm run dev Frontend default: http://localhost:5173 +## Development Checks and Agent Workflow + +Use Node 22 for development and CI. After installing root, backend, and frontend +dependencies with `npm ci`, run `npm run check` from the repository root. +It runs offline backend behavior tests and builds the frontend. + +See [development workflow](docs/development-workflow.md) for security checks, +PR review, and GitHub branch protection setup, and [AGENTS.md](AGENTS.md) for +project instructions used by coding agents. + ## Deployment Notes - Frontend deploy target: Vercel, root directory frontend diff --git a/backend/package-lock.json b/backend/package-lock.json index a12d94c..da4eea2 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -35,9 +35,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.8", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -48,7 +48,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.15.1", + "qs": "~6.16.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -58,21 +58,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -596,9 +581,9 @@ "license": "MIT" }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -606,15 +591,20 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -725,14 +715,14 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, diff --git a/backend/package.json b/backend/package.json index 387f22a..c2987d0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,6 +4,7 @@ "description": "DevTrack backend API", "main": "server.js", "scripts": { + "test": "node --test", "start": "node server.js", "dev": "node --watch server.js" }, @@ -13,5 +14,8 @@ "dependencies": { "dotenv": "^17.4.2", "express": "^4.19.2" + }, + "overrides": { + "qs": "^6.16.0" } } diff --git a/backend/server.js b/backend/server.js index 609cc13..b7d8b76 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,4 +1,6 @@ -require('dotenv').config(); +if (require.main === module) { + require('dotenv').config(); +} const express = require('express'); const githubRoutes = require('./routes/githubRoutes'); @@ -31,6 +33,10 @@ app.use('/api/v1/health', healthRoutes); app.use(notFound); app.use(errorHandler); -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); -}); \ No newline at end of file +if (require.main === module) { + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); +} + +module.exports = app; diff --git a/backend/test/api.test.js b/backend/test/api.test.js new file mode 100644 index 0000000..c9907e3 --- /dev/null +++ b/backend/test/api.test.js @@ -0,0 +1,60 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const http = require('node:http'); +const { once } = require('node:events'); +const app = require('../server'); + +// Use http rather than fetch for local requests so the GitHub fetch mock stays isolated. +const request = (server, path) => new Promise((resolve, reject) => { + http.get({ hostname: '127.0.0.1', port: server.address().port, path }, (response) => { + let body = ''; + response.setEncoding('utf8'); + response.on('data', (chunk) => { body += chunk; }); + response.on('end', () => resolve({ status: response.statusCode, + headers: response.headers, body: JSON.parse(body) })); + response.on('error', reject); + }).on('error', reject); +}); + +test('HTTP API preserves success, cache, health, and failure contracts', async (t) => { + const server = app.listen(0, '127.0.0.1'); + t.after(() => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))); + await once(server, 'listening'); + let upstreamStatus = 200; + t.mock.method(global, 'fetch', async () => upstreamStatus === 200 + ? Response.json([]) : new Response(null, { status: upstreamStatus })); + + const health = await request(server, '/api/v1/health'); + assert.equal(health.status, 200); + assert.equal(health.body.data.status, 'ok'); + + const first = await request(server, '/api/v1/github/ApiFixture'); + assert.equal(first.status, 200); + assert.equal(first.body.success, true); + assert.equal(first.headers['x-cache'], 'MISS'); + assert.equal(first.body.meta.cached, false); + assert.ok(Number.isFinite(Date.parse(first.body.meta.timestamp))); + assert.equal(first.body.data.username, 'ApiFixture'); + assert.equal(first.body.data.hireabilityScore, 0); + assert.equal(first.body.data.commitActivity.length, 12); + assert.equal(first.body.data.scoreBreakdown.length, 4); + assert.ok(Array.isArray(first.body.data.languageBreakdown)); + assert.deepEqual(Object.keys(first.body.data.insights.roleFit).sort(), ['backend', 'frontend', 'fullstack']); + + const cached = await request(server, '/api/v1/github/apifixture'); + assert.equal(cached.headers['x-cache'], 'HIT'); + assert.equal(cached.body.meta.cached, true); + assert.deepEqual(cached.body.data, first.body.data); + + for (const status of [404, 429, 502]) { + upstreamStatus = status; + const failed = await request(server, `/api/v1/github/failure${status}`); + assert.equal(failed.status, status); + assert.equal(failed.body.success, false); + assert.equal(failed.body.error.statusCode, status); + assert.equal(typeof failed.body.error.message, 'string'); + } + + assert.equal((await request(server, '/api/v1/github/%20')).status, 400); + assert.equal((await request(server, '/unknown')).status, 404); +}); diff --git a/backend/test/cache.test.js b/backend/test/cache.test.js new file mode 100644 index 0000000..50fa13f --- /dev/null +++ b/backend/test/cache.test.js @@ -0,0 +1,20 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { getCachedAnalysis, setCachedAnalysis } = require('../services/cache/cacheService'); + +test('cache misses return null and entries expire exactly at their TTL', (t) => { + const ttlSeconds = Number(process.env.CACHE_TTL_SECONDS || 300); + const ttl = Number.isFinite(ttlSeconds) && ttlSeconds > 0 ? ttlSeconds * 1000 : 300000; + let now = 1000000; + t.mock.method(Date, 'now', () => now); + assert.equal(getCachedAnalysis('missing'), null); + const data = { username: 'cache-fixture' }; + setCachedAnalysis('cache-fixture', data); + assert.deepEqual(getCachedAnalysis('cache-fixture'), data); + now += ttl - 1; + assert.deepEqual(getCachedAnalysis('cache-fixture'), data); + now += 1; + assert.equal(getCachedAnalysis('cache-fixture'), null); + now -= ttl; + assert.equal(getCachedAnalysis('cache-fixture'), null); +}); diff --git a/backend/test/github.test.js b/backend/test/github.test.js new file mode 100644 index 0000000..12a5bc0 --- /dev/null +++ b/backend/test/github.test.js @@ -0,0 +1,75 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { fetchUserRepositories, fetchUserEvents } = require('../services/github/githubApi'); +const { analyzeGithubProfile } = require('../services'); +const { inFlightAnalyses } = require('../services/cache/cacheService'); + +test('repositories paginate and encode usernames', async (t) => { + const urls = []; + t.mock.method(global, 'fetch', async (url) => { + urls.push(url); + return Response.json(urls.length === 1 ? Array.from({ length: 100 }, (_, id) => ({ id })) : [{ id: 100 }]); + }); + const repos = await fetchUserRepositories('a/b'); + assert.equal(repos.length, 101); + assert.match(urls[0], /a%2Fb\/repos.*page=1/); + assert.match(urls[1], /page=2/); +}); + +for (const status of [403, 429]) { + test(`GitHub ${status} becomes a rate-limit error with retry metadata`, async (t) => { + t.mock.method(global, 'fetch', async () => new Response(null, { + status, headers: { 'Retry-After': '60' } + })); + await assert.rejects(fetchUserRepositories('limited'), (error) => { + assert.equal(error.statusCode, 429); + assert.equal(error.retryAfter, '60'); + return true; + }); + }); +} + +test('missing users and upstream errors remain explicit failures', async (t) => { + for (const status of [404, 502]) { + const mock = t.mock.method(global, 'fetch', async () => new Response(null, { status })); + await assert.rejects(fetchUserRepositories('missing'), { statusCode: status }); + mock.mock.restore(); + } +}); + +test('event failures preserve previously fetched activity', async (t) => { + const events = Array.from({ length: 100 }, () => ({ created_at: new Date().toISOString() })); + let calls = 0; + t.mock.method(global, 'fetch', async () => ++calls === 1 + ? Response.json(events) : new Response(null, { status: 429 })); + assert.deepEqual(await fetchUserEvents('partial'), events); + assert.equal(calls, 2); +}); + +test('concurrent analyses share upstream calls and cache usernames without case sensitivity', async (t) => { + let calls = 0; + t.mock.method(global, 'fetch', async () => { + calls += 1; + return Response.json([]); + }); + const [first, second] = await Promise.all([ + analyzeGithubProfile('ConcurrentFixture'), analyzeGithubProfile('concurrentfixture') + ]); + assert.equal(calls, 2); + assert.equal(first.cacheHit, false); + assert.equal(second.cacheHit, true); + assert.deepEqual(first.data, second.data); + assert.equal(inFlightAnalyses.size, 0); + assert.equal((await analyzeGithubProfile('CONCURRENTFIXTURE')).cacheHit, true); + assert.equal(calls, 2); +}); + +test('failed analyses release in-flight state and can be retried', async (t) => { + let failed = true; + t.mock.method(global, 'fetch', async () => failed + ? new Response(null, { status: 502 }) : Response.json([])); + await assert.rejects(analyzeGithubProfile('retryfixture'), { statusCode: 502 }); + assert.equal(inFlightAnalyses.has('retryfixture'), false); + failed = false; + assert.equal((await analyzeGithubProfile('retryfixture')).cacheHit, false); +}); diff --git a/backend/test/scoring.test.js b/backend/test/scoring.test.js new file mode 100644 index 0000000..02c5eb0 --- /dev/null +++ b/backend/test/scoring.test.js @@ -0,0 +1,47 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { buildHireabilityScore } = require('../services/scoring/scoringService'); + +test('a profile with no repositories or activity scores zero', () => { + const result = buildHireabilityScore( + { totalRepositories: 0, totalStars: 0, totalForks: 0 }, + { repositoriesAnalyzed: 0 }, [] + ); + assert.equal(result.hireabilityScore, 0); + assert.equal(result.scoreBreakdown.length, 4); + assert.ok(result.scoreBreakdown.every(({ score }) => score === 0)); +}); + +test('maximum signals produce 100 and respect component weights', () => { + const result = buildHireabilityScore( + { totalRepositories: 20, totalStars: 1000, totalForks: 0 }, + { repositoriesAnalyzed: 20, repositoriesWithDescription: 20, + repositoriesWithLicense: 20, recentlyUpdatedRepositories: 20 }, + Array.from({ length: 12 }, () => ({ commits: 10 })) + ); + assert.equal(result.hireabilityScore, 100); + assert.deepEqual(result.scoreBreakdown.map(({ score }) => score), [40, 30, 20, 10]); +}); + +test('long inactive gaps reduce consistency for the same activity total', () => { + const summary = { totalRepositories: 0, totalStars: 0, totalForks: 0 }; + const quality = { repositoriesAnalyzed: 0 }; + const score = (weeks) => buildHireabilityScore(summary, quality, + weeks.map((commits) => ({ commits }))); + const grouped = score([10, 10, 0, 0]); + const spread = score([10, 0, 10, 0]); + assert.ok(spread.hireabilityScore > grouped.hireabilityScore); + assert.equal(spread.scoreBreakdown[3].score, grouped.scoreBreakdown[3].score); +}); + +test('very large valid signals remain bounded and the total equals its breakdown', () => { + const result = buildHireabilityScore( + { totalRepositories: 10000, totalStars: 1000000, totalForks: 1000000 }, + { repositoriesAnalyzed: 1, repositoriesWithDescription: 1, + repositoriesWithLicense: 1, recentlyUpdatedRepositories: 1 }, + [{ commits: 1000000 }] + ); + assert.equal(result.hireabilityScore, 100); + assert.equal(result.hireabilityScore, + result.scoreBreakdown.reduce((sum, item) => sum + item.score, 0)); +}); diff --git a/docs/development-workflow.md b/docs/development-workflow.md new file mode 100644 index 0000000..5481595 --- /dev/null +++ b/docs/development-workflow.md @@ -0,0 +1,75 @@ +# Development workflow + +Start each task with expected behavior and acceptance criteria. Implement a +focused change on a branch, run the checks, and open a PR with validation evidence. +A human reviews the resulting diff before merging. Deployment follows the +repository's hosting configuration. + +## Local checks + +Use Node 22 and install locked dependencies: + +```sh +npm ci +npm --prefix backend ci +npm --prefix frontend ci +npm run check +``` + +The backend tests are offline and use mocked GitHub responses. They cover scoring +boundaries, inactive periods, cache expiry, shared concurrent requests, failed +request cleanup, repository pagination, upstream errors, partial event data, +and HTTP response contracts. The frontend check is a production build; it does +not substitute for browser testing of interactions or visual changes. + +Audit all three lockfiles before dependency changes are merged: + +The backend overrides Express 4's older `qs` range to a patched 6.x release. +Remove the override once Express itself requests a patched version; validate +query parsing and API tests when updating it. + +```sh +npm audit --audit-level=high +npm --prefix backend audit --audit-level=high +npm --prefix frontend audit --audit-level=high +``` + +With Gitleaks installed, scan history: + +```sh +gitleaks git . --redact --log-opts="--all" +``` + +To check uncommitted file contents, use `gitleaks dir . --redact` in a clean +checkout without real `.env` files or +dependency/build directories. Do not print secrets when investigating findings. +If a real secret was committed, rotate it; removing the file alone is insufficient. + +## GitHub setup after pushing + +The `CI` workflow runs for PRs and pushes to main/master. It runs offline tests, +the production build, audits all lockfiles (blocking high/critical advisories), +and uses the standalone Gitleaks CLI image to scan full Git history with redacted +output. CI has read-only repository permissions and receives no production secrets. +Dependabot proposes weekly npm and Actions updates for review. + +These files do not change GitHub repository settings. In a branch ruleset for +the default branch, require a PR, at least one approving human review, and the +`Tests and frontend build`, `Dependency audit`, and `Secret scan` status checks. +Enable dependency graph, secret scanning, and push protection where the repository +plan supports them. Review bypass permissions so the rules have practical effect. + +Check Vercel and Render settings separately: deployments must follow the intended +merge policy. Existing deployment configuration is not modified by this workflow. + +## Agent access and progress + +Configure access in the agent host: checkout-scoped writes, restricted network +access, mocked tests, and development-only credentials when a task truly needs them. +`AGENTS.md` documents behavior but cannot enforce a sandbox. Keep production +credentials outside the agent environment. + +For each task, record the PR, check results, elapsed time, retries, and corrections +needed during review. Use that evidence to decide whether parallel agents or a +different model actually improve accepted changes per unit of time/cost. Introduce +parallel work only on request, using independent tasks and separate worktrees. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a7142b8..0c92c9b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -116,12 +116,12 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.129.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", - "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@reduxjs/toolkit": { @@ -160,10 +160,26 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", - "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", "cpu": [ "arm64" ], @@ -177,9 +193,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -193,9 +209,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", - "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", "cpu": [ "x64" ], @@ -209,9 +225,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", - "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", "cpu": [ "x64" ], @@ -225,9 +241,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", - "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", "cpu": [ "arm" ], @@ -241,9 +257,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", - "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", "cpu": [ "arm64" ], @@ -260,9 +276,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", - "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", "cpu": [ "arm64" ], @@ -279,9 +295,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", - "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", "cpu": [ "ppc64" ], @@ -298,9 +314,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", - "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", "cpu": [ "s390x" ], @@ -317,9 +333,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", - "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", "cpu": [ "x64" ], @@ -336,9 +352,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", - "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", "cpu": [ "x64" ], @@ -355,9 +371,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", - "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", "cpu": [ "arm64" ], @@ -370,28 +386,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", - "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", - "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", "cpu": [ "arm64" ], @@ -405,9 +403,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", - "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", "cpu": [ "x64" ], @@ -421,9 +419,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", - "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, "node_modules/@standard-schema/spec": { @@ -1371,9 +1369,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "funding": [ { "type": "github", @@ -1395,9 +1393,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -1407,9 +1405,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", "funding": [ { "type": "opencollective", @@ -1426,9 +1424,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1537,13 +1535,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", - "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.129.0", - "@rolldown/pluginutils": "1.0.0" + "@oxc-project/types": "=0.153.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -1552,21 +1550,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0", - "@rolldown/binding-darwin-arm64": "1.0.0", - "@rolldown/binding-darwin-x64": "1.0.0", - "@rolldown/binding-freebsd-x64": "1.0.0", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", - "@rolldown/binding-linux-arm64-gnu": "1.0.0", - "@rolldown/binding-linux-arm64-musl": "1.0.0", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0", - "@rolldown/binding-linux-s390x-gnu": "1.0.0", - "@rolldown/binding-linux-x64-gnu": "1.0.0", - "@rolldown/binding-linux-x64-musl": "1.0.0", - "@rolldown/binding-openharmony-arm64": "1.0.0", - "@rolldown/binding-wasm32-wasi": "1.0.0", - "@rolldown/binding-win32-arm64-msvc": "1.0.0", - "@rolldown/binding-win32-x64-msvc": "1.0.0" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" } }, "node_modules/scheduler": { @@ -1576,9 +1574,9 @@ "license": "MIT" }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -1610,9 +1608,9 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -1663,16 +1661,16 @@ } }, "node_modules/vite": { - "version": "8.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", - "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.0", - "tinyglobby": "^0.2.16" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -1688,7 +1686,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -1738,6 +1736,267 @@ "optional": true } } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } } } } diff --git a/package-lock.json b/package-lock.json index d1d05cd..2d816bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,15 +66,15 @@ } }, "node_modules/concurrently": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", - "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.6.tgz", + "integrity": "sha512-qgICXXFp2/3nAebRLmxcINKEXM1mLK3ij/y31P02VTs/KIkddIeC05n73DxOXsXMB9LFSqNU1/na3lDlOhcbIw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "5.6.2", "rxjs": "7.8.2", - "shell-quote": "1.8.4", + "shell-quote": "1.12.0", "supports-color": "10.2.2", "tree-kill": "1.2.2", "yargs": "18.0.0" @@ -141,9 +141,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index c45eb41..a597fe9 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "version": "1.0.0", "description": "DevTrack monorepo", "scripts": { + "test": "npm --prefix backend test", + "build": "npm --prefix frontend run build", + "check": "npm test && npm run build", "start": "npm --prefix backend start", "dev": "npm --prefix backend run dev", "start:backend": "npm --prefix backend start", From 30370ecc482b6f8b0d088980b6ac36be410ad376 Mon Sep 17 00:00:00 2001 From: esnoko Date: Tue, 13 Oct 2026 14:46:40 +0200 Subject: [PATCH 2/3] Document solo maintainer pull request review policy --- docs/development-workflow.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/development-workflow.md b/docs/development-workflow.md index 5481595..27a8b22 100644 --- a/docs/development-workflow.md +++ b/docs/development-workflow.md @@ -53,9 +53,15 @@ and uses the standalone Gitleaks CLI image to scan full Git history with redacte output. CI has read-only repository permissions and receives no production secrets. Dependabot proposes weekly npm and Actions updates for review. -These files do not change GitHub repository settings. In a branch ruleset for -the default branch, require a PR, at least one approving human review, and the -`Tests and frontend build`, `Dependency audit`, and `Secret scan` status checks. +These files do not change GitHub repository settings. Protect the default branch +by requiring a PR and the `Tests and frontend build`, `Dependency audit`, and +`Secret scan` status checks. Require the branch to be current with the base branch +and resolve review conversations before merging. + +DevTrack currently has one collaborator. Set required approvals to zero so the +owner can review the diff and checks and merge their own PR. GitHub does not allow +authors to approve their own PRs. Once another reviewer is available, consider +requiring one approval and dismissing approvals when new changes are pushed. Enable dependency graph, secret scanning, and push protection where the repository plan supports them. Review bypass permissions so the rules have practical effect. From 1c68d310ba4467f8d2442566e7904f4901126804 Mon Sep 17 00:00:00 2001 From: esnoko Date: Tue, 13 Oct 2026 15:00:19 +0200 Subject: [PATCH 3/3] Require a free development workflow and document account billing locks --- AGENTS.md | 8 ++++++++ docs/development-workflow.md | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 8551704..9a50dd3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,6 +33,14 @@ On Windows PowerShell, use `npm.cmd` if execution policy blocks `npm.ps1`. 4. Report the resulting behavior, validation, and any remaining limitations. PRs should include evidence of checks and require human review before merging. +## Cost policy + +Keep this workflow free to operate. Do not introduce paid services, subscriptions, +larger GitHub runners, or billable usage without explicit user authorization. +The public repository uses standard GitHub-hosted runners, Node's built-in tests, +npm audit, and the standalone Gitleaks CLI. An account billing lock is separate +from these tools' pricing; do not remove security checks merely to hide that lock. + ## Access boundaries Use the coding tool's actual sandbox controls to limit writes to this checkout diff --git a/docs/development-workflow.md b/docs/development-workflow.md index 27a8b22..4cc255a 100644 --- a/docs/development-workflow.md +++ b/docs/development-workflow.md @@ -47,6 +47,24 @@ If a real secret was committed, rotate it; removing the file alone is insufficie ## GitHub setup after pushing +### Cost policy + +This workflow requires no paid subscription while DevTrack remains public. +Standard GitHub-hosted runners are free for public repositories. The tests, +npm audit, standalone Gitleaks CLI, and public-repository branch protections +do not require paid tools. Do not switch to larger runners or add subscriptions +without explicit approval. + +An account billing lock can still prevent GitHub from starting these free jobs. +Check existing subscriptions in Billing and licensing; the workflow does not +require upgrading or adding a payment method. GitHub documents downgrading to +Free as an option for locked accounts with public repositories. Existing charges +and separate subscriptions must be checked independently. + +References: [standard runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners), +[locked accounts](https://docs.github.com/en/billing/how-tos/troubleshooting/locked-account), +and [downgrading plans](https://docs.github.com/en/billing/how-tos/manage-plan-and-licenses/downgrade-plan). + The `CI` workflow runs for PRs and pushes to main/master. It runs offline tests, the production build, audits all lockfiles (blocking high/critical advisories), and uses the standalone Gitleaks CLI image to scan full Git history with redacted