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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Apache ECharts MCP (https://github.com/apache/echarts-mcp)
# Optional — only for `npm run mcp:echarts` (Apache ECharts MCP image publish).
# The Learn The FAR app does not read these variables to build or run.
SERVER_PORT=8081
BOS_AK=
BOS_SK=
Expand Down
56 changes: 42 additions & 14 deletions .git-hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
#!/usr/bin/env bash
# Pre-commit GATE for learn-the-far (gate-only — this hook does NOT deploy).
#
# Prefer the shared dotagents gate-lib when present (authoring machine).
# Otherwise run the same checks directly so forks and contributors can commit
# without installing private tooling.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; cd "$ROOT"
# shellcheck source=/dev/null
source "${DOTAGENTS_GATE_LIB:-$HOME/code/dotagents/gate/gate-lib.sh}" || {
echo "✗ dotagents gate-lib not found (expected ~/code/dotagents/gate/gate-lib.sh) — re-run install-local-agent-runtime.sh." >&2
exit 1
}
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

GATE_LIB="${DOTAGENTS_GATE_LIB:-${HOME:-}/code/dotagents/gate/gate-lib.sh}"

run_public_gate() {
echo "→ pre-commit: using built-in gate (dotagents gate-lib not found)"

gate_begin_commit "learn-the-far"
gate_require_node
gate_npm_ci --if-stale
# Match gate-lib: message-only amend / empty index should not run the suite.
if git diff --cached --quiet 2>/dev/null; then
echo "▶ no staged changes vs HEAD — skipping the gate (message-only amend / empty commit)"
exit 0
fi

run_step "dep grounding" gate_check_dep_grounding .git-hooks/pre-commit
run_step "actionlint" npm run check:actions
run_step "astro check (types)" npm run check:ts
run_step "build" npm run build
if [[ ! -d node_modules ]] \
|| [[ ! -f node_modules/.package-lock.json ]] \
|| [[ package-lock.json -nt node_modules/.package-lock.json ]]; then
echo "→ npm ci (node_modules missing or lockfile newer than install)"
npm ci
fi

npm run check:actions
npm run check:ts
npm run build
echo "✓ pre-commit gate passed — commit will land; push to a PR, Vercel deploys via GitHub integration on merge."
}

echo "✓ pre-commit gate passed — commit will land; push to a PR, Vercel deploys via GitHub integration on merge."
if [[ -n "$GATE_LIB" && -f "$GATE_LIB" ]]; then
# shellcheck source=/dev/null
source "$GATE_LIB"
gate_begin_commit "learn-the-far"
gate_require_node
gate_npm_ci --if-stale
run_step "dep grounding" gate_check_dep_grounding .git-hooks/pre-commit
run_step "actionlint" npm run check:actions
run_step "astro check (types)" npm run check:ts
run_step "build" npm run build
echo "✓ pre-commit gate passed — commit will land; push to a PR, Vercel deploys via GitHub integration on merge."
else
run_public_gate
fi
3 changes: 1 addition & 2 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ name: Auto Merge
# Only PRs labeled ship-auto-merge (added by /ship) get auto-merge — not every open PR.
# Create the label in GitHub (Settings → Labels) before first use.
# Job-level label gate: unlabeled PRs skip with ZERO billed runner minutes (label is in
# the webhook payload — no gh API round-trip). StockTextAlerts uses the same if: on a
# Blacksmith ARM runner; fleet default is ubuntu-latest.
# the webhook payload — no gh API round-trip).

on:
pull_request:
Expand Down
43 changes: 29 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,49 @@ Production URL: <https://learnthefar.com>

## Development

When starting the dev server, use background mode:
```bash
npm ci
npm run dev
```

When starting the Astro CLI in background mode:

```bash
astro dev --background
```

Manage the background server with `astro dev stop`, `astro dev status`, and `astro dev logs`.
Manage with `astro dev stop`, `astro dev status`, and `astro dev logs`. Default local URL is typically <http://localhost:4321>.

No `.env` is required to run or build the app. `.env.example` is only for the optional ECharts MCP helper.

## Worktrees

Fresh worktrees run `npm run worktree:init` through Cursor's setup command or the manual Git `post-checkout` hook. This is required because staged Markdown lint runs before the gate's lazy dependency check. The app has no required gitignored local configuration or state, so `.worktreeinclude` is intentionally absent.
Fresh worktrees run `npm run worktree:init` (`npm ci`) via Cursor’s setup command or the optional Git `post-checkout` hook. The app has no required gitignored local configuration, so `.worktreeinclude` is intentionally absent.

## Pre-commit gate

`.git-hooks/pre-commit` runs actionlint, `astro check`, and `astro build`. If the optional shared `dotagents` gate-lib is present on the machine, it is used; otherwise the same checks run directly. Never weaken or skip the gate with `--no-verify`.

## Deploy / fork

Static Vercel deploy only — see README **Deploy your own copy**. For a different domain, update `astro.config.mjs` `site`, `public/robots.txt`, and `vercel.json` host redirects. No production secrets or server env vars.

## Documentation

Full documentation: <https://docs.astro.build>
- Project README: [README.md](./README.md)
- Astro docs: <https://docs.astro.build>

Consult these guides before working on related tasks:
Useful Astro guides:

- [Adding pages, dynamic routes, or middleware](https://docs.astro.build/en/guides/routing/)
- [Working with Astro components](https://docs.astro.build/en/basics/astro-components/)
- [Using React, Vue, Svelte, or other framework components](https://docs.astro.build/en/guides/framework-components/)
- [Adding or managing content](https://docs.astro.build/en/guides/content-collections/)
- [Adding styles or using Tailwind](https://docs.astro.build/en/guides/styling/)
- [Supporting multiple languages](https://docs.astro.build/en/guides/internationalization/)
- [Routing](https://docs.astro.build/en/guides/routing/)
- [Astro components](https://docs.astro.build/en/basics/astro-components/)
- [Framework components](https://docs.astro.build/en/guides/framework-components/)
- [Content collections](https://docs.astro.build/en/guides/content-collections/)
- [Styling](https://docs.astro.build/en/guides/styling/)

## Local UI verification

No auth — public UI only. Follow `rules/frontend-verification.md` (fleet smoke: desktop + mobile screenshots, console clean).
No auth — public UI only. Smoke the changed routes on desktop and mobile widths; confirm the browser console is clean.

- **Dev server:** `astro dev --background` (manage with `astro dev status` / `astro dev logs` / `astro dev stop`). Default local URL is the Astro dev host printed on start (typically <http://localhost:4321>).
- **Auth:** none — public pages only. No `DEFAULT_USER` / `DEFAULT_PASSWORD`.
- **Dev server:** `astro dev --background` (or `npm run dev`).
- **Auth:** none — public pages only.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 John Solly

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
85 changes: 64 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,84 @@
# Learn The FAR

A local-first flashcard app for studying the Federal Acquisition Regulation (FAR). It is built as a pure static SPA with Astro, Svelte, shadcn-svelte, and TypeScript.
A local-first flashcard app for studying the Federal Acquisition Regulation (FAR). Built as a pure static site with Astro, Svelte 5, shadcn-svelte, Tailwind CSS, and TypeScript.

User progress is stored only in the browser with `localStorage`. There is no database, authentication, API, or AWS infrastructure.
Progress lives only in the browser (`localStorage`). There is no database, authentication, API, or backend infrastructure.

## Tech Stack
Live site: <https://learnthefar.com>

- Astro static build
- Svelte 5 for the interactive study experience
- shadcn-svelte UI components
- Tailwind CSS
- TypeScript
- Vercel hosting
## Quick start

## Commands
Requirements: Node.js 24.x (see `.nvmrc`).

```bash
git clone https://github.com/jsolly/learn-the-far.git
cd learn-the-far
npm ci
npm run dev
```

Open the URL Astro prints (typically <http://localhost:4321>).

| Command | Action |
| :-- | :-- |
| `npm install` | Install dependencies |
| `npm ci` | Install dependencies from the lockfile |
| `npm run dev` | Start the local dev server |
| `npm run mcp:echarts` | Start the project-local Apache ECharts MCP server |
| `npm run check` | Run Astro/Svelte type checks |
| `npm run build` | Build the static app to `dist/` |
| `npm run preview` | Preview the production build locally |
| `npm run check:actions` | Lint GitHub Actions workflows |

Optional (Cursor only): `npm run mcp:echarts` starts a project-local Apache ECharts MCP server. Copy `.env.example` to `.env` and fill Baidu BOS credentials if you use that server to publish chart images. The app itself does **not** need `.env` to build or run.

## Architecture

- **Static SPA** — `astro build` emits `dist/`; Vercel serves that directory (`vercel.json`).
- **Svelte islands** — interactive study/quiz UI under `src/components/app/`.
- **FAR content** — chapters and decks live under `src/lib/far/` (shelves, glossary, questions). Source links point at [acquisition.gov](https://www.acquisition.gov/browse/index/far).
- **Routes** — home (`/`) plus shareable learn routes (`/learn/[unit]/`, `/learn/[unit]/[chapter]/`).
- **State** — quiz progress and reading state in `localStorage` only (`src/lib/local-storage.ts`, `src/lib/reading-storage.ts`).

No env vars are required for the production app. Site URL for the sitemap is set in `astro.config.mjs` (`site`).

## Deploy your own copy (Vercel)

This project is designed to fork and redeploy with zero backend setup.

1. Fork (or clone) the repository on GitHub.
2. In [Vercel](https://vercel.com), **Add New Project** → import the fork.
3. Leave Framework Preset as Astro (or Other). Build settings are already in `vercel.json`:
- **Build Command:** `npm run build`
- **Output Directory:** `dist`
4. Deploy. No environment variables are required for the app.
5. (Optional) Attach a custom domain in the Vercel project settings.

### Pointing the site at your domain

If you are not deploying to `learnthefar.com`, update:

| File | What to change |
| :-- | :-- |
| `astro.config.mjs` | `site: 'https://your-domain.example'` |
| `public/robots.txt` | Sitemap URL host |
| `vercel.json` | `www` → apex redirect `has` / `destination` hosts (or remove that redirect) |

Production for this repo stays Git-connected: pushes to `main` deploy automatically after CI.

## CI and commits

The ECharts MCP connection is scoped to this repository in `.cursor/mcp.json`. Before
starting it, copy `.env.example` to `.env` and add Baidu BOS credentials; the upstream
server requires BOS to publish generated chart images.
GitHub Actions (`.github/workflows/ci.yml`) runs on every PR and on `main`: actionlint, typecheck, and build.

## Hosting
Local pre-commit (`.git-hooks/pre-commit`) runs the same checks. `npm run prepare` points `core.hooksPath` at `.git-hooks` when you are not in CI. Contributors do not need any private tooling — the hook falls back to a built-in gate when the author’s optional shared gate library is absent.

Production: <https://learnthefar.com> (Vercel, Git-connected to this repo).
## Contributing

Vercel builds the app with `npm run build` and serves the generated `dist/` directory. The deployment settings are captured in `vercel.json`. Pushes to `main` trigger a production deploy automatically.
1. Create a branch from `main`.
2. Make changes; keep the app static and local-first (no new secrets or server APIs without discussion).
3. Run `npm run check` and `npm run build` before opening a PR.
4. Open a pull request against `main`.

Because this is a static SPA, no server adapter or cloud database is required.
UI is public-only (no login). Prefer small, focused PRs.

## FAR Content
## License

The current MVP includes a bundled sample deck with source links back to [acquisition.gov](https://www.acquisition.gov/browse/index/far). A fuller FAR import can replace or extend `src/lib/far/sample-deck.ts` later.
MIT — see [LICENSE](./LICENSE).
15 changes: 3 additions & 12 deletions scripts/check-actions.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
#!/usr/bin/env bash
# Lint GitHub Actions workflows with actionlint + shellcheck.
#
# Shared by CI (npm run check:actions), pre-commit, and local gate runs.
# Both tools are lockfile-pinned npm deps (node_modules/.bin). CI and local
# gates share the same binaries — no mise/brew/PATH dependence, no sourcing
# ~/code/dotagents (gate-lib is for local hooks only; scripts CI runs must be
# repo-self-contained — see rules/dependency-grounding.md).
#
# The `shellcheck` npm package lazily downloads the official koalaman binary
# on first invoke; warm it here so actionlint's -shellcheck path is a real
# executable before the lint runs.
#
# Copy into each workflow repo as scripts/check-actions.sh, then:
# "check:actions": "bash scripts/check-actions.sh"
# in package.json, plus `run_step "actionlint" npm run check:actions` in
# .git-hooks/pre-commit and `- run: npm run check:actions` in ci.yml.
# gates share the same binaries — no mise/brew/PATH dependence. This script is
# self-contained so forks and CI never need private authoring tooling.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
Expand Down