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
73 changes: 73 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
quality:
name: Quality (typecheck, lint, test, coverage)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Test with coverage
run: npm run test:coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: ./coverage/lcov.info
fail_ci_if_error: false

secrets:
name: Secret scan (gitleaks)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

# The gitleaks *Action* requires a paid license for organization repos;
# the gitleaks *binary* is free. Install and run it directly — no license.
# The download is checksum-verified against the release's published
# checksums before it is extracted or installed.
- name: Install gitleaks
env:
GITLEAKS_VERSION: 8.30.1
run: |
base="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
tarball="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSfL "${base}/${tarball}" -o "${tarball}"
curl -sSfL "${base}/gitleaks_${GITLEAKS_VERSION}_checksums.txt" -o checksums.txt
grep " ${tarball}\$" checksums.txt | sha256sum -c -
tar -xzf "${tarball}" gitleaks
sudo install gitleaks /usr/local/bin/gitleaks
gitleaks version
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Scan for secrets (full history)
run: gitleaks git . --redact --no-banner --exit-code 1
40 changes: 40 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '30 4 * * 1'

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read

strategy:
fail-fast: false
matrix:
language: ['javascript-typescript']

steps:
- name: Checkout
uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: '/language:${{ matrix.language }}'
36 changes: 36 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Scorecard

on:
branch_protection_rule:
schedule:
- cron: '30 4 * * 1'
push:
branches: [main]

permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write

steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Run analysis
uses: ossf/scorecard-action@v2
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
node_modules/
dist/
coverage/
.next/
.env
.env.*
!.env.example
*.tsbuildinfo
.DS_Store
*.log
.vercel/
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ Open-source, serverless helpdesk — shared inbox, threaded email conversations,

TypeScript on Vercel (Fluid Compute, Workflows, Queues, Cron — no daemons, no polling) plus Supabase (Postgres, Auth, Storage, Realtime). Inbound mail arrives by push webhook, not IMAP polling. Threading authority lives on the outbound side: signed reply tokens in the Message-IDs the engine emits. See the charter for the reasoning.

## Development

Prerequisites: Node 20+.

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify the code-fence language.

Line 22 triggers markdownlint MD040. Mark this as a shell fence:

-```
+```sh
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 22, Update the code fence in README.md around line 22 to
specify the shell language by changing the opening fence to ```sh, resolving
markdownlint MD040.

Source: Linters/SAST tools

npm install
```

Scripts:

- `npm run typecheck` — TypeScript, strict, no emit.
- `npm run lint` / `npm run lint:fix` — Biome lint (and autofix).
- `npm run format` — Biome format.
- `npm test` / `npm run test:watch` — Vitest.
- `npm run test:coverage` — Vitest with v8 coverage (text + lcov).

## Contributing

Not accepting external contributions yet — see [CONTRIBUTING.md](CONTRIBUTING.md). Issues and discussion are welcome.
Expand Down
29 changes: 29 additions & 0 deletions STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Helpthread — Status

**Current state:** pre-code foundation is laid — charter, specs, provider interfaces, fixtures, and a full CI/quality skeleton are in place; the mail engine itself has not been started.

## Now

**Phase 1 — Core engine, dogfooded.** Laying the guarded foundation the engine lands into: every PR runs typecheck, lint, tests-with-coverage, a secret scan, CodeQL, and OpenSSF Scorecard before anything merges. No engine code has shipped yet.

## Done

- Founding charter ([CHARTER.md](CHARTER.md)) — mission, principles, licensing, architecture, roadmap.
- Behavioral specs: the conversation API contract (`specs/api/conversations-v1.md`), mail threading (`specs/mail/threading.md`).
- Platform provider interfaces (`src/providers/`) — queue, scheduler, blob storage, inbound email — the seam that keeps the engine Vercel-first, not Vercel-only.
- Black-box acceptance fixtures (`fixtures/mail/`).
- This CI/quality foundation: TypeScript (strict, NodeNext) + Biome (lint/format) + Vitest (tests + v8 coverage), CI workflow (quality + secret scan), CodeQL, and OpenSSF Scorecard.

## Next

Engine increments, in dependency order: parse inbound mail → thread conversations → store → send (with signed reply tokens in outbound Message-IDs). The six-operation conversation API and an agent inbox UI follow per the charter's API-first rule.

## Not yet / deferred

- Live Vercel + Supabase deployment — deferred until the first deployable milestone; provider adapters are stubbed behind interfaces, not built yet.
- Agent inbox UI.
- Marketplace (paid modules, license keys, module registry).

---

_Last updated: 2026-07-10_
43 changes: 43 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!node_modules", "!dist", "!coverage", "!fixtures/mail/observed"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": {
"preset": "recommended"
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
},
"json": {
"formatter": {
"enabled": true
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
Loading
Loading