chore: merge main into task 19
This commit is contained in:
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Compare the *rendered* text of a legacy page against its Astro replacement.
|
||||||
|
//
|
||||||
|
// node .agents/scripts/rendered-text-diff.mjs full-guide
|
||||||
|
// node .agents/scripts/rendered-text-diff.mjs full-guide --pt
|
||||||
|
//
|
||||||
|
// Why this exists: scripts/verify.mjs reads the legacy files, so a migrated
|
||||||
|
// page can drop half its content and still pass the gate. Task 15d shipped
|
||||||
|
// /full-guide/ missing 86 rendered spans -- the entire verification section,
|
||||||
|
// the hands-on exercise brief, and both "Clone from Gitea" links -- and every
|
||||||
|
// check was green.
|
||||||
|
//
|
||||||
|
// Static HTML comparison is useless here: the guide's tab panels are injected
|
||||||
|
// by an island at runtime, so half the legacy page's markup has no static
|
||||||
|
// counterpart. This walks the live DOM instead and skips anything the browser
|
||||||
|
// is not painting -- which also drops the hidden Portuguese half of each
|
||||||
|
// bilingual pair, so the two sides line up.
|
||||||
|
//
|
||||||
|
// Requires playwright (devDependency) and two static servers; it starts both.
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import { cpSync, mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { createServer } from 'node:net';
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
// `index` is the landing page: it lives at the repository root, not in a
|
||||||
|
// directory of its own, so it needs a different path on the legacy side.
|
||||||
|
const route = process.argv[2];
|
||||||
|
if (!route) {
|
||||||
|
console.error('usage: rendered-text-diff.mjs <route> [--pt] e.g. full-guide, or index');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `--pt` clicks the language toggle on both pages first. English parity is
|
||||||
|
// only half the contract: a page can render every English string and still
|
||||||
|
// leave a restored block untranslated, because the Portuguese half is a
|
||||||
|
// separate set of nodes. Only /full-guide/ and /rules/ have a toggle.
|
||||||
|
const portuguese = process.argv.includes('--pt');
|
||||||
|
const legacyPath = route === 'index' ? 'index.html' : `${route}/index.html`;
|
||||||
|
const astroPath = route === 'index' ? '' : `${route}/`;
|
||||||
|
|
||||||
|
// The built site expects to be served under the configured base path.
|
||||||
|
const staging = mkdtempSync(join(tmpdir(), 'af-rtd-'));
|
||||||
|
cpSync('dist', join(staging, 'ai-for-dummies'), { recursive: true });
|
||||||
|
|
||||||
|
// Ask the kernel for a free port rather than pinning one. Back-to-back runs
|
||||||
|
// used to collide: the previous run's server was still holding the fixed port
|
||||||
|
// while its staging directory had already been deleted, so every page came
|
||||||
|
// back as a 404 and the diff reported the whole route missing.
|
||||||
|
const freePort = () =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const probe = createServer();
|
||||||
|
probe.on('error', reject);
|
||||||
|
probe.listen(0, '127.0.0.1', () => {
|
||||||
|
const { port } = probe.address();
|
||||||
|
probe.close(() => resolve(port));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const legacyPort = await freePort();
|
||||||
|
const astroPort = await freePort();
|
||||||
|
|
||||||
|
const serve = (dir, port) =>
|
||||||
|
spawn('python3', ['-m', 'http.server', String(port), '-d', dir], { stdio: 'ignore' });
|
||||||
|
const servers = [serve('.', legacyPort), serve(staging, astroPort)];
|
||||||
|
const stop = () => {
|
||||||
|
servers.forEach((s) => s.kill());
|
||||||
|
rmSync(staging, { recursive: true, force: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Visible text nodes, in document order, whitespace collapsed.
|
||||||
|
const visibleText = () => {
|
||||||
|
const out = [];
|
||||||
|
const walk = (node) => {
|
||||||
|
for (const child of node.childNodes) {
|
||||||
|
if (child.nodeType === Node.TEXT_NODE) {
|
||||||
|
const text = child.textContent.replace(/\s+/g, ' ').trim();
|
||||||
|
if (text) out.push(text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (child.nodeType !== Node.ELEMENT_NODE) continue;
|
||||||
|
if (child.tagName === 'SCRIPT' || child.tagName === 'STYLE') continue;
|
||||||
|
const style = getComputedStyle(child);
|
||||||
|
if (child.hidden || style.display === 'none' || style.visibility === 'hidden') continue;
|
||||||
|
walk(child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(document.body);
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const grab = async (url) => {
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1400, height: 1000 } });
|
||||||
|
const response = await page.goto(url, { waitUntil: 'load' });
|
||||||
|
// A 404 renders as four spans of python's error page and the diff then
|
||||||
|
// reports the entire route as missing, which reads exactly like a real
|
||||||
|
// regression. Fail loudly instead.
|
||||||
|
if (!response || !response.ok()) {
|
||||||
|
throw new Error(`${url} returned ${response ? response.status() : 'no response'}`);
|
||||||
|
}
|
||||||
|
// The islands hydrate and render their initial panel on load; without this
|
||||||
|
// every panel's copy reads as missing.
|
||||||
|
await page.waitForTimeout(1200);
|
||||||
|
if (portuguese) {
|
||||||
|
const toggle = await page.$('[data-lang="pt"]');
|
||||||
|
if (!toggle) throw new Error(`no language toggle on ${url}`);
|
||||||
|
await toggle.click();
|
||||||
|
await page.waitForTimeout(1200);
|
||||||
|
}
|
||||||
|
// Islands hydrate at their own pace, and the language toggle repaints in
|
||||||
|
// more than one frame. A single read after a fixed wait is flaky, so read
|
||||||
|
// until two consecutive reads agree.
|
||||||
|
let spans = await page.evaluate(visibleText);
|
||||||
|
for (let i = 0; i < 10; i += 1) {
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
const next = await page.evaluate(visibleText);
|
||||||
|
if (next.length === spans.length && next.every((span, j) => span === spans[j])) {
|
||||||
|
spans = next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
spans = next;
|
||||||
|
}
|
||||||
|
await page.close();
|
||||||
|
return spans;
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacy = await grab(`http://localhost:${legacyPort}/${legacyPath}`);
|
||||||
|
const astro = await grab(`http://localhost:${astroPort}/ai-for-dummies/${astroPath}`);
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
// Count occurrences, not membership. A set comparison reports zero when a
|
||||||
|
// string the legacy page paints four times is painted three times here --
|
||||||
|
// exactly the kind of near-miss that got past the earlier checks.
|
||||||
|
const tally = (spans) => {
|
||||||
|
const counts = new Map();
|
||||||
|
for (const span of spans) counts.set(span, (counts.get(span) || 0) + 1);
|
||||||
|
return counts;
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyCounts = tally(legacy);
|
||||||
|
const astroCounts = tally(astro);
|
||||||
|
|
||||||
|
const missing = [];
|
||||||
|
for (const [span, count] of legacyCounts) {
|
||||||
|
const short = count - (astroCounts.get(span) || 0);
|
||||||
|
for (let i = 0; i < short; i += 1) missing.push(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both directions. A string the Astro page paints and the legacy page does
|
||||||
|
// not is just as wrong: it means a translation was invented, or an English
|
||||||
|
// string was left standing where the legacy page swaps it.
|
||||||
|
const extra = [];
|
||||||
|
for (const [span, count] of astroCounts) {
|
||||||
|
const over = count - (legacyCounts.get(span) || 0);
|
||||||
|
for (let i = 0; i < over; i += 1) extra.push(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order counts too. Both pages can paint the same strings while a block
|
||||||
|
// sits in the wrong place -- the Portuguese eyebrow, or a reordered card
|
||||||
|
// deck -- and a count-only comparison calls that clean.
|
||||||
|
const firstOutOfOrder = legacy.findIndex((span, i) => astro[i] !== span);
|
||||||
|
|
||||||
|
const mode = portuguese ? 'pt' : 'en';
|
||||||
|
console.log(
|
||||||
|
`${mode} · legacy ${legacy.length} spans · astro ${astro.length} spans · missing ${missing.length} · extra ${extra.length}`,
|
||||||
|
);
|
||||||
|
for (const span of missing) console.log(` - ${span}`);
|
||||||
|
for (const span of extra) console.log(` + ${span}`);
|
||||||
|
if (firstOutOfOrder !== -1) {
|
||||||
|
console.log(` order diverges at span ${firstOutOfOrder}`);
|
||||||
|
console.log(` legacy: ${legacy[firstOutOfOrder]}`);
|
||||||
|
console.log(` astro: ${astro[firstOutOfOrder]}`);
|
||||||
|
}
|
||||||
|
process.exitCode = missing.length === 0 && extra.length === 0 && firstOutOfOrder === -1 ? 0 : 1;
|
||||||
|
} finally {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
# Task 15f — Restore the content 15d dropped from /full-guide/
|
||||||
|
|
||||||
|
**Agent**: `page-migrator` · **Model**: Codex **Depends on**: 15d **Blocks**:
|
||||||
|
19, 20 **Worktree**: `.agents/scripts/worktree.sh start 15f full-guide-restore`
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
`/full-guide/` is missing about a fifth of the page. Put it back, in the right
|
||||||
|
place, bilingual where the legacy page is bilingual.
|
||||||
|
|
||||||
|
Task 15d assembled the page and passed every check. It also dropped 87 rendered
|
||||||
|
text spans. One of them — the `.chapter-route` section, the only route out of
|
||||||
|
the guide to the summary, models, agents and skills chapters — has already been
|
||||||
|
restored (`c2b35d5`). The remaining 86 are your task.
|
||||||
|
|
||||||
|
Nothing caught this. `scripts/verify.mjs` reads `full-guide/index.html`, the
|
||||||
|
legacy file, which still has every one of these sections. The gate was green the
|
||||||
|
whole time. Task 19 is blocked on you: it re-pointed its assertions at `dist/`
|
||||||
|
and they now fail, correctly, on exactly this content.
|
||||||
|
|
||||||
|
## How to see the gap
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm run build
|
||||||
|
node .agents/scripts/rendered-text-diff.mjs full-guide
|
||||||
|
```
|
||||||
|
|
||||||
|
It walks the live DOM of both pages and prints the text the legacy page paints
|
||||||
|
and the Astro page does not. It skips hidden nodes, so the Portuguese half of
|
||||||
|
each bilingual pair does not register as a difference, and it waits for the
|
||||||
|
islands to hydrate, so the tab panels' injected copy counts as present.
|
||||||
|
|
||||||
|
**This script reaching zero is the task.** Do not edit it to make it pass.
|
||||||
|
|
||||||
|
## What is missing
|
||||||
|
|
||||||
|
Whole blocks, not scattered strings. By CSS class, present in
|
||||||
|
`full-guide/index.html` and absent from `dist/full-guide/index.html`:
|
||||||
|
|
||||||
|
`verify-intro`, `verify-cta`, `verify-cta-grid`, `verify-card`,
|
||||||
|
`verify-card-source`, `verify-antipatterns`, `ap-grid`, `comparison-strip`,
|
||||||
|
`exercise-brief`, `builder-intro`, `builder-loop`, `builder-artifact`,
|
||||||
|
`artifact-head`, `artifact-command`, `starter-link-group`, `starter-link-source`
|
||||||
|
|
||||||
|
That covers, at minimum: the three-layer verification section and its code
|
||||||
|
lines, the "four ways a green report is false" grid, the two hands-on lab cards
|
||||||
|
with both "Clone from Gitea →" links, the exercise brief (stack / dependencies /
|
||||||
|
files), the four-row comparison strip, and the skill-forge output package tree
|
||||||
|
with its validate and after-real-use panels.
|
||||||
|
|
||||||
|
The exact 86 spans, in document order:
|
||||||
|
|
||||||
|
```
|
||||||
|
- default start
|
||||||
|
- name: review-ui · check focus, mobile, reduced motion · run verification · return evidence
|
||||||
|
- The skill forge
|
||||||
|
- Teach the decision.
|
||||||
|
- Keep the context
|
||||||
|
- light.
|
||||||
|
- Do not package everything you know. Capture the non-obvious choices that repeatedly improve an outcome, then prove the skill changes behavior.
|
||||||
|
- Observe
|
||||||
|
- find repeated friction
|
||||||
|
- Define trigger
|
||||||
|
- route precisely
|
||||||
|
- Choose anatomy
|
||||||
|
- only needed files
|
||||||
|
- Write guidance
|
||||||
|
- decisions, not trivia
|
||||||
|
- Validate
|
||||||
|
- test real behavior
|
||||||
|
- OUTPUT / SKILL PACKAGE
|
||||||
|
- review-ui/
|
||||||
|
- ├── SKILL.md
|
||||||
|
- ├── agents/
|
||||||
|
- │ └── openai.yaml
|
||||||
|
- ├── references/
|
||||||
|
- │ └── accessibility.md
|
||||||
|
- └── scripts/
|
||||||
|
- └── verify.mjs
|
||||||
|
- VALIDATE
|
||||||
|
- quick_validate.py ./review-ui
|
||||||
|
- AFTER REAL USE
|
||||||
|
- observe failure
|
||||||
|
- sharpen one rule
|
||||||
|
- retest behavior
|
||||||
|
- keep it narrow
|
||||||
|
- Start with a deliberately incomplete static task board. Run one prompt as written, reset, then run the skill-enabled version. Compare diff size, verification evidence, and unnecessary complexity.
|
||||||
|
- Clone from Gitea →
|
||||||
|
- Clone from Gitea →
|
||||||
|
- THE MISSING FEATURE
|
||||||
|
- Add All / Open / Done filters that survive reload and browser navigation.
|
||||||
|
- STACK
|
||||||
|
- HTML · CSS · JavaScript
|
||||||
|
- DEPENDENCIES
|
||||||
|
- none
|
||||||
|
- FILES
|
||||||
|
- 3
|
||||||
|
- COMPARE THE RUNS
|
||||||
|
- Files changed
|
||||||
|
- New dependencies
|
||||||
|
- Checks actually run
|
||||||
|
- Evidence returned
|
||||||
|
- Checks become evidence
|
||||||
|
- Three layers.
|
||||||
|
- Run each one alone.
|
||||||
|
- Run a gate on its own line, print its exit code, attach the output. The result is the deliverable.
|
||||||
|
- Format, lint, type-check. Fast and scoped to one file. Run on every save.
|
||||||
|
- pnpm lint; echo "lint=$?" pnpm typecheck; echo "typecheck=$?"
|
||||||
|
- pnpm test; echo "test=$?" cd services/api && go test ./...
|
||||||
|
- Drive the actual UI, API, or browser. Slower and flakier — only this catches mobile overflow and a missing 404.
|
||||||
|
- pnpm check:ui; echo "ui=$?" TURBO_FORCE=true pnpm e2e
|
||||||
|
- FOUR WAYS A GREEN REPORT IS FALSE
|
||||||
|
- 1
|
||||||
|
- Pipe a gate
|
||||||
|
- tail, grep, or head hide the real exit code — a pipeline returns the last command's status.
|
||||||
|
- 2
|
||||||
|
- Swallow a rejection
|
||||||
|
- A silent
|
||||||
|
- .catch(() => {})
|
||||||
|
- hides a panic, an upstream limit, or a partial failure.
|
||||||
|
- 3
|
||||||
|
- Trust the cache
|
||||||
|
- Turbo caches results. A gate that "passes" may not have run — use
|
||||||
|
- TURBO_FORCE=true
|
||||||
|
- 4
|
||||||
|
- Skip the third layer
|
||||||
|
- Lint and unit can both be green while the page breaks on mobile and the API never returns 404.
|
||||||
|
- RUN IT YOURSELF · two labs, under 10 minutes each
|
||||||
|
- Path A · verification lab
|
||||||
|
- Fill the four-row comparison strip on the starter. Run A naively, Run B with
|
||||||
|
- $gate-discipline
|
||||||
|
- and
|
||||||
|
- $webapp-testing
|
||||||
|
- Clone ↗
|
||||||
|
- git.marcospaulo.dev.br/.../src/branch/pages/hands-on/starter
|
||||||
|
- Path B · rules lab
|
||||||
|
- Toggle every rule off, run the prompt. Toggle every rule on, run it again. Compare diff size, gate invocations, and the names of checks the agent names back.
|
||||||
|
- Clone ↗
|
||||||
|
- git.marcospaulo.dev.br/.../src/branch/pages/hands-on/rules
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
1. Read the legacy source for each block out of `full-guide/index.html`. Copy
|
||||||
|
the strings; do not retype them. Several contain box-drawing characters
|
||||||
|
(`├──`, `└──`), `·` separators, and `$`-prefixed skill names.
|
||||||
|
2. Place each block where the legacy page has it — the section order is part of
|
||||||
|
the argument the page is making.
|
||||||
|
3. Bilingual pairs follow `.agents/rules/content-i18n.md`: render the fragment
|
||||||
|
twice, `data-language-content="en"` visible and `data-language-content="pt"`
|
||||||
|
hidden. **Check `translations.pt` in `app.js` before assuming a block is
|
||||||
|
bilingual.** Several of these are English-only on the live site —
|
||||||
|
`.chapter-route` was — and inventing Portuguese for them is a regression in
|
||||||
|
the other direction.
|
||||||
|
4. Reuse the existing blocks in `src/components/blocks/`. If a block does not
|
||||||
|
exist, this is assembly work that 15d should have done and you may write the
|
||||||
|
markup inline in the page, as 15d did elsewhere. Do not write a new island.
|
||||||
|
|
||||||
|
## Do not
|
||||||
|
|
||||||
|
- Do not touch `full-guide/index.html`, `app.js`, or any other legacy file.
|
||||||
|
- Do not weaken or delete an assertion in `scripts/verify.mjs`.
|
||||||
|
- Do not reformat files you are not restoring content into. 15d ran prettier
|
||||||
|
across the whole repo on one attempt and it had to be reverted.
|
||||||
|
|
||||||
|
## Done when
|
||||||
|
|
||||||
|
- [ ] `node .agents/scripts/rendered-text-diff.mjs full-guide` reports 0 missing
|
||||||
|
- [ ] Every restored bilingual block has both `en` and `pt`; every English-only
|
||||||
|
block is English-only in `translations.pt` too, and you say which is which
|
||||||
|
- [ ] Section order matches the legacy page
|
||||||
|
- [ ] `pnpm run gate` green
|
||||||
|
|
||||||
|
## Attempt 1: English is exact, Portuguese is not
|
||||||
|
|
||||||
|
`8f82304`, tagged `rejected/15f-attempt-1`. Keep it and continue from it — the
|
||||||
|
English restoration is correct and complete:
|
||||||
|
|
||||||
|
```
|
||||||
|
en · legacy 432 spans · astro 432 spans · missing 0 · extra 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Every block is back, in the legacy order, with no invented content. That half of
|
||||||
|
the task is done.
|
||||||
|
|
||||||
|
The Portuguese half was not checked, because the tool could not check it when
|
||||||
|
you started. `rendered-text-diff.mjs` now takes `--pt`: it clicks the language
|
||||||
|
toggle on both pages before reading, and it reports **both** directions — a
|
||||||
|
string the Astro page paints and the legacy page does not is as wrong as one it
|
||||||
|
drops. On your branch:
|
||||||
|
|
||||||
|
```
|
||||||
|
pt · legacy 431 spans · astro 431 spans · missing 27 · extra 26
|
||||||
|
```
|
||||||
|
|
||||||
|
The totals match because nothing is structurally missing. The content is English
|
||||||
|
where the legacy page shows Portuguese.
|
||||||
|
|
||||||
|
### Two separate causes — fix both
|
||||||
|
|
||||||
|
**1. The blocks you restored are English-only under PT.** "Create a skill",
|
||||||
|
"INSTALL PACK", "Hands-on", "Tiny Tasks lab", "Good prompt", both long lab
|
||||||
|
prompts, the two `Clone from Gitea →` links and the rest of the hands-on and
|
||||||
|
skill-forge copy. The Portuguese for these is in `translations.pt` in `app.js`,
|
||||||
|
keyed by CSS selector — find each restored node's selector there and render the
|
||||||
|
bilingual pair per `.agents/rules/content-i18n.md`.
|
||||||
|
|
||||||
|
Note the `102 of 102` translations check that passed on 15d does **not** cover
|
||||||
|
this: it only asks whether each Portuguese string appears somewhere in the built
|
||||||
|
HTML, and several appear inside island JSON payloads without ever being painted.
|
||||||
|
`--pt` is the real check.
|
||||||
|
|
||||||
|
**2. Pre-existing over-translation in the hero, from 15d, not from you.** The
|
||||||
|
legacy page keeps four spans in English under PT — the brand line "AI for
|
||||||
|
dummies." and the "THINK" / "MAKE" labels. The Astro page translates them to "IA
|
||||||
|
para iniciantes." and "PENSE" / "FAÇA". `translations.pt` has no entry for them,
|
||||||
|
so English is the correct Portuguese rendering. Reconcile to legacy.
|
||||||
|
|
||||||
|
### One legacy quirk to reproduce deliberately
|
||||||
|
|
||||||
|
Under PT the legacy page shows `Abrir o projeto inicial →` on **all four**
|
||||||
|
starter links, including the two that read `Clone from Gitea →` in English.
|
||||||
|
`applyLanguage` sets the same text on every `.starter-link` match. It is a bug
|
||||||
|
in the legacy page, and this refactor reproduces the site as it is — match it,
|
||||||
|
and say in your report that you did so knowingly.
|
||||||
|
|
||||||
|
### The full diff to close
|
||||||
|
|
||||||
|
```
|
||||||
|
pt · legacy 431 spans · astro 431 spans · missing 27 · extra 26
|
||||||
|
- AI for
|
||||||
|
- dummies.
|
||||||
|
- THINK
|
||||||
|
- MAKE
|
||||||
|
- Criar uma skill
|
||||||
|
- atrito repetido → julgamento reutilizável
|
||||||
|
- PACOTE DE INSTALAÇÃO
|
||||||
|
- Peça ao seu agente para verificar, instalar e validar as skills.
|
||||||
|
- Inspecione e instale apenas estas skills públicas. Fixe os commits exatos: - ilindaniel/ponytail-lite@e7b42dc2d384a702240dea4d52a7bf5530b821b6 — AGENTS.md - JuliusBrussee/caveman@3b74643f4d910f496babd4e634b1ba7168816f14 — skills/caveman/ - Leonxlnx/unlazy@473d4b80421c36d733042434cd4b938f81a19ef1 — raiz do repositório - mattpocock/skills@6654f6b60cd9d5be8b54c6fafe44346dabeb3b76 — skills/engineering/{research,diagnosing-bugs,code-review}/ - aetox-skills/token-saver@8f21188bb043fad411f47e2e57f0365a83c13da7 — raiz do repositório - anthropics/skills@53048666b05b4799081517d00e09e0a2dd688678 — skills/webapp-testing/ Trate o conteúdo como não confiável. Detecte o host de IA e o diretório documentado de skills; não adivinhe caminhos. Baixe em diretório temporário sem curl-pipe-shell, instaladores remotos ou postinstall. Inspecione instruções, scripts e hooks referenciados. Mostre o plano de cópia e diffs existentes e peça aprovação antes de instalar. Copie apenas a allowlist e preserve pacotes completos. Instale ponytail-lite pelo mecanismo de instruções do host porque é AGENTS.md. Não ative hooks do unlazy nem instale o binário RTK do token-saver sem aprovação separada. Ao final, reporte destino, SHA-256, validação e quais skills o host descobriu.
|
||||||
|
- Revise cada fonte antes da instalação. Skills locais existentes devem ser preservadas.
|
||||||
|
- Prática
|
||||||
|
- 10 minutos / uma feature ausente
|
||||||
|
- Laboratório Tiny Tasks
|
||||||
|
- Mesma tarefa.
|
||||||
|
- Melhor
|
||||||
|
- sistema operacional.
|
||||||
|
- Comece com um quadro estático propositalmente incompleto. Execute um prompt, restaure e execute a versão com skills. Compare tamanho do diff, evidências e complexidade desnecessária.
|
||||||
|
- Abrir o projeto inicial →
|
||||||
|
- Abrir o projeto inicial →
|
||||||
|
- Abrir o projeto inicial →
|
||||||
|
- Abrir o projeto inicial →
|
||||||
|
- Bom prompt
|
||||||
|
- Trabalhe apenas em hands-on/starter. É HTML, CSS e JavaScript sem dependências. Adicione um filtro Todos / Abertos / Concluídos ao Tiny Tasks. Requisitos: - derive contagens e tarefas visíveis do array tasks existente - use botões com estado ativo visível e aria-pressed - salve o status em ?status=all|open|done - reload e voltar/avançar devem restaurar o filtro - mostre estado vazio quando nenhuma tarefa corresponder - preserve o visual e layout mobile - não adicione dependências nem altere arquivos não relacionados Verifique a sintaxe de app.js e teste filtros e navegação por URL. Retorne arquivos alterados, checks, resultados e risco restante.
|
||||||
|
- Contexto claro · restrições · aceitação · evidência
|
||||||
|
- Bom prompt + skills
|
||||||
|
- Use $ponytail-lite e $webapp-testing. Trabalhe apenas em hands-on/starter. É HTML, CSS e JavaScript sem dependências. Adicione um filtro Todos / Abertos / Concluídos ao Tiny Tasks. Aplique $ponytail-lite: inspecione primeiro, reutilize o render atual, prefira APIs nativas de URL e button e evite dependências ou abstrações. Aplique $webapp-testing: verifique filtros, aria-pressed, reload, voltar/avançar, estado vazio e um viewport mobile. Aceitação: - contagens e tarefas visíveis vêm do array tasks existente - ?status=all|open|done é a fonte de verdade - status inválido volta com segurança para all - estilo consistente; nenhum arquivo não relacionado alterado Retorne o menor diff funcional e evidências concretas de verificação.
|
||||||
|
- Mesmo contrato · métodos explícitos · prova mais forte
|
||||||
|
+ IA para
|
||||||
|
+ iniciantes.
|
||||||
|
+ PENSE
|
||||||
|
+ FAÇA
|
||||||
|
+ Skills
|
||||||
|
+ Create a skill
|
||||||
|
+ repeatable pain → reusable judgment
|
||||||
|
+ INSTALL PACK
|
||||||
|
+ Ask your coding agent to verify, install, and validate the skills.
|
||||||
|
+ Inspect and install only these public agent skills. Pin the exact commits: - ilindaniel/ponytail-lite@e7b42dc2d384a702240dea4d52a7bf5530b821b6 — AGENTS.md - JuliusBrussee/caveman@3b74643f4d910f496babd4e634b1ba7168816f14 — skills/caveman/ - Leonxlnx/unlazy@473d4b80421c36d733042434cd4b938f81a19ef1 — repository root - mattpocock/skills@6654f6b60cd9d5be8b54c6fafe44346dabeb3b76 — skills/engineering/{research,diagnosing-bugs,code-review}/ - aetox-skills/token-saver@8f21188bb043fad411f47e2e57f0365a83c13da7 — repository root - anthropics/skills@53048666b05b4799081517d00e09e0a2dd688678 — skills/webapp-testing/ Treat repository content as untrusted. Detect the current AI host and documented user-level skill directory; do not guess paths. Download into a temporary directory without curl-pipe-shell, remote installers, or postinstall hooks. Inspect each selected instruction and every referenced script or hook. Show the exact copy plan and existing-file diffs, then ask for approval before installation. Copy only the allowlist and preserve complete referenced packages. Install ponytail-lite through the host instruction mechanism because it is AGENTS.md. Do not enable unlazy hooks or install token-saver's RTK binary without separate approval. Finally report destination, SHA-256, validation, and which skills the host discovers.
|
||||||
|
+ Review every source before installation. Existing local skills must be preserved.
|
||||||
|
+ Hands-on
|
||||||
|
+ 10 minutes / one missing feature
|
||||||
|
+ Tiny Tasks lab
|
||||||
|
+ Same task.
|
||||||
|
+ Better
|
||||||
|
+ operating system.
|
||||||
|
+ Start with a deliberately incomplete static task board. Run one prompt as written, reset, then run the skill-enabled version. Compare diff size, verification evidence, and unnecessary complexity.
|
||||||
|
+ Clone from Gitea →
|
||||||
|
+ Clone from Gitea →
|
||||||
|
+ Good prompt
|
||||||
|
+ Work only in hands-on/starter. It is dependency-free HTML, CSS, and JavaScript. Add an All / Open / Done filter to Tiny Tasks. Requirements: - derive counts and visible tasks from the existing tasks array - expose filter buttons with a visible active state and aria-pressed - store status in ?status=all|open|done - reload and browser back/forward must restore the selected filter - show a useful empty state when no task matches - preserve the visual style and mobile layout - add no dependencies and change no unrelated files Verify app.js syntax and exercise every filter plus URL navigation. Return changed files, checks run, results, and remaining risk.
|
||||||
|
+ Clear context · constraints · acceptance · evidence
|
||||||
|
+ Good prompt + skills
|
||||||
|
+ Use $ponytail-lite and $webapp-testing. Work only in hands-on/starter. It is dependency-free HTML, CSS, and JavaScript. Add an All / Open / Done filter to Tiny Tasks. Apply $ponytail-lite: inspect first, reuse the current render flow, prefer native URL and button APIs, and avoid dependencies or abstractions. Apply $webapp-testing: verify all filters, aria-pressed, reload, browser back/forward, empty state, and one mobile viewport. Acceptance: - counts and visible tasks come from the existing tasks array - ?status=all|open|done is the source of truth - invalid status falls back safely to all - style remains consistent; unrelated files remain untouched Return the smallest working diff and concrete verification evidence.
|
||||||
|
+ Same contract · explicit working methods · stronger proof
|
||||||
|
```
|
||||||
|
|
||||||
|
## Done when (attempt 2)
|
||||||
|
|
||||||
|
- [ ] `node .agents/scripts/rendered-text-diff.mjs full-guide` →
|
||||||
|
`missing 0 · extra 0`
|
||||||
|
- [ ] `node .agents/scripts/rendered-text-diff.mjs full-guide --pt` →
|
||||||
|
`missing 0 · extra 0`
|
||||||
|
- [ ] No legacy file touched; no assertion in `scripts/verify.mjs` touched
|
||||||
|
- [ ] `pnpm run gate` green
|
||||||
@@ -121,7 +121,8 @@ const { target } = Astro.props;
|
|||||||
if (!id) return;
|
if (!id) return;
|
||||||
const target = document.querySelector('#' + id);
|
const target = document.querySelector('#' + id);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
const value = target.textContent || '';
|
const visible = target.querySelector(':scope > [data-language-content]:not([hidden])');
|
||||||
|
const value = visible?.textContent || target.textContent || '';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof navigator !== 'undefined' &&
|
typeof navigator !== 'undefined' &&
|
||||||
|
|||||||
@@ -403,8 +403,13 @@ fix(api): scope session query</code></pre>
|
|||||||
select('[data-skill]', id, 'skill');
|
select('[data-skill]', id, 'skill');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keys whose copy carries markup, so the swap must write innerHTML.
|
||||||
|
// `skillsText` was missing while the markup renders it with `set:html`:
|
||||||
|
// the island rewrote it as text on load and every visitor saw a literal
|
||||||
|
// `<code>.agents/skills/</code>` in the paragraph.
|
||||||
const HTML_KEYS = {
|
const HTML_KEYS = {
|
||||||
heroTitle: 1,
|
heroTitle: 1,
|
||||||
|
skillsText: 1,
|
||||||
pipelineTitle: 1,
|
pipelineTitle: 1,
|
||||||
skillsTitle: 1,
|
skillsTitle: 1,
|
||||||
ratchetTitle: 1,
|
ratchetTitle: 1,
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
"skillReadSkill": "READ SKILL ↗",
|
"skillReadSkill": "READ SKILL ↗",
|
||||||
"skillReadSkillPt": "LER SKILL ↗",
|
"skillReadSkillPt": "LER SKILL ↗",
|
||||||
"skillTriggerLabel": "TRIGGER",
|
"skillTriggerLabel": "TRIGGER",
|
||||||
"skillTriggerLabelPt": "GATILHO",
|
|
||||||
"copyStatus": "Prompt copied.",
|
"copyStatus": "Prompt copied.",
|
||||||
"copyStatusPt": "Prompt copiado.",
|
"copyStatusPt": "Prompt copiado.",
|
||||||
"copyFallback": "Select the text manually.",
|
"copyFallback": "Select the text manually.",
|
||||||
@@ -118,8 +117,7 @@
|
|||||||
"stageOpenSourcePt": "ABRIR FONTE ↗",
|
"stageOpenSourcePt": "ABRIR FONTE ↗",
|
||||||
"skillReadSkill": "LER SKILL ↗",
|
"skillReadSkill": "LER SKILL ↗",
|
||||||
"skillReadSkillPt": "LER SKILL ↗",
|
"skillReadSkillPt": "LER SKILL ↗",
|
||||||
"skillTriggerLabel": "TRIGGER",
|
"skillTriggerLabel": "GATILHO",
|
||||||
"skillTriggerLabelPt": "GATILHO",
|
|
||||||
"copyStatus": "Prompt copied.",
|
"copyStatus": "Prompt copied.",
|
||||||
"copyStatusPt": "Prompt copiado.",
|
"copyStatusPt": "Prompt copiado.",
|
||||||
"copyFallback": "Select the text manually.",
|
"copyFallback": "Select the text manually.",
|
||||||
|
|||||||
+388
-39
@@ -55,6 +55,9 @@ const efforts = toRecord(effortEntries);
|
|||||||
const skillFiles = toRecord(skillFileEntries);
|
const skillFiles = toRecord(skillFileEntries);
|
||||||
const skillWorkflow = toRecord(workflowEntries);
|
const skillWorkflow = toRecord(workflowEntries);
|
||||||
const commonSkills = toRecord(commonSkillEntries);
|
const commonSkills = toRecord(commonSkillEntries);
|
||||||
|
const sortedCommonSkills = Object.values(commonSkills).sort((a, b) =>
|
||||||
|
a.number.localeCompare(b.number),
|
||||||
|
);
|
||||||
const handsOn = promptEntries.find(({ data }) => data.id === 'tiny-tasks')?.data;
|
const handsOn = promptEntries.find(({ data }) => data.id === 'tiny-tasks')?.data;
|
||||||
const install = installEntries.find(({ data }) => data.id === 'install')?.data;
|
const install = installEntries.find(({ data }) => data.id === 'install')?.data;
|
||||||
if (!handsOn || !install) throw new Error('full-guide content collections are incomplete');
|
if (!handsOn || !install) throw new Error('full-guide content collections are incomplete');
|
||||||
@@ -82,6 +85,31 @@ const labels = {
|
|||||||
source: { en: 'GITHUB SOURCE ↗', pt: 'FONTE NO GITHUB ↗' },
|
source: { en: 'GITHUB SOURCE ↗', pt: 'FONTE NO GITHUB ↗' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const builderSteps: Record<
|
||||||
|
string,
|
||||||
|
{ title: { en: string; pt: string }; tagline: { en: string; pt: string } }
|
||||||
|
> = {
|
||||||
|
observe: {
|
||||||
|
title: { en: 'Observe', pt: 'Observar' },
|
||||||
|
tagline: { en: 'find repeated friction', pt: 'encontre atrito repetido' },
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
title: { en: 'Define trigger', pt: 'Definir gatilho' },
|
||||||
|
tagline: { en: 'route precisely', pt: 'roteie com precisão' },
|
||||||
|
},
|
||||||
|
scaffold: {
|
||||||
|
title: { en: 'Choose anatomy', pt: 'Escolher anatomia' },
|
||||||
|
tagline: { en: 'only needed files', pt: 'apenas arquivos necessários' },
|
||||||
|
},
|
||||||
|
write: {
|
||||||
|
title: { en: 'Write guidance', pt: 'Escrever orientação' },
|
||||||
|
tagline: { en: 'decisions, not trivia', pt: 'decisões, não trivialidades' },
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
title: { en: 'Validate', pt: 'Validar' },
|
||||||
|
tagline: { en: 'test real behavior', pt: 'teste comportamento real' },
|
||||||
|
},
|
||||||
|
};
|
||||||
const selectorData = {
|
const selectorData = {
|
||||||
phases,
|
phases,
|
||||||
workers,
|
workers,
|
||||||
@@ -235,7 +263,7 @@ const base = import.meta.env.BASE_URL;
|
|||||||
<section class="hero" id="top-pt" data-language-content="pt" hidden>
|
<section class="hero" id="top-pt" data-language-content="pt" hidden>
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Uma apresentação para quem entrega software</p><h1>
|
<p class="eyebrow">Uma apresentação para quem entrega software</p><h1>
|
||||||
IA para<br /><em>iniciantes.</em>
|
AI for<br /><em>dummies.</em>
|
||||||
</h1><p class="lede">
|
</h1><p class="lede">
|
||||||
Você não precisa de um exército de modelos. Precisa de um sistema: uma mente para
|
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.
|
enquadrar o trabalho, várias mãos para executá-lo e uma fronteira clara entre cada tarefa.
|
||||||
@@ -307,7 +335,7 @@ const base = import.meta.env.BASE_URL;
|
|||||||
>Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.</strong
|
>Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.</strong
|
||||||
>
|
>
|
||||||
</div><div class="signal" aria-hidden="true">
|
</div><div class="signal" aria-hidden="true">
|
||||||
<b>PENSE</b><i></i><i></i><i></i><b>FAÇA</b>
|
<b>THINK</b><i></i><i></i><i></i><b>MAKE</b>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -418,7 +446,8 @@ const base = import.meta.env.BASE_URL;
|
|||||||
data-language-content="pt"
|
data-language-content="pt"
|
||||||
hidden>Clique em uma fase.<br /><em>Veja a passagem.</em></span
|
hidden>Clique em uma fase.<br /><em>Veja a passagem.</em></span
|
||||||
>
|
>
|
||||||
</h2><p>
|
</h2><!-- The legacy PT render paints this paragraph twice; preserve that output. -->
|
||||||
|
<p>
|
||||||
<span data-language-content="en"
|
<span data-language-content="en"
|
||||||
>Delegation means moving one bounded task into a smaller context—not giving away
|
>Delegation means moving one bounded task into a smaller context—not giving away
|
||||||
responsibility.</span
|
responsibility.</span
|
||||||
@@ -690,7 +719,16 @@ const base = import.meta.env.BASE_URL;
|
|||||||
></small
|
></small
|
||||||
></button
|
></button
|
||||||
><button class="active" data-effort="medium" role="tab" aria-selected="true"
|
><button class="active" data-effort="medium" role="tab" aria-selected="true"
|
||||||
>MEDIUM</button
|
><b
|
||||||
|
><span data-language-content="en">MEDIUM</span><span data-language-content="pt" hidden
|
||||||
|
>MÉDIO</span
|
||||||
|
></b
|
||||||
|
><small
|
||||||
|
><span data-language-content="en">default start</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>ponto inicial</span
|
||||||
|
></small
|
||||||
|
></button
|
||||||
><button data-effort="high" role="tab" aria-selected="false"
|
><button data-effort="high" role="tab" aria-selected="false"
|
||||||
><b
|
><b
|
||||||
><span data-language-content="en">HIGH</span><span data-language-content="pt" hidden
|
><span data-language-content="en">HIGH</span><span data-language-content="pt" hidden
|
||||||
@@ -731,8 +769,15 @@ const base = import.meta.env.BASE_URL;
|
|||||||
<section class="skills" id="skills">
|
<section class="skills" id="skills">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">
|
<p class="eyebrow">
|
||||||
|
{
|
||||||
|
/* Under Portuguese the legacy page paints the long skill sentence
|
||||||
|
here, not "Skills": its `.skills > div:first-child > p` selector
|
||||||
|
also matches this eyebrow and overwrites it. Reproduced on
|
||||||
|
purpose -- see .agents/rules/content-i18n.md. */
|
||||||
|
}
|
||||||
<span data-language-content="en">Skills</span><span data-language-content="pt" hidden
|
<span data-language-content="en">Skills</span><span data-language-content="pt" hidden
|
||||||
>Skills</span
|
>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.</span
|
||||||
>
|
>
|
||||||
</p><h2>
|
</p><h2>
|
||||||
<span data-language-content="en">Write the right way<br /><em>once.</em></span><span
|
<span data-language-content="en">Write the right way<br /><em>once.</em></span><span
|
||||||
@@ -778,16 +823,51 @@ const base = import.meta.env.BASE_URL;
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div><pre><code>name: review-ui · check focus, mobile, reduced motion · run verification · return evidence</code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="skill-builder" id="create-skill">
|
<section class="skill-builder" id="create-skill">
|
||||||
<div class="section-label">
|
<div class="section-label">
|
||||||
<span>Create a skill</span><span>repeatable pain → reusable judgment</span>
|
<span
|
||||||
|
><span data-language-content="en">Create a skill</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Criar uma skill</span
|
||||||
|
></span
|
||||||
|
><span
|
||||||
|
><span data-language-content="en">repeatable pain → reusable judgment</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>atrito repetido → julgamento reutilizável</span
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
</div><div class="builder-intro">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">
|
||||||
|
<span data-language-content="en">The skill forge</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>A forja de skills</span
|
||||||
|
>
|
||||||
|
</p><h2>
|
||||||
|
<span data-language-content="en"
|
||||||
|
>Teach the decision.<br />Keep the context <em>light.</em></span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Ensine a decisão.<br />Mantenha o contexto <em>leve.</em></span
|
||||||
|
>
|
||||||
|
</h2>
|
||||||
|
</div><p>
|
||||||
|
<span data-language-content="en"
|
||||||
|
>Do not package everything you know. Capture the non-obvious choices that repeatedly
|
||||||
|
improve an outcome, then prove the skill changes behavior.</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Não empacote tudo o que você sabe. Capture as escolhas não óbvias que melhoram
|
||||||
|
resultados repetidamente e prove que a skill muda o comportamento.</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
</div><div class="builder-workbench">
|
</div><div class="builder-workbench">
|
||||||
<nav class="builder-steps" role="tablist" aria-label="Skill creation workflow">
|
<nav class="builder-steps" role="tablist" aria-label="Skill creation workflow">
|
||||||
{
|
{
|
||||||
Object.values(skillWorkflow).map((step) => (
|
['observe', 'trigger', 'scaffold', 'write', 'validate'].map((id) => {
|
||||||
|
const step = skillWorkflow[id];
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
class:list={{ active: step.id === 'observe' }}
|
class:list={{ active: step.id === 'observe' }}
|
||||||
data-skill-step={step.id}
|
data-skill-step={step.id}
|
||||||
@@ -796,10 +876,28 @@ const base = import.meta.env.BASE_URL;
|
|||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<b>{step.number}</b>
|
<b>{step.number}</b>
|
||||||
<span>{step.title.en}</span>
|
<>
|
||||||
|
<span>
|
||||||
|
<>
|
||||||
|
<span data-language-content="en">{builderSteps[step.id].title.en}</span>
|
||||||
|
<span data-language-content="pt" hidden>
|
||||||
|
{builderSteps[step.id].title.pt}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</span>
|
||||||
|
<small>
|
||||||
|
<>
|
||||||
|
<span data-language-content="en">{builderSteps[step.id].tagline.en}</span>
|
||||||
|
<span data-language-content="pt" hidden>
|
||||||
|
{builderSteps[step.id].tagline.pt}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</small>
|
||||||
|
</>
|
||||||
</>
|
</>
|
||||||
</button>
|
</button>
|
||||||
))
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</nav><article class="builder-detail" id="builder-detail" aria-live="polite">
|
</nav><article class="builder-detail" id="builder-detail" aria-live="polite">
|
||||||
<header>
|
<header>
|
||||||
@@ -819,7 +917,39 @@ const base = import.meta.env.BASE_URL;
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article><aside class="builder-artifact">
|
||||||
|
<div class="artifact-head">
|
||||||
|
<span
|
||||||
|
><span data-language-content="en">OUTPUT / SKILL PACKAGE</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>SAÍDA / PACOTE DE SKILL</span
|
||||||
|
></span
|
||||||
|
><i></i>
|
||||||
|
</div><pre
|
||||||
|
aria-label="Example skill structure"><code>review-ui/<br />├── SKILL.md<br />├── agents/<br />│ └── openai.yaml<br />├── references/<br />│ └── accessibility.md<br />└── scripts/<br /> └── verify.mjs</code></pre><div
|
||||||
|
class="artifact-command"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
><span data-language-content="en">VALIDATE</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>VALIDAR</span
|
||||||
|
></span
|
||||||
|
><code>quick_validate.py ./review-ui</code>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div><div class="builder-loop">
|
||||||
|
<span
|
||||||
|
><span data-language-content="en">AFTER REAL USE</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>APÓS USO REAL</span
|
||||||
|
></span
|
||||||
|
><div data-language-content="en">
|
||||||
|
<b>observe failure</b><i>→</i><b>sharpen one rule</b><i>→</i><b>retest behavior</b><i>→</i
|
||||||
|
><b>keep it narrow</b>
|
||||||
|
</div><div data-language-content="pt" hidden>
|
||||||
|
<b>observar falha</b><i>→</i><b>refinar uma regra</b><i>→</i><b>retestar comportamento</b
|
||||||
|
><i>→</i><b>manter estreita</b>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -866,7 +996,10 @@ const base = import.meta.env.BASE_URL;
|
|||||||
</div><div class="skill-deck">
|
</div><div class="skill-deck">
|
||||||
<div class="skill-index" role="tablist" aria-label="Common agent skills">
|
<div class="skill-index" role="tablist" aria-label="Common agent skills">
|
||||||
{
|
{
|
||||||
Object.values(commonSkills).map((skill) => (
|
/* getCollection's order is not the deck's order. Sort by the
|
||||||
|
card number so the tabs stay 01..07 as the legacy page has
|
||||||
|
them, instead of shifting between builds. */
|
||||||
|
sortedCommonSkills.map((skill) => (
|
||||||
<button
|
<button
|
||||||
class:list={{ active: skill.id === 'ponytail' }}
|
class:list={{ active: skill.id === 'ponytail' }}
|
||||||
data-common-skill={skill.id}
|
data-common-skill={skill.id}
|
||||||
@@ -934,53 +1067,184 @@ const base = import.meta.env.BASE_URL;
|
|||||||
</div><article class="install-skills">
|
</div><article class="install-skills">
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<span>INSTALL PACK</span><strong
|
<span
|
||||||
>Ask your coding agent to verify, install, and validate the skills.</strong
|
><span data-language-content="en">INSTALL PACK</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>PACOTE DE INSTALAÇÃO</span
|
||||||
|
></span
|
||||||
|
><strong
|
||||||
|
><span data-language-content="en"
|
||||||
|
>Ask your coding agent to verify, install, and validate the skills.</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Peça ao seu agente para verificar, instalar e validar as skills.</span
|
||||||
|
></strong
|
||||||
>
|
>
|
||||||
</div><CopyPrompt target="prompt-install-skills" />
|
</div><CopyPrompt target="prompt-install-skills" />
|
||||||
</header><pre><code id="prompt-install-skills">{install.en}</code></pre><footer>
|
</header><pre><code id="prompt-install-skills"><span data-language-content="en">{install.en}</span><span data-language-content="pt" hidden>{install.pt}</span></code></pre><footer
|
||||||
Review every source before installation. Existing local skills must be preserved.
|
>
|
||||||
|
<span data-language-content="en"
|
||||||
|
>Review every source before installation. Existing local skills must be preserved.</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Revise cada fonte antes da instalação. Skills locais existentes devem ser preservadas.</span
|
||||||
|
>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="hands-on" id="hands-on">
|
<section class="hands-on" id="hands-on">
|
||||||
<div class="section-label">
|
<div class="section-label">
|
||||||
<span>Hands-on</span><span>10 minutes / one missing feature</span>
|
<span
|
||||||
|
><span data-language-content="en">Hands-on</span><span data-language-content="pt" hidden
|
||||||
|
>Prática</span
|
||||||
|
></span
|
||||||
|
><span
|
||||||
|
><span data-language-content="en">10 minutes / one missing feature</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>10 minutos / uma feature ausente</span
|
||||||
|
></span
|
||||||
|
>
|
||||||
</div><div class="hands-intro">
|
</div><div class="hands-intro">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Tiny Tasks lab</p><h2>
|
<p class="eyebrow">
|
||||||
Same task.<br />Better <em>operating system.</em>
|
<span data-language-content="en">Tiny Tasks lab</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Laboratório Tiny Tasks</span
|
||||||
|
>
|
||||||
|
</p><h2>
|
||||||
|
<span data-language-content="en">Same task.<br />Better <em>operating system.</em></span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Mesma tarefa.<br />Melhor <em>sistema operacional.</em></span
|
||||||
|
>
|
||||||
</h2>
|
</h2>
|
||||||
</div><div>
|
</div><div>
|
||||||
<p>
|
<p data-language-content="en">
|
||||||
Start with a deliberately incomplete static task board. Run one prompt as written,
|
Start with a deliberately incomplete static task board. Run one prompt as written,
|
||||||
reset, then run the skill-enabled version.
|
reset, then run the skill-enabled version. Compare diff size, verification evidence, and
|
||||||
|
unnecessary complexity.
|
||||||
|
</p><p data-language-content="pt" hidden>
|
||||||
|
Comece com um quadro estático propositalmente incompleto. Execute um prompt, restaure e
|
||||||
|
execute a versão com skills. Compare tamanho do diff, evidências e complexidade
|
||||||
|
desnecessária.
|
||||||
</p><div class="starter-links">
|
</p><div class="starter-links">
|
||||||
<a href={`${base}hands-on/starter/`} class="starter-link">Open the starter →</a><a
|
<div class="starter-link-group">
|
||||||
href={`${base}hands-on/rules/`}
|
<a href={`${base}hands-on/starter/`} class="starter-link"
|
||||||
class="starter-link">Open the rules lab →</a
|
><span data-language-content="en">Open the starter →</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Abrir o projeto inicial →</span
|
||||||
|
></a
|
||||||
|
><a
|
||||||
|
href="https://git.marcospaulo.dev.br/netcracker/ai-for-dummies/src/branch/pages/hands-on/starter"
|
||||||
|
class="starter-link starter-link-source"
|
||||||
|
><span data-language-content="en">Clone from Gitea →</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Abrir o projeto inicial →</span
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
</div><div class="starter-link-group">
|
||||||
|
<a href={`${base}hands-on/rules/`} class="starter-link"
|
||||||
|
><span data-language-content="en">Open the rules lab →</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Abrir o projeto inicial →</span
|
||||||
|
></a
|
||||||
|
><a
|
||||||
|
href="https://git.marcospaulo.dev.br/netcracker/ai-for-dummies/src/branch/pages/hands-on/rules"
|
||||||
|
class="starter-link starter-link-source"
|
||||||
|
><span data-language-content="en">Clone from Gitea →</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Abrir o projeto inicial →</span
|
||||||
|
></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><div class="exercise-brief">
|
||||||
|
<span
|
||||||
|
><span data-language-content="en">THE MISSING FEATURE</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>A FEATURE AUSENTE</span
|
||||||
|
></span
|
||||||
|
><strong
|
||||||
|
><span data-language-content="en"
|
||||||
|
>Add All / Open / Done filters that survive reload and browser navigation.</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Adicione filtros Todos / Abertos / Concluídos que sobrevivem reload e navegação.</span
|
||||||
|
></strong
|
||||||
|
><div>
|
||||||
|
<span data-language-content="en"
|
||||||
|
><b>STACK</b> HTML · CSS · JavaScript <b>DEPENDENCIES</b> none <b>FILES</b> 3</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
><b>STACK</b> HTML · CSS · JavaScript <b>DEPENDÊNCIAS</b> nenhuma <b>ARQUIVOS</b> 3</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div><div class="prompt-compare">
|
</div><div class="prompt-compare">
|
||||||
<article class="prompt-card">
|
<article class="prompt-card">
|
||||||
<header>
|
<header>
|
||||||
<div><span>RUN A</span><strong>Good prompt</strong></div><CopyPrompt
|
<div>
|
||||||
target="prompt-basic"
|
<span>RUN A</span><strong
|
||||||
/>
|
><span data-language-content="en">Good prompt</span><span
|
||||||
</header><pre><code id="prompt-basic">{handsOn.en.basic}</code></pre><footer>
|
data-language-content="pt"
|
||||||
Clear context · constraints · acceptance · evidence
|
hidden>Bom prompt</span
|
||||||
|
></strong
|
||||||
|
>
|
||||||
|
</div><CopyPrompt target="prompt-basic" />
|
||||||
|
</header><pre><code id="prompt-basic"><span data-language-content="en">{handsOn.en.basic}</span><span data-language-content="pt" hidden>{handsOn.pt.basic}</span></code></pre><footer
|
||||||
|
>
|
||||||
|
<span data-language-content="en"
|
||||||
|
>Clear context · constraints · acceptance · evidence</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Contexto claro · restrições · aceitação · evidência</span
|
||||||
|
>
|
||||||
</footer>
|
</footer>
|
||||||
</article><article class="prompt-card enhanced">
|
</article><article class="prompt-card enhanced">
|
||||||
<header>
|
<header>
|
||||||
<div><span>RUN B</span><strong>Good prompt + skills</strong></div><CopyPrompt
|
<div>
|
||||||
target="prompt-skills"
|
<span>RUN B</span><strong
|
||||||
/>
|
><span data-language-content="en">Good prompt + skills</span><span
|
||||||
</header><pre><code id="prompt-skills">{handsOn.en.skills}</code></pre><footer>
|
data-language-content="pt"
|
||||||
Same contract · explicit working methods · stronger proof
|
hidden>Bom prompt + skills</span
|
||||||
|
></strong
|
||||||
|
>
|
||||||
|
</div><CopyPrompt target="prompt-skills" />
|
||||||
|
</header><pre><code id="prompt-skills"><span data-language-content="en">{handsOn.en.skills}</span><span data-language-content="pt" hidden>{handsOn.pt.skills}</span></code></pre><footer
|
||||||
|
>
|
||||||
|
<span data-language-content="en"
|
||||||
|
>Same contract · explicit working methods · stronger proof</span
|
||||||
|
><span data-language-content="pt" hidden
|
||||||
|
>Mesmo contrato · métodos explícitos · prova mais forte</span
|
||||||
|
>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
|
</div><div class="comparison-strip">
|
||||||
|
<span
|
||||||
|
><span data-language-content="en">COMPARE THE RUNS</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>COMPARE AS EXECUÇÕES</span
|
||||||
|
></span
|
||||||
|
><div>
|
||||||
|
<b>01</b>
|
||||||
|
<span data-language-content="en">Files changed</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Arquivos alterados</span
|
||||||
|
>
|
||||||
|
</div><div>
|
||||||
|
<b>02</b>
|
||||||
|
<span data-language-content="en">New dependencies</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Novas dependências</span
|
||||||
|
>
|
||||||
|
</div><div>
|
||||||
|
<b>03</b>
|
||||||
|
<span data-language-content="en">Checks actually run</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Checks executados</span
|
||||||
|
>
|
||||||
|
</div><div>
|
||||||
|
<b>04</b>
|
||||||
|
<span data-language-content="en">Evidence returned</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Evidências retornadas</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div><p class="copy-status" id="copy-status" role="status" aria-live="polite"></p>
|
</div><p class="copy-status" id="copy-status" role="status" aria-live="polite"></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1017,21 +1281,90 @@ const base = import.meta.env.BASE_URL;
|
|||||||
<section class="verification" id="verification">
|
<section class="verification" id="verification">
|
||||||
<div class="section-label">
|
<div class="section-label">
|
||||||
<span>Verification</span><span>run each gate separately</span>
|
<span>Verification</span><span>run each gate separately</span>
|
||||||
|
</div><div class="verify-intro">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Checks become evidence</p><h2>
|
||||||
|
Three layers.<br />Run each one alone.
|
||||||
|
</h2>
|
||||||
|
</div><p>
|
||||||
|
Run a gate on its own line, print its exit code, attach the output. The result is the
|
||||||
|
deliverable.
|
||||||
|
</p>
|
||||||
</div><div class="verify-layers">
|
</div><div class="verify-layers">
|
||||||
<article>
|
<article>
|
||||||
<span>01 · STATIC</span><h3>Lint and types</h3><p>
|
<span>01 · STATIC</span><h3>Lint and types</h3><p>
|
||||||
Format, lint, type-check. Fast and scoped to one file.
|
Format, lint, type-check. Fast and scoped to one file. Run on every save.
|
||||||
</p><code>pnpm lint; echo "lint=$?"</code>
|
</p><code>pnpm lint; echo "lint=$?" pnpm typecheck; echo "typecheck=$?"</code>
|
||||||
</article><article>
|
</article><article>
|
||||||
<span>02 · BEHAVIOR</span><h3>Unit and contract</h3><p>
|
<span>02 · BEHAVIOR</span><h3>Unit and contract</h3><p>
|
||||||
Tests that repeat. Run before claiming done.
|
Tests that repeat. Run before claiming done.
|
||||||
</p><code>pnpm test; echo "test=$?"</code>
|
</p><code>pnpm test; echo "test=$?" cd services/api && go test ./...</code>
|
||||||
</article><article>
|
</article><article>
|
||||||
<span>03 · INTEGRATION</span><h3>Real UI and API</h3><p>
|
<span>03 · INTEGRATION</span><h3>Real UI and API</h3><p>
|
||||||
Drive the actual UI, API, or browser.
|
Drive the actual UI, API, or browser. Slower and flakier — only this catches mobile
|
||||||
</p><code>pnpm check:ui; echo "ui=$?"</code>
|
overflow and a missing 404.
|
||||||
|
</p><code>pnpm check:ui; echo "ui=$?" TURBO_FORCE=true pnpm e2e</code>
|
||||||
|
</article>
|
||||||
|
</div><div class="verify-antipatterns">
|
||||||
|
<span>FOUR WAYS A GREEN REPORT IS FALSE</span><div class="ap-grid">
|
||||||
|
<article>
|
||||||
|
<b>1</b><div>
|
||||||
|
<strong>Pipe a gate</strong><p>
|
||||||
|
tail, grep, or head hide the real exit code — a pipeline returns the last command's
|
||||||
|
status.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article><article>
|
||||||
|
<b>2</b><div>
|
||||||
|
<strong>Swallow a rejection</strong><p>
|
||||||
|
A silent <code>{'.catch(() => {})'}</code> hides a panic, an upstream limit, or a partial
|
||||||
|
failure.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article><article>
|
||||||
|
<b>3</b><div>
|
||||||
|
<strong>Trust the cache</strong><p>
|
||||||
|
Turbo caches results. A gate that "passes" may not have run — use <code
|
||||||
|
>TURBO_FORCE=true</code
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article><article>
|
||||||
|
<b>4</b><div>
|
||||||
|
<strong>Skip the third layer</strong><p>
|
||||||
|
Lint and unit can both be green while the page breaks on mobile and the API never
|
||||||
|
returns 404.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
</div><article class="verify-cta">
|
||||||
|
<span>RUN IT YOURSELF · two labs, under 10 minutes each</span><div class="verify-cta-grid">
|
||||||
|
<a href={`${base}hands-on/starter/`} class="verify-card"
|
||||||
|
><strong>Path A · verification lab</strong><p>
|
||||||
|
Fill the four-row comparison strip on the starter. Run A naively, Run B with <code
|
||||||
|
>$gate-discipline</code
|
||||||
|
> and <code>$webapp-testing</code>.
|
||||||
|
</p><small>Open the starter →</small><small class="verify-card-source"
|
||||||
|
>Clone ↗ <span>git.marcospaulo.dev.br/.../src/branch/pages/hands-on/starter</span
|
||||||
|
></small
|
||||||
|
></a
|
||||||
|
><a href={`${base}hands-on/rules/`} class="verify-card"
|
||||||
|
><strong>Path B · rules lab</strong><p>
|
||||||
|
Toggle every rule off, run the prompt. Toggle every rule on, run it again. Compare
|
||||||
|
diff size, gate invocations, and the names of checks the agent names back.
|
||||||
|
</p><small
|
||||||
|
><span data-language-content="en">Open the rules lab →</span><span
|
||||||
|
data-language-content="pt"
|
||||||
|
hidden>Open the rules lab →</span
|
||||||
|
></small
|
||||||
|
><small class="verify-card-source"
|
||||||
|
>Clone ↗ <span>git.marcospaulo.dev.br/.../src/branch/pages/hands-on/rules</span
|
||||||
|
></small
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</section>
|
</section>
|
||||||
<section class="sources">
|
<section class="sources">
|
||||||
<div class="section-label">
|
<div class="section-label">
|
||||||
@@ -1064,6 +1397,22 @@ const base = import.meta.env.BASE_URL;
|
|||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- Legacy full-guide/index.html closes with this section after `.sources`.
|
||||||
|
Task 15d dropped it, and nothing caught that: verify.mjs reads the
|
||||||
|
legacy file, so its chapter-route assertion kept passing. It has no
|
||||||
|
`translations.pt` entry, so it is English-only on the live site too. -->
|
||||||
|
<section class="chapter-route">
|
||||||
|
<div class="section-label">
|
||||||
|
<span>Navigate by idea</span><span>short chapters / one system</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Prefer a focused chapter? Start with the <a href={`${base}summary/`}>route map</a>, then
|
||||||
|
jump directly to <a href={`${base}models/`}>models</a>, <a href={`${base}agents/`}
|
||||||
|
>agents and worktrees</a
|
||||||
|
>, <a href={`${base}skills/`}>skill creation</a>, <a href={`${base}rules/`}>rules</a>, or
|
||||||
|
the <a href={`${base}skills-review/`}>skills review desk</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<GuideSelector rootSelector="#full-guide" data={selectorData} />
|
<GuideSelector rootSelector="#full-guide" data={selectorData} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user