Compare commits
15 Commits
0a12e9cbcc
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 61de2f6270 | |||
| ef5e8e8b42 | |||
| 25ef5af63e | |||
| be271f65c1 | |||
| 2ecfdf1d67 | |||
| 6b4f2e6bd0 | |||
| dc6cb5a0a3 | |||
| 9015e7bd1d | |||
| d88d8b89eb | |||
| b119b92948 | |||
| 580293867d | |||
| 9cb1e0d242 | |||
| 12c32d2fd7 | |||
| 5c76a13b4d | |||
| 91e8d380d0 |
@@ -1,45 +1,47 @@
|
|||||||
# Context: architecture, current and target
|
# Context: architecture
|
||||||
|
|
||||||
## Current (no build step)
|
## Current (Astro, static output)
|
||||||
|
|
||||||
Ten hand-written HTML pages, each linking its own CSS and one ES module:
|
Ten routes, one `src/pages/` entry each, built to `dist/`:
|
||||||
|
|
||||||
| Route | Page | Script | Stylesheets |
|
| Route | Page | Islands |
|
||||||
| -------------------- | -------------------------- | ---------------------- | --------------------------------------------- |
|
| -------------------- | ------------------------------- | ----------------------------------------------- |
|
||||||
| `/` | `index.html` | — | `chapters.css`, `landing.css` |
|
| `/` | `src/pages/index.astro` | — |
|
||||||
| `/full-guide/` | `full-guide/index.html` | `app.js` (50 KB) | `styles.css`, `responsive.css`, `audit.css` |
|
| `/full-guide/` | `src/pages/full-guide.astro` | `GuideSelector`, `LanguageToggle`, `CopyPrompt` |
|
||||||
| `/summary/` | `summary/index.html` | — | `chapters.css` |
|
| `/summary/` | `src/pages/summary.astro` | — |
|
||||||
| `/models/` | `models/index.html` | — | `chapters.css` |
|
| `/models/` | `src/pages/models.astro` | — |
|
||||||
| `/agents/` | `agents/index.html` | — | `chapters.css` |
|
| `/agents/` | `src/pages/agents.astro` | — |
|
||||||
| `/skills/` | `skills/index.html` | `skills/app.js` | `skills/styles.css` |
|
| `/skills/` | `src/pages/skills.astro` | `SkillPackageExplorer` |
|
||||||
| `/rules/` | `rules/index.html` | `rules/app.js` | `rules/styles.css` |
|
| `/rules/` | `src/pages/rules.astro` | `RulesInteractive` |
|
||||||
| `/skills-review/` | `skills-review/index.html` | `skills-review/app.js` | `skills-review/styles.css`, `change-lens.css` |
|
| `/skills-review/` | `src/pages/skills-review.astro` | `legacy/skills-review/app.js` |
|
||||||
| `/hands-on/starter/` | lab fixture | own | own |
|
| `/hands-on/starter/` | `public/` lab fixture | own |
|
||||||
| `/hands-on/rules/` | lab fixture | own | own |
|
| `/hands-on/rules/` | `public/` lab fixture | own |
|
||||||
|
|
||||||
Weight is concentrated: `app.js` 50 KB, `responsive.css` 30 KB,
|
## What is still unmigrated
|
||||||
`skills-review/catalog.js` 27 KB, `skills-review/submitted-catalog.js` 18 KB.
|
|
||||||
|
|
||||||
### What each big file actually is
|
`legacy/` holds the parts the migration did not componentize. They are not dead
|
||||||
|
files — the pages listed above import them, and the build fails without them.
|
||||||
|
|
||||||
- **`app.js`** — not really application code. It is a **bilingual content
|
- **`legacy/styles/guide.css`** (was `styles.css`) — the editorial visual
|
||||||
database** (`phases`, `handsOnPrompts`, `modelGuide`, `skillSources`,
|
system, imported by `full-guide.astro`.
|
||||||
`skillInstallPrompts`, each keyed `{en, pt}`) plus ~12 small `render*`
|
- **`legacy/styles/audit.css`** (was `full-guide/audit.css`) — responsive audit
|
||||||
functions that swap `innerHTML` on tab clicks. ~50 `en:` keys. The content
|
overrides, imported by `full-guide.astro`.
|
||||||
should become data; only the tab behaviour is interactive.
|
- **`legacy/styles/chapters.css`** — imported by `ChapterLayout.astro`.
|
||||||
- **`responsive.css`** — a 30 KB append-only layer of overrides bolted on top of
|
- **`legacy/styles/skills.css`**, **`skills-review.css`**, **`change-lens.css`**
|
||||||
`styles.css`. Expect large parts to be dead once layout moves into components.
|
— imported by their respective pages.
|
||||||
Do not port it verbatim.
|
- **`legacy/skills-review/`** — `app.js` and the module graph under it
|
||||||
- **`skills-review/catalog.js`** — the real data model of the review desk: one
|
(`catalog.js`, `submitted-catalog.js`, `files.js`, `submitted-files.js`,
|
||||||
entry per submitted skill with `id`, `author`, `title`, `status`, `focus`,
|
`vote.js`). `catalog.js` + `submitted-catalog.js` are the review desk's real
|
||||||
`wins[]`, `improve[]`, `extras`, `improved` (full markdown). 24 entries across
|
data model, 24 entries; they are a content collection in all but name.
|
||||||
`catalog.js` + `submitted-catalog.js`. This is already a content collection in
|
|
||||||
all but name.
|
|
||||||
- **`skills-review/files.js` / `submitted-files.js`** — generated file
|
|
||||||
manifests.
|
|
||||||
- **`vote.js`** — the vote widget island; talks to `vote-service/`.
|
|
||||||
|
|
||||||
## Target (Astro)
|
These sit outside `src/` deliberately: `check-tokens.mjs` sweeps `src`, and
|
||||||
|
these files are full of raw hex and unnamed breakpoints. Moving one into `src/`
|
||||||
|
means migrating it to tokens in the same change, not adding an exclusion.
|
||||||
|
|
||||||
|
`responsive.css`, `landing.css`, `app.js`, `rules/app.js`, `rules/styles.css`,
|
||||||
|
and `skills/app.js` were deleted at cutover: their content lives in components.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
@@ -52,15 +54,15 @@ public/
|
|||||||
hands-on/ lab fixtures copied verbatim, never processed
|
hands-on/ lab fixtures copied verbatim, never processed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Non-negotiables for the target
|
### Non-negotiables
|
||||||
|
|
||||||
- **URLs do not change.** `/full-guide/`, `/skills-review/`,
|
- **URLs do not change.** `/full-guide/`, `/skills-review/`,
|
||||||
`/hands-on/starter/` and the rest must resolve exactly as they do now,
|
`/hands-on/starter/` and the rest must resolve exactly as they do now,
|
||||||
trailing slash included. Existing links (including `docs/`, SilverBullet, and
|
trailing slash included. Existing links (including `docs/`, SilverBullet, and
|
||||||
shared URLs with `?author=…&skill=…&view=…` query params) must keep working.
|
shared URLs with `?author=…&skill=…&view=…` query params) must keep working.
|
||||||
- **Zero JS by default.** Seven of the ten pages ship no JavaScript today. They
|
- **Zero JS by default.** Seven of the ten pages ship no JavaScript. They must
|
||||||
must still ship none. Islands are opt-in, per component, and justified.
|
still ship none. Islands are opt-in, per component, and justified.
|
||||||
- **`hands-on/` stays vanilla.** It goes in `public/` untouched. It is a lab
|
- **`hands-on/` stays vanilla.** It lives in `public/` untouched. It is a lab
|
||||||
fixture, not a component.
|
fixture, not a component.
|
||||||
- **No external runtime requests.** `audit-ui.mjs` enforces this and it is part
|
- **No external runtime requests.** `audit-ui.mjs` enforces this and it is part
|
||||||
of the site's thesis. Self-host anything you add.
|
of the site's thesis. Self-host anything you add.
|
||||||
@@ -70,7 +72,12 @@ public/
|
|||||||
|
|
||||||
## Companion service
|
## Companion service
|
||||||
|
|
||||||
`vote-service/` is a Go API on its own Kubernetes deploy cycle, reached by the
|
The vote API is a Go service on its own Kubernetes deploy cycle, reached by the
|
||||||
review desk over `window.SKILLS_REVIEW_VOTE_API`. The refactor does not touch
|
review desk over `window.SKILLS_REVIEW_VOTE_API`. Its source left this
|
||||||
it. Keep the global, or replace it with a build-time `PUBLIC_VOTE_API` env var —
|
repository on 2026-09-06; the deployed service is unchanged, and the review desk
|
||||||
but if you do, update `vote-service/README.md` in the same change.
|
still calls it. Keep the global, or replace it with a build-time
|
||||||
|
`PUBLIC_VOTE_API` env var — but if you do, update the service's own README in
|
||||||
|
the same change.
|
||||||
|
|
||||||
|
Its one-vote-per-IP assertion left `verify.mjs` with it. See
|
||||||
|
[`assertion-removals.md`](assertion-removals.md).
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Assertion removal ledger
|
||||||
|
|
||||||
|
`scripts/verify.mjs` may only lose an assertion by adding an entry here. The
|
||||||
|
gate counts the `## ` headings in this file and allows exactly that many
|
||||||
|
removals below the recorded floor — so a reduction is impossible without a
|
||||||
|
written reason landing in the same commit, as a visible diff.
|
||||||
|
|
||||||
|
Adding an entry is not a formality. An assertion pins a real contract; removing
|
||||||
|
one means that contract is now unverified. Say where it moved, or say plainly
|
||||||
|
that nothing checks it any more.
|
||||||
|
|
||||||
|
## vote-service one-vote-per-IP contract
|
||||||
|
|
||||||
|
**Removed:** 2026-09-06, when `vote-service/` was taken out of this repository.
|
||||||
|
|
||||||
|
**What it asserted:** that `vote-service/main.go` contained both
|
||||||
|
`X-Forwarded-For` and `one active vote per skill` — the review desk's only
|
||||||
|
anti-abuse control, one vote per visitor enforced server-side by source IP.
|
||||||
|
|
||||||
|
**Why it went:** there is no file left to read. The check was a substring match
|
||||||
|
against source that now lives elsewhere.
|
||||||
|
|
||||||
|
**Where it must be re-asserted:** in whichever repository holds the service. The
|
||||||
|
deployed service still enforces the contract; nothing in this repository proves
|
||||||
|
it. If `vote-service/` ever comes back here, restore the assertion and delete
|
||||||
|
this entry.
|
||||||
@@ -19,7 +19,7 @@ Only these need interactivity. Anything else claiming island status is wrong:
|
|||||||
| --------------------------------- | ---------------------------------- | ---------------- |
|
| --------------------------------- | ---------------------------------- | ---------------- |
|
||||||
| Guide phase/tab switchers | click-driven panel swap | `client:visible` |
|
| Guide phase/tab switchers | click-driven panel swap | `client:visible` |
|
||||||
| Review desk catalog + file viewer | search, filter, fetch source files | `client:load` |
|
| Review desk catalog + file viewer | search, filter, fetch source files | `client:load` |
|
||||||
| Vote widget | talks to `vote-service/` | `client:visible` |
|
| Vote widget | talks to the vote API | `client:visible` |
|
||||||
| Language toggle | swaps EN/PT across the page | `client:idle` |
|
| Language toggle | swaps EN/PT across the page | `client:idle` |
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ const PROPERTIES = [
|
|||||||
'overflow',
|
'overflow',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// The legacy pages were deleted at cutover; run this from a pre-cutover
|
||||||
|
// worktree, or the legacy side will 404.
|
||||||
const legacyPath = route === 'index' ? 'index.html' : `${route}/index.html`;
|
const legacyPath = route === 'index' ? 'index.html' : `${route}/index.html`;
|
||||||
const astroPath = route === 'index' ? '' : `${route}/`;
|
const astroPath = route === 'index' ? '' : `${route}/`;
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,20 @@ step "types"
|
|||||||
pnpm exec astro check
|
pnpm exec astro check
|
||||||
|
|
||||||
step "build"
|
step "build"
|
||||||
pnpm run build
|
# `astro build` exits 0 even when vite fails to resolve an asset: the cutover
|
||||||
|
# left a stale `@import` in a moved stylesheet and every gate stayed green for
|
||||||
|
# it. Treat a logged error as a failed build.
|
||||||
|
build_log=$(mktemp)
|
||||||
|
if ! pnpm run build 2>&1 | tee "$build_log"; then
|
||||||
|
rm -f "$build_log"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if grep -q '\[ERROR\]' "$build_log"; then
|
||||||
|
echo "gate: astro build logged an error and still exited 0. See above." >&2
|
||||||
|
rm -f "$build_log"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f "$build_log"
|
||||||
|
|
||||||
step "content contracts"
|
step "content contracts"
|
||||||
pnpm run verify
|
pnpm run verify
|
||||||
@@ -51,11 +64,19 @@ pnpm run verify
|
|||||||
# The assertion count is the thing agents are most tempted to "fix" downward.
|
# The assertion count is the thing agents are most tempted to "fix" downward.
|
||||||
# Compare against origin/main and refuse a silent reduction.
|
# Compare against origin/main and refuse a silent reduction.
|
||||||
step "assertion coverage"
|
step "assertion coverage"
|
||||||
|
# Task 19 restored the 42 legacy facts and added ten output snapshots: 84 is the
|
||||||
|
# floor, in addition to whatever origin/main currently requires.
|
||||||
|
#
|
||||||
|
# A removal is allowed only by writing a reason into the ledger. The gate counts
|
||||||
|
# its entries and lowers the bar by exactly that many, so the bar cannot move
|
||||||
|
# without a visible diff explaining why. Deleting an entry to buy headroom is
|
||||||
|
# the same offence as deleting the assertion was.
|
||||||
|
ledger=.agents/context/assertion-removals.md
|
||||||
current=$(grep -c 'throw new Error' scripts/verify.mjs)
|
current=$(grep -c 'throw new Error' scripts/verify.mjs)
|
||||||
# Task 19 restored the 42 legacy facts and added ten output snapshots: 84 is
|
allowed=$(grep -c '^## ' "$ledger" 2>/dev/null || echo 0)
|
||||||
# now the floor, in addition to whatever origin/main currently requires.
|
|
||||||
baseline=$(git show origin/main:scripts/verify.mjs 2>/dev/null | grep -c 'throw new Error' || echo 0)
|
baseline=$(git show origin/main:scripts/verify.mjs 2>/dev/null | grep -c 'throw new Error' || echo 0)
|
||||||
if [ "$baseline" -lt 84 ]; then baseline=84; fi
|
if [ "$baseline" -lt 84 ]; then baseline=84; fi
|
||||||
|
baseline=$((baseline - allowed))
|
||||||
if [ "$current" -lt "$baseline" ]; then
|
if [ "$current" -lt "$baseline" ]; then
|
||||||
echo "gate: verify.mjs coverage fell from $baseline to $current assertions." >&2
|
echo "gate: verify.mjs coverage fell from $baseline to $current assertions." >&2
|
||||||
echo " Only verification-engineer may reduce it, with a reason per removal." >&2
|
echo " Only verification-engineer may reduce it, with a reason per removal." >&2
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the site and publish it to the `pages` branch.
|
||||||
|
#
|
||||||
|
# .agents/scripts/publish-pages.sh # publish
|
||||||
|
# .agents/scripts/publish-pages.sh --dry-run # build and report, push nothing
|
||||||
|
# .agents/scripts/publish-pages.sh --pending X # main is *about* to become X
|
||||||
|
#
|
||||||
|
# `--pending` exists for the pre-push hook. Git has no post-push hook, so the
|
||||||
|
# hook necessarily runs before main lands on the remote and the usual "HEAD must
|
||||||
|
# equal origin/main" check cannot hold yet. The caller asserts the SHA the push
|
||||||
|
# will create, and the hook only asserts it after confirming the push is a
|
||||||
|
# fast-forward.
|
||||||
|
#
|
||||||
|
# `pages` is what the Gitea Pages Server actually serves. Publishing overwrites
|
||||||
|
# the live site. There is no staging environment between here and visitors.
|
||||||
|
#
|
||||||
|
# This never checks `pages` out. It writes a tree straight from `dist/` with
|
||||||
|
# plumbing (`write-tree` + `commit-tree`), so your working tree is untouched and
|
||||||
|
# a failure halfway through leaves nothing behind. The commit is parented on the
|
||||||
|
# current `pages`, so the branch keeps its history and rollback is one push.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
dry_run=0
|
||||||
|
pending=''
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--dry-run) dry_run=1 ;;
|
||||||
|
--pending)
|
||||||
|
shift
|
||||||
|
pending="${1:-}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "publish-pages: unknown argument '$1'" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "publish-pages: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Publishing a build made from uncommitted work means the live site shows
|
||||||
|
# something no commit describes, and nobody can reproduce it later.
|
||||||
|
[ -z "$(git status --porcelain)" ] || fail 'working tree is dirty; commit or stash first'
|
||||||
|
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
[ "$branch" = 'main' ] || fail "publishing from '$branch'; only main is publishable"
|
||||||
|
|
||||||
|
git fetch --quiet origin pages
|
||||||
|
head=$(git rev-parse HEAD)
|
||||||
|
if [ -n "$pending" ]; then
|
||||||
|
[ "$head" = "$(git rev-parse "$pending")" ] ||
|
||||||
|
fail "HEAD is $head but the pending push is $pending"
|
||||||
|
else
|
||||||
|
git fetch --quiet origin main
|
||||||
|
[ "$head" = "$(git rev-parse origin/main)" ] ||
|
||||||
|
fail 'HEAD is not origin/main; push main first so the site matches a pushed commit'
|
||||||
|
fi
|
||||||
|
|
||||||
|
previous=$(git rev-parse origin/pages)
|
||||||
|
|
||||||
|
echo "publish-pages: building $head"
|
||||||
|
# `astro build` exits 0 even when vite fails to resolve an asset, so the exit
|
||||||
|
# code alone is not enough to know the build is whole. The gate greps for this
|
||||||
|
# too; repeat it here because this script is also run by hand.
|
||||||
|
build_log=$(mktemp)
|
||||||
|
trap 'rm -f "$build_log"' EXIT
|
||||||
|
pnpm run build >"$build_log" 2>&1 || {
|
||||||
|
cat "$build_log" >&2
|
||||||
|
fail 'astro build failed'
|
||||||
|
}
|
||||||
|
if grep -q '\[ERROR\]' "$build_log"; then
|
||||||
|
cat "$build_log" >&2
|
||||||
|
fail 'astro build logged an error and still exited 0; refusing to publish'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A build can succeed and still emit a stub -- that is exactly how this site
|
||||||
|
# would go down. Check the routes exist before overwriting anything live.
|
||||||
|
for route in index full-guide/index summary/index models/index agents/index \
|
||||||
|
skills/index rules/index skills-review/index \
|
||||||
|
hands-on/starter/index hands-on/rules/index; do
|
||||||
|
[ -s "dist/$route.html" ] || fail "dist/$route.html missing or empty; refusing to publish"
|
||||||
|
done
|
||||||
|
|
||||||
|
# GIT_INDEX_FILE must name a path that does not exist yet: git reads an existing
|
||||||
|
# empty file as a truncated index and dies with "index file smaller than
|
||||||
|
# expected". mktemp -d gives a private directory to put that path in.
|
||||||
|
index_dir=$(mktemp -d)
|
||||||
|
index="$index_dir/index"
|
||||||
|
trap 'rm -rf "$index_dir"; rm -f "$build_log"' EXIT
|
||||||
|
# `--force` because the repository .gitignore lists `dist`; here `dist` *is* the
|
||||||
|
# work tree, so those rules would otherwise exclude everything we mean to ship.
|
||||||
|
GIT_INDEX_FILE="$index" git --work-tree=dist add --all --force .
|
||||||
|
tree=$(GIT_INDEX_FILE="$index" git write-tree)
|
||||||
|
|
||||||
|
if [ "$tree" = "$(git rev-parse "$previous^{tree}")" ]; then
|
||||||
|
echo "publish-pages: dist is identical to the published tree; nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
subject="chore: publish $(git rev-parse --short "$head")"
|
||||||
|
commit=$(git commit-tree "$tree" -p "$previous" -m "$subject
|
||||||
|
|
||||||
|
Built from main $head
|
||||||
|
$(git log -1 --format=%s "$head")")
|
||||||
|
|
||||||
|
if [ "$dry_run" -eq 1 ]; then
|
||||||
|
echo "publish-pages: would push $commit to pages (previous $previous)"
|
||||||
|
echo "publish-pages: dry run, nothing pushed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "publish-pages: rollback point is $previous"
|
||||||
|
echo " git push --force origin $previous:refs/heads/pages"
|
||||||
|
|
||||||
|
# AF_PUBLISHING stops the pre-push hook recursing into this script.
|
||||||
|
AF_PUBLISHING=1 git push --force origin "$commit:refs/heads/pages"
|
||||||
|
echo "publish-pages: published $commit"
|
||||||
@@ -17,6 +17,10 @@
|
|||||||
// bilingual pair, so the two sides line up.
|
// bilingual pair, so the two sides line up.
|
||||||
//
|
//
|
||||||
// Requires playwright (devDependency) and two static servers; it starts both.
|
// Requires playwright (devDependency) and two static servers; it starts both.
|
||||||
|
//
|
||||||
|
// The legacy pages were deleted at cutover, so this needs a pre-cutover tree:
|
||||||
|
// git worktree add /tmp/vanilla <pre-cutover-sha>
|
||||||
|
// and run from there, or run it from a checkout that still has them.
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { cpSync, mkdtempSync, rmSync } from 'node:fs';
|
import { cpSync, mkdtempSync, rmSync } from 'node:fs';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ description:
|
|||||||
|
|
||||||
The snapshot is the only objective evidence that no content was lost.
|
The snapshot is the only objective evidence that no content was lost.
|
||||||
|
|
||||||
|
The vanilla site was deleted at cutover. To compare against it, check the
|
||||||
|
pre-cutover tree out into a scratch worktree first:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run serve & # vanilla site on :4173
|
git worktree add /tmp/vanilla <pre-cutover-sha>
|
||||||
|
(cd /tmp/vanilla && python3 -m http.server 4173) &
|
||||||
node .agents/scripts/snapshot-route.mjs http://localhost:4173/models/ \
|
node .agents/scripts/snapshot-route.mjs http://localhost:4173/models/ \
|
||||||
> .agents/snapshots/models.txt
|
> .agents/snapshots/models.txt
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ a bug.
|
|||||||
```bash
|
```bash
|
||||||
python3 - <<'PY'
|
python3 - <<'PY'
|
||||||
import re
|
import re
|
||||||
files=['styles.css','chapters.css','landing.css','rules/styles.css','skills/styles.css',
|
files=['legacy/styles/guide.css','legacy/styles/chapters.css','legacy/styles/skills.css',
|
||||||
'skills-review/styles.css','hands-on/starter/styles.css','hands-on/rules/styles.css']
|
'legacy/styles/skills-review.css','legacy/styles/change-lens.css',
|
||||||
|
'legacy/styles/audit.css','public/hands-on/starter/styles.css',
|
||||||
|
'public/hands-on/rules/styles.css']
|
||||||
seen={}
|
seen={}
|
||||||
for f in files:
|
for f in files:
|
||||||
for m in re.finditer(r'--([a-z-]+):\s*([^;}]+)', open(f).read()):
|
for m in re.finditer(r'--([a-z-]+):\s*([^;}]+)', open(f).read()):
|
||||||
|
|||||||
@@ -33,8 +33,9 @@ with sync_playwright() as p:
|
|||||||
browser.close()
|
browser.close()
|
||||||
```
|
```
|
||||||
|
|
||||||
Run once against the vanilla site (`pnpm run serve`), once against
|
Run once against `pnpm run preview`. To compare against the vanilla site, serve
|
||||||
`pnpm run preview`. Keep both sets.
|
a pre-cutover worktree on :4173 first — those files are no longer on `main`.
|
||||||
|
Keep both sets.
|
||||||
|
|
||||||
## Compare
|
## Compare
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 444 KiB |
|
Before Width: | Height: | Size: 341 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 465 KiB |
|
Before Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 438 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 444 KiB |
|
Before Width: | Height: | Size: 341 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 465 KiB |
|
Before Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 438 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 104 KiB |
@@ -288,5 +288,16 @@
|
|||||||
"1960px",
|
"1960px",
|
||||||
"2200px",
|
"2200px",
|
||||||
"2880px"
|
"2880px"
|
||||||
|
],
|
||||||
|
"breakpoints": [
|
||||||
|
"520px",
|
||||||
|
"560px",
|
||||||
|
"600px",
|
||||||
|
"800px",
|
||||||
|
"880px",
|
||||||
|
"1050px",
|
||||||
|
"1100px",
|
||||||
|
"1600px",
|
||||||
|
"2200px"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
[
|
||||||
|
"01 frota",
|
||||||
|
"02 worktrees",
|
||||||
|
"03 modelos",
|
||||||
|
"04 skills",
|
||||||
|
"05 criar",
|
||||||
|
"06 kit de campo",
|
||||||
|
"07 prática",
|
||||||
|
"ENGENHARIA DE IA <i></i> 01 / 2026",
|
||||||
|
"Uma apresentação para quem entrega software",
|
||||||
|
"Você não precisa de um exército de modelos. Precisa de um sistema: uma mente para enquadrar o trabalho, várias mãos para executá-lo e uma fronteira clara entre cada tarefa.",
|
||||||
|
"NOTA DE CAMPO / 001",
|
||||||
|
"Entregue o<br /><em>sistema.</em>",
|
||||||
|
"Skills · agentes · worktrees · evidências",
|
||||||
|
"modelo forte<br />para ambiguidade",
|
||||||
|
"workers delimitados<br />em paralelo",
|
||||||
|
"iterações<br />com evidências",
|
||||||
|
"Leia isto como um mapa de rota, não como uma receita de prompt.",
|
||||||
|
"REGRA ZERO",
|
||||||
|
"Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.",
|
||||||
|
"Uma pequena frota",
|
||||||
|
"coordenação antes do paralelismo",
|
||||||
|
"ORQUESTRADOR",
|
||||||
|
"Decide o que<br />precisa acontecer.",
|
||||||
|
"Componentes e estados visuais",
|
||||||
|
"Casos de aceitação",
|
||||||
|
"Guia e exemplos",
|
||||||
|
"O orquestrador preserva a intenção, escreve pequenos contratos e reúne resultados verificáveis. Ele não precisa digitar cada linha.",
|
||||||
|
"Por que a fronteira importa",
|
||||||
|
"uma tarefa vaga / três falhas previsíveis",
|
||||||
|
"Sopa de contexto",
|
||||||
|
"Cada worker lê tudo. Ninguém sabe quais fatos são essenciais.",
|
||||||
|
"Colisão de branches",
|
||||||
|
"Dois agentes usam o mesmo checkout. O caminho mais rápido vira resolução de conflitos.",
|
||||||
|
"Desvio confiante",
|
||||||
|
"O diff parece ótimo, mas ninguém verifica se resolveu o problema original.",
|
||||||
|
"O ciclo de subagentes",
|
||||||
|
"Clique em uma fase.<br /><em>Veja a passagem.</em>",
|
||||||
|
"Delegar é mover uma tarefa delimitada para um contexto menor — não abrir mão da responsabilidade.",
|
||||||
|
"O que atravessa contextos",
|
||||||
|
"brief → diff → evidência",
|
||||||
|
"Pacote",
|
||||||
|
"Contém",
|
||||||
|
"Por que importa",
|
||||||
|
"Git worktrees",
|
||||||
|
"Uma branch<br />por <em>mão.</em>",
|
||||||
|
"Um worktree é outro diretório ligado ao mesmo repositório. Cada agente recebe seu próprio checkout e índice; o histórico continua compartilhado.",
|
||||||
|
"Selecione um nó para inspecionar checkout, responsável e próxima ação.",
|
||||||
|
"topologia do repositório",
|
||||||
|
"<i></i> 4 checkouts",
|
||||||
|
"RAIZ",
|
||||||
|
"AGENTE DE UI",
|
||||||
|
"AGENTE DE TESTES",
|
||||||
|
"AGENTE DE DOCS",
|
||||||
|
"● limpo",
|
||||||
|
"3 arquivos · trabalhando",
|
||||||
|
"8 verificações · pronto",
|
||||||
|
"2 páginas · revisão",
|
||||||
|
"Roteamento de modelos",
|
||||||
|
"Não pague por<br />raciocínio onde precisa<br />de <em>ritmo.</em>",
|
||||||
|
"Escolha um trabalho para entender por que o perfil do modelo muda.",
|
||||||
|
"Trabalho",
|
||||||
|
"Perfil",
|
||||||
|
"Formato do prompt",
|
||||||
|
"Planejar",
|
||||||
|
"Construir",
|
||||||
|
"Explorar",
|
||||||
|
"Revisar",
|
||||||
|
"Skills",
|
||||||
|
"Escreva do jeito certo<br /><em>uma vez.</em>",
|
||||||
|
"Uma skill é um procedimento reutilizável. Ela pode carregar instruções, referências, scripts e assets. Não é memória mágica e não substitui critérios de aceitação.",
|
||||||
|
"01 / defina o gatilho",
|
||||||
|
"02 / carregue detalhes sob demanda",
|
||||||
|
"03 / devolva evidências",
|
||||||
|
"PACOTE DE SKILL",
|
||||||
|
"Skills comuns",
|
||||||
|
"escolha o comportamento antes do modelo",
|
||||||
|
"O kit de campo",
|
||||||
|
"Trabalhos diferentes.<br />Instintos <em>diferentes.</em>",
|
||||||
|
"Uma skill muda como o agente aborda o trabalho. Algumas moldam a comunicação. Outras impõem pesquisa, diagnóstico, revisão ou disciplina de conclusão. Selecione uma para inspecionar sua regra operacional.",
|
||||||
|
"SIMPLIFICAR",
|
||||||
|
"código mínimo que funciona",
|
||||||
|
"COMUNICAR",
|
||||||
|
"sinal sem excesso",
|
||||||
|
"CONCLUIR",
|
||||||
|
"gates e evidências",
|
||||||
|
"INVESTIGAR",
|
||||||
|
"fontes primárias primeiro",
|
||||||
|
"DIAGNOSTICAR",
|
||||||
|
"ciclo curto de feedback",
|
||||||
|
"REVISAR",
|
||||||
|
"padrões × especificação",
|
||||||
|
"ECONOMIZAR",
|
||||||
|
"comprima saídas ruidosas",
|
||||||
|
"UM LOADOUT PRÁTICO",
|
||||||
|
"<b>PLANEJAR</b> unlazy <i>→</i> <b>CONSTRUIR</b> ponytail-lite <i>→</i> <b>DIAGNOSTICAR</b> diagnosing-bugs <i>→</i> <b>REPORTAR</b> caveman",
|
||||||
|
"O PAPEL HUMANO",
|
||||||
|
"O agente pode ser autônomo na execução. Intenção, limites e evidências continuam sendo seus.",
|
||||||
|
"COMECE AQUI",
|
||||||
|
"Comece com um agente e uma skill. Adicione paralelismo apenas quando as tarefas forem realmente independentes.",
|
||||||
|
"Continue aprendendo",
|
||||||
|
"12 novas leituras + documentação primária",
|
||||||
|
"Aprofunde com documentação oficial, casos de produção, Medium e fluxos de praticantes. <a href=\"rules/\">Estudo de caso sobre regras e enforcement →</a> <a href=\"docs/references/README.md\">Referências primárias →</a> <a href=\"docs/references/additional-reading.md\">Trilha com 12 leituras →</a>"
|
||||||
|
]
|
||||||
@@ -40,11 +40,11 @@ jobs:
|
|||||||
# back in once it can actually diff. See task 03's report.
|
# back in once it can actually diff. See task 03's report.
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
# Until the migration finishes, `dist/` holds only /summary/ and the two
|
# `dist/` now holds all ten routes, so the stub hazard that forced this to
|
||||||
# hands-on fixtures, while the live `pages` branch serves ten pages.
|
# manual dispatch is gone. It stays manual anyway: the step below is a
|
||||||
# Publishing on every push to main would take the site down to a stub, so
|
# force-push over the live `pages` branch, and making it fire on every push
|
||||||
# this job runs only when a human asks for it. Make it unconditional on
|
# to main means every merge republishes with no human in the loop. Flipping
|
||||||
# main again at task 20 (cutover), not before.
|
# it to `push` on main is a deliberate decision, not a leftover TODO.
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.publish
|
if: github.event_name == 'workflow_dispatch' && inputs.publish
|
||||||
needs: gate
|
needs: gate
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1,4 +1,40 @@
|
|||||||
# Tier 2: the real gate. Whole project. Budget < 90s.
|
# Tier 2: the real gate. Whole project. Budget < 90s.
|
||||||
# Takes a cross-worktree lock so parallel agents queue instead of thrashing.
|
# Takes a cross-worktree lock so parallel agents queue instead of thrashing.
|
||||||
|
|
||||||
exec .agents/scripts/gate.sh
|
# The publish step below re-enters git push. Without this, that inner push would
|
||||||
|
# fire this hook again, run the gate again, and publish again, forever.
|
||||||
|
if [ "${AF_PUBLISHING:-0}" = '1' ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
.agents/scripts/gate.sh || exit 1
|
||||||
|
|
||||||
|
# Publishing to `pages` overwrites the live site. It happens here, on a push of
|
||||||
|
# main to origin, and nowhere else.
|
||||||
|
#
|
||||||
|
# Set AF_NO_PUBLISH=1 to push main without republishing:
|
||||||
|
# AF_NO_PUBLISH=1 git push
|
||||||
|
[ "${AF_NO_PUBLISH:-0}" = '1' ] && exit 0
|
||||||
|
|
||||||
|
remote_name=$1
|
||||||
|
[ "$remote_name" = 'origin' ] || exit 0
|
||||||
|
|
||||||
|
# stdin gives one line per ref being pushed:
|
||||||
|
# <local ref> <local sha> <remote ref> <remote sha>
|
||||||
|
zero='0000000000000000000000000000000000000000'
|
||||||
|
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||||
|
[ "$remote_ref" = 'refs/heads/main' ] || continue
|
||||||
|
# A deletion has no build to publish.
|
||||||
|
[ "$local_sha" = "$zero" ] && continue
|
||||||
|
|
||||||
|
# This hook runs before the push lands, so `pages` would go live ahead of
|
||||||
|
# `main` if the push then failed. Publish only when the push cannot be
|
||||||
|
# rejected as a non-fast-forward: the remote tip must already be an ancestor.
|
||||||
|
if [ "$remote_sha" != "$zero" ] && ! git merge-base --is-ancestor "$remote_sha" "$local_sha"; then
|
||||||
|
echo "pre-push: main is not a fast-forward; not publishing." >&2
|
||||||
|
echo " Push main first, then run .agents/scripts/publish-pages.sh" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
.agents/scripts/publish-pages.sh --pending "$local_sha" || exit 1
|
||||||
|
done
|
||||||
|
|||||||
@@ -9,21 +9,9 @@ vote-service
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
public/submitted-skills
|
public/submitted-skills
|
||||||
|
|
||||||
# Legacy site sources, slated for deletion at cutover (task 20). These are
|
# Unmigrated legacy sources, kept verbatim under `legacy/`. These are
|
||||||
# hand-written files with very long lines; prettier re-wraps them into hundreds
|
# hand-written files with very long lines; prettier re-wraps them into hundreds
|
||||||
# of changed lines the moment any agent stages one. Task 15 touched app.js to
|
# of changed lines the moment any agent stages one. Task 15 touched the old
|
||||||
# add four lines and produced an 829-line diff. verify.mjs asserts substrings
|
# app.js to add four lines and produced an 829-line diff. A reformat here is
|
||||||
# against several of these, so a reformat is churn at best and a broken
|
# churn at best.
|
||||||
# assertion at worst.
|
/legacy/
|
||||||
#
|
|
||||||
# Root-anchored on purpose: a bare `rules` would also swallow .agents/rules/,
|
|
||||||
# whose markdown we do want formatted.
|
|
||||||
/app.js
|
|
||||||
/styles.css
|
|
||||||
/landing.css
|
|
||||||
/chapters.css
|
|
||||||
/responsive.css
|
|
||||||
/skills-review/
|
|
||||||
/rules/
|
|
||||||
/skills/
|
|
||||||
/full-guide/
|
|
||||||
|
|||||||
@@ -7,23 +7,16 @@ public/submitted-skills
|
|||||||
skill-reviews
|
skill-reviews
|
||||||
vote-service
|
vote-service
|
||||||
|
|
||||||
# Legacy site sources, slated for deletion at cutover (task 20). Same list and
|
# Unmigrated legacy stylesheets, kept verbatim under `legacy/`. Same list and
|
||||||
# same reasoning as .prettierignore: these are minified, single-line
|
# same reasoning as .prettierignore: these are minified, single-line
|
||||||
# stylesheets. stylelint's `declaration-block-single-line-max-declarations`
|
# stylesheets. stylelint's `declaration-block-single-line-max-declarations`
|
||||||
# fires once per rule in them — ~180 errors for `styles.css` alone — so staging
|
# fires once per rule in them — ~180 errors for `guide.css` alone — so staging
|
||||||
# one to change a single declaration blocks the commit outright. The rule is
|
# one to change a single declaration blocks the commit outright. The rule is
|
||||||
# about hand-written source readability and says nothing useful about minified
|
# about hand-written source readability and says nothing useful about minified
|
||||||
# output that is about to be deleted.
|
# legacy output. Migrating one of these into `src/` means bringing it up to the
|
||||||
|
# design system in the same change, at which point it gets linted like any
|
||||||
|
# other source file.
|
||||||
#
|
#
|
||||||
# `public/fonts/fonts.css` is deliberately NOT here: it is new, hand-written,
|
# `public/fonts/fonts.css` is deliberately NOT here: it is new, hand-written,
|
||||||
# and must stay linted.
|
# and must stay linted.
|
||||||
#
|
/legacy/
|
||||||
# Root-anchored on purpose: a bare `rules` would also swallow .agents/rules/.
|
|
||||||
/styles.css
|
|
||||||
/landing.css
|
|
||||||
/chapters.css
|
|
||||||
/responsive.css
|
|
||||||
/skills-review/
|
|
||||||
/rules/
|
|
||||||
/skills/
|
|
||||||
/full-guide/
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
"hands-on/**",
|
"hands-on/**",
|
||||||
"public/hands-on/**",
|
"public/hands-on/**",
|
||||||
"submitted-skills/**",
|
"submitted-skills/**",
|
||||||
"skill-reviews/**",
|
"skill-reviews/**"
|
||||||
"vote-service/**"
|
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"custom-property-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
"custom-property-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
||||||
|
|||||||
@@ -12,48 +12,54 @@ rules, and verification.** It is published as a static site on a self-hosted
|
|||||||
Gitea Pages Server, and it doubles as its own teaching artifact: the hands-on
|
Gitea Pages Server, and it doubles as its own teaching artifact: the hands-on
|
||||||
labs are dependency-free HTML/CSS/JS that workshop attendees point an agent at.
|
labs are dependency-free HTML/CSS/JS that workshop attendees point an agent at.
|
||||||
|
|
||||||
- **Current stack**: hand-written HTML + CSS + ES modules, no build step, no
|
- **Stack**: Astro, static output, no runtime dependencies. The migration
|
||||||
dependencies
|
recorded in [`plans/astro-refactor/`](plans/astro-refactor/README.md) is
|
||||||
- **Target stack**: Astro (see
|
complete; the hand-written pages it replaced are gone. What remains unmigrated
|
||||||
[`plans/astro-refactor/`](plans/astro-refactor/README.md)) — migration in
|
is the editorial CSS and the review-desk modules under `legacy/`, still
|
||||||
progress
|
imported by the pages that need them.
|
||||||
- **Languages**: English and Brazilian Portuguese, toggled client-side
|
- **Languages**: English and Brazilian Portuguese, toggled client-side
|
||||||
- **Companion service**: `vote-service/` (Go + Kubernetes) — separate lifecycle,
|
- **Companion service**: a Go + Kubernetes vote API, reached over
|
||||||
see its own README
|
`window.SKILLS_REVIEW_VOTE_API`. Its source is no longer in this repository
|
||||||
|
|
||||||
## Essential commands
|
## Essential commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run verify # content + interaction contracts (scripts/verify.mjs) — the gate
|
pnpm run dev # http://localhost:4321/ai-for-dummies/
|
||||||
|
pnpm run build # writes dist/ — every check below reads it
|
||||||
|
bash .agents/scripts/gate.sh # the full gate: check, build, verify, audit, tokens
|
||||||
|
pnpm run verify # content + interaction contracts (scripts/verify.mjs)
|
||||||
node scripts/audit-ui.mjs # responsive / no-external-dependency audit
|
node scripts/audit-ui.mjs # responsive / no-external-dependency audit
|
||||||
node scripts/build-skill-review.mjs # regenerate skill-reviews/improved/ from src/content/reviews/
|
node scripts/build-skill-review.mjs # regenerate skill-reviews/improved/ from src/content/reviews/
|
||||||
pnpm run serve # python3 -m http.server 4173
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`pnpm run verify` is not a formality. It is a set of ~42 string-token assertions
|
`pnpm run verify` is not a formality. It is a set of 84 string-token assertions
|
||||||
that pin the site's real content and interactions. **A refactor that "passes" by
|
that pin the site's real content and interactions, read from the built output.
|
||||||
deleting assertions has failed.** See
|
**A refactor that "passes" by deleting assertions has failed.** See
|
||||||
[`.agents/context/verification.md`](.agents/context/verification.md).
|
[`.agents/context/verification.md`](.agents/context/verification.md).
|
||||||
|
|
||||||
## Publishing
|
## Publishing
|
||||||
|
|
||||||
`main` is the source of truth. The `pages` branch is what the Gitea Pages Server
|
`main` is the source of truth. The `pages` branch is what the Gitea Pages Server
|
||||||
actually serves, and its tree must end up identical to `main`'s. The full
|
actually serves, and it now carries **build output**, not a copy of `main`'s
|
||||||
procedure — including why `merge --ff-only` does _not_ work here — is in
|
tree.
|
||||||
[`docs/operations-guide.md`](docs/operations-guide.md).
|
|
||||||
|
|
||||||
Adding a build step changes this contract. Read
|
**Pushing `main` republishes the live site.** The `pre-push` hook runs the gate,
|
||||||
[`.agents/context/publishing.md`](.agents/context/publishing.md) before doing
|
then `.agents/scripts/publish-pages.sh`, which builds and force-pushes `dist/`
|
||||||
so.
|
to `pages`. Use `AF_NO_PUBLISH=1 git push` to land a commit without publishing.
|
||||||
|
`pages` keeps its history, so rollback is a single force-push to an earlier tip;
|
||||||
|
`pages-backup-2026-09-06` is the last commit of the hand-written site. The full
|
||||||
|
procedure is in [`docs/operations-guide.md`](docs/operations-guide.md); read
|
||||||
|
[`.agents/context/publishing.md`](.agents/context/publishing.md) before changing
|
||||||
|
it.
|
||||||
|
|
||||||
## Never touch
|
## Never touch
|
||||||
|
|
||||||
- `hands-on/starter/` and `hands-on/rules/` — **lab fixtures.** The exercise
|
- `public/hands-on/starter/` and `public/hands-on/rules/` — **lab fixtures.**
|
||||||
_is_ that they are dependency-free vanilla HTML/CSS/JS an attendee can hand to
|
The exercise _is_ that they are dependency-free vanilla HTML/CSS/JS an
|
||||||
an agent. Componentizing them destroys the lesson. They ship as static assets.
|
attendee can hand to an agent. Componentizing them destroys the lesson. They
|
||||||
|
ship as static assets.
|
||||||
- `submitted-skills/` — other people's submitted work, reproduced verbatim
|
- `submitted-skills/` — other people's submitted work, reproduced verbatim
|
||||||
- `skill-reviews/improved/` — generated; edit `src/content/reviews/*.md` instead
|
- `skill-reviews/improved/` — generated; edit `src/content/reviews/*.md` instead
|
||||||
- `vote-service/` — separate deploy lifecycle; do not fold into the site build
|
|
||||||
- `dist/`, `node_modules/` — build output, never committed
|
- `dist/`, `node_modules/` — build output, never committed
|
||||||
- `pnpm-lock.yaml` — **committed, but never hand-edited.** Change it only as a
|
- `pnpm-lock.yaml` — **committed, but never hand-edited.** Change it only as a
|
||||||
side effect of `pnpm install`. Every worktree spins up with
|
side effect of `pnpm install`. Every worktree spins up with
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
# Gates: review desk privacy and improved-draft audit
|
# Gates: review desk privacy and improved-draft audit
|
||||||
|
|
||||||
OWNS: skills-review/**, submitted-skills/Anonymous Operational Submission/**,
|
OWNS: src/pages/skills-review.astro, src/components/blocks/{ReviewDetail,
|
||||||
|
ChangeLens,VoteWidget,PreviewPane,FileTabs}.astro, legacy/skills-review/**,
|
||||||
|
submitted-skills/Anonymous Operational Submission/**,
|
||||||
skill-reviews/improved/ndo-repro/**, scripts/verify.mjs
|
skill-reviews/improved/ndo-repro/**, scripts/verify.mjs
|
||||||
|
|
||||||
|
The gate commands below now read `dist/`; run `pnpm run build` before them.
|
||||||
|
|
||||||
Scope: Redact the operational submission's identity and URLs from the published
|
Scope: Redact the operational submission's identity and URLs from the published
|
||||||
review desk, keep package files usable in either preview mode, and explain each
|
review desk, keep package files usable in either preview mode, and explain each
|
||||||
improved draft as a concrete diff.
|
improved draft as a concrete diff.
|
||||||
|
|||||||
@@ -15,47 +15,58 @@ skill links to a pinned source with an approval-first installation prompt.
|
|||||||
|
|
||||||
## Run locally
|
## Run locally
|
||||||
|
|
||||||
This is a dependency-free static site:
|
This is an Astro static site. It builds to `dist/` and ships no runtime
|
||||||
|
dependencies.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m http.server 4173
|
pnpm install
|
||||||
|
pnpm run dev # http://localhost:4321/ai-for-dummies/
|
||||||
|
pnpm run build # writes dist/
|
||||||
|
pnpm run preview # serves the built output
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open <http://localhost:4173>.
|
|
||||||
|
|
||||||
Verify the content and interaction contracts with:
|
Verify the content and interaction contracts with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run verify
|
pnpm run verify # reads dist/, so build first
|
||||||
|
```
|
||||||
|
|
||||||
|
The full gate — `astro check`, `astro build`, `verify.mjs`, `audit-ui.mjs`,
|
||||||
|
`check-tokens.mjs`, and the assertion-count floor — runs as:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash .agents/scripts/gate.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
- `index.html` — default route map and focused chapter navigation
|
- `src/pages/` — one file per route: the landing route map, the complete
|
||||||
- `full-guide/` — the complete bilingual presentation, with responsive audit
|
bilingual `full-guide`, the chapter pages, `rules`, `skills`, and
|
||||||
overrides
|
`skills-review`
|
||||||
- `styles.css` / `app.js` — editorial visual system and bilingual field-guide
|
- `src/components/` — blocks and islands; the interactive diagrams, selectors,
|
||||||
interactions
|
and the language toggle
|
||||||
- `responsive.css` — interactive diagrams and Full HD-to-4K adaptations
|
- `src/content/` — the content collections every page renders from
|
||||||
|
- `src/styles/tokens.css` — the design tokens
|
||||||
|
- `legacy/` — the editorial visual system and the review-desk modules, not yet
|
||||||
|
migrated into components. Still imported by the pages that need them; see
|
||||||
|
`.agents/context/architecture.md`
|
||||||
|
- `public/` — assets copied to the site root verbatim: fonts, the hands-on labs,
|
||||||
|
and `submitted-skills/`
|
||||||
- `docs/references/` — bundled research sources and notes
|
- `docs/references/` — bundled research sources and notes
|
||||||
- `docs/operations-guide.md` — canonical SilverBullet operations and skills
|
- `docs/operations-guide.md` — canonical SilverBullet operations and skills
|
||||||
guide
|
guide
|
||||||
- `hands-on/starter/` — dependency-free Tiny Tasks exercise
|
- `public/hands-on/starter/` — dependency-free Tiny Tasks exercise
|
||||||
- `hands-on/rules/` — dependency-free Guardrails lab; toggles rule sources into
|
- `public/hands-on/rules/` — dependency-free Guardrails lab; toggles rule
|
||||||
the prompt
|
sources into the prompt
|
||||||
- `rules/` — bilingual case study of skills, CLI ratchets, Husky, and PR review
|
- `skills/` — reusable design and rules-case-study skills
|
||||||
- `skills/` — reusable design and rules-case-study skills, plus an interactive
|
|
||||||
package anatomy explorer
|
|
||||||
- `skills-review/` — static review desk for submitted skills; its reader vote
|
|
||||||
widget calls the separate `vote-service`
|
|
||||||
- `vote-service/` — small Go API + Kubernetes manifests backing the
|
|
||||||
skills-review vote widget (see `vote-service/README.md`)
|
|
||||||
- `GATES.md` — acceptance ledger for the project
|
- `GATES.md` — acceptance ledger for the project
|
||||||
|
|
||||||
## Publishing
|
## Publishing
|
||||||
|
|
||||||
The Gitea instance has a Pages Server configured to publish a repository’s
|
The Gitea instance has a Pages Server configured to publish a repository’s
|
||||||
`pages` branch under `pages.marcospaulo.dev.br`. The intended site address is:
|
`pages` branch under `pages.marcospaulo.dev.br`. `pages` now carries the
|
||||||
|
**built** site — the contents of `dist/` — not a copy of `main`. The intended
|
||||||
|
site address is:
|
||||||
|
|
||||||
<https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/>
|
<https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/>
|
||||||
|
|
||||||
@@ -70,12 +81,11 @@ skill workflow, see [docs/operations-guide.md](docs/operations-guide.md).
|
|||||||
## Reader voting on the skills-review desk
|
## Reader voting on the skills-review desk
|
||||||
|
|
||||||
`skills-review/` is static, so its "which draft would you ship?" vote widget
|
`skills-review/` is static, so its "which draft would you ship?" vote widget
|
||||||
calls a separate stateful service — `vote-service/`, a small Go API on its own
|
calls a separate stateful service — a small Go API on its own pod, one vote per
|
||||||
pod, one vote per visitor enforced server-side by IP (a MAC address is never
|
visitor enforced server-side by IP (a MAC address is never visible to a server
|
||||||
visible to a server across the internet, so it cannot be used). See
|
across the internet, so it cannot be used). Its source no longer lives in this
|
||||||
[vote-service/README.md](vote-service/README.md) for the API, the anti-abuse
|
repository; the deployed service is unchanged. `src/pages/skills-review.astro`
|
||||||
design, and the build/push/deploy steps; `skills-review/index.html` sets
|
sets `window.SKILLS_REVIEW_VOTE_API` to point at it.
|
||||||
`window.SKILLS_REVIEW_VOTE_API` to point at it once deployed.
|
|
||||||
|
|
||||||
## Research
|
## Research
|
||||||
|
|
||||||
|
|||||||