Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -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.
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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"
61 changes: 61 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 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.

## 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
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.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 21 additions & 31 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "DevTrack backend API",
"main": "server.js",
"scripts": {
"test": "node --test",
"start": "node server.js",
"dev": "node --watch server.js"
},
Expand All @@ -13,5 +14,8 @@
"dependencies": {
"dotenv": "^17.4.2",
"express": "^4.19.2"
},
"overrides": {
"qs": "^6.16.0"
}
}
14 changes: 10 additions & 4 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require('dotenv').config();
if (require.main === module) {
require('dotenv').config();
}

const express = require('express');
const githubRoutes = require('./routes/githubRoutes');
Expand Down Expand Up @@ -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}`);
});
if (require.main === module) {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
}

module.exports = app;
60 changes: 60 additions & 0 deletions backend/test/api.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
Loading
Loading