Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
732cfdd
chore: add a SynAc verification skill and feature map
cursoragent Oct 12, 2026
00077f4
fix: start the verify web server with next exec
cursoragent Oct 12, 2026
2480c71
fix: make the SynAc verify drive match the live UI
cursoragent Oct 12, 2026
3c04202
fix: keep a SynAc verify run from leaking processes or a dirty tree
cursoragent Oct 15, 2026
ba037ff
fix: run the verify smoke workflow only on dispatch
cursoragent Oct 15, 2026
6ef85b2
fix: signal each recorded verify pid once
cursoragent Oct 15, 2026
2c77ad7
docs: note that verify cleanup signals a shared pid once
cursoragent Oct 15, 2026
8783c43
fix: stop verify restore from reverting hand-edited files
cursoragent Oct 15, 2026
f9ab70e
fix: report an unreachable ingest as inconclusive
cursoragent Oct 15, 2026
b0d0daa
fix: restore verify writes to the launch snapshot
cursoragent Oct 15, 2026
662253c
fix: classify ingest from the final thrown error
cursoragent Oct 15, 2026
545ff7e
fix: keep each verify backup in its own directory
cursoragent Oct 15, 2026
8669231
fix: bind verify snapshots to the checkout that wrote them
cursoragent Oct 15, 2026
f6c0504
fix: ignore a verify snapshot that has no checkout identity
cursoragent Oct 15, 2026
4b59b77
fix: claim verify restores only from the tool step that wrote them
cursoragent Oct 15, 2026
65c2ae1
fix: keep cleanup moving when the verify snapshot is not this checkout
cursoragent Oct 15, 2026
78d3bdd
fix: signal the verify SIGKILL fixture only after it ignores SIGTERM
cursoragent Oct 15, 2026
ccb9fb7
fix: isolate verify state by checkout and classify the last ingest error
cursoragent Oct 15, 2026
2db8aa9
fix: restore an unfinished verify launch and ignore unnamed warnings
cursoragent Oct 15, 2026
337ce80
fix: keep a fetch error dump and the ingest CLI line inconclusive
cursoragent Oct 15, 2026
fb5018c
fix: classify a captured Node 24 fetch dump without a live lookup
cursoragent Oct 15, 2026
543c804
fix: keep the tmux session test out of pnpm gate
cursoragent Oct 15, 2026
488d579
fix: run the tmux session test in required CI
cursoragent Oct 15, 2026
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
251 changes: 251 additions & 0 deletions .agents/skills/verify-synac/SKILL.md

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions .agents/skills/verify-synac/playwright.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { evidenceDir } from './scripts/runtime.mjs';

const skillDir = path.dirname(fileURLToPath(import.meta.url));

export default {
testDir: path.join(skillDir, 'scripts'),
outputDir: path.join(evidenceDir, 'test-results'),
fullyParallel: false,
workers: 1,
retries: 0,
timeout: 60_000,
expect: { timeout: 15_000 },
reporter: [
['line'],
['html', { outputFolder: path.join(evidenceDir, 'playwright-report'), open: 'never' }],
],
use: {
baseURL: process.env.E2E_BASE_URL ?? 'http://127.0.0.1:3000',
trace: 'on',
screenshot: 'on',
viewport: { width: 1280, height: 800 },
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
},
],
};
77 changes: 77 additions & 0 deletions .agents/skills/verify-synac/references/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# SynAc feature map

Behavior-level inventory of the public glossary and the maintainer CLI. Agents use this map to decide what to drive and what evidence counts. The order below is the regression sweep.

## Baseline preconditions

- Node 24 on `PATH`. Run `pnpm verify:launch` and clear a `NOT_READY` doctor before driving the UI.
- Doctor `READY` means `/api/healthz` is ok, the home page has the glossary search landmark, and `GET /api/v1/search?q=soc` has `meta.total` greater than 0.
- One stack only: web on `127.0.0.1:3000`, Convex on `127.0.0.1:3210`. Launch refuses a second owner of those ports.
- Prefer accessible names (`Search`, `Theme: Dark`, `Term letters`, `Copy link to this sense`). The header `Search` control is a button. The page search field is a textbox with the same accessible name.
- Seeded slugs: term `access-control`, term `domain`, acronym `black`, redirect `2fa`, tag `identity-access`, source `rfc4949`, query `soc`.
- `pnpm gate` is the full checkpoint in `SKILL.md`. Run it once on the final head. Use the named fast group while iterating.

## Proof and skip reporting

The relevant feature file is the coverage set. When a change crosses search, an entry, and the CLI, also read `journeys.md`.

- Exercise every reachable entry point the change can affect, including empty, error, and hostile inputs listed in Gotchas.
- Record the action and the final observable state, including files written and records read back.
- Mocks count only where the product already isolates an upstream fetch. The glossary reads the synced Convex dataset. Do not stub those reads.
- When a path is unreachable, name it and the concrete prerequisite. A live RFC ingest is `INCONCLUSIVE` only when the final thrown error and its cause are a network failure with no HTTP status. Any HTTP status or other ingest failure fails the drive. `POST /api/v1/internal/revalidate` is `INCONCLUSIVE` when `SYNAC_REVALIDATE_SECRET` is unset.

## Full sweep

Walk this list top to bottom. Finish with `journeys.md`.

## Home and search

- [home](home.md): Landing page, glossary search, recently updated.
- [search](search.md): Query, type filter, entries versus meanings, empty and ignored queries.
- [command-palette](command-palette.md): Header search button and keyboard palette.

## Browse

- [terms-browse](terms-browse.md): Term index, letters, filter, sort.
- [acronyms-browse](acronyms-browse.md): Acronym index.
- [tags](tags.md): Tag directory and a tag page.
- [sources](sources.md): Source index and a source page.
- [recent](recent.md): Recently updated index.

## Entry

- [entry](entry.md): Term and acronym pages, senses, copy link, citation record.
- [not-found](not-found.md): Unknown slug and the `2fa` redirect.

## Chrome

- [site-chrome](site-chrome.md): Header, footer, mobile menu.
- [theme](theme.md): System, dark, and light, kept in localStorage.
- [about](about.md): About page.
- [legal](legal.md): Privacy policy and terms of use.
- [changelog](changelog.md): Changelog page and RSS feed.

## HTTP API

- [public-api](public-api.md): Public read API under `/api/v1`.

## Maintainer CLI

- [content-check](content-check.md): `pnpm content:check`.
- [content-compile](content-compile.md): `pnpm content:compile` writes `compiled.json`.
- [content-history](content-history.md): `pnpm content:history`.
- [content-sync](content-sync.md): sync into the local Convex deployment.
- [ingest](ingest.md): regenerate one bundle from upstream.

## Journeys

- [journeys](journeys.md): Search to an entry, then a theme that survives navigation.

## Entry contract

Every feature file uses the same four H2s:

1. `Sub-features`
2. `How to get to it (user POV)`
3. `Driving it with Playwright` or `Driving it with the CLI`
4. `Gotchas`
22 changes: 22 additions & 0 deletions .agents/skills/verify-synac/references/features/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# About

A static explanation of how entries are structured and maintained.

## Sub-features

- page: `/about`, heading `About SynAc`

## How to get to it (user POV)

Header link `About`, footer link `About`, or the palette row `About`.

## Driving it with Playwright

`drive.spec.ts` test `about page`.

Open `/about` and expect the heading `About SynAc`. Screenshot `about.png`.

## Gotchas

- The page is prose. A proof is the heading plus a visible paragraph, not a screenshot of the hero alone.
- There is no form and no write. An empty dataset does not blank this page.
24 changes: 24 additions & 0 deletions .agents/skills/verify-synac/references/features/acronyms-browse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Acronyms browse

Alphabetical index of published acronyms. The controls match the term index.

## Sub-features

- letters: navigation `Acronym letters`
- filter, sort, tag chips, and paging: same parameters as terms, on `/acronyms`

## How to get to it (user POV)

Header link `Acronyms`, or `/acronyms`. The heading is `Acronyms`.

## Driving it with Playwright

`drive.spec.ts` test `acronyms browse opens BLACK`.

Open `/acronyms`, click `b` in `Acronym letters` (the letter text is lowercase), then open `/acronym/black`. The heading is `BLACK`. Screenshot `acronym-black.png`.

## Gotchas

- Letter `b` is sorted by title. `BLACK` is near the start of that letter. If a future corpus pushes it past page 1, follow pagination `Next` until the link is visible and say so.
- Empty letters use the sentence `No published acronyms yet for <letter>`.
- A hostile `letter` value falls back to A, the same way the term index does.
25 changes: 25 additions & 0 deletions .agents/skills/verify-synac/references/features/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Changelog

Versioned notes, plus an RSS feed.

## Sub-features

- page: `/changelog`, heading `Changelog`, one section per version
- rss: link `RSS` to `/changelog/rss.xml`
- empty: `No changelog entries yet.` when the list is empty

## How to get to it (user POV)

Footer link `Changelog`, or the palette row `Changelog`.

## Driving it with Playwright

`drive.spec.ts` test `changelog page and rss feed`.

Open `/changelog`, expect the heading, and expect the `RSS` link href `/changelog/rss.xml`. `GET /changelog/rss.xml` is 200 and the body contains `<rss`. Screenshot `changelog.png`.

## Gotchas

- RSS is XML. A 200 HTML shell is a failure even if the page looks fine.
- An empty changelog is a product state. Assert the empty sentence when the list length is 0.
- Version anchors are on the page. Linking to one is optional unless the change touches anchor ids.
35 changes: 35 additions & 0 deletions .agents/skills/verify-synac/references/features/command-palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Command palette

The header search button opens a dialog for navigation and entry lookup.

## Sub-features

- open: button `Search`, or `Control+K` / `Meta+K`, or `/` when focus is outside a text field
- empty: Go to links for terms, acronyms, tags, sources, recent, about, changelog
- query: at least two characters fetches `/api/v1/search` and lists up to eight entries, plus `Search for "..."`
- dismiss: Escape, or the same shortcut again

## How to get to it (user POV)

Click `Search` in the header, or press Ctrl+K (Command+K on macOS). Type, then Enter the highlighted row.

## Driving it with Playwright

`drive.spec.ts` test `command palette opens from the keyboard and navigates`.

Wait until the header button `Search` is visible so the listener is attached. Press `Control+KeyK` until the combobox `Search terms and acronyms` is visible. Fill `soc`. The first option is `Search for "soc"`. Enter lands on `/search` or an entry. Screenshot `palette.png`.

```bash
NODE_PATH="$PWD/apps/e2e/node_modules" \
node apps/e2e/node_modules/@playwright/test/cli.js test \
--config .agents/skills/verify-synac/playwright.config.mjs \
.agents/skills/verify-synac/scripts/drive.spec.ts \
-g "command palette"
```

## Gotchas

- `/` does nothing while a text field is focused. Click the page background first.
- Queries shorter than two characters show navigation matches only. They do not call the API.
- A 429 from the API shows `Search is busy, try again`. An HTTP failure shows `Search is unavailable`. Zero rows show `No results`.
- The shortcut listener attaches after hydration. Press until the combobox appears. A single early keypress can miss.
39 changes: 39 additions & 0 deletions .agents/skills/verify-synac/references/features/content-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Content check

Validates `content/` and prints the compiled counts. It does not write a dataset file.

## Sub-features

- success: `pnpm content:check` prints `content ok:` and a short content version
- schema failure: a content dir that cannot be parsed exits non-zero

## How to get to it (user POV)

A maintainer runs it from the repo root after editing `content/`. Readers never see this command.

## Driving it with the CLI

`pnpm verify:smoke` runs this as the first CLI step. The log is `/tmp/synac-verify-<hash>/evidence/cli-content-check.log`.

```bash
pnpm content:check
```

Stdout contains `content ok:` and `(version ` plus 12 hex characters. Exit 0.

The hostile check uses a scratch directory so the repo stays untouched:

```bash
dir=/tmp/synac-verify-<hash>/hostile-content
mkdir -p "$dir"
printf '{' > "$dir/tags.json"
SYNAC_CONTENT_DIR="$dir" pnpm content:check
```

Exit is non-zero and the output contains `content check failed` or `invalid JSON`. Log: `cli-content-hostile.log`.

## Gotchas

- `SYNAC_CONTENT_DIR` redirects the read. Forget it and the command checks the real `content/` tree.
- Warnings print as `⚠` and still exit 0 when there are no errors. A proof of failure needs a non-zero exit.
- This command does not write `tools/content/dist/compiled.json`. That file is the compile feature.
31 changes: 31 additions & 0 deletions .agents/skills/verify-synac/references/features/content-compile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Content compile

Compiles `content/` and writes the dataset to disk.

## Sub-features

- emit: `pnpm content:compile` (the `check` script with `--emit`)
- file: `tools/content/dist/compiled.json`

## How to get to it (user POV)

A maintainer runs it when they need the compiled JSON locally. The web app does not read this file. Sync reads the compiler in process.

## Driving it with the CLI

`pnpm verify:smoke` runs compile after content check.

```bash
pnpm content:compile
test -s tools/content/dist/compiled.json
```

Stdout contains `compiled dataset written to tools/content/dist/compiled.json`. The JSON has `contentVersion` starting with the 12 hex characters from the `content ok:` line, and `entries` is a non-empty array. Log: `cli-content-compile.log`.

`dist/` is gitignored. Leave the file in the working tree. Do not commit it.

## Gotchas

- A failed check exits before the write. If the file already existed from an older run, a failed compile does not prove the new run wrote it. Compare `contentVersion` to the log line from the same run.
- The path is fixed. `SYNAC_CONTENT_DIR` changes the input directory and still writes `tools/content/dist/compiled.json` under the repo.
- Compile does not push to Convex. Search staying empty after compile means sync has not run.
28 changes: 28 additions & 0 deletions .agents/skills/verify-synac/references/features/content-history.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Content history

Checks that `content/tag-assignments.json` history still chains from a git base ref.

## Sub-features

- unchanged: prints `tag assignment history ok:` when the file matches the base
- changed: validates `previousAssignmentsHash` when the file differs from the base

## How to get to it (user POV)

A maintainer runs `pnpm content:history` before a content PR. CI runs it inside `pnpm gate`.

## Driving it with the CLI

`pnpm verify:drive` runs it. Smoke skips it.

```bash
pnpm content:history
```

Stdout contains `tag assignment history ok:` and the path `content/tag-assignments.json`. Log: `cli-content-history.log`. A non-zero exit fails the drive.

## Gotchas

- The default base is the merge base of `HEAD` and `origin/main`. When that ref is missing, the command falls back to `HEAD`, the diff is empty, and it still prints the ok line. That pass does not prove history against `origin/main`.
- `--base <ref>` overrides the base. A missing value is an error: `--base requires a Git ref`.
- This command reads git. It does not write assignments.
32 changes: 32 additions & 0 deletions .agents/skills/verify-synac/references/features/content-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Content sync

Pushes the compiled corpus into the local Convex deployment and checks tag counts.

## Sub-features

- push: `pnpm --filter @synac/content-tools sync`
- status: stdout `sync status:` and `tag convergence: verified`
- read-back: the site search returns seeded rows after sync

## How to get to it (user POV)

A maintainer runs sync after `npx convex dev` and `npx convex env set SYNAC_CONVEX_SERVICE_KEY local-dev`. Readers then see the entries on the site.

## Driving it with the CLI

`pnpm verify:launch` runs sync once and copies stdout to `/tmp/synac-verify-<hash>/evidence/cli-sync.log`. `<hash>` is the first 12 hex characters of sha256 of the repo root.

```bash
pnpm --filter @synac/content-tools sync
```

The log contains `tag convergence: verified` and `to the local dev deployment`. Then doctor `READY` is the read-back: `GET /api/v1/search?q=soc` has `meta.total` greater than 0, and `/term/access-control` renders.

`--prod` targets a production deployment and needs `CONVEX_DEPLOY_KEY`. The verify skill does not pass `--prod`. A production sync is `INCONCLUSIVE` in this environment.

## Gotchas

- Sync against a Convex process this skill did not start is refused by launch. Do not point sync at a shared deployment to "save time".
- Re-running sync is idempotent. A second `tag convergence: verified` still counts. The proof is the line plus a search that returns data.
- If the service key is unset, sync's Convex calls fail. Launch sets `SYNAC_CONVEX_SERVICE_KEY=local-dev` before sync.
- The command writes records. It does not rewrite `content/generated/`.
30 changes: 30 additions & 0 deletions .agents/skills/verify-synac/references/features/entry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Entry

A published term or acronym: title, senses, copy link, and citation.

## Sub-features

- title: heading level 1 is the entry title (`access control`, `BLACK`)
- senses: anchors `#sense-...`, table-of-contents links
- copy: button `Copy link to this sense`, status `Link copied`
- cite: summary `Cite`, then link `Citation record (JSON)`
- related: region `Related entries` when the entry has relationships

## How to get to it (user POV)

Open a search result, a browse row, or a direct URL such as `/term/access-control` or `/acronym/black`.

## Driving it with Playwright

`drive.spec.ts` test `entry page copy link and citation record`.

Open `/term/access-control`. The heading is `access control`. Click the first visible `#sense-` link (the mobile chip row is hidden at desktop width) and expect that fragment in the URL. Click `Copy link to this sense` and expect the status `Link copied`. Read the clipboard. It contains `/term/access-control#`. Open `Cite`, follow `Citation record (JSON)`, and expect HTTP 200. The body `slug` is `access-control` and `primaryCitation.sourceSlug` is set. The lead sense on this entry is whichever source the compiler ranked first (NICCS in the current corpus), so do not require `rfc4949` on that first record. Screenshot `entry-access-control.png`.

The acronym path is the end of the acronyms browse test: heading `BLACK`.

## Gotchas

- Clipboard write needs a browser permission and a click. A headless context without `clipboard-read` and `clipboard-write` shows `Could not copy the link`. That failure is the copy path, not a missing button.
- A sense with no definition text says `No definition yet.`
- Citation JSON is a real route, `/api/v1/senses/citation.json`. Fetch the href the page rendered. Do not invent the sense key.
- Multi-sense terms such as `domain` show a numbered list. `access-control` may show a single sense. The copy button is still there.
Loading
Loading