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: 14 additions & 8 deletions .agents/skills/ci-and-devcontainer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ description: Use when changing Rust toolchain setup, Linux GUI dependencies, CI,

- **One complete devcontainer.** `.devcontainer/Dockerfile` installs Study's build,
rendering and PDF dependencies, the browser desktop, Rust tools, and the agent
tools (Node, pinned Claude Code, Herdr and reviewr, Codex, basedpyright and
GitHub CLI). `.devcontainer/devcontainer.json` is the only configuration.
tools (Node, Bun for the website, pinned Claude Code, Herdr and reviewr, Codex,
basedpyright and GitHub CLI). `.devcontainer/devcontainer.json` is the only configuration.
`just agents` must work there after a rebuild. Linux CI and Linux releases use it too.
Add tools to the Dockerfile, never in a recipe.
- **`post-create.py` configures mounted host credentials** at runtime:
Expand All @@ -24,22 +24,28 @@ description: Use when changing Rust toolchain setup, Linux GUI dependencies, CI,
release. `.github/actions/setup` installs the build subset of the Dockerfile's apt
packages, Rust from `rust-toolchain.toml` and pinned `just`, and restores the build cache
(every branch saves its own; a pull request starts from main's). Keep its packages and pins aligned with the Dockerfile.
- CI (`ci.yml`): lint (`check-shell fmt-check deny lint docs`) and `just test` run as
parallel jobs, with dependencies built unoptimized. `Test and build` is the one required
check; it passes when both jobs passed. The runner's own Docker runs the tests that need
- CI (`ci.yml`): lint (`check-shell fmt-check deny lint docs`), `just test` and the
website's type check (`landing-check`, with Bun alone) run as parallel jobs, with
dependencies built unoptimized. `Test and build` is the one required check; it passes
when every job passed. The runner's own Docker runs the tests that need
real services. Ubuntu 24.04's glibc 2.39 is enough for the prebuilt onnxruntime.
- Devcontainer (`devcontainer.yml`): when `.devcontainer/` changes, builds the image,
starts the container and runs its lifecycle commands, so a broken environment fails
before merge.
- Demo (`demo.yml`): by hand only. It runs `just demo` and opens a pull request with the
new `assets/demo.gif`.
- Landing (`landing.yml`): on every push to main, or by hand, runs `just landing-build`
and deploys `apps/landing/dist` to GitHub Pages under `/study/`. A fresh checkout has
no captures of the app, so the recipe first makes all of them (`just docs-media`,
`just demo`) on the showcase's private desktop with software Vulkan, as the setup
action's extra packages provide, then builds the site with Bun, pinned by
`packageManager` in `apps/landing/package.json` to the Dockerfile's `BUN_VERSION`. The
README's demo GIF is published with the site; nothing it makes is committed.
- Releases: Linux x64 and ARM64 build inside the devcontainer; macOS Intel/Apple Silicon
and Windows x64 use native GitHub runners. They install Rust and pinned cargo-packager;
they must never require a second devcontainer.
- Linux AppImages still need glibc 2.41 or newer.
- **One release pipeline.** `.github/workflows/release.yml` runs on a pushed `vX.Y.Z` tag
that matches the workspace version, then publishes only when all five signed packages exist.
`packaging/packager.json` and `packaging/release.sh` own packaging and the
`apps/desktop/packaging/packager.json` and `apps/desktop/packaging/release.sh` own packaging and the
static `latest.json` updater feed; keep the platform keys aligned with cargo-packager-updater.
Keep `cargo-packager` pinned identically in the Dockerfile and release workflow.
Never put signing keys in the image or repository. Releases require the public-key
Expand Down
5 changes: 3 additions & 2 deletions .agents/team/fleet.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# One project-wide team. Named sessions isolate unrelated work.
session = "study"
workspace = "study-team"
# Seven regular roles plus one shared slot for scenario preparation, upgrade review or
# PR delivery.
# Seven regular roles plus one shared slot for scenario preparation, upgrade review, PR
# delivery or keeping the website in sync.
max_agents = 8
# The Herdr plugin that opens the review panel beside the lead.
reviewr = "persiyanov.reviewr"
Expand Down Expand Up @@ -37,3 +37,4 @@ pushback = "build"
upgrade-reviewer = "build"
pr-maker = "build"
scenario = "build"
site = "build"
3 changes: 3 additions & 0 deletions .agents/team/roles/lead.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ slot before delivery, give it the diff and base, then stop it after its one resu
For missing test or demo data, give `scenario` the required state and acceptance criteria.
Use the shared optional slot as described in the team brief; pass its verified handoff to
QA/student and later PR maker. Do not carry stale fixture assumptions into a new request.

When a delivered change or release alters what the website says or shows, start `site`
in the shared optional slot as the team brief describes, then stop it after its result.
73 changes: 73 additions & 0 deletions .agents/team/roles/site.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Site keeper

## Required skills

Before the matching work, you MUST read:

- `.agents/skills/graphify/SKILL.md` before tracing what an app change touches.
- `.agents/skills/see-the-app/SKILL.md` before writing or changing a scene of the app.
- The installed `impeccable` plugin skill before changing the website's layout or styling,
with the project's `PRODUCT.md` and `DESIGN.md`.

You keep the public website in `apps/landing/` (Astro, static, published to GitHub Pages by
`.github/workflows/landing.yml`) true to the app as it is now. Stay idle until the lead
assigns a scope: a merged change, a release, a diff or a request to audit the whole site.
You write only files under `apps/landing/` and the docs' scenes in
`crates/study-showcase/src/docs.rs`; app code, README and copy changes belong to others.

## What to watch

Read the assigned diff, or `git log` and `git diff` since the last site update, for what
a visitor would notice:

- new features, removed features and changed behaviour in the app;
- renamed or reworded UI copy in `crates/study-localization` (the site uses the app's
words for buttons, pages and settings);
- `README.md` and `PRODUCT.md`, which state the pitch and positioning the site follows;
- releases: version, supported platforms and download links (`.github/workflows/release.yml`,
`apps/desktop/packaging/`).

Then update what they affect:

- the landing page: `apps/landing/src/pages/index.astro` and `apps/landing/src/pages/it/index.astro`, with
their strings in `apps/landing/src/i18n/`;
- the docs: `apps/landing/src/content/docs/{en,it}/`, one page per feature, the same file
name in both languages (`.mdx` when it shows captures, `.md` otherwise);
- the captures of the app: scenes in `crates/study-showcase/src/docs.rs`, made by
`just docs-media` into `apps/landing/src/assets/shots/` and `clips/` as
`{scene}-{en|it}-{light|dark}`. They are never committed; the Landing workflow makes them
again on every deploy, so change the scene, never the file.

## Rules

- **English and Italian stay equal.** Every page, section, string and screenshot exists in
both, saying the same thing. Write Italian as a native speaker would, not word for word.
- **Never invent claims.** No customers, testimonials, user counts, benchmarks, prices,
dates or features the code does not have. Every statement must be traceable to the
code, `README.md` or `PRODUCT.md`; when you cannot verify one, leave it out and tell the
lead. Describe what the app does today, not plans.
- Remove or rewrite what a change made false rather than adding next to it.
- Links work under the `/study` base path; use the helpers the site already has.

## Captures

Every picture of the app on the site is a scene, made from the showcase sample on a private
desktop: never a screenshot taken by hand, and never the development data. A page shows one
as `![What it shows](shot:name "Caption")` or `clip:name`; a test in `study-showcase`
fails when a page names a scene that does not exist, or a scene no page shows. To change a
capture, change its scene's steps; for a new one, add a scene. Then make only those with
`just docs-media <scene>` and look at every variant before using it: no hover highlights,
tooltips or half-rendered content. Tell the lead when a page needs state the showcase
sample does not have; seeding belongs to `scenario`.

## Hand off

Before reporting, check it and build it as the Landing workflow does:

```sh
just check-crate study-showcase && just landing-check && just landing-build
```

They must pass with no warnings you introduced. Report to the lead once: what app change
you followed, the files changed in both languages, scenes changed, claims you left
out because you could not verify them, and the build result. Do not commit or push.
13 changes: 12 additions & 1 deletion .agents/team/team.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ One team works across the project: lead, developer, PM, QA, student, reviewer an
The lead coordinates; the developer primarily implements; the other roles contribute
on assigned questions. Crates are architecture boundaries, not separate workspaces or
teams. The seven regular roles share one extra slot for scenario preparation, upgrade
review or PR delivery. Do not create nested agents, per-crate teams, or duplicate sessions for the same task.
review, PR delivery or the website. Do not create nested agents, per-crate teams, or
duplicate sessions for the same task.

Each role has a different lens. Availability does not require every role to inspect every
change: the lead assigns only useful work, and unassigned agents stay idle. Product and
Expand Down Expand Up @@ -132,3 +133,13 @@ information as unknown; never invent a model response and report it as observed
Keep task-specific sample content and assumptions out of permanent agent memory. Store
reproducible setup and limitations in the session handoff, and do not load old scenarios
merely because their files survived a reset. Durable fixtures belong in code and tests.

## Keeping the website in sync

The website in `apps/landing/` follows the app. When a delivered change or a release alters
what a visitor would see or read (features, behaviour, UI copy, the README pitch, platforms),
the lead starts `site` in the shared optional slot with
`just agents site --session "$session" --no-attach`, sends it the change and base through
`agent prompt site` using the report-file pattern above, and stops it after its one result.
It writes only under `apps/landing/` and the docs' scenes, keeps English and Italian equal
and invents no claims.
17 changes: 17 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ USER root

ARG GH_VERSION=2.102.0
ARG NODE_VERSION=22.23.3
ARG BUN_VERSION=1.4.2
ARG HERDR_VERSION=0.9.3
ARG CLAUDE_VERSION=2.1.287
ARG CODEX_VERSION=0.159.3
Expand All @@ -66,6 +67,22 @@ rm /tmp/gh.deb
gh pr create --help | grep -q -- --attach
SH

# Bun builds and serves the website in apps/landing.
RUN <<'SH'
set -eu
case "$(uname -m)" in
aarch64) arch=aarch64; checksum=54328bbc2d9c8e0c9f892c544d66c57a83b84139e34909e5ee81758f1ac8fda7 ;;
x86_64) arch=x64; checksum=36368faef7527875d5ffa52e53cd48021741f2a83eb6208a8dd64068d422a913 ;;
*) echo "Unsupported Bun architecture: $(uname -m)" >&2; exit 1 ;;
esac
curl -fsSL -o /tmp/bun.zip "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-linux-${arch}.zip"
printf '%s /tmp/bun.zip\n' "$checksum" | sha256sum --check
python3 -c "import zipfile; zipfile.ZipFile('/tmp/bun.zip').extract('bun-linux-${arch}/bun', '/tmp')"
install -m 755 "/tmp/bun-linux-${arch}/bun" /usr/local/bin/bun
rm -r /tmp/bun.zip "/tmp/bun-linux-${arch}"
bun --version
SH

# Node for the npm-installed agents.
RUN case "$(uname -m)" in aarch64) node_arch=arm64 ;; *) node_arch=x64 ;; esac \
&& curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${node_arch}.tar.xz" \
Expand Down
21 changes: 17 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
push:
branches: [main]
pull_request:
# The Demo workflow starts this on the pull request it opens, which starts nothing by itself.
workflow_dispatch:

permissions:
Expand All @@ -20,8 +19,8 @@ concurrency:
env:
CARGO_TERM_COLOR: always

# Straight on the runner, no devcontainer (see .github/actions/setup). Lint and tests run in
# parallel, each with its own cache.
# Straight on the runner, no devcontainer (see .github/actions/setup). Lint, tests and the
# website run in parallel, each with its own cache.
jobs:
lint:
name: Format, Clippy, docs and dependencies
Expand Down Expand Up @@ -50,10 +49,24 @@ jobs:
- name: Test
run: just test

site:
name: Website
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v7
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: apps/landing/package.json
# Types and content only: the captures it shows are made when it is published.
- name: Check
run: cd apps/landing && bun install --frozen-lockfile && bun run check

done:
# The one required check: its name is in the `main` ruleset, so keep it.
name: Test and build
needs: [lint, test]
needs: [lint, test, site]
if: always()
runs-on: ubuntu-24.04
steps:
Expand Down
51 changes: 0 additions & 51 deletions .github/workflows/demo.yml

This file was deleted.

64 changes: 64 additions & 0 deletions .github/workflows/landing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Landing

# Builds the website in apps/landing/ (Astro, static) and publishes it to GitHub Pages at
# https://nicksan222.github.io/study/, so astro.config sets `base: '/study'`.
#
# Every capture of the app on the site is made here, from the code being deployed: the docs'
# stills and clips (`just docs-media`) and the README's demo GIF (`just demo`), which the
# site publishes at /study/demo.gif. None of them is committed, so any change to the app
# runs this to keep them current.
on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

# One deployment at a time; a newer push waits instead of cancelling one in progress.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
name: Build
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v7
- name: Set up
uses: ./.github/actions/setup
with:
# The private headless desktop the showcase records on, with software Vulkan.
packages: >-
sway grim wtype wf-recorder ffmpeg
libvulkan1 mesa-vulkan-drivers libgl1-mesa-dri libegl-mesa0
# The app runs for real here, so it is built as in development.
optimize-dependencies: 'true'
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: apps/landing/package.json
# A fresh checkout has no captures, so this makes every one, then builds.
- name: Capture the app and build
run: just landing-build
- name: Upload
uses: actions/upload-pages-artifact@v5
with:
path: apps/landing/dist

deploy:
name: Deploy
needs: build
runs-on: ubuntu-24.04
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v5
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
- name: Test and package
run: |
cargo test --locked -p study-ai --lib updates
packaging/release.sh package ${{ matrix.platform }}
apps/desktop/packaging/release.sh package ${{ matrix.platform }}
- name: Upload package
uses: actions/upload-artifact@v7
with:
Expand All @@ -133,6 +133,6 @@ jobs:
path: dist
merge-multiple: true
- name: Build update feed
run: packaging/release.sh feed "$GITHUB_REF_NAME"
run: apps/desktop/packaging/release.sh feed "$GITHUB_REF_NAME"
- name: Publish release
run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --title "$GITHUB_REF_NAME" --generate-notes
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,18 @@ core.*
*.sqlite
*.sqlite-*
/dist/

# Design tool working files; its settings in .impeccable/config.json are kept.
**/.impeccable/*
!/.impeccable/config.json

# Website dependencies and build output.
/apps/landing/node_modules/
/apps/landing/dist/
/apps/landing/.astro/

# Captures of the app, made from sample data on every website build (`just docs-media`,
# `just demo`), never committed.
/apps/landing/src/assets/shots/
/apps/landing/src/assets/clips/
/apps/landing/public/demo.gif
Loading