feat: scaffold astro publishing pipeline
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
AI For Dummies — Route map
|
||||
← AI FOR DUMMIES
|
||||
00 / ROUTE MAP
|
||||
review desk ↗
|
||||
Start here
|
||||
Ship the
|
||||
system.
|
||||
This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result.
|
||||
01
|
||||
Models
|
||||
Capability and effort are separate knobs.
|
||||
Open chapter →
|
||||
02
|
||||
Agents & trees
|
||||
Bound roles, handoffs, and worktrees.
|
||||
Open chapter →
|
||||
03
|
||||
Skills
|
||||
Capture repeatable decisions.
|
||||
Open chapter →
|
||||
04
|
||||
Rules
|
||||
Connect guidance to enforcement.
|
||||
Open chapter →
|
||||
05
|
||||
Practice
|
||||
Compare prompts and skill-enabled runs.
|
||||
Open lab →
|
||||
06
|
||||
Review desk
|
||||
Browse original files and improved drafts.
|
||||
Open desk →
|
||||
Full field guide
|
||||
Operations guide
|
||||
Each chapter stands alone; the order follows a real task becoming a reliable change.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Tier 3 gate and publication. The act-runner registration is stored in an
|
||||
# emptyDir: after a runner pod restart, re-register it before debugging CI.
|
||||
name: verify-and-publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm install --package-lock=false --prefer-offline
|
||||
- run: npm run lint
|
||||
- run: ./.agents/scripts/gate.sh
|
||||
- name: visual regression
|
||||
run: |
|
||||
npx playwright install --with-deps chromium
|
||||
node .agents/scripts/visual-regression.mjs
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: screenshots
|
||||
path: .agents/snapshots/diff/
|
||||
|
||||
publish:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
needs: gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm install --package-lock=false --prefer-offline
|
||||
- run: npm run build
|
||||
- name: Publish generated site to pages
|
||||
run: |
|
||||
git config user.name 'gitea-actions[bot]'
|
||||
git config user.email 'gitea-actions[bot]@users.noreply.local'
|
||||
git switch --orphan pages
|
||||
git rm -rf .
|
||||
cp -a dist/. .
|
||||
git add --all
|
||||
git commit -m 'chore: publish site'
|
||||
git push --force origin HEAD:pages
|
||||
+4
-14
@@ -1,16 +1,6 @@
|
||||
{
|
||||
"*.{js,mjs,ts,astro}": [
|
||||
"prettier --write",
|
||||
"eslint --fix --max-warnings=0"
|
||||
],
|
||||
"*.css": [
|
||||
"prettier --write",
|
||||
"stylelint --fix --max-warnings=0"
|
||||
],
|
||||
"*.{astro,css}": [
|
||||
"node .agents/scripts/check-tokens.mjs"
|
||||
],
|
||||
"*.{json,md,yml,yaml}": [
|
||||
"prettier --write"
|
||||
]
|
||||
"*.{js,mjs,ts,astro}": ["prettier --write", "eslint --fix --max-warnings=0 --no-warn-ignored"],
|
||||
"*.css": ["prettier --write", "stylelint --fix --max-warnings=0"],
|
||||
"src/**/*.{astro,css}": ["node .agents/scripts/check-tokens.mjs"],
|
||||
"*.{json,md,yml,yaml}": ["prettier --write"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
dist
|
||||
node_modules
|
||||
public/hands-on
|
||||
submitted-skills
|
||||
skill-reviews
|
||||
vote-service
|
||||
.agents/snapshots
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-astro"],
|
||||
"overrides": [
|
||||
{ "files": "*.astro", "options": { "parser": "astro" } },
|
||||
{ "files": "*.md", "options": { "proseWrap": "always", "printWidth": 80 } }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": ["stylelint-config-standard"],
|
||||
"ignoreFiles": [
|
||||
"dist/**",
|
||||
"hands-on/**",
|
||||
"public/hands-on/**",
|
||||
"submitted-skills/**",
|
||||
"skill-reviews/**",
|
||||
"vote-service/**"
|
||||
],
|
||||
"rules": {
|
||||
"custom-property-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
||||
"declaration-property-value-disallowed-list": {
|
||||
"/^transition/": ["/width/", "/height/", "/^top/", "/^left/", "/margin/"],
|
||||
"/^animation/": ["/width/", "/height/"]
|
||||
},
|
||||
"media-feature-name-no-unknown": true,
|
||||
"no-descending-specificity": null,
|
||||
"selector-class-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
base: '/ai-for-dummies',
|
||||
trailingSlash: 'always',
|
||||
});
|
||||
+128
-132
@@ -19,7 +19,7 @@ worktree practices taught by the presentation fit together.
|
||||
## Quick links
|
||||
|
||||
| Resource | Location |
|
||||
| :--- | :--- |
|
||||
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Live presentation | [https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/](https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/) |
|
||||
| Gitea repository | [https://git.marcospaulo.dev.br/netcracker/ai-for-dummies](https://git.marcospaulo.dev.br/netcracker/ai-for-dummies) |
|
||||
| Local checkout | `/home/marcos/Projects/ai-for-dummies` |
|
||||
@@ -31,32 +31,32 @@ worktree practices taught by the presentation fit together.
|
||||
|
||||
## How the site is built
|
||||
|
||||
The presentation is deliberately dependency-free. Gitea Pages serves the
|
||||
repository files directly; there is no bundler or generated `dist/` folder.
|
||||
The presentation is authored in Astro. `main` holds source; the Gitea Pages
|
||||
Server still serves a branch directly, so the `pages` branch holds generated
|
||||
`dist/` output and is not a source branch.
|
||||
|
||||
| File | Responsibility |
|
||||
| :--- | :--- |
|
||||
| `index.html` | Default route map and focused chapter navigation |
|
||||
| `full-guide/index.html` | Complete bilingual field guide, controls, labels, and English source copy |
|
||||
| `styles.css` | Base editorial visual system |
|
||||
| `responsive.css` | Interactive diagrams and Full HD, 4K, tablet, and mobile adaptations |
|
||||
| `app.js` | Interactions, state, and Portuguese translations |
|
||||
| :---------------------------- | :--------------------------------------------------------- |
|
||||
| `src/` | Astro routes, layouts, components, and source styles |
|
||||
| `public/hands-on/` | Verbatim vanilla lab fixtures; Astro does not process them |
|
||||
| `astro.config.mjs` | Static build with `base: '/ai-for-dummies'` |
|
||||
| `scripts/verify.mjs` | Content and interaction contract checks |
|
||||
| `docs/references/` | Primary documentation and additional reading |
|
||||
| `.gitea/workflows/verify.yml` | Gate, build, and machine-owned publication to `pages` |
|
||||
|
||||
The English HTML is the fallback when JavaScript is unavailable. Portuguese
|
||||
copy is applied by `app.js`; the language preference is stored in
|
||||
`localStorage`, and the document language changes to `pt-BR`.
|
||||
Astro ships no JavaScript by default. Interactive islands opt in per component;
|
||||
the bilingual behaviour remains a client-side concern until its dedicated
|
||||
migration task.
|
||||
|
||||
## Normal edit and publish workflow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
E[Edit main] --> V[npm run verify]
|
||||
E[Edit main] --> V[npm run gate]
|
||||
V --> C[Commit]
|
||||
C --> M[Push main]
|
||||
M --> P[Fast-forward pages]
|
||||
P --> S[Gitea Pages Server]
|
||||
C --> P[Push main]
|
||||
P --> G[Gitea Actions: gate + build]
|
||||
G --> B[Force-push dist to pages]
|
||||
B --> S[Gitea Pages Server]
|
||||
S --> L[Live URL]
|
||||
```
|
||||
|
||||
@@ -69,41 +69,36 @@ git pull --ff-only
|
||||
git status --short --branch
|
||||
```
|
||||
|
||||
Do not overwrite unrelated local changes. The untracked
|
||||
`scripts/inspect.py` and `scripts/__pycache__/` are local visual-test artifacts
|
||||
and are intentionally not part of the published site.
|
||||
Do not overwrite unrelated local changes. The untracked `scripts/inspect.py` and
|
||||
`scripts/__pycache__/` are local visual-test artifacts and are intentionally not
|
||||
part of the published site.
|
||||
|
||||
### 2. Preview locally
|
||||
|
||||
```bash
|
||||
python3 -m http.server 4173
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:4173](http://localhost:4173). Check English and
|
||||
Portuguese, keyboard focus, the interactive panels, and at least one desktop
|
||||
and one mobile viewport.
|
||||
Open
|
||||
[http://localhost:4321/ai-for-dummies/](http://localhost:4321/ai-for-dummies/).
|
||||
Check English and Portuguese, keyboard focus, the interactive panels, and at
|
||||
least one desktop and one mobile viewport. The production site is under the
|
||||
`/ai-for-dummies/` base path, so do not test only root-relative URLs.
|
||||
|
||||
### 3. Verify before committing
|
||||
|
||||
```bash
|
||||
npm run verify
|
||||
node --check app.js
|
||||
node scripts/audit-ui.mjs
|
||||
git diff --check
|
||||
npm run gate
|
||||
```
|
||||
|
||||
Expected project verifier output:
|
||||
|
||||
```text
|
||||
content verification passed
|
||||
interaction verification passed
|
||||
standalone verification passed
|
||||
```
|
||||
The gate runs Astro types and build checks, content contracts, the runtime
|
||||
dependency audit, and the token check. It also refuses a reduced count of
|
||||
`verify.mjs` assertions.
|
||||
|
||||
### 4. Commit and push the source branch
|
||||
|
||||
```bash
|
||||
git add README.md app.js index.html full-guide/ styles.css responsive.css scripts/verify.mjs scripts/audit-ui.mjs docs/
|
||||
git add <only-files-for-this-change>
|
||||
git commit -m "feat: describe the change"
|
||||
git push origin main
|
||||
```
|
||||
@@ -111,54 +106,44 @@ git push origin main
|
||||
Stage only files that belong to the change. Review `git status --short` before
|
||||
committing.
|
||||
|
||||
### 5. Fast-forward the published branch
|
||||
### 5. CI publication and its manual fallback
|
||||
|
||||
Use a temporary worktree so the current checkout stays on `main`:
|
||||
On a successful `main` push, Gitea Actions builds `dist/` and force-pushes it to
|
||||
`pages`. This force-push is intentional: `pages` is machine-owned generated
|
||||
output, and no person or other workflow may write it.
|
||||
|
||||
This Gitea's act-runner registration is kept in an `emptyDir`. A pod restart
|
||||
silently removes the registration; if a site does not update, check and
|
||||
re-register the runner before changing the workflow.
|
||||
|
||||
If the runner is unavailable, use the following manual fallback from a clean
|
||||
`main` checkout. It deliberately replaces the generated branch tree only:
|
||||
|
||||
```bash
|
||||
git worktree add /tmp/ai-for-dummies-pages pages
|
||||
git -C /tmp/ai-for-dummies-pages merge --ff-only origin/pages # local pages is often stale
|
||||
git -C /tmp/ai-for-dummies-pages merge --no-edit main
|
||||
git -C /tmp/ai-for-dummies-pages push origin pages
|
||||
npm install --package-lock=false --prefer-offline
|
||||
npm run build
|
||||
git worktree add /tmp/ai-for-dummies-pages --detach pages
|
||||
git -C /tmp/ai-for-dummies-pages rm -rf .
|
||||
cp -a dist/. /tmp/ai-for-dummies-pages/
|
||||
git -C /tmp/ai-for-dummies-pages add --all
|
||||
git -C /tmp/ai-for-dummies-pages commit -m "chore: publish site"
|
||||
git -C /tmp/ai-for-dummies-pages push --force origin HEAD:pages
|
||||
git worktree remove /tmp/ai-for-dummies-pages
|
||||
```
|
||||
|
||||
The `pages` branch should represent the exact published source. Avoid editing
|
||||
it directly and avoid force-pushing it.
|
||||
|
||||
The two histories have diverged — `pages` carries merge commits and
|
||||
cherry-picked duplicates of `main` commits — so `merge --ff-only main` fails
|
||||
with `Not possible to fast-forward`. A normal merge is correct here, and
|
||||
matches the `Merge branch 'main' into pages` commits already on the branch.
|
||||
The invariant worth checking is the *tree*, not the history:
|
||||
|
||||
```bash
|
||||
git rev-parse main^{tree} pages^{tree} # must print the same hash twice
|
||||
```
|
||||
|
||||
If the merge conflicts (duplicated commits touching the same lines will do
|
||||
it), resolve by taking `main` wholesale, since `main` is the source of truth
|
||||
for published content:
|
||||
|
||||
```bash
|
||||
git -C /tmp/ai-for-dummies-pages checkout main -- .
|
||||
git -C /tmp/ai-for-dummies-pages add -A
|
||||
git -C /tmp/ai-for-dummies-pages diff --cached main --stat # must be empty
|
||||
git -C /tmp/ai-for-dummies-pages commit --no-edit
|
||||
```
|
||||
|
||||
### 6. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -I https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/
|
||||
curl -sS -o /dev/null -w '%{http_code}\n' \
|
||||
"https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/summary/?v=$(git rev-parse --short HEAD)"
|
||||
```
|
||||
|
||||
If the edge still shows an older page, retry with the current commit as a
|
||||
cache-busting query:
|
||||
Also check a nested static asset; base-path problems usually appear on assets
|
||||
first:
|
||||
|
||||
```bash
|
||||
git rev-parse --short HEAD
|
||||
curl -I "https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/?v=COMMIT"
|
||||
curl -sS -o /dev/null -w '%{http_code}\n' \
|
||||
"https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/hands-on/starter/?v=$(git rev-parse --short HEAD)"
|
||||
```
|
||||
|
||||
The correct URL pattern is **owner subdomain + repository path**:
|
||||
@@ -173,12 +158,12 @@ certificate.
|
||||
|
||||
## Skills-review vote service
|
||||
|
||||
`skills-review/` is served by the same static Pages Server as the rest of
|
||||
this site, so it cannot itself remember votes. `vote-service/` is a separate
|
||||
Go API on its own pod for that: one JSON file as the store, one vote per
|
||||
visitor enforced by IP (a MAC address never reaches a server across the
|
||||
internet). It is deployed independently of `main`/`pages` — the site can be
|
||||
republished without touching it, and vice versa.
|
||||
`skills-review/` is served by the same static Pages Server as the rest of this
|
||||
site, so it cannot itself remember votes. `vote-service/` is a separate Go API
|
||||
on its own pod for that: one JSON file as the store, one vote per visitor
|
||||
enforced by IP (a MAC address never reaches a server across the internet). It is
|
||||
deployed independently of `main`/`pages` — the site can be republished without
|
||||
touching it, and vice versa.
|
||||
|
||||
```bash
|
||||
cd vote-service
|
||||
@@ -197,23 +182,22 @@ microk8s kubectl apply -f deploy/ingress.yaml
|
||||
microk8s kubectl -n ai-for-dummies rollout restart deploy ai-for-dummies-vote
|
||||
```
|
||||
|
||||
Namespace `ai-for-dummies`, `ingressClassName: public`, no per-ingress TLS.
|
||||
The Deployment is pinned to node `kubernets` with a `nodeSelector`: the
|
||||
Namespace `ai-for-dummies`, `ingressClassName: public`, no per-ingress TLS. The
|
||||
Deployment is pinned to node `kubernets` with a `nodeSelector`: the
|
||||
`microk8s-hostpath` PV carries a `nodeAffinity` for whichever node first binds
|
||||
it, so scheduling and storage have to agree on one node.
|
||||
|
||||
The vote widget's browser-side `fetch` calls must reach the API over the public
|
||||
internet — a cluster-internal-only Service would be unreachable from a
|
||||
visitor's browser even if the Pages Server happens to run on the same
|
||||
network. Exposure is therefore public, terminated by **Caddy on the Oracle VPS
|
||||
over Tailscale** (the same path as every other public host here, not the
|
||||
cloudflared tunnel), with `ALLOWED_ORIGIN`/CORS as the boundary that restricts
|
||||
which site's script may call it. After deploying, keep
|
||||
`window.SKILLS_REVIEW_VOTE_API` in `skills-review/index.html` in sync with
|
||||
`ALLOWED_ORIGIN` on the service.
|
||||
internet — a cluster-internal-only Service would be unreachable from a visitor's
|
||||
browser even if the Pages Server happens to run on the same network. Exposure is
|
||||
therefore public, terminated by **Caddy on the Oracle VPS over Tailscale** (the
|
||||
same path as every other public host here, not the cloudflared tunnel), with
|
||||
`ALLOWED_ORIGIN`/CORS as the boundary that restricts which site's script may
|
||||
call it. After deploying, keep `window.SKILLS_REVIEW_VOTE_API` in
|
||||
`skills-review/index.html` in sync with `ALLOWED_ORIGIN` on the service.
|
||||
|
||||
One cluster-wide gotcha worth knowing before reading the vote code: the ingress
|
||||
controller runs with `use-forwarded-headers` off, so nginx *overwrites*
|
||||
controller runs with `use-forwarded-headers` off, so nginx _overwrites_
|
||||
`X-Forwarded-For`/`X-Real-IP` with the VPS's tailnet address. Caddy stamps the
|
||||
true client address into `X-Client-IP` instead. Full rationale, the Caddy block,
|
||||
and the anti-abuse design are in
|
||||
@@ -221,7 +205,8 @@ and the anti-abuse design are in
|
||||
|
||||
## Adding or changing a presentation section
|
||||
|
||||
1. Add semantic HTML and stable `data-*` hooks in the focused chapter or `full-guide/index.html`; keep `index.html` as the short route map.
|
||||
1. Add semantic HTML and stable `data-*` hooks in the focused chapter or
|
||||
`full-guide/index.html`; keep `index.html` as the short route map.
|
||||
2. Put interactive content in a data object inside `app.js`.
|
||||
3. Add one focused render function and bind its controls once.
|
||||
4. Add Portuguese static copy to `translations.pt` and dynamic copy to the
|
||||
@@ -251,8 +236,8 @@ flowchart TD
|
||||
|
||||
Use a strong model where ambiguity dominates: repository inspection,
|
||||
architecture, decomposition, risk analysis, and review. Use faster or cheaper
|
||||
models for bounded implementation only after the brief defines the goal,
|
||||
files, constraints, and checks.
|
||||
models for bounded implementation only after the brief defines the goal, files,
|
||||
constraints, and checks.
|
||||
|
||||
Every worker should return:
|
||||
|
||||
@@ -266,8 +251,8 @@ coordination cost, context cost, and integration risk.
|
||||
|
||||
## Worktree-per-worker model
|
||||
|
||||
A Git branch isolates history; a Git worktree also isolates the active files
|
||||
and index. Give each editing agent one task, one branch, and one worktree.
|
||||
A Git branch isolates history; a Git worktree also isolates the active files and
|
||||
index. Give each editing agent one task, one branch, and one worktree.
|
||||
|
||||
```bash
|
||||
git worktree add ../task-ui -b agent/ui
|
||||
@@ -285,14 +270,14 @@ Recommended lifecycle:
|
||||
5. Merge, request changes, or discard.
|
||||
6. Remove the finished worktree with `git worktree remove PATH`.
|
||||
|
||||
Worktrees prevent agents from changing the same checkout underneath each
|
||||
other. They do not eliminate semantic merge conflicts; task ownership and
|
||||
review still matter.
|
||||
Worktrees prevent agents from changing the same checkout underneath each other.
|
||||
They do not eliminate semantic merge conflicts; task ownership and review still
|
||||
matter.
|
||||
|
||||
## What a skill is
|
||||
|
||||
A skill is a reusable procedure that changes how an agent makes decisions. It
|
||||
is not magical memory and does not replace a task brief or acceptance criteria.
|
||||
A skill is a reusable procedure that changes how an agent makes decisions. It is
|
||||
not magical memory and does not replace a task brief or acceptance criteria.
|
||||
|
||||
```text
|
||||
skill-name/
|
||||
@@ -309,8 +294,8 @@ Progressive disclosure keeps context light:
|
||||
2. **SKILL.md** loads when the skill applies.
|
||||
3. **References, scripts, and assets** load only when the workflow needs them.
|
||||
|
||||
Do not create empty resource directories. Every file should have a real
|
||||
consumer and should improve a decision or repeatable operation.
|
||||
Do not create empty resource directories. Every file should have a real consumer
|
||||
and should improve a decision or repeatable operation.
|
||||
|
||||
## Skill-creation workflow
|
||||
|
||||
@@ -329,14 +314,17 @@ likely false activation.
|
||||
```yaml
|
||||
---
|
||||
name: review-ui
|
||||
description: Review frontend changes for focus, responsive layout, and reduced-motion behavior.
|
||||
description:
|
||||
Review frontend changes for focus, responsive layout, and reduced-motion
|
||||
behavior.
|
||||
---
|
||||
```
|
||||
|
||||
### 3. Choose the smallest anatomy
|
||||
|
||||
- Put shared workflow and constraints in `SKILL.md`.
|
||||
- Add `scripts/` when deterministic execution prevents repeated reimplementation.
|
||||
- Add `scripts/` when deterministic execution prevents repeated
|
||||
reimplementation.
|
||||
- Add `references/` for details needed only in certain modes.
|
||||
- Add `assets/` for templates or generated-output inputs.
|
||||
- Add `agents/openai.yaml` only when UI metadata or invocation policy is useful.
|
||||
@@ -361,7 +349,7 @@ accumulating universal instructions.
|
||||
## Common skills and when to use them
|
||||
|
||||
| Skill | Use it for | Core rule | Avoid when |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| :---------------- | :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- |
|
||||
| `ponytail-lite` | Requests inviting unnecessary frameworks or abstractions | Stop at the first sufficient solution: reuse, standard library, native platform, existing dependency, then minimum new code | Simplification would remove validation, security, accessibility, or real edge cases |
|
||||
| `caveman` | Routine status, handoffs, and technical summaries | Put signal first and remove filler | Security warnings, irreversible actions, or sequences where terse wording can be misread |
|
||||
| `unlazy` | Substantial builds, audits, and parallel work | Define observable gates and finish against evidence | Trivial edits or factual answers |
|
||||
@@ -373,17 +361,21 @@ accumulating universal instructions.
|
||||
|
||||
Useful compositions:
|
||||
|
||||
- **Large feature:** `unlazy` → `ponytail-lite` → implementation → `code-review`.
|
||||
- **Hard regression:** `diagnosing-bugs` → fix → `code-review` → `caveman` handoff.
|
||||
- **Documentation with unstable facts:** `research` → writing → cited verification.
|
||||
- **Interactive presentation:** `frontend-design` → `webapp-testing` → responsive evidence.
|
||||
- **Large feature:** `unlazy` → `ponytail-lite` → implementation →
|
||||
`code-review`.
|
||||
- **Hard regression:** `diagnosing-bugs` → fix → `code-review` → `caveman`
|
||||
handoff.
|
||||
- **Documentation with unstable facts:** `research` → writing → cited
|
||||
verification.
|
||||
- **Interactive presentation:** `frontend-design` → `webapp-testing` →
|
||||
responsive evidence.
|
||||
|
||||
## Model and effort routing
|
||||
|
||||
Treat model tier and reasoning effort as separate controls:
|
||||
|
||||
| Work shape | Capability tier | Effort baseline |
|
||||
| :--- | :--- | :--- |
|
||||
| :------------------------------------------ | :------------------------ | :----------------------------- |
|
||||
| Formatting, lookup, narrow edit | Luna / Haiku / Flash-Lite | Low or minimal where supported |
|
||||
| Normal implementation and tests | Terra / Sonnet / Flash | Medium |
|
||||
| Architecture, orchestration, hard debugging | Sol / Opus / Pro | High |
|
||||
@@ -414,14 +406,14 @@ includes a copy-ready installation request that tells the coding agent to:
|
||||
Important exceptions: `ponytail-lite` is published as `AGENTS.md`, not a
|
||||
conventional skill package; `token-saver` expects a separate RTK binary; and
|
||||
`unlazy` includes optional hooks. The prompt does not install binaries or enable
|
||||
hooks without separate approval. See [skill-sources.md](references/skill-sources.md)
|
||||
for exact commits, package paths, and confidence notes.
|
||||
hooks without separate approval. See
|
||||
[skill-sources.md](references/skill-sources.md) for exact commits, package
|
||||
paths, and confidence notes.
|
||||
|
||||
## Hands-on lab
|
||||
|
||||
The presentation includes a dependency-free starter at
|
||||
`hands-on/starter/`. It renders a small task board but intentionally omits the
|
||||
All / Open / Done filter.
|
||||
The presentation includes a dependency-free starter at `hands-on/starter/`. It
|
||||
renders a small task board but intentionally omits the All / Open / Done filter.
|
||||
|
||||
Run it from the repository root:
|
||||
|
||||
@@ -429,7 +421,8 @@ Run it from the repository root:
|
||||
python3 -m http.server 4173
|
||||
```
|
||||
|
||||
Open [http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/).
|
||||
Open
|
||||
[http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/).
|
||||
In a fresh coding-agent session, copy **Run A — Good prompt** from the
|
||||
presentation. Record changed files, dependencies, checks, and evidence. Restore
|
||||
the starter, then repeat with **Run B — Good prompt + skills**.
|
||||
@@ -440,14 +433,14 @@ The skill-enabled prompt invokes only two working methods:
|
||||
- `$webapp-testing` verifies filters, URL state, history navigation,
|
||||
accessibility state, empty state, and mobile layout.
|
||||
|
||||
The goal is not to prove that a longer prompt is better. Both prompts define
|
||||
the same task contract. Run B adds reusable operating discipline without
|
||||
repeating those skill instructions inside the prompt.
|
||||
The goal is not to prove that a longer prompt is better. Both prompts define the
|
||||
same task contract. Run B adds reusable operating discipline without repeating
|
||||
those skill instructions inside the prompt.
|
||||
|
||||
Compare:
|
||||
|
||||
| Signal | Useful question |
|
||||
| :--- | :--- |
|
||||
| :------------ | :-------------------------------------------------------------- |
|
||||
| Files changed | Did the agent stay inside `hands-on/starter/`? |
|
||||
| Dependencies | Did it add a library where native APIs were enough? |
|
||||
| Verification | Did it actually exercise URL reload and browser history? |
|
||||
@@ -457,10 +450,10 @@ Compare:
|
||||
### Hands-on rules lab
|
||||
|
||||
A second lab at `hands-on/rules/` mirrors the starter's visual system and runs
|
||||
the same exercise against rule sources. It lists five toggleable rule sources
|
||||
— `AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook,
|
||||
the `check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the
|
||||
**ruled** prompt live as each toggle flips.
|
||||
the same exercise against rule sources. It lists five toggleable rule sources —
|
||||
`AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook, the
|
||||
`check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the **ruled**
|
||||
prompt live as each toggle flips.
|
||||
|
||||
Run it:
|
||||
|
||||
@@ -468,15 +461,16 @@ Run it:
|
||||
python3 -m http.server 4173
|
||||
```
|
||||
|
||||
Open [http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/).
|
||||
Open
|
||||
[http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/).
|
||||
Compare the **naive** and **ruled** prompt panels. Toggle rules off to shrink
|
||||
the prompt; toggle them on to add more guards. Copy the final prompt and run
|
||||
it against a real coding agent.
|
||||
the prompt; toggle them on to add more guards. Copy the final prompt and run it
|
||||
against a real coding agent.
|
||||
|
||||
## Rules and enforcement case study
|
||||
|
||||
The separate `/rules/` page uses `netcracker/interview` as a concrete example
|
||||
of repository-level control. Its interactive pipeline shows five layers:
|
||||
The separate `/rules/` page uses `netcracker/interview` as a concrete example of
|
||||
repository-level control. Its interactive pipeline shows five layers:
|
||||
|
||||
1. `AGENTS.md` gives every agent the same product and toolchain context.
|
||||
2. `.agents/skills/` loads narrow procedures for frontend, Go API, gates,
|
||||
@@ -495,7 +489,7 @@ together when the underlying interview workflow changes.
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Check | Fix |
|
||||
| :--- | :--- | :--- |
|
||||
| :------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------- |
|
||||
| Live page is old | Compare `main`, `pages`, and remote SHAs | Fast-forward and push `pages`; retry with `?v=COMMIT` |
|
||||
| `ERR_SSL_PROTOCOL_ERROR` | Confirm the hostname | Use `netcracker.pages.marcospaulo.dev.br/ai-for-dummies/` |
|
||||
| Portuguese copy is missing | Inspect `translations.pt` and dynamic interaction data | Add both static and dynamic translations; reload after clearing saved language if needed |
|
||||
@@ -506,7 +500,8 @@ together when the underlying interview workflow changes.
|
||||
|
||||
## Safe rollback
|
||||
|
||||
Prefer a normal revert so history and the `pages` branch remain fast-forwardable:
|
||||
Prefer a normal revert so history and the `pages` branch remain
|
||||
fast-forwardable:
|
||||
|
||||
```bash
|
||||
git switch main
|
||||
@@ -532,7 +527,8 @@ ordinary content recovery.
|
||||
- [ ] `main` is pushed.
|
||||
- [ ] `pages` fast-forwards to the same commit.
|
||||
- [ ] Live endpoint returns HTTP 200 and contains the new section.
|
||||
- [ ] Research links and this SilverBullet guide are updated when the workflow changes.
|
||||
- [ ] Research links and this SilverBullet guide are updated when the workflow
|
||||
changes.
|
||||
|
||||
## Resumo rápido em português
|
||||
|
||||
@@ -542,6 +538,6 @@ Gitea Pages publica diretamente essa branch. Use o endereço com
|
||||
`netcracker.pages.../ai-for-dummies/`; o formato inverso quebra o TLS.
|
||||
|
||||
Para agentes: modelo forte planeja e revisa; workers delimitados implementam em
|
||||
worktrees separados; evidências voltam ao orquestrador. Para skills: capture
|
||||
uma decisão repetida, defina um gatilho preciso, crie apenas os recursos úteis,
|
||||
worktrees separados; evidências voltam ao orquestrador. Para skills: capture uma
|
||||
decisão repetida, defina um gatilho preciso, crie apenas os recursos úteis,
|
||||
escreva orientação que muda decisões e valide estrutura **e** comportamento.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import js from '@eslint/js';
|
||||
import astro from 'eslint-plugin-astro';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'.agents/**',
|
||||
'.astro/**',
|
||||
'app.js',
|
||||
'dist/**',
|
||||
'full-guide/**',
|
||||
'hands-on/**',
|
||||
'public/hands-on/**',
|
||||
'rules/**',
|
||||
'scripts/**',
|
||||
'skill-reviews/**',
|
||||
'skills-review/**',
|
||||
'skills/**',
|
||||
'submitted-skills/**',
|
||||
'vote-service/**',
|
||||
],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...astro.configs.recommended,
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'hands-on/**',
|
||||
'public/hands-on/**',
|
||||
'submitted-skills/**',
|
||||
'skill-reviews/**',
|
||||
'vote-service/**',
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
eqeqeq: ['error', 'always'],
|
||||
'no-var': 'error',
|
||||
'prefer-const': 'error',
|
||||
},
|
||||
},
|
||||
];
|
||||
+34
-1
@@ -1,5 +1,38 @@
|
||||
{
|
||||
"name": "ai-for-dummies",
|
||||
"private": true,
|
||||
"scripts": { "verify": "node scripts/verify.mjs", "serve": "python3 -m http.server 4173" }
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"check": "astro check",
|
||||
"lint": "eslint . --max-warnings=0 && stylelint --allow-empty-input 'src/**/*.css' --max-warnings=0",
|
||||
"format": "prettier --write .",
|
||||
"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs",
|
||||
"gate": "./.agents/scripts/gate.sh",
|
||||
"snapshot": "node .agents/scripts/snapshot-route.mjs",
|
||||
"serve": "python3 -m http.server 4173",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "0.9.4",
|
||||
"@eslint/js": "^9",
|
||||
"@typescript-eslint/parser": "^8",
|
||||
"astro": "5.5.0",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-astro": "^1",
|
||||
"husky": "^9",
|
||||
"lint-staged": "^16",
|
||||
"prettier": "^3",
|
||||
"prettier-plugin-astro": "^0.14",
|
||||
"stylelint": "^16",
|
||||
"stylelint-config-standard": "^39",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"overrides": {
|
||||
"@astrojs/language-server": "2.15.0",
|
||||
"defu": "6.1.7",
|
||||
"vite": "6.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Hands-on · Rules
|
||||
|
||||
A tiny, zero-dependency demo showing how rule sources reshape the same prompt.
|
||||
|
||||
## Run
|
||||
|
||||
Open `index.html` directly. No build, no server, no `npm install`.
|
||||
|
||||
## What it shows
|
||||
|
||||
- Five rule sources, taken from a real monorepo (`netcracker/interview`):
|
||||
- `AGENTS.md` — repo-wide instruction file.
|
||||
- `.agents/skills/gate-discipline/SKILL.md` — skill body loaded on demand.
|
||||
- `.husky/pre-commit` — git hook that runs other enforcers.
|
||||
- `scripts/check-ui-contract.mjs` — custom CLI enforcer (ratchet).
|
||||
- `commitlint.config.cjs` — commit-msg linter.
|
||||
- Each rule has an on/off switch. Toggling a rule prepends its body to the **ruled** prompt.
|
||||
- EN ↔ PT toggle keeps both languages useful.
|
||||
- "Copy ruled prompt" copies the current ruled-prompt text to clipboard.
|
||||
- Responsive on mobile, Full HD, and 4K (one column under 720 px).
|
||||
|
||||
## Mirror of `/hands-on/starter`
|
||||
|
||||
Same visual system as the starter (`--paper`, `--ink`, `--blue`, `--gold`). Drop-in replacement under `hands-on/rules/`.
|
||||
|
||||
## Token budget
|
||||
|
||||
Page weight: ~5 KB total, no framework, no fetch.
|
||||
@@ -0,0 +1,124 @@
|
||||
// Five rule sources lifted from netcracker/interview.
|
||||
// Toggling a rule injects its body into the ruled prompt.
|
||||
const RULES = [
|
||||
{
|
||||
id: 'agents',
|
||||
name: 'AGENTS.md',
|
||||
kind: 'Repo-wide instruction',
|
||||
path: 'AGENTS.md',
|
||||
en: 'Read AGENTS.md before touching this repo. Stack: pnpm + turbo monorepo, Go API, Next.js apps. Gates run from the monorepo root: pnpm lint, typecheck, test.',
|
||||
pt: 'Leia AGENTS.md antes de tocar neste repo. Stack: pnpm + turbo monorepo, API em Go, apps Next.js. Gates rodam da raiz: pnpm lint, typecheck, test.'
|
||||
},
|
||||
{
|
||||
id: 'skill',
|
||||
name: 'gate-discipline skill',
|
||||
kind: 'Skill body',
|
||||
path: '.agents/skills/gate-discipline/SKILL.md',
|
||||
en: 'Skill `gate-discipline`: every gate runs separately with `$?`. No `| tail`. `TURBO_FORCE=true` if a pass looks too cheap. Generated code is regenerated, never hand-edited.',
|
||||
pt: 'Skill `gate-discipline`: cada gate roda separado com `$?`. Nada de `| tail`. `TURBO_FORCE=true` se o passar for bom demais. Código gerado se regenera, nunca se edita.'
|
||||
},
|
||||
{
|
||||
id: 'husky',
|
||||
name: 'Husky pre-commit',
|
||||
kind: 'Git hook (commit time)',
|
||||
path: '.husky/pre-commit',
|
||||
en: 'Pre-commit runs `pnpm exec lint-staged`, then `node scripts/check-ui-contract.mjs` (UI ratchet), then commitlint. Counts in the baseline may only go DOWN.',
|
||||
pt: 'Pre-commit roda `pnpm exec lint-staged`, depois `node scripts/check-ui-contract.mjs` (ratchet de UI), depois commitlint. Contadores do baseline só podem DIMINUIR.'
|
||||
},
|
||||
{
|
||||
id: 'enforcer',
|
||||
name: 'check-ui-contract.mjs',
|
||||
kind: 'Custom enforcer (CLI)',
|
||||
path: 'scripts/check-ui-contract.mjs',
|
||||
en: 'Enforcer scans for raw <button>, silent catches, pages without h1, hardcoded colors, duplicated components. Fails when a count goes UP. Fix = `--accept` to re-baseline lower.',
|
||||
pt: 'Enforcer varre <button> cru, catch silencioso, páginas sem h1, cores hardcoded, componentes duplicados. Falha quando contador SOBE. Corrigir = `--accept` para re-baseline menor.'
|
||||
},
|
||||
{
|
||||
id: 'commitlint',
|
||||
name: 'commitlint',
|
||||
kind: 'Commit-msg linter',
|
||||
path: 'commitlint.config.cjs',
|
||||
en: 'Commitlint enforces Conventional Commits. Format: `<type>(<scope>): <subject>`. Types: feat, fix, docs, refactor, test, chore, build, ci, perf, style.',
|
||||
pt: 'Commitlint enforça Commits Convencionais. Formato: `<tipo>(<escopo>): <assunto>`. Tipos: feat, fix, docs, refactor, test, chore, build, ci, perf, style.'
|
||||
}
|
||||
];
|
||||
|
||||
// The task we're asking the agent to perform.
|
||||
const TASK = {
|
||||
en: { goal: 'Refactor the `getUserById` endpoint to return 404 instead of throwing.', ctx: 'services/api/internal/user/handler.go. No DB schema change.' },
|
||||
pt: { goal: 'Refatorar o endpoint `getUserById` para retornar 404 em vez de lançar exceção.', ctx: 'services/api/internal/user/handler.go. Sem mudança de schema.' }
|
||||
};
|
||||
|
||||
const NAIVE = {
|
||||
en: `Task: ${TASK.en.goal}\nFile: ${TASK.en.ctx}\nPlease make the change and tell me when done.`,
|
||||
pt: `Tarefa: ${TASK.pt.goal}\nArquivo: ${TASK.pt.ctx}\nFaça a mudança e me avise quando terminar.`
|
||||
};
|
||||
|
||||
const state = { enabled: new Set(['agents']), lang: 'en' };
|
||||
|
||||
const ruleList = document.querySelector('#rule-list');
|
||||
const ruleCount = document.querySelector('#rule-count');
|
||||
const promptNaive = document.querySelector('#prompt-naive');
|
||||
const promptRuled = document.querySelector('#prompt-ruled');
|
||||
const copyBtn = document.querySelector('#copy-btn');
|
||||
const langButtons = document.querySelectorAll('.lang-switch button');
|
||||
|
||||
function renderRules() {
|
||||
ruleList.innerHTML = RULES.map((rule) => `
|
||||
<article class="rule" data-id="${rule.id}">
|
||||
<div>
|
||||
<h3>${rule.name}</h3>
|
||||
<p>${rule.kind} <code>${rule.path}</code></p>
|
||||
</div>
|
||||
<span class="rule-tag">${rule.id}</span>
|
||||
<button class="toggle" type="button" data-rule="${rule.id}" aria-pressed="${state.enabled.has(rule.id)}" aria-label="Toggle ${rule.name}"></button>
|
||||
</article>
|
||||
`).join('');
|
||||
ruleCount.textContent = `${state.enabled.size} / ${RULES.length} active`;
|
||||
}
|
||||
|
||||
function renderPrompts() {
|
||||
const lang = state.lang;
|
||||
promptNaive.textContent = NAIVE[lang];
|
||||
|
||||
const active = RULES.filter((r) => state.enabled.has(r.id));
|
||||
const blocks = active.map((r) => `# ${r.name} (${r.path})\n${r[lang]}`).join('\n\n');
|
||||
const head = `Task: ${TASK[lang].goal}\nFile: ${TASK[lang].ctx}`;
|
||||
const tail = lang === 'en'
|
||||
? '\n\nRun each gate separately and print $? before claiming done.'
|
||||
: '\n\nRode cada gate separado e imprima $? antes de dizer que terminou.';
|
||||
promptRuled.textContent = blocks ? `${head}\n\n${blocks}${tail}` : head + tail;
|
||||
}
|
||||
|
||||
function bind() {
|
||||
ruleList.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.toggle');
|
||||
if (!btn) return;
|
||||
const id = btn.dataset.rule;
|
||||
if (state.enabled.has(id)) state.enabled.delete(id); else state.enabled.add(id);
|
||||
btn.setAttribute('aria-pressed', String(state.enabled.has(id)));
|
||||
ruleCount.textContent = `${state.enabled.size} / ${RULES.length} active`;
|
||||
renderPrompts();
|
||||
});
|
||||
|
||||
langButtons.forEach((btn) => btn.addEventListener('click', () => {
|
||||
state.lang = btn.dataset.lang;
|
||||
langButtons.forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
|
||||
renderPrompts();
|
||||
}));
|
||||
|
||||
copyBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(promptRuled.textContent);
|
||||
copyBtn.dataset.copied = 'true';
|
||||
copyBtn.textContent = 'Copied';
|
||||
setTimeout(() => { copyBtn.dataset.copied = 'false'; copyBtn.textContent = 'Copy ruled prompt'; }, 1400);
|
||||
} catch {
|
||||
copyBtn.textContent = 'Copy failed — select and copy manually';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderRules();
|
||||
renderPrompts();
|
||||
bind();
|
||||
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Guardrails — Hands-on Rules</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div><span>HANDS-ON / RULES</span><h1>Guardrails</h1></div>
|
||||
<p>Toggle rules. Same task, different coverage.</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="rules-heading">
|
||||
<div class="section-head"><h2 id="rules-heading">Rule sources</h2><span id="rule-count">0 / 5 active</span></div>
|
||||
<div id="rule-list" class="rule-list"></div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="prompt-heading">
|
||||
<div class="section-head"><h2 id="prompt-heading">Prompt diff</h2>
|
||||
<div class="lang-switch" role="group" aria-label="Language">
|
||||
<button type="button" data-lang="en" aria-pressed="true">EN</button>
|
||||
<button type="button" data-lang="pt" aria-pressed="false">PT</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prompt-grid">
|
||||
<article class="prompt-card" data-side="naive">
|
||||
<header><span>NAIVE</span><h3>Plain prompt</h3></header>
|
||||
<pre id="prompt-naive"></pre>
|
||||
</article>
|
||||
<article class="prompt-card" data-side="ruled">
|
||||
<header><span>RULED</span><h3>With guardrails</h3></header>
|
||||
<pre id="prompt-ruled"></pre>
|
||||
<button id="copy-btn" type="button">Copy ruled prompt</button>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
:root{--paper:#f4f3ef;--ink:#173044;--muted:#687d8c;--blue:#5683a1;--gold:#efc86d;--line:#d5dde1}*{box-sizing:border-box}body{margin:0;min-width:320px;background:var(--paper);color:var(--ink);font-family:Arial,sans-serif}main{width:min(880px,calc(100% - 40px));margin:0 auto;padding:70px 0}header,.section-head{display:flex;justify-content:space-between;gap:30px;align-items:end}header{padding-bottom:50px;border-bottom:1px solid var(--line)}header span,.prompt-card>header span{font:10px monospace;letter-spacing:.08em}h1{margin:12px 0 0;font-size:clamp(48px,10vw,100px);letter-spacing:-.08em}header p{max-width:220px;color:var(--muted);line-height:1.5}section{padding-top:45px}h2{font-size:24px}.section-head>span{color:var(--blue);font:11px monospace}.rule-list,.prompt-grid{display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}.rule{display:grid;grid-template-columns:1fr auto auto;gap:20px;padding:22px;background:var(--paper);align-items:center}.rule h3{margin:0 0 6px;font-size:17px}.rule p{margin:0;color:var(--muted);font-size:13px;line-height:1.45}.rule code{font:11px ui-monospace,monospace;color:var(--ink);background:var(--paper);padding:1px 5px;border:1px solid var(--line)}.rule-tag{font:10px monospace;letter-spacing:.08em;color:var(--muted);text-transform:uppercase}.toggle{appearance:none;width:44px;height:24px;border:1px solid var(--line);background:var(--paper);border-radius:12px;position:relative;cursor:pointer;transition:background .15s ease}.toggle::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;background:var(--ink);border-radius:50%;transition:transform .15s ease,background .15s ease}.toggle[aria-pressed="true"]{background:var(--blue);border-color:var(--blue)}.toggle[aria-pressed="true"]::after{transform:translateX(20px);background:var(--gold)}.prompt-grid{grid-template-columns:1fr 1fr;margin-top:18px}.prompt-card{background:var(--paper);padding:22px;display:flex;flex-direction:column;gap:14px}.prompt-card>header{display:flex;justify-content:space-between;align-items:end;padding-bottom:0;border-bottom:0}.prompt-card h3{margin:0;font-size:17px}.prompt-card pre{margin:0;font:12px ui-monospace,monospace;white-space:pre-wrap;word-break:break-word;color:var(--ink);background:var(--paper);border:1px solid var(--line);padding:14px;min-height:160px;line-height:1.5}#copy-btn{align-self:flex-start;appearance:none;border:1px solid var(--ink);background:var(--ink);color:var(--paper);font:11px monospace;letter-spacing:.08em;padding:9px 14px;cursor:pointer;text-transform:uppercase}#copy-btn[data-copied="true"]{background:var(--blue);border-color:var(--blue)}.lang-switch{display:flex;gap:1px;border:1px solid var(--line)}.lang-switch button{appearance:none;border:0;background:var(--paper);color:var(--muted);font:11px monospace;letter-spacing:.08em;padding:6px 10px;cursor:pointer;text-transform:uppercase}.lang-switch button[aria-pressed="true"]{background:var(--ink);color:var(--paper)}@media(max-width:720px){.prompt-grid{grid-template-columns:1fr}.rule{grid-template-columns:1fr}.rule-tag{display:none}}@media(max-width:560px){header{display:block}header p{margin-top:24px}.lang-switch{flex:1}.lang-switch button{flex:1}}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Tiny Tasks hands-on starter
|
||||
|
||||
A dependency-free HTML/CSS/JavaScript exercise used by the AI For Dummies
|
||||
presentation. The task list renders; status filtering is intentionally absent.
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 4173
|
||||
```
|
||||
|
||||
Open <http://localhost:4173/hands-on/starter/> and paste either prompt from the
|
||||
presentation into a fresh coding-agent session rooted at this repository.
|
||||
@@ -0,0 +1,15 @@
|
||||
const tasks = [
|
||||
{ title: 'Review pull request', owner: 'Maya', status: 'open' },
|
||||
{ title: 'Write release notes', owner: 'Theo', status: 'done' },
|
||||
{ title: 'Check mobile layout', owner: 'Lina', status: 'open' }
|
||||
];
|
||||
|
||||
const list = document.querySelector('#task-list');
|
||||
const count = document.querySelector('#task-count');
|
||||
|
||||
function renderTasks() {
|
||||
list.innerHTML = tasks.map((task) => `<article class="task" data-status="${task.status}"><div><h3>${task.title}</h3><p>Owner: ${task.owner}</p></div><span class="task-meta">${task.status}</span></article>`).join('');
|
||||
count.textContent = `${tasks.length} tasks`;
|
||||
}
|
||||
|
||||
renderTasks();
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Tiny Tasks — Hands-on Starter</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div><span>HANDS-ON / STARTER</span><h1>Tiny Tasks</h1></div>
|
||||
<p>Three tasks. One missing filter.</p>
|
||||
</header>
|
||||
<section aria-labelledby="task-heading">
|
||||
<div class="section-head"><h2 id="task-heading">Today</h2><span id="task-count"></span></div>
|
||||
<div id="task-list" class="task-list"></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
:root{--paper:#f4f3ef;--ink:#173044;--muted:#687d8c;--blue:#5683a1;--gold:#efc86d;--line:#d5dde1}*{box-sizing:border-box}body{margin:0;min-width:320px;background:var(--paper);color:var(--ink);font-family:Arial,sans-serif}main{width:min(880px,calc(100% - 40px));margin:0 auto;padding:70px 0}header,.section-head{display:flex;justify-content:space-between;gap:30px;align-items:end}header{padding-bottom:50px;border-bottom:1px solid var(--line)}header span,.task-meta{font:10px monospace;letter-spacing:.08em}h1{margin:12px 0 0;font-size:clamp(48px,10vw,100px);letter-spacing:-.08em}header p{max-width:220px;color:var(--muted);line-height:1.5}section{padding-top:45px}h2{font-size:24px}.section-head>span{color:var(--blue);font:11px monospace}.task-list{display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}.task{display:grid;grid-template-columns:1fr auto;gap:20px;padding:22px;background:var(--paper)}.task h3{margin:0 0 8px;font-size:17px}.task p{margin:0;color:var(--muted);font-size:13px}.task-meta{align-self:center;padding:7px 9px;color:var(--ink);background:var(--gold)}.task[data-status="done"] .task-meta{color:var(--paper);background:var(--blue)}@media(max-width:560px){header{display:block}header p{margin-top:24px}.task{grid-template-columns:1fr}.task-meta{justify-self:start}}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
const { title, description, lang = 'en' } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
<slot name="styles" />
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import chaptersStylesheet from '../../chapters.css?url';
|
||||
|
||||
const base = import.meta.env.BASE_URL;
|
||||
const introduction =
|
||||
'This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result.';
|
||||
---
|
||||
|
||||
<BaseLayout title="AI For Dummies — Route map" description="">
|
||||
<link slot="styles" rel="stylesheet" href={chaptersStylesheet} />
|
||||
<main>
|
||||
<header class="top">
|
||||
<a href={`${base}full-guide/`}>← AI FOR DUMMIES</a>
|
||||
<span>00 / ROUTE MAP</span>
|
||||
<a href={`${base}skills-review/`}>review desk ↗</a>
|
||||
</header>
|
||||
<section class="hero">
|
||||
<p class="eyebrow">Start here</p>
|
||||
<h1>Ship the<br /><em>system.</em></h1>
|
||||
<p>{introduction}</p>
|
||||
</section>
|
||||
<section class="grid">
|
||||
<article class="card">
|
||||
<b>01</b><h2>Models</h2><p>Capability and effort are separate knobs.</p><a
|
||||
href={`${base}models/`}>Open chapter →</a
|
||||
>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>02</b><h2>Agents & trees</h2><p>Bound roles, handoffs, and worktrees.</p><a
|
||||
href={`${base}agents/`}>Open chapter →</a
|
||||
>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>03</b><h2>Skills</h2><p>Capture repeatable decisions.</p><a href={`${base}skills/`}
|
||||
>Open chapter →</a
|
||||
>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>04</b><h2>Rules</h2><p>Connect guidance to enforcement.</p><a href={`${base}rules/`}
|
||||
>Open chapter →</a
|
||||
>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>05</b><h2>Practice</h2><p>Compare prompts and skill-enabled runs.</p><a
|
||||
href={`${base}hands-on/starter/`}>Open lab →</a
|
||||
>
|
||||
</article>
|
||||
<article class="card">
|
||||
<b>06</b><h2>Review desk</h2><p>Browse original files and improved drafts.</p><a
|
||||
href={`${base}skills-review/`}>Open desk →</a
|
||||
>
|
||||
</article>
|
||||
</section>
|
||||
<nav class="links">
|
||||
<a href={`${base}full-guide/`}>Full field guide</a>
|
||||
<a href={`${base}docs/operations-guide.md`}>Operations guide</a>
|
||||
</nav>
|
||||
<footer>
|
||||
Each chapter stands alone; the order follows a real task becoming a reliable change.
|
||||
</footer>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user