Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d88d8b89eb | |||
| b119b92948 | |||
| 580293867d | |||
| 9cb1e0d242 | |||
| 12c32d2fd7 | |||
| 5c76a13b4d | |||
| 91e8d380d0 | |||
| 0a12e9cbcc | |||
| e3ce8abe89 | |||
| 138d7c5e4e | |||
| 33ce544a1e | |||
| f53159f0b4 | |||
| 0c02780dd9 | |||
| aa28158534 | |||
| 429b4e2e87 | |||
| c9796a3e7f | |||
| 9486188983 | |||
| 2560437bfa | |||
| bf46363ea9 | |||
| 18862e8e13 | |||
| 6974800d2e | |||
| bd3805f883 | |||
| 8f82304758 | |||
| a97c2a4034 | |||
| a1eb1e79ea | |||
| c2b35d5355 | |||
| 8c7ca2aded | |||
| c7d06a120d | |||
| aefbd206f9 | |||
| 0bca43cbfd | |||
| 47fff7d289 | |||
| 2b621843a2 | |||
| d15c301fdb | |||
| 9ca3f48def | |||
| 12bc1dc067 | |||
| 71c74fb7f1 | |||
| edd03f9716 | |||
| 6bfae2033e | |||
| af1133f019 | |||
| c2a046d6f7 | |||
| 64a7f2e4c5 | |||
| 83c956c2b6 | |||
| 208ec9447b |
@@ -1,45 +1,47 @@
|
|||||||
# Context: architecture, current and target
|
# Context: architecture
|
||||||
|
|
||||||
## Current (no build step)
|
## Current (Astro, static output)
|
||||||
|
|
||||||
Ten hand-written HTML pages, each linking its own CSS and one ES module:
|
Ten routes, one `src/pages/` entry each, built to `dist/`:
|
||||||
|
|
||||||
| Route | Page | Script | Stylesheets |
|
| Route | Page | Islands |
|
||||||
| -------------------- | -------------------------- | ---------------------- | --------------------------------------------- |
|
| -------------------- | ------------------------------- | ----------------------------------------------- |
|
||||||
| `/` | `index.html` | — | `chapters.css`, `landing.css` |
|
| `/` | `src/pages/index.astro` | — |
|
||||||
| `/full-guide/` | `full-guide/index.html` | `app.js` (50 KB) | `styles.css`, `responsive.css`, `audit.css` |
|
| `/full-guide/` | `src/pages/full-guide.astro` | `GuideSelector`, `LanguageToggle`, `CopyPrompt` |
|
||||||
| `/summary/` | `summary/index.html` | — | `chapters.css` |
|
| `/summary/` | `src/pages/summary.astro` | — |
|
||||||
| `/models/` | `models/index.html` | — | `chapters.css` |
|
| `/models/` | `src/pages/models.astro` | — |
|
||||||
| `/agents/` | `agents/index.html` | — | `chapters.css` |
|
| `/agents/` | `src/pages/agents.astro` | — |
|
||||||
| `/skills/` | `skills/index.html` | `skills/app.js` | `skills/styles.css` |
|
| `/skills/` | `src/pages/skills.astro` | `SkillPackageExplorer` |
|
||||||
| `/rules/` | `rules/index.html` | `rules/app.js` | `rules/styles.css` |
|
| `/rules/` | `src/pages/rules.astro` | `RulesInteractive` |
|
||||||
| `/skills-review/` | `skills-review/index.html` | `skills-review/app.js` | `skills-review/styles.css`, `change-lens.css` |
|
| `/skills-review/` | `src/pages/skills-review.astro` | `legacy/skills-review/app.js` |
|
||||||
| `/hands-on/starter/` | lab fixture | own | own |
|
| `/hands-on/starter/` | `public/` lab fixture | own |
|
||||||
| `/hands-on/rules/` | lab fixture | own | own |
|
| `/hands-on/rules/` | `public/` lab fixture | own |
|
||||||
|
|
||||||
Weight is concentrated: `app.js` 50 KB, `responsive.css` 30 KB,
|
## What is still unmigrated
|
||||||
`skills-review/catalog.js` 27 KB, `skills-review/submitted-catalog.js` 18 KB.
|
|
||||||
|
|
||||||
### What each big file actually is
|
`legacy/` holds the parts the migration did not componentize. They are not dead
|
||||||
|
files — the pages listed above import them, and the build fails without them.
|
||||||
|
|
||||||
- **`app.js`** — not really application code. It is a **bilingual content
|
- **`legacy/styles/guide.css`** (was `styles.css`) — the editorial visual
|
||||||
database** (`phases`, `handsOnPrompts`, `modelGuide`, `skillSources`,
|
system, imported by `full-guide.astro`.
|
||||||
`skillInstallPrompts`, each keyed `{en, pt}`) plus ~12 small `render*`
|
- **`legacy/styles/audit.css`** (was `full-guide/audit.css`) — responsive audit
|
||||||
functions that swap `innerHTML` on tab clicks. ~50 `en:` keys. The content
|
overrides, imported by `full-guide.astro`.
|
||||||
should become data; only the tab behaviour is interactive.
|
- **`legacy/styles/chapters.css`** — imported by `ChapterLayout.astro`.
|
||||||
- **`responsive.css`** — a 30 KB append-only layer of overrides bolted on top of
|
- **`legacy/styles/skills.css`**, **`skills-review.css`**, **`change-lens.css`**
|
||||||
`styles.css`. Expect large parts to be dead once layout moves into components.
|
— imported by their respective pages.
|
||||||
Do not port it verbatim.
|
- **`legacy/skills-review/`** — `app.js` and the module graph under it
|
||||||
- **`skills-review/catalog.js`** — the real data model of the review desk: one
|
(`catalog.js`, `submitted-catalog.js`, `files.js`, `submitted-files.js`,
|
||||||
entry per submitted skill with `id`, `author`, `title`, `status`, `focus`,
|
`vote.js`). `catalog.js` + `submitted-catalog.js` are the review desk's real
|
||||||
`wins[]`, `improve[]`, `extras`, `improved` (full markdown). 24 entries across
|
data model, 24 entries; they are a content collection in all but name.
|
||||||
`catalog.js` + `submitted-catalog.js`. This is already a content collection in
|
|
||||||
all but name.
|
|
||||||
- **`skills-review/files.js` / `submitted-files.js`** — generated file
|
|
||||||
manifests.
|
|
||||||
- **`vote.js`** — the vote widget island; talks to `vote-service/`.
|
|
||||||
|
|
||||||
## Target (Astro)
|
These sit outside `src/` deliberately: `check-tokens.mjs` sweeps `src`, and
|
||||||
|
these files are full of raw hex and unnamed breakpoints. Moving one into `src/`
|
||||||
|
means migrating it to tokens in the same change, not adding an exclusion.
|
||||||
|
|
||||||
|
`responsive.css`, `landing.css`, `app.js`, `rules/app.js`, `rules/styles.css`,
|
||||||
|
and `skills/app.js` were deleted at cutover: their content lives in components.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
@@ -52,15 +54,15 @@ public/
|
|||||||
hands-on/ lab fixtures copied verbatim, never processed
|
hands-on/ lab fixtures copied verbatim, never processed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Non-negotiables for the target
|
### Non-negotiables
|
||||||
|
|
||||||
- **URLs do not change.** `/full-guide/`, `/skills-review/`,
|
- **URLs do not change.** `/full-guide/`, `/skills-review/`,
|
||||||
`/hands-on/starter/` and the rest must resolve exactly as they do now,
|
`/hands-on/starter/` and the rest must resolve exactly as they do now,
|
||||||
trailing slash included. Existing links (including `docs/`, SilverBullet, and
|
trailing slash included. Existing links (including `docs/`, SilverBullet, and
|
||||||
shared URLs with `?author=…&skill=…&view=…` query params) must keep working.
|
shared URLs with `?author=…&skill=…&view=…` query params) must keep working.
|
||||||
- **Zero JS by default.** Seven of the ten pages ship no JavaScript today. They
|
- **Zero JS by default.** Seven of the ten pages ship no JavaScript. They must
|
||||||
must still ship none. Islands are opt-in, per component, and justified.
|
still ship none. Islands are opt-in, per component, and justified.
|
||||||
- **`hands-on/` stays vanilla.** It goes in `public/` untouched. It is a lab
|
- **`hands-on/` stays vanilla.** It lives in `public/` untouched. It is a lab
|
||||||
fixture, not a component.
|
fixture, not a component.
|
||||||
- **No external runtime requests.** `audit-ui.mjs` enforces this and it is part
|
- **No external runtime requests.** `audit-ui.mjs` enforces this and it is part
|
||||||
of the site's thesis. Self-host anything you add.
|
of the site's thesis. Self-host anything you add.
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Compare the *computed* styles of a legacy page against its Astro
|
||||||
|
// replacement, at several viewport widths.
|
||||||
|
//
|
||||||
|
// node .agents/scripts/computed-style-diff.mjs full-guide
|
||||||
|
// node .agents/scripts/computed-style-diff.mjs full-guide --widths 560,880,1050
|
||||||
|
//
|
||||||
|
// Why this exists: a ported media query can sit in the built stylesheet,
|
||||||
|
// match the viewport, and still do nothing. Astro scopes a component's rules
|
||||||
|
// as `.tree-node[data-astro-cid-lsutp3lb]` (specificity 0,2,0); a rule ported
|
||||||
|
// verbatim as `.tree-node` (0,1,0) loses to it and never applies. Task 15e
|
||||||
|
// attempt 4 shipped exactly that: `@media (max-width: 1050px) .tree-node
|
||||||
|
// { width: 145px }` was present in dist and the node stayed 180px wide.
|
||||||
|
//
|
||||||
|
// Checking that the breakpoint *appears* in the built CSS cannot catch this.
|
||||||
|
// Only asking the browser what it actually computed can.
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import { cpSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||||
|
import { createServer } from 'node:net';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
const route = process.argv[2];
|
||||||
|
if (!route) {
|
||||||
|
console.error('usage: computed-style-diff.mjs <route> [--widths a,b,c]');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const widthsArg = process.argv.indexOf('--widths');
|
||||||
|
const widths =
|
||||||
|
widthsArg === -1
|
||||||
|
? [520, 560, 600, 620, 720, 800, 880, 1050, 1100, 1600]
|
||||||
|
: process.argv[widthsArg + 1].split(',').map(Number);
|
||||||
|
|
||||||
|
// The selectors worth checking are the ones the responsive layer moves at a
|
||||||
|
// breakpoint, so read them out of the legacy stylesheet's @media blocks only.
|
||||||
|
// Taking every class in the file buries the signal under generic ones like
|
||||||
|
// `.active`, whose state the islands own anyway.
|
||||||
|
const responsive = readFileSync(new URL('../../responsive.css', import.meta.url), 'utf8');
|
||||||
|
const mediaBlocks = [];
|
||||||
|
for (const match of responsive.matchAll(/@media[^{]*\{/g)) {
|
||||||
|
let depth = 0;
|
||||||
|
for (let i = match.index; i < responsive.length; i += 1) {
|
||||||
|
if (responsive[i] === '{') depth += 1;
|
||||||
|
else if (responsive[i] === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
mediaBlocks.push(responsive.slice(match.index + match[0].length, i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const selectors = [...new Set(mediaBlocks.join('\n').match(/\.[a-z][a-z0-9-]*/g) || [])].sort();
|
||||||
|
|
||||||
|
// Properties a responsive rule actually moves. Comparing every property would
|
||||||
|
// drown the signal in font stacks and inherited colour.
|
||||||
|
const PROPERTIES = [
|
||||||
|
'display',
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-rows',
|
||||||
|
'flex-direction',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'max-width',
|
||||||
|
'padding',
|
||||||
|
'margin',
|
||||||
|
'gap',
|
||||||
|
'font-size',
|
||||||
|
'position',
|
||||||
|
'inset',
|
||||||
|
'overflow',
|
||||||
|
];
|
||||||
|
|
||||||
|
// The legacy pages were deleted at cutover; run this from a pre-cutover
|
||||||
|
// worktree, or the legacy side will 404.
|
||||||
|
const legacyPath = route === 'index' ? 'index.html' : `${route}/index.html`;
|
||||||
|
const astroPath = route === 'index' ? '' : `${route}/`;
|
||||||
|
|
||||||
|
const staging = mkdtempSync(join(tmpdir(), 'af-csd-'));
|
||||||
|
cpSync('dist', join(staging, 'ai-for-dummies'), { recursive: true });
|
||||||
|
|
||||||
|
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 });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Every element matching each selector, so a rule that applies to the first
|
||||||
|
// node and not the rest cannot pass.
|
||||||
|
const collect = ([selectors, properties]) => {
|
||||||
|
const out = {};
|
||||||
|
for (const selector of selectors) {
|
||||||
|
const nodes = [...document.querySelectorAll(selector)];
|
||||||
|
out[selector] = nodes.map((node) => {
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
return properties
|
||||||
|
.map((property) => `${property}:${style.getPropertyValue(property)}`)
|
||||||
|
.join(';');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
let failures = 0;
|
||||||
|
try {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const read = async (url, width) => {
|
||||||
|
const page = await browser.newPage({ viewport: { width, height: 900 } });
|
||||||
|
const response = await page.goto(url, { waitUntil: 'load' });
|
||||||
|
if (!response || !response.ok()) {
|
||||||
|
throw new Error(`${url} returned ${response ? response.status() : 'no response'}`);
|
||||||
|
}
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
const styles = await page.evaluate(collect, [selectors, PROPERTIES]);
|
||||||
|
await page.close();
|
||||||
|
return styles;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const width of widths) {
|
||||||
|
const legacy = await read(`http://localhost:${legacyPort}/${legacyPath}`, width);
|
||||||
|
const astro = await read(`http://localhost:${astroPort}/ai-for-dummies/${astroPath}`, width);
|
||||||
|
|
||||||
|
for (const selector of selectors) {
|
||||||
|
const before = legacy[selector];
|
||||||
|
const after = astro[selector];
|
||||||
|
if (before.length === 0 && after.length === 0) continue;
|
||||||
|
if (before.length !== after.length) {
|
||||||
|
console.log(
|
||||||
|
`${width}px ${selector} legacy ${before.length} nodes, astro ${after.length}`,
|
||||||
|
);
|
||||||
|
failures += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let reported = 0;
|
||||||
|
before.forEach((expected, index) => {
|
||||||
|
if (expected === after[index]) return;
|
||||||
|
failures += 1;
|
||||||
|
// Three examples is enough to identify a rule that did not apply.
|
||||||
|
reported += 1;
|
||||||
|
if (reported > 3) return;
|
||||||
|
const differing = expected
|
||||||
|
.split(';')
|
||||||
|
.filter((pair, i) => pair !== after[index].split(';')[i]);
|
||||||
|
const got = after[index].split(';').filter((pair, i) => pair !== expected.split(';')[i]);
|
||||||
|
console.log(`${width}px ${selector}[${index}]`);
|
||||||
|
console.log(` legacy ${differing.join(' ')}`);
|
||||||
|
console.log(` astro ${got.join(' ')}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
console.log(failures === 0 ? 'computed styles match' : `${failures} computed-style differences`);
|
||||||
|
process.exitCode = failures === 0 ? 0 : 1;
|
||||||
|
} finally {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
@@ -52,7 +52,10 @@ pnpm run verify
|
|||||||
# Compare against origin/main and refuse a silent reduction.
|
# Compare against origin/main and refuse a silent reduction.
|
||||||
step "assertion coverage"
|
step "assertion coverage"
|
||||||
current=$(grep -c 'throw new Error' scripts/verify.mjs)
|
current=$(grep -c 'throw new Error' scripts/verify.mjs)
|
||||||
baseline=$(git show origin/main:scripts/verify.mjs 2>/dev/null | grep -c 'throw new Error' || echo "$current")
|
# Task 19 restored the 42 legacy facts and added ten output snapshots: 84 is
|
||||||
|
# now the floor, in addition to whatever origin/main currently requires.
|
||||||
|
baseline=$(git show origin/main:scripts/verify.mjs 2>/dev/null | grep -c 'throw new Error' || echo 0)
|
||||||
|
if [ "$baseline" -lt 84 ]; then baseline=84; fi
|
||||||
if [ "$current" -lt "$baseline" ]; then
|
if [ "$current" -lt "$baseline" ]; then
|
||||||
echo "gate: verify.mjs coverage fell from $baseline to $current assertions." >&2
|
echo "gate: verify.mjs coverage fell from $baseline to $current assertions." >&2
|
||||||
echo " Only verification-engineer may reduce it, with a reason per removal." >&2
|
echo " Only verification-engineer may reduce it, with a reason per removal." >&2
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#!/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.
|
||||||
|
//
|
||||||
|
// The legacy pages were deleted at cutover, so this needs a pre-cutover tree:
|
||||||
|
// git worktree add /tmp/vanilla <pre-cutover-sha>
|
||||||
|
// and run from there, or run it from a checkout that still has them.
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import { 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();
|
||||||
|
}
|
||||||
@@ -12,8 +12,12 @@ description:
|
|||||||
|
|
||||||
The snapshot is the only objective evidence that no content was lost.
|
The snapshot is the only objective evidence that no content was lost.
|
||||||
|
|
||||||
|
The vanilla site was deleted at cutover. To compare against it, check the
|
||||||
|
pre-cutover tree out into a scratch worktree first:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run serve & # vanilla site on :4173
|
git worktree add /tmp/vanilla <pre-cutover-sha>
|
||||||
|
(cd /tmp/vanilla && python3 -m http.server 4173) &
|
||||||
node .agents/scripts/snapshot-route.mjs http://localhost:4173/models/ \
|
node .agents/scripts/snapshot-route.mjs http://localhost:4173/models/ \
|
||||||
> .agents/snapshots/models.txt
|
> .agents/snapshots/models.txt
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ a bug.
|
|||||||
```bash
|
```bash
|
||||||
python3 - <<'PY'
|
python3 - <<'PY'
|
||||||
import re
|
import re
|
||||||
files=['styles.css','chapters.css','landing.css','rules/styles.css','skills/styles.css',
|
files=['legacy/styles/guide.css','legacy/styles/chapters.css','legacy/styles/skills.css',
|
||||||
'skills-review/styles.css','hands-on/starter/styles.css','hands-on/rules/styles.css']
|
'legacy/styles/skills-review.css','legacy/styles/change-lens.css',
|
||||||
|
'legacy/styles/audit.css','public/hands-on/starter/styles.css',
|
||||||
|
'public/hands-on/rules/styles.css']
|
||||||
seen={}
|
seen={}
|
||||||
for f in files:
|
for f in files:
|
||||||
for m in re.finditer(r'--([a-z-]+):\s*([^;}]+)', open(f).read()):
|
for m in re.finditer(r'--([a-z-]+):\s*([^;}]+)', open(f).read()):
|
||||||
|
|||||||
@@ -33,8 +33,9 @@ with sync_playwright() as p:
|
|||||||
browser.close()
|
browser.close()
|
||||||
```
|
```
|
||||||
|
|
||||||
Run once against the vanilla site (`pnpm run serve`), once against
|
Run once against `pnpm run preview`. To compare against the vanilla site, serve
|
||||||
`pnpm run preview`. Keep both sets.
|
a pre-cutover worktree on :4173 first — those files are no longer on `main`.
|
||||||
|
Keep both sets.
|
||||||
|
|
||||||
## Compare
|
## Compare
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,303 @@
|
|||||||
|
{
|
||||||
|
"colors": [
|
||||||
|
"#081621",
|
||||||
|
"#0b1b27",
|
||||||
|
"#0c1a25",
|
||||||
|
"#0f2230",
|
||||||
|
"#102536",
|
||||||
|
"#102837",
|
||||||
|
"#102b3a",
|
||||||
|
"#112a3b",
|
||||||
|
"#122534",
|
||||||
|
"#123042",
|
||||||
|
"#132b3b",
|
||||||
|
"#172f42",
|
||||||
|
"#173046",
|
||||||
|
"#173245",
|
||||||
|
"#173b4f",
|
||||||
|
"#18364a",
|
||||||
|
"#19364a",
|
||||||
|
"#1a4b42",
|
||||||
|
"#1c425a",
|
||||||
|
"#1d455b",
|
||||||
|
"#1f3a4b",
|
||||||
|
"#215675",
|
||||||
|
"#244760",
|
||||||
|
"#29455a",
|
||||||
|
"#2a4150",
|
||||||
|
"#315f80",
|
||||||
|
"#344c5d",
|
||||||
|
"#41596b",
|
||||||
|
"#426070",
|
||||||
|
"#466274",
|
||||||
|
"#486175",
|
||||||
|
"#496274",
|
||||||
|
"#527085",
|
||||||
|
"#527f9f",
|
||||||
|
"#557080",
|
||||||
|
"#572f32",
|
||||||
|
"#596f9a",
|
||||||
|
"#5b7098",
|
||||||
|
"#65717a",
|
||||||
|
"#697b89",
|
||||||
|
"#6b668f",
|
||||||
|
"#6c6898",
|
||||||
|
"#7c78a8",
|
||||||
|
"#80c69a",
|
||||||
|
"#80c69a20",
|
||||||
|
"#80c69a22",
|
||||||
|
"#8ca1af",
|
||||||
|
"#91aab7",
|
||||||
|
"#9ba7a5",
|
||||||
|
"#9bcba7",
|
||||||
|
"#9eabb4",
|
||||||
|
"#9eb0bb",
|
||||||
|
"#a7483f",
|
||||||
|
"#a9b6be",
|
||||||
|
"#a9bcc8",
|
||||||
|
"#a9e3ae",
|
||||||
|
"#aebbc3",
|
||||||
|
"#aebfc7",
|
||||||
|
"#aebfc9",
|
||||||
|
"#afbec7",
|
||||||
|
"#b0bac1",
|
||||||
|
"#b5c0c7",
|
||||||
|
"#b7c7d1",
|
||||||
|
"#b8c8d2",
|
||||||
|
"#b9c8d0",
|
||||||
|
"#b9c8d1",
|
||||||
|
"#bed0dc",
|
||||||
|
"#bfccd4",
|
||||||
|
"#c1d1d8",
|
||||||
|
"#c4cdd3",
|
||||||
|
"#c6d2d7",
|
||||||
|
"#c9d5dc",
|
||||||
|
"#cbd9e1",
|
||||||
|
"#d0d5d2",
|
||||||
|
"#d4dfe3",
|
||||||
|
"#d5dde2",
|
||||||
|
"#d5f1d6",
|
||||||
|
"#d6e1e4",
|
||||||
|
"#d8dee2",
|
||||||
|
"#e5e3ef",
|
||||||
|
"#e5eeeb",
|
||||||
|
"#e89a8e",
|
||||||
|
"#e8ecee",
|
||||||
|
"#e9ecee",
|
||||||
|
"#e9eeed",
|
||||||
|
"#ebbf58",
|
||||||
|
"#eceaf5",
|
||||||
|
"#eceff0",
|
||||||
|
"#edf0f1",
|
||||||
|
"#eeedf6",
|
||||||
|
"#efc76b",
|
||||||
|
"#efc76b18",
|
||||||
|
"#f0eef8",
|
||||||
|
"#f1f0f7",
|
||||||
|
"#f5f4f1",
|
||||||
|
"#f6f3ed",
|
||||||
|
"#ffb5a8",
|
||||||
|
"#ffd7d0",
|
||||||
|
"#fff",
|
||||||
|
"#ffffff05",
|
||||||
|
"#ffffff06",
|
||||||
|
"#ffffff1f",
|
||||||
|
"#ffffff2b",
|
||||||
|
"#ffffff2d",
|
||||||
|
"#ffffff30",
|
||||||
|
"#ffffff32",
|
||||||
|
"#ffffff40",
|
||||||
|
"#ffffff42",
|
||||||
|
"#ffffff50",
|
||||||
|
"#ffffff66",
|
||||||
|
"rgb(255 255 255 / 14.1176%)",
|
||||||
|
"rgb(255 255 255 / 22.7451%)",
|
||||||
|
"rgb(255 255 255 / 25.098%)",
|
||||||
|
"rgb(255 255 255 / 31.3725%)"
|
||||||
|
],
|
||||||
|
"sizes": [
|
||||||
|
"01em",
|
||||||
|
"1em",
|
||||||
|
"1px",
|
||||||
|
"1.2em",
|
||||||
|
"1.45em",
|
||||||
|
"1.5em",
|
||||||
|
"1.8em",
|
||||||
|
"02em",
|
||||||
|
"2px",
|
||||||
|
"2.4vw",
|
||||||
|
"2.5vw",
|
||||||
|
"2.6vw",
|
||||||
|
"3em",
|
||||||
|
"3px",
|
||||||
|
"3vw",
|
||||||
|
"3.3vw",
|
||||||
|
"3.4vw",
|
||||||
|
"04em",
|
||||||
|
"4px",
|
||||||
|
"4vw",
|
||||||
|
"05em",
|
||||||
|
"5em",
|
||||||
|
"5px",
|
||||||
|
"5vw",
|
||||||
|
"5.6vw",
|
||||||
|
"06em",
|
||||||
|
"6px",
|
||||||
|
"6vw",
|
||||||
|
"07em",
|
||||||
|
"7px",
|
||||||
|
"7vw",
|
||||||
|
"08em",
|
||||||
|
"8px",
|
||||||
|
"8vw",
|
||||||
|
"8.3vw",
|
||||||
|
"09em",
|
||||||
|
"9em",
|
||||||
|
"9px",
|
||||||
|
"9vw",
|
||||||
|
"10px",
|
||||||
|
"10.5px",
|
||||||
|
"11px",
|
||||||
|
"12em",
|
||||||
|
"12px",
|
||||||
|
"12vh",
|
||||||
|
"13px",
|
||||||
|
"14px",
|
||||||
|
"15px",
|
||||||
|
"16px",
|
||||||
|
"17px",
|
||||||
|
"17vw",
|
||||||
|
"18px",
|
||||||
|
"19px",
|
||||||
|
"20px",
|
||||||
|
"22px",
|
||||||
|
"23px",
|
||||||
|
"24px",
|
||||||
|
"25px",
|
||||||
|
"26px",
|
||||||
|
"27px",
|
||||||
|
"28px",
|
||||||
|
"30px",
|
||||||
|
"32px",
|
||||||
|
"34px",
|
||||||
|
"35em",
|
||||||
|
"35px",
|
||||||
|
"36px",
|
||||||
|
"38px",
|
||||||
|
"40px",
|
||||||
|
"42px",
|
||||||
|
"44px",
|
||||||
|
"045em",
|
||||||
|
"45px",
|
||||||
|
"46px",
|
||||||
|
"48px",
|
||||||
|
"50px",
|
||||||
|
"52px",
|
||||||
|
"54px",
|
||||||
|
"55px",
|
||||||
|
"56px",
|
||||||
|
"58px",
|
||||||
|
"60px",
|
||||||
|
"62px",
|
||||||
|
"64px",
|
||||||
|
"65px",
|
||||||
|
"70px",
|
||||||
|
"72px",
|
||||||
|
"075em",
|
||||||
|
"75px",
|
||||||
|
"76px",
|
||||||
|
"78px",
|
||||||
|
"80px",
|
||||||
|
"82px",
|
||||||
|
"85px",
|
||||||
|
"90px",
|
||||||
|
"92px",
|
||||||
|
"95px",
|
||||||
|
"96px",
|
||||||
|
"100px",
|
||||||
|
"105px",
|
||||||
|
"108px",
|
||||||
|
"110px",
|
||||||
|
"112px",
|
||||||
|
"120px",
|
||||||
|
"126px",
|
||||||
|
"130px",
|
||||||
|
"135px",
|
||||||
|
"140px",
|
||||||
|
"145px",
|
||||||
|
"148px",
|
||||||
|
"150px",
|
||||||
|
"160px",
|
||||||
|
"164px",
|
||||||
|
"170px",
|
||||||
|
"175px",
|
||||||
|
"180px",
|
||||||
|
"190px",
|
||||||
|
"200px",
|
||||||
|
"210px",
|
||||||
|
"220px",
|
||||||
|
"230px",
|
||||||
|
"240px",
|
||||||
|
"255px",
|
||||||
|
"260px",
|
||||||
|
"270px",
|
||||||
|
"280px",
|
||||||
|
"290px",
|
||||||
|
"300px",
|
||||||
|
"305px",
|
||||||
|
"320px",
|
||||||
|
"330px",
|
||||||
|
"340px",
|
||||||
|
"360px",
|
||||||
|
"380px",
|
||||||
|
"390px",
|
||||||
|
"410px",
|
||||||
|
"420px",
|
||||||
|
"440px",
|
||||||
|
"460px",
|
||||||
|
"500px",
|
||||||
|
"520px",
|
||||||
|
"530px",
|
||||||
|
"540px",
|
||||||
|
"560px",
|
||||||
|
"570px",
|
||||||
|
"600px",
|
||||||
|
"620px",
|
||||||
|
"650px",
|
||||||
|
"680px",
|
||||||
|
"700px",
|
||||||
|
"720px",
|
||||||
|
"730px",
|
||||||
|
"750px",
|
||||||
|
"780px",
|
||||||
|
"800px",
|
||||||
|
"850px",
|
||||||
|
"880px",
|
||||||
|
"900px",
|
||||||
|
"950px",
|
||||||
|
"1000px",
|
||||||
|
"1040px",
|
||||||
|
"1050px",
|
||||||
|
"1100px",
|
||||||
|
"1400px",
|
||||||
|
"1420px",
|
||||||
|
"1500px",
|
||||||
|
"1600px",
|
||||||
|
"1840px",
|
||||||
|
"1920px",
|
||||||
|
"1960px",
|
||||||
|
"2200px",
|
||||||
|
"2880px"
|
||||||
|
],
|
||||||
|
"breakpoints": [
|
||||||
|
"520px",
|
||||||
|
"560px",
|
||||||
|
"600px",
|
||||||
|
"800px",
|
||||||
|
"880px",
|
||||||
|
"1050px",
|
||||||
|
"1100px",
|
||||||
|
"1600px",
|
||||||
|
"2200px"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
[
|
||||||
|
"01 frota",
|
||||||
|
"02 worktrees",
|
||||||
|
"03 modelos",
|
||||||
|
"04 skills",
|
||||||
|
"05 criar",
|
||||||
|
"06 kit de campo",
|
||||||
|
"07 prática",
|
||||||
|
"ENGENHARIA DE IA <i></i> 01 / 2026",
|
||||||
|
"Uma apresentação para quem entrega software",
|
||||||
|
"Você não precisa de um exército de modelos. Precisa de um sistema: uma mente para enquadrar o trabalho, várias mãos para executá-lo e uma fronteira clara entre cada tarefa.",
|
||||||
|
"NOTA DE CAMPO / 001",
|
||||||
|
"Entregue o<br /><em>sistema.</em>",
|
||||||
|
"Skills · agentes · worktrees · evidências",
|
||||||
|
"modelo forte<br />para ambiguidade",
|
||||||
|
"workers delimitados<br />em paralelo",
|
||||||
|
"iterações<br />com evidências",
|
||||||
|
"Leia isto como um mapa de rota, não como uma receita de prompt.",
|
||||||
|
"REGRA ZERO",
|
||||||
|
"Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.",
|
||||||
|
"Uma pequena frota",
|
||||||
|
"coordenação antes do paralelismo",
|
||||||
|
"ORQUESTRADOR",
|
||||||
|
"Decide o que<br />precisa acontecer.",
|
||||||
|
"Componentes e estados visuais",
|
||||||
|
"Casos de aceitação",
|
||||||
|
"Guia e exemplos",
|
||||||
|
"O orquestrador preserva a intenção, escreve pequenos contratos e reúne resultados verificáveis. Ele não precisa digitar cada linha.",
|
||||||
|
"Por que a fronteira importa",
|
||||||
|
"uma tarefa vaga / três falhas previsíveis",
|
||||||
|
"Sopa de contexto",
|
||||||
|
"Cada worker lê tudo. Ninguém sabe quais fatos são essenciais.",
|
||||||
|
"Colisão de branches",
|
||||||
|
"Dois agentes usam o mesmo checkout. O caminho mais rápido vira resolução de conflitos.",
|
||||||
|
"Desvio confiante",
|
||||||
|
"O diff parece ótimo, mas ninguém verifica se resolveu o problema original.",
|
||||||
|
"O ciclo de subagentes",
|
||||||
|
"Clique em uma fase.<br /><em>Veja a passagem.</em>",
|
||||||
|
"Delegar é mover uma tarefa delimitada para um contexto menor — não abrir mão da responsabilidade.",
|
||||||
|
"O que atravessa contextos",
|
||||||
|
"brief → diff → evidência",
|
||||||
|
"Pacote",
|
||||||
|
"Contém",
|
||||||
|
"Por que importa",
|
||||||
|
"Git worktrees",
|
||||||
|
"Uma branch<br />por <em>mão.</em>",
|
||||||
|
"Um worktree é outro diretório ligado ao mesmo repositório. Cada agente recebe seu próprio checkout e índice; o histórico continua compartilhado.",
|
||||||
|
"Selecione um nó para inspecionar checkout, responsável e próxima ação.",
|
||||||
|
"topologia do repositório",
|
||||||
|
"<i></i> 4 checkouts",
|
||||||
|
"RAIZ",
|
||||||
|
"AGENTE DE UI",
|
||||||
|
"AGENTE DE TESTES",
|
||||||
|
"AGENTE DE DOCS",
|
||||||
|
"● limpo",
|
||||||
|
"3 arquivos · trabalhando",
|
||||||
|
"8 verificações · pronto",
|
||||||
|
"2 páginas · revisão",
|
||||||
|
"Roteamento de modelos",
|
||||||
|
"Não pague por<br />raciocínio onde precisa<br />de <em>ritmo.</em>",
|
||||||
|
"Escolha um trabalho para entender por que o perfil do modelo muda.",
|
||||||
|
"Trabalho",
|
||||||
|
"Perfil",
|
||||||
|
"Formato do prompt",
|
||||||
|
"Planejar",
|
||||||
|
"Construir",
|
||||||
|
"Explorar",
|
||||||
|
"Revisar",
|
||||||
|
"Skills",
|
||||||
|
"Escreva do jeito certo<br /><em>uma vez.</em>",
|
||||||
|
"Uma skill é um procedimento reutilizável. Ela pode carregar instruções, referências, scripts e assets. Não é memória mágica e não substitui critérios de aceitação.",
|
||||||
|
"01 / defina o gatilho",
|
||||||
|
"02 / carregue detalhes sob demanda",
|
||||||
|
"03 / devolva evidências",
|
||||||
|
"PACOTE DE SKILL",
|
||||||
|
"Skills comuns",
|
||||||
|
"escolha o comportamento antes do modelo",
|
||||||
|
"O kit de campo",
|
||||||
|
"Trabalhos diferentes.<br />Instintos <em>diferentes.</em>",
|
||||||
|
"Uma skill muda como o agente aborda o trabalho. Algumas moldam a comunicação. Outras impõem pesquisa, diagnóstico, revisão ou disciplina de conclusão. Selecione uma para inspecionar sua regra operacional.",
|
||||||
|
"SIMPLIFICAR",
|
||||||
|
"código mínimo que funciona",
|
||||||
|
"COMUNICAR",
|
||||||
|
"sinal sem excesso",
|
||||||
|
"CONCLUIR",
|
||||||
|
"gates e evidências",
|
||||||
|
"INVESTIGAR",
|
||||||
|
"fontes primárias primeiro",
|
||||||
|
"DIAGNOSTICAR",
|
||||||
|
"ciclo curto de feedback",
|
||||||
|
"REVISAR",
|
||||||
|
"padrões × especificação",
|
||||||
|
"ECONOMIZAR",
|
||||||
|
"comprima saídas ruidosas",
|
||||||
|
"UM LOADOUT PRÁTICO",
|
||||||
|
"<b>PLANEJAR</b> unlazy <i>→</i> <b>CONSTRUIR</b> ponytail-lite <i>→</i> <b>DIAGNOSTICAR</b> diagnosing-bugs <i>→</i> <b>REPORTAR</b> caveman",
|
||||||
|
"O PAPEL HUMANO",
|
||||||
|
"O agente pode ser autônomo na execução. Intenção, limites e evidências continuam sendo seus.",
|
||||||
|
"COMECE AQUI",
|
||||||
|
"Comece com um agente e uma skill. Adicione paralelismo apenas quando as tarefas forem realmente independentes.",
|
||||||
|
"Continue aprendendo",
|
||||||
|
"12 novas leituras + documentação primária",
|
||||||
|
"Aprofunde com documentação oficial, casos de produção, Medium e fluxos de praticantes. <a href=\"rules/\">Estudo de caso sobre regras e enforcement →</a> <a href=\"docs/references/README.md\">Referências primárias →</a> <a href=\"docs/references/additional-reading.md\">Trilha com 12 leituras →</a>"
|
||||||
|
]
|
||||||
@@ -28,8 +28,11 @@ A presentation for humans who ship
|
|||||||
Uma apresentação para quem entrega software
|
Uma apresentação para quem entrega software
|
||||||
AI for
|
AI for
|
||||||
dummies.
|
dummies.
|
||||||
You do not need an army of models. You need a system: one mind to frame the work, several hands to execute it, and a clean boundary between every task.
|
You do not need an army of models. You need a system: one mind to frame the work,
|
||||||
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.
|
several hands to execute it, and a clean boundary between every task.
|
||||||
|
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.
|
||||||
FIELD NOTE / 001
|
FIELD NOTE / 001
|
||||||
NOTA DE CAMPO / 001
|
NOTA DE CAMPO / 001
|
||||||
Ship the
|
Ship the
|
||||||
@@ -39,8 +42,8 @@ sistema.
|
|||||||
Skills · agents · worktrees · proof
|
Skills · agents · worktrees · proof
|
||||||
Skills · agentes · worktrees · evidências
|
Skills · agentes · worktrees · evidências
|
||||||
Uma apresentação para quem entrega software
|
Uma apresentação para quem entrega software
|
||||||
IA para
|
AI for
|
||||||
iniciantes.
|
dummies.
|
||||||
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.
|
||||||
NOTA DE CAMPO / 001
|
NOTA DE CAMPO / 001
|
||||||
@@ -85,8 +88,8 @@ MAKE
|
|||||||
REGRA ZERO
|
REGRA ZERO
|
||||||
Modelo forte para ambiguidade.
|
Modelo forte para ambiguidade.
|
||||||
Modelo leve para trabalho delimitado.
|
Modelo leve para trabalho delimitado.
|
||||||
PENSE
|
THINK
|
||||||
FAÇA
|
MAKE
|
||||||
A small fleet
|
A small fleet
|
||||||
Uma pequena frota
|
Uma pequena frota
|
||||||
coordination before parallelism
|
coordination before parallelism
|
||||||
@@ -114,8 +117,10 @@ agent/docs
|
|||||||
Interface worker
|
Interface worker
|
||||||
Receives: component contract + visual states
|
Receives: component contract + visual states
|
||||||
Returns: focused diff + viewport evidence
|
Returns: focused diff + viewport evidence
|
||||||
The orchestrator preserves intent, writes small contracts, and gathers results that can be
|
The orchestrator preserves intent, writes small contracts, and gathers results that can
|
||||||
verified. It does not need to type every line.
|
be verified. It does not need to type every line.
|
||||||
|
O orquestrador preserva a intenção, escreve pequenos contratos e reúne resultados
|
||||||
|
verificáveis. Ele não precisa digitar cada linha.
|
||||||
Why the boundary matters
|
Why the boundary matters
|
||||||
Por que a fronteira importa
|
Por que a fronteira importa
|
||||||
one vague task / three predictable failures
|
one vague task / three predictable failures
|
||||||
@@ -129,7 +134,8 @@ Cada worker lê tudo. Ninguém sabe quais fatos são essenciais.
|
|||||||
Branch collision
|
Branch collision
|
||||||
Colisão de branches
|
Colisão de branches
|
||||||
Two agents touch the same checkout. The fastest path becomes conflict resolution.
|
Two agents touch the same checkout. The fastest path becomes conflict resolution.
|
||||||
Dois agentes usam o mesmo checkout. O caminho mais rápido vira resolução de conflitos.
|
Dois agentes usam o mesmo checkout. O caminho mais rápido vira resolução de
|
||||||
|
conflitos.
|
||||||
03
|
03
|
||||||
Confident drift
|
Confident drift
|
||||||
Desvio confiante
|
Desvio confiante
|
||||||
@@ -143,6 +149,8 @@ Clique em uma fase.
|
|||||||
Veja a passagem.
|
Veja a passagem.
|
||||||
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.
|
responsibility.
|
||||||
|
Delegar é mover uma tarefa delimitada para um contexto menor — não abrir mão da
|
||||||
|
responsabilidade.
|
||||||
01
|
01
|
||||||
PLAN
|
PLAN
|
||||||
02
|
02
|
||||||
@@ -186,6 +194,8 @@ por
|
|||||||
mão.
|
mão.
|
||||||
A worktree is another directory linked to the same repository. Each agent gets its own
|
A worktree is another directory linked to the same repository. Each agent gets its own
|
||||||
checkout and index; history remains shared.
|
checkout and index; history remains shared.
|
||||||
|
Um worktree é outro diretório ligado ao mesmo repositório. Cada agente recebe seu
|
||||||
|
próprio checkout e índice; o histórico continua compartilhado.
|
||||||
Select a node to inspect its checkout, owner, and next action.
|
Select a node to inspect its checkout, owner, and next action.
|
||||||
Selecione um nó para inspecionar checkout, responsável e próxima ação.
|
Selecione um nó para inspecionar checkout, responsável e próxima ação.
|
||||||
repository topology
|
repository topology
|
||||||
@@ -193,8 +203,10 @@ topologia do repositório
|
|||||||
4 checkouts
|
4 checkouts
|
||||||
4 checkouts
|
4 checkouts
|
||||||
ROOT
|
ROOT
|
||||||
|
RAIZ
|
||||||
main
|
main
|
||||||
● clean
|
● clean
|
||||||
|
● limpo
|
||||||
UI AGENT
|
UI AGENT
|
||||||
AGENTE DE UI
|
AGENTE DE UI
|
||||||
agent/ui
|
agent/ui
|
||||||
@@ -229,8 +241,11 @@ ritmo.
|
|||||||
Choose a job to see why the model profile changes.
|
Choose a job to see why the model profile changes.
|
||||||
Escolha um trabalho para entender por que o perfil do modelo muda.
|
Escolha um trabalho para entender por que o perfil do modelo muda.
|
||||||
Work
|
Work
|
||||||
|
Trabalho
|
||||||
Profile
|
Profile
|
||||||
|
Perfil
|
||||||
Prompt shape
|
Prompt shape
|
||||||
|
Formato do prompt
|
||||||
Plan
|
Plan
|
||||||
Planejar
|
Planejar
|
||||||
strong / broad
|
strong / broad
|
||||||
@@ -262,8 +277,12 @@ gear.
|
|||||||
Escolha o motor.
|
Escolha o motor.
|
||||||
Depois escolha a
|
Depois escolha a
|
||||||
marcha.
|
marcha.
|
||||||
A stronger model changes the capability ceiling. Higher reasoning effort gives that model more room to work. Start with the lightest combination that passes your real checks, then move one knob at a time.
|
A stronger model changes the capability ceiling. Higher reasoning effort gives that
|
||||||
Um modelo mais forte muda o teto de capacidade. Mais esforço de raciocínio dá mais espaço para esse modelo trabalhar. Comece com a combinação mais leve que passa seus checks e mova um controle por vez.
|
model more room to work. Start with the lightest combination that passes your real
|
||||||
|
checks, then move one knob at a time.
|
||||||
|
Um modelo mais forte muda o teto de capacidade. Mais esforço de raciocínio dá mais
|
||||||
|
espaço para esse modelo trabalhar. Comece com a combinação mais leve que passa seus
|
||||||
|
checks e mova um controle por vez.
|
||||||
OPENAI
|
OPENAI
|
||||||
CLAUDE
|
CLAUDE
|
||||||
GEMINI
|
GEMINI
|
||||||
@@ -278,6 +297,9 @@ BAIXO
|
|||||||
bounded + fast
|
bounded + fast
|
||||||
delimitado + rápido
|
delimitado + rápido
|
||||||
MEDIUM
|
MEDIUM
|
||||||
|
MÉDIO
|
||||||
|
default start
|
||||||
|
ponto inicial
|
||||||
HIGH
|
HIGH
|
||||||
ALTO
|
ALTO
|
||||||
complex + costly
|
complex + costly
|
||||||
@@ -287,17 +309,29 @@ Balanced starting point for normal implementation, tests, and review. Measure be
|
|||||||
reasoning: { effort: "medium" }
|
reasoning: { effort: "medium" }
|
||||||
ROUTING RULE
|
ROUTING RULE
|
||||||
REGRA DE ROTEAMENTO
|
REGRA DE ROTEAMENTO
|
||||||
Use strong models for ambiguity and judgment. Use lighter models for bounded execution. Raise effort only when evaluation shows a gain.
|
Use strong models for ambiguity and judgment. Use lighter models for bounded execution.
|
||||||
Use modelos fortes para ambiguidade e julgamento. Use modelos leves para execução delimitada. Aumente o esforço apenas quando a avaliação mostrar ganho.
|
Raise effort only when evaluation shows a gain.
|
||||||
Skills
|
Use modelos fortes para ambiguidade e julgamento. Use modelos leves para execução
|
||||||
|
delimitada. Aumente o esforço apenas quando a avaliação mostrar ganho.
|
||||||
Skills
|
Skills
|
||||||
|
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.
|
||||||
Write the right way
|
Write the right way
|
||||||
once.
|
once.
|
||||||
Escreva do jeito certo
|
Escreva do jeito certo
|
||||||
uma vez.
|
uma vez.
|
||||||
A skill is a reusable procedure. It can carry instructions, references, scripts, and assets. It is not magical memory, and it does not replace acceptance criteria.
|
A skill is a reusable procedure. It can carry instructions, references, scripts, and
|
||||||
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.
|
assets. It is not magical memory, and it does not replace acceptance criteria.
|
||||||
|
Uma skill é um procedimento reutilizável. Ela pode carregar instruções, referências,
|
||||||
|
scripts e assets. Não é memória mágica e não substitui critérios de aceitação.
|
||||||
|
01 / trigger clearly
|
||||||
|
01 / defina o gatilho
|
||||||
|
02 / load detail on demand
|
||||||
|
02 / carregue detalhes sob demanda
|
||||||
|
03 / return evidence
|
||||||
|
03 / devolva evidências
|
||||||
SKILL PACKAGE
|
SKILL PACKAGE
|
||||||
|
PACOTE DE SKILL
|
||||||
SKILL.md
|
SKILL.md
|
||||||
procedure and limits
|
procedure and limits
|
||||||
references/
|
references/
|
||||||
@@ -310,18 +344,48 @@ templates and examples
|
|||||||
SKILL.md
|
SKILL.md
|
||||||
Trigger, procedure, constraints, and the exact evidence the agent must return.
|
Trigger, procedure, constraints, and the exact evidence the agent must return.
|
||||||
select another file to explore
|
select another file to explore
|
||||||
|
name: review-ui · check focus, mobile, reduced motion · run verification · return evidence
|
||||||
Create a skill
|
Create a skill
|
||||||
|
Criar uma skill
|
||||||
repeatable pain → reusable judgment
|
repeatable pain → reusable judgment
|
||||||
03
|
atrito repetido → julgamento reutilizável
|
||||||
Choose only useful anatomy
|
The skill forge
|
||||||
|
A forja de skills
|
||||||
|
Teach the decision.
|
||||||
|
Keep the context
|
||||||
|
light.
|
||||||
|
Ensine a decisão.
|
||||||
|
Mantenha o contexto
|
||||||
|
leve.
|
||||||
|
Do not package everything you know. Capture the non-obvious choices that repeatedly
|
||||||
|
improve an outcome, then prove the skill changes behavior.
|
||||||
|
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.
|
||||||
01
|
01
|
||||||
Start from repeated friction
|
Observe
|
||||||
05
|
Observar
|
||||||
Test behavior, then sharpen
|
find repeated friction
|
||||||
|
encontre atrito repetido
|
||||||
02
|
02
|
||||||
Make discovery precise
|
Define trigger
|
||||||
|
Definir gatilho
|
||||||
|
route precisely
|
||||||
|
roteie com precisão
|
||||||
|
03
|
||||||
|
Choose anatomy
|
||||||
|
Escolher anatomia
|
||||||
|
only needed files
|
||||||
|
apenas arquivos necessários
|
||||||
04
|
04
|
||||||
Write what changes decisions
|
Write guidance
|
||||||
|
Escrever orientação
|
||||||
|
decisions, not trivia
|
||||||
|
decisões, não trivialidades
|
||||||
|
05
|
||||||
|
Validate
|
||||||
|
Validar
|
||||||
|
test real behavior
|
||||||
|
teste comportamento real
|
||||||
01
|
01
|
||||||
QUESTION
|
QUESTION
|
||||||
Start from repeated friction
|
Start from repeated friction
|
||||||
@@ -332,31 +396,88 @@ ARTIFACT
|
|||||||
A narrow capability and concrete examples.
|
A narrow capability and concrete examples.
|
||||||
PROOF
|
PROOF
|
||||||
Without the skill, agents repeatedly make the same avoidable mistake.
|
Without the skill, agents repeatedly make the same avoidable mistake.
|
||||||
|
OUTPUT / SKILL PACKAGE
|
||||||
|
SAÍDA / PACOTE DE SKILL
|
||||||
|
review-ui/
|
||||||
|
├── SKILL.md
|
||||||
|
├── agents/
|
||||||
|
│ └── openai.yaml
|
||||||
|
├── references/
|
||||||
|
│ └── accessibility.md
|
||||||
|
└── scripts/
|
||||||
|
└── verify.mjs
|
||||||
|
VALIDATE
|
||||||
|
VALIDAR
|
||||||
|
quick_validate.py ./review-ui
|
||||||
|
AFTER REAL USE
|
||||||
|
APÓS USO REAL
|
||||||
|
observe failure
|
||||||
|
→
|
||||||
|
sharpen one rule
|
||||||
|
→
|
||||||
|
retest behavior
|
||||||
|
→
|
||||||
|
keep it narrow
|
||||||
|
observar falha
|
||||||
|
→
|
||||||
|
refinar uma regra
|
||||||
|
→
|
||||||
|
retestar comportamento
|
||||||
|
→
|
||||||
|
manter estreita
|
||||||
Common skills
|
Common skills
|
||||||
Skills comuns
|
Skills comuns
|
||||||
choose behavior before model
|
choose behavior before model
|
||||||
escolha o comportamento antes do modelo
|
escolha o comportamento antes do modelo
|
||||||
COMMUNICATION STYLE
|
The field kit
|
||||||
caveman
|
O kit de campo
|
||||||
Use for routine status, handoffs, and technical summaries where speed matters. Short fragments make actions and evidence easy to scan.
|
Different jobs.
|
||||||
DIAGNOSTIC LOOP
|
Different
|
||||||
diagnosing-bugs
|
instincts.
|
||||||
Use for hard bugs, flakes, and regressions. First build a fast deterministic reproduction, then minimize, rank hypotheses, instrument, and fix the root cause.
|
Trabalhos diferentes.
|
||||||
SIMPLIFICATION INSTINCT
|
Instintos
|
||||||
|
diferentes.
|
||||||
|
A skill changes how an agent approaches work. Some shape communication. Others enforce
|
||||||
|
research, debugging, review, or completion discipline. Select one to inspect its
|
||||||
|
operating rule and verified source.
|
||||||
|
Uma skill muda como o agente aborda o trabalho. Algumas moldam a comunicação. Outras
|
||||||
|
impõem pesquisa, diagnóstico, revisão ou disciplina de conclusão. Selecione uma para
|
||||||
|
inspecionar sua regra operacional.
|
||||||
|
SIMPLIFY
|
||||||
|
SIMPLIFICAR
|
||||||
ponytail-lite
|
ponytail-lite
|
||||||
Use when a request invites frameworks, dependencies, abstractions, or speculative scaffolding. It checks reuse, standard library, and native platform features before adding code.
|
minimum code that holds
|
||||||
CONTEXT ECONOMY
|
código mínimo que funciona
|
||||||
token-saver
|
COMMUNICATE
|
||||||
Use around verbose tests, builds, Git output, and logs. Filtering preserves context for reasoning while retaining full failure output for recovery.
|
COMUNICAR
|
||||||
SOURCE DISCIPLINE
|
caveman
|
||||||
research
|
signal without filler
|
||||||
Use when APIs, standards, architecture facts, or current behavior must be verified. Capture findings in a cited note, prioritizing primary sources.
|
sinal sem excesso
|
||||||
COMPLETION DISCIPLINE
|
COMPLETE
|
||||||
|
CONCLUIR
|
||||||
unlazy
|
unlazy
|
||||||
Use for substantial autonomous builds, audits, and parallel work where quiet omissions are expensive. It turns “done” into runnable acceptance checks.
|
gates and evidence
|
||||||
INDEPENDENT REVIEW
|
gates e evidências
|
||||||
|
INVESTIGATE
|
||||||
|
INVESTIGAR
|
||||||
|
research
|
||||||
|
primary sources first
|
||||||
|
fontes primárias primeiro
|
||||||
|
DIAGNOSE
|
||||||
|
DIAGNOSTICAR
|
||||||
|
diagnosing-bugs
|
||||||
|
tight feedback loop
|
||||||
|
ciclo curto de feedback
|
||||||
|
REVIEW
|
||||||
|
REVISAR
|
||||||
code-review
|
code-review
|
||||||
Use on a branch or PR. One axis checks repository standards; another checks whether the change actually satisfies its originating specification.
|
standards × spec
|
||||||
|
padrões × especificação
|
||||||
|
ECONOMIZE
|
||||||
|
ECONOMIZAR
|
||||||
|
token-saver
|
||||||
|
compress noisy output
|
||||||
|
comprima saídas ruidosas
|
||||||
01
|
01
|
||||||
SIMPLIFICATION INSTINCT
|
SIMPLIFICATION INSTINCT
|
||||||
ponytail-lite
|
ponytail-lite
|
||||||
@@ -368,8 +489,34 @@ Date picker? Start with <input type="date">.
|
|||||||
WATCH OUT
|
WATCH OUT
|
||||||
Never simplify away security, accessibility, validation, or real edge cases.
|
Never simplify away security, accessibility, validation, or real edge cases.
|
||||||
GITHUB SOURCE ↗
|
GITHUB SOURCE ↗
|
||||||
|
ONE PRACTICAL LOADOUT
|
||||||
|
UM LOADOUT PRÁTICO
|
||||||
|
PLAN
|
||||||
|
unlazy
|
||||||
|
→
|
||||||
|
BUILD
|
||||||
|
ponytail-lite
|
||||||
|
→
|
||||||
|
DEBUG
|
||||||
|
diagnosing-bugs
|
||||||
|
→
|
||||||
|
REPORT
|
||||||
|
caveman
|
||||||
|
PLANEJAR
|
||||||
|
unlazy
|
||||||
|
→
|
||||||
|
CONSTRUIR
|
||||||
|
ponytail-lite
|
||||||
|
→
|
||||||
|
DIAGNOSTICAR
|
||||||
|
diagnosing-bugs
|
||||||
|
→
|
||||||
|
REPORTAR
|
||||||
|
caveman
|
||||||
INSTALL PACK
|
INSTALL PACK
|
||||||
|
PACOTE DE INSTALAÇÃO
|
||||||
Ask your coding agent to verify, install, and validate the skills.
|
Ask your coding agent to verify, install, and validate the skills.
|
||||||
|
Peça ao seu agente para verificar, instalar e validar as skills.
|
||||||
COPY
|
COPY
|
||||||
↗
|
↗
|
||||||
Inspect and install only these public agent skills. Pin the exact commits:
|
Inspect and install only these public agent skills. Pin the exact commits:
|
||||||
@@ -380,19 +527,61 @@ Inspect and install only these public agent skills. Pin the exact commits:
|
|||||||
- aetox-skills/token-saver@8f21188bb043fad411f47e2e57f0365a83c13da7 — repository root
|
- aetox-skills/token-saver@8f21188bb043fad411f47e2e57f0365a83c13da7 — repository root
|
||||||
- anthropics/skills@53048666b05b4799081517d00e09e0a2dd688678 — skills/webapp-testing/
|
- 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.
|
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.
|
||||||
|
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.
|
||||||
Review every source before installation. Existing local skills must be preserved.
|
Review every source before installation. Existing local skills must be preserved.
|
||||||
|
Revise cada fonte antes da instalação. Skills locais existentes devem ser preservadas.
|
||||||
Hands-on
|
Hands-on
|
||||||
|
Prática
|
||||||
10 minutes / one missing feature
|
10 minutes / one missing feature
|
||||||
|
10 minutos / uma feature ausente
|
||||||
Tiny Tasks lab
|
Tiny Tasks lab
|
||||||
|
Laboratório Tiny Tasks
|
||||||
Same task.
|
Same task.
|
||||||
Better
|
Better
|
||||||
operating system.
|
operating system.
|
||||||
|
Mesma tarefa.
|
||||||
|
Melhor
|
||||||
|
sistema operacional.
|
||||||
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.
|
||||||
|
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.
|
||||||
Open the starter →
|
Open the starter →
|
||||||
|
Abrir o projeto inicial →
|
||||||
|
Clone from Gitea →
|
||||||
|
Abrir o projeto inicial →
|
||||||
Open the rules lab →
|
Open the rules lab →
|
||||||
|
Abrir o projeto inicial →
|
||||||
|
Clone from Gitea →
|
||||||
|
Abrir o projeto inicial →
|
||||||
|
THE MISSING FEATURE
|
||||||
|
A FEATURE AUSENTE
|
||||||
|
Add All / Open / Done filters that survive reload and browser navigation.
|
||||||
|
Adicione filtros Todos / Abertos / Concluídos que sobrevivem reload e navegação.
|
||||||
|
STACK
|
||||||
|
HTML · CSS · JavaScript
|
||||||
|
DEPENDENCIES
|
||||||
|
none
|
||||||
|
FILES
|
||||||
|
3
|
||||||
|
STACK
|
||||||
|
HTML · CSS · JavaScript
|
||||||
|
DEPENDÊNCIAS
|
||||||
|
nenhuma
|
||||||
|
ARQUIVOS
|
||||||
|
3
|
||||||
RUN A
|
RUN A
|
||||||
Good prompt
|
Good prompt
|
||||||
|
Bom prompt
|
||||||
COPY
|
COPY
|
||||||
↗
|
↗
|
||||||
Work only in hands-on/starter. It is dependency-free HTML, CSS, and JavaScript.
|
Work only in hands-on/starter. It is dependency-free HTML, CSS, and JavaScript.
|
||||||
@@ -407,9 +596,23 @@ Requirements:
|
|||||||
- add no dependencies and change no unrelated files
|
- add no dependencies and change no unrelated files
|
||||||
Verify app.js syntax and exercise every filter plus URL navigation.
|
Verify app.js syntax and exercise every filter plus URL navigation.
|
||||||
Return changed files, checks run, results, and remaining risk.
|
Return changed files, checks run, results, and remaining risk.
|
||||||
|
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.
|
||||||
Clear context · constraints · acceptance · evidence
|
Clear context · constraints · acceptance · evidence
|
||||||
|
Contexto claro · restrições · aceitação · evidência
|
||||||
RUN B
|
RUN B
|
||||||
Good prompt + skills
|
Good prompt + skills
|
||||||
|
Bom prompt + skills
|
||||||
COPY
|
COPY
|
||||||
↗
|
↗
|
||||||
Use $ponytail-lite and $webapp-testing.
|
Use $ponytail-lite and $webapp-testing.
|
||||||
@@ -423,25 +626,101 @@ Acceptance:
|
|||||||
- invalid status falls back safely to all
|
- invalid status falls back safely to all
|
||||||
- style remains consistent; unrelated files remain untouched
|
- style remains consistent; unrelated files remain untouched
|
||||||
Return the smallest working diff and concrete verification evidence.
|
Return the smallest working diff and concrete verification evidence.
|
||||||
|
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.
|
||||||
Same contract · explicit working methods · stronger proof
|
Same contract · explicit working methods · stronger proof
|
||||||
|
Mesmo contrato · métodos explícitos · prova mais forte
|
||||||
|
COMPARE THE RUNS
|
||||||
|
COMPARE AS EXECUÇÕES
|
||||||
|
01
|
||||||
|
Files changed
|
||||||
|
Arquivos alterados
|
||||||
|
02
|
||||||
|
New dependencies
|
||||||
|
Novas dependências
|
||||||
|
03
|
||||||
|
Checks actually run
|
||||||
|
Checks executados
|
||||||
|
04
|
||||||
|
Evidence returned
|
||||||
|
Evidências retornadas
|
||||||
THE HUMAN JOB
|
THE HUMAN JOB
|
||||||
O PAPEL HUMANO
|
O PAPEL HUMANO
|
||||||
The agent may be autonomous in execution. Intent, boundaries, and evidence remain yours.
|
The agent may be autonomous in execution. Intent, boundaries, and evidence remain yours.
|
||||||
O agente pode ser autônomo na execução. Intenção, limites e evidências continuam sendo seus.
|
O agente pode ser autônomo na execução. Intenção, limites e evidências continuam sendo
|
||||||
|
seus.
|
||||||
|
START HERE
|
||||||
|
COMECE AQUI
|
||||||
|
Begin with one agent and one skill. Add parallelism only when the tasks are truly
|
||||||
|
independent.
|
||||||
|
Comece com um agente e uma skill. Adicione paralelismo apenas quando as tarefas forem
|
||||||
|
realmente independentes.
|
||||||
Verification
|
Verification
|
||||||
run each gate separately
|
run each gate separately
|
||||||
|
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.
|
||||||
01 · STATIC
|
01 · STATIC
|
||||||
Lint and types
|
Lint and types
|
||||||
Format, lint, type-check. Fast and scoped to one file.
|
Format, lint, type-check. Fast and scoped to one file. Run on every save.
|
||||||
pnpm lint; echo "lint=$?"
|
pnpm lint; echo "lint=$?" pnpm typecheck; echo "typecheck=$?"
|
||||||
02 · BEHAVIOR
|
02 · BEHAVIOR
|
||||||
Unit and contract
|
Unit and contract
|
||||||
Tests that repeat. Run before claiming done.
|
Tests that repeat. Run before claiming done.
|
||||||
pnpm test; echo "test=$?"
|
pnpm test; echo "test=$?" cd services/api && go test ./...
|
||||||
03 · INTEGRATION
|
03 · INTEGRATION
|
||||||
Real UI and API
|
Real UI and API
|
||||||
Drive the actual UI, API, or browser.
|
Drive the actual UI, API, or browser. Slower and flakier — only this catches mobile
|
||||||
pnpm check:ui; echo "ui=$?"
|
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
|
||||||
|
.
|
||||||
|
Open the starter →
|
||||||
|
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.
|
||||||
|
Open the rules lab →
|
||||||
|
Open the rules lab →
|
||||||
|
Clone ↗
|
||||||
|
git.marcospaulo.dev.br/.../src/branch/pages/hands-on/rules
|
||||||
Keep learning
|
Keep learning
|
||||||
Continue aprendendo
|
Continue aprendendo
|
||||||
12 new readings + primary docs
|
12 new readings + primary docs
|
||||||
@@ -450,8 +729,26 @@ Go deeper with official documentation, production case studies, Medium, and prac
|
|||||||
workflows.
|
workflows.
|
||||||
Rules and enforcement case study →
|
Rules and enforcement case study →
|
||||||
Skills review desk →
|
Skills review desk →
|
||||||
|
Primary references →
|
||||||
12-part reading path →
|
12-part reading path →
|
||||||
Aprofunde com documentação oficial, casos de produção, Medium e fluxos de praticantes.
|
Aprofunde com documentação oficial, casos de produção, Medium e fluxos de praticantes.
|
||||||
Estudo de caso sobre regras e enforcement →
|
Estudo de caso sobre regras e enforcement →
|
||||||
Skills review desk →
|
Referências primárias →
|
||||||
Trilha com 12 leituras →
|
Trilha com 12 leituras →
|
||||||
|
Navigate by idea
|
||||||
|
short chapters / one system
|
||||||
|
Prefer a focused chapter? Start with the
|
||||||
|
route map
|
||||||
|
, then
|
||||||
|
jump directly to
|
||||||
|
models
|
||||||
|
,
|
||||||
|
agents and worktrees
|
||||||
|
,
|
||||||
|
skill creation
|
||||||
|
,
|
||||||
|
rules
|
||||||
|
, or
|
||||||
|
the
|
||||||
|
skills review desk
|
||||||
|
.
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ jobs:
|
|||||||
# back in once it can actually diff. See task 03's report.
|
# back in once it can actually diff. See task 03's report.
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
# Until the migration finishes, `dist/` holds only /summary/ and the two
|
# `dist/` now holds all ten routes, so the stub hazard that forced this to
|
||||||
# hands-on fixtures, while the live `pages` branch serves ten pages.
|
# manual dispatch is gone. It stays manual anyway: the step below is a
|
||||||
# Publishing on every push to main would take the site down to a stub, so
|
# force-push over the live `pages` branch, and making it fire on every push
|
||||||
# this job runs only when a human asks for it. Make it unconditional on
|
# to main means every merge republishes with no human in the loop. Flipping
|
||||||
# main again at task 20 (cutover), not before.
|
# it to `push` on main is a deliberate decision, not a leftover TODO.
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.publish
|
if: github.event_name == 'workflow_dispatch' && inputs.publish
|
||||||
needs: gate
|
needs: gate
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
+5
-17
@@ -9,21 +9,9 @@ vote-service
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
public/submitted-skills
|
public/submitted-skills
|
||||||
|
|
||||||
# Legacy site sources, slated for deletion at cutover (task 20). These are
|
# Unmigrated legacy sources, kept verbatim under `legacy/`. These are
|
||||||
# hand-written files with very long lines; prettier re-wraps them into hundreds
|
# hand-written files with very long lines; prettier re-wraps them into hundreds
|
||||||
# of changed lines the moment any agent stages one. Task 15 touched app.js to
|
# of changed lines the moment any agent stages one. Task 15 touched the old
|
||||||
# add four lines and produced an 829-line diff. verify.mjs asserts substrings
|
# app.js to add four lines and produced an 829-line diff. A reformat here is
|
||||||
# against several of these, so a reformat is churn at best and a broken
|
# churn at best.
|
||||||
# assertion at worst.
|
/legacy/
|
||||||
#
|
|
||||||
# Root-anchored on purpose: a bare `rules` would also swallow .agents/rules/,
|
|
||||||
# whose markdown we do want formatted.
|
|
||||||
/app.js
|
|
||||||
/styles.css
|
|
||||||
/landing.css
|
|
||||||
/chapters.css
|
|
||||||
/responsive.css
|
|
||||||
/skills-review/
|
|
||||||
/rules/
|
|
||||||
/skills/
|
|
||||||
/full-guide/
|
|
||||||
|
|||||||
+6
-13
@@ -7,23 +7,16 @@ public/submitted-skills
|
|||||||
skill-reviews
|
skill-reviews
|
||||||
vote-service
|
vote-service
|
||||||
|
|
||||||
# Legacy site sources, slated for deletion at cutover (task 20). Same list and
|
# Unmigrated legacy stylesheets, kept verbatim under `legacy/`. Same list and
|
||||||
# same reasoning as .prettierignore: these are minified, single-line
|
# same reasoning as .prettierignore: these are minified, single-line
|
||||||
# stylesheets. stylelint's `declaration-block-single-line-max-declarations`
|
# stylesheets. stylelint's `declaration-block-single-line-max-declarations`
|
||||||
# fires once per rule in them — ~180 errors for `styles.css` alone — so staging
|
# fires once per rule in them — ~180 errors for `guide.css` alone — so staging
|
||||||
# one to change a single declaration blocks the commit outright. The rule is
|
# one to change a single declaration blocks the commit outright. The rule is
|
||||||
# about hand-written source readability and says nothing useful about minified
|
# about hand-written source readability and says nothing useful about minified
|
||||||
# output that is about to be deleted.
|
# legacy output. Migrating one of these into `src/` means bringing it up to the
|
||||||
|
# design system in the same change, at which point it gets linted like any
|
||||||
|
# other source file.
|
||||||
#
|
#
|
||||||
# `public/fonts/fonts.css` is deliberately NOT here: it is new, hand-written,
|
# `public/fonts/fonts.css` is deliberately NOT here: it is new, hand-written,
|
||||||
# and must stay linted.
|
# and must stay linted.
|
||||||
#
|
/legacy/
|
||||||
# Root-anchored on purpose: a bare `rules` would also swallow .agents/rules/.
|
|
||||||
/styles.css
|
|
||||||
/landing.css
|
|
||||||
/chapters.css
|
|
||||||
/responsive.css
|
|
||||||
/skills-review/
|
|
||||||
/rules/
|
|
||||||
/skills/
|
|
||||||
/full-guide/
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ rules, and verification.** It is published as a static site on a self-hosted
|
|||||||
Gitea Pages Server, and it doubles as its own teaching artifact: the hands-on
|
Gitea Pages Server, and it doubles as its own teaching artifact: the hands-on
|
||||||
labs are dependency-free HTML/CSS/JS that workshop attendees point an agent at.
|
labs are dependency-free HTML/CSS/JS that workshop attendees point an agent at.
|
||||||
|
|
||||||
- **Current stack**: hand-written HTML + CSS + ES modules, no build step, no
|
- **Stack**: Astro, static output, no runtime dependencies. The migration
|
||||||
dependencies
|
recorded in [`plans/astro-refactor/`](plans/astro-refactor/README.md) is
|
||||||
- **Target stack**: Astro (see
|
complete; the hand-written pages it replaced are gone. What remains unmigrated
|
||||||
[`plans/astro-refactor/`](plans/astro-refactor/README.md)) — migration in
|
is the editorial CSS and the review-desk modules under `legacy/`, still
|
||||||
progress
|
imported by the pages that need them.
|
||||||
- **Languages**: English and Brazilian Portuguese, toggled client-side
|
- **Languages**: English and Brazilian Portuguese, toggled client-side
|
||||||
- **Companion service**: `vote-service/` (Go + Kubernetes) — separate lifecycle,
|
- **Companion service**: `vote-service/` (Go + Kubernetes) — separate lifecycle,
|
||||||
see its own README
|
see its own README
|
||||||
@@ -24,33 +24,34 @@ labs are dependency-free HTML/CSS/JS that workshop attendees point an agent at.
|
|||||||
## Essential commands
|
## Essential commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run verify # content + interaction contracts (scripts/verify.mjs) — the gate
|
pnpm run dev # http://localhost:4321/ai-for-dummies/
|
||||||
|
pnpm run build # writes dist/ — every check below reads it
|
||||||
|
bash .agents/scripts/gate.sh # the full gate: check, build, verify, audit, tokens
|
||||||
|
pnpm run verify # content + interaction contracts (scripts/verify.mjs)
|
||||||
node scripts/audit-ui.mjs # responsive / no-external-dependency audit
|
node scripts/audit-ui.mjs # responsive / no-external-dependency audit
|
||||||
node scripts/build-skill-review.mjs # regenerate skill-reviews/improved/ from src/content/reviews/
|
node scripts/build-skill-review.mjs # regenerate skill-reviews/improved/ from src/content/reviews/
|
||||||
pnpm run serve # python3 -m http.server 4173
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`pnpm run verify` is not a formality. It is a set of ~42 string-token assertions
|
`pnpm run verify` is not a formality. It is a set of 84 string-token assertions
|
||||||
that pin the site's real content and interactions. **A refactor that "passes" by
|
that pin the site's real content and interactions, read from the built output.
|
||||||
deleting assertions has failed.** See
|
**A refactor that "passes" by deleting assertions has failed.** See
|
||||||
[`.agents/context/verification.md`](.agents/context/verification.md).
|
[`.agents/context/verification.md`](.agents/context/verification.md).
|
||||||
|
|
||||||
## Publishing
|
## Publishing
|
||||||
|
|
||||||
`main` is the source of truth. The `pages` branch is what the Gitea Pages Server
|
`main` is the source of truth. The `pages` branch is what the Gitea Pages Server
|
||||||
actually serves, and its tree must end up identical to `main`'s. The full
|
actually serves, and it now carries **build output**, not a copy of `main`'s
|
||||||
procedure — including why `merge --ff-only` does _not_ work here — is in
|
tree. The publish job force-pushes `dist/` over it. The full procedure is in
|
||||||
[`docs/operations-guide.md`](docs/operations-guide.md).
|
[`docs/operations-guide.md`](docs/operations-guide.md); read
|
||||||
|
[`.agents/context/publishing.md`](.agents/context/publishing.md) before changing
|
||||||
Adding a build step changes this contract. Read
|
it.
|
||||||
[`.agents/context/publishing.md`](.agents/context/publishing.md) before doing
|
|
||||||
so.
|
|
||||||
|
|
||||||
## Never touch
|
## Never touch
|
||||||
|
|
||||||
- `hands-on/starter/` and `hands-on/rules/` — **lab fixtures.** The exercise
|
- `public/hands-on/starter/` and `public/hands-on/rules/` — **lab fixtures.**
|
||||||
_is_ that they are dependency-free vanilla HTML/CSS/JS an attendee can hand to
|
The exercise _is_ that they are dependency-free vanilla HTML/CSS/JS an
|
||||||
an agent. Componentizing them destroys the lesson. They ship as static assets.
|
attendee can hand to an agent. Componentizing them destroys the lesson. They
|
||||||
|
ship as static assets.
|
||||||
- `submitted-skills/` — other people's submitted work, reproduced verbatim
|
- `submitted-skills/` — other people's submitted work, reproduced verbatim
|
||||||
- `skill-reviews/improved/` — generated; edit `src/content/reviews/*.md` instead
|
- `skill-reviews/improved/` — generated; edit `src/content/reviews/*.md` instead
|
||||||
- `vote-service/` — separate deploy lifecycle; do not fold into the site build
|
- `vote-service/` — separate deploy lifecycle; do not fold into the site build
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
# Gates: review desk privacy and improved-draft audit
|
# Gates: review desk privacy and improved-draft audit
|
||||||
|
|
||||||
OWNS: skills-review/**, submitted-skills/Anonymous Operational Submission/**,
|
OWNS: src/pages/skills-review.astro, src/components/blocks/{ReviewDetail,
|
||||||
|
ChangeLens,VoteWidget,PreviewPane,FileTabs}.astro, legacy/skills-review/**,
|
||||||
|
submitted-skills/Anonymous Operational Submission/**,
|
||||||
skill-reviews/improved/ndo-repro/**, scripts/verify.mjs
|
skill-reviews/improved/ndo-repro/**, scripts/verify.mjs
|
||||||
|
|
||||||
|
The gate commands below now read `dist/`; run `pnpm run build` before them.
|
||||||
|
|
||||||
Scope: Redact the operational submission's identity and URLs from the published
|
Scope: Redact the operational submission's identity and URLs from the published
|
||||||
review desk, keep package files usable in either preview mode, and explain each
|
review desk, keep package files usable in either preview mode, and explain each
|
||||||
improved draft as a concrete diff.
|
improved draft as a concrete diff.
|
||||||
|
|||||||
@@ -15,39 +15,50 @@ skill links to a pinned source with an approval-first installation prompt.
|
|||||||
|
|
||||||
## Run locally
|
## Run locally
|
||||||
|
|
||||||
This is a dependency-free static site:
|
This is an Astro static site. It builds to `dist/` and ships no runtime
|
||||||
|
dependencies.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m http.server 4173
|
pnpm install
|
||||||
|
pnpm run dev # http://localhost:4321/ai-for-dummies/
|
||||||
|
pnpm run build # writes dist/
|
||||||
|
pnpm run preview # serves the built output
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open <http://localhost:4173>.
|
|
||||||
|
|
||||||
Verify the content and interaction contracts with:
|
Verify the content and interaction contracts with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run verify
|
pnpm run verify # reads dist/, so build first
|
||||||
|
```
|
||||||
|
|
||||||
|
The full gate — `astro check`, `astro build`, `verify.mjs`, `audit-ui.mjs`,
|
||||||
|
`check-tokens.mjs`, and the assertion-count floor — runs as:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash .agents/scripts/gate.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
- `index.html` — default route map and focused chapter navigation
|
- `src/pages/` — one file per route: the landing route map, the complete
|
||||||
- `full-guide/` — the complete bilingual presentation, with responsive audit
|
bilingual `full-guide`, the chapter pages, `rules`, `skills`, and
|
||||||
overrides
|
`skills-review`
|
||||||
- `styles.css` / `app.js` — editorial visual system and bilingual field-guide
|
- `src/components/` — blocks and islands; the interactive diagrams, selectors,
|
||||||
interactions
|
and the language toggle
|
||||||
- `responsive.css` — interactive diagrams and Full HD-to-4K adaptations
|
- `src/content/` — the content collections every page renders from
|
||||||
|
- `src/styles/tokens.css` — the design tokens
|
||||||
|
- `legacy/` — the editorial visual system and the review-desk modules, not yet
|
||||||
|
migrated into components. Still imported by the pages that need them; see
|
||||||
|
`.agents/context/architecture.md`
|
||||||
|
- `public/` — assets copied to the site root verbatim: fonts, the hands-on labs,
|
||||||
|
and `submitted-skills/`
|
||||||
- `docs/references/` — bundled research sources and notes
|
- `docs/references/` — bundled research sources and notes
|
||||||
- `docs/operations-guide.md` — canonical SilverBullet operations and skills
|
- `docs/operations-guide.md` — canonical SilverBullet operations and skills
|
||||||
guide
|
guide
|
||||||
- `hands-on/starter/` — dependency-free Tiny Tasks exercise
|
- `public/hands-on/starter/` — dependency-free Tiny Tasks exercise
|
||||||
- `hands-on/rules/` — dependency-free Guardrails lab; toggles rule sources into
|
- `public/hands-on/rules/` — dependency-free Guardrails lab; toggles rule
|
||||||
the prompt
|
sources into the prompt
|
||||||
- `rules/` — bilingual case study of skills, CLI ratchets, Husky, and PR review
|
- `skills/` — reusable design and rules-case-study skills
|
||||||
- `skills/` — reusable design and rules-case-study skills, plus an interactive
|
|
||||||
package anatomy explorer
|
|
||||||
- `skills-review/` — static review desk for submitted skills; its reader vote
|
|
||||||
widget calls the separate `vote-service`
|
|
||||||
- `vote-service/` — small Go API + Kubernetes manifests backing the
|
- `vote-service/` — small Go API + Kubernetes manifests backing the
|
||||||
skills-review vote widget (see `vote-service/README.md`)
|
skills-review vote widget (see `vote-service/README.md`)
|
||||||
- `GATES.md` — acceptance ledger for the project
|
- `GATES.md` — acceptance ledger for the project
|
||||||
@@ -55,7 +66,9 @@ pnpm run verify
|
|||||||
## Publishing
|
## Publishing
|
||||||
|
|
||||||
The Gitea instance has a Pages Server configured to publish a repository’s
|
The Gitea instance has a Pages Server configured to publish a repository’s
|
||||||
`pages` branch under `pages.marcospaulo.dev.br`. The intended site address is:
|
`pages` branch under `pages.marcospaulo.dev.br`. `pages` now carries the
|
||||||
|
**built** site — the contents of `dist/` — not a copy of `main`. The intended
|
||||||
|
site address is:
|
||||||
|
|
||||||
<https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/>
|
<https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/>
|
||||||
|
|
||||||
@@ -74,7 +87,7 @@ calls a separate stateful service — `vote-service/`, a small Go API on its own
|
|||||||
pod, one vote per visitor enforced server-side by IP (a MAC address is never
|
pod, one vote per visitor enforced server-side by IP (a MAC address is never
|
||||||
visible to a server across the internet, so it cannot be used). See
|
visible to a server across the internet, so it cannot be used). See
|
||||||
[vote-service/README.md](vote-service/README.md) for the API, the anti-abuse
|
[vote-service/README.md](vote-service/README.md) for the API, the anti-abuse
|
||||||
design, and the build/push/deploy steps; `skills-review/index.html` sets
|
design, and the build/push/deploy steps; `src/pages/skills-review.astro` sets
|
||||||
`window.SKILLS_REVIEW_VOTE_API` to point at it once deployed.
|
`window.SKILLS_REVIEW_VOTE_API` to point at it once deployed.
|
||||||
|
|
||||||
## Research
|
## Research
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<title>AI For Dummies — Agents and trees</title>
|
|
||||||
<link rel="stylesheet" href="../chapters.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<header class="top">
|
|
||||||
<a href="../summary/">← ROUTE MAP</a><span>02 / AGENTS & TREES</span
|
|
||||||
><a href="../full-guide/">field guide ↗</a>
|
|
||||||
</header>
|
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">Subagent workflow</p>
|
|
||||||
<h1>One branch<br />per <em>hand.</em></h1>
|
|
||||||
<p>
|
|
||||||
Agents work when roles, files, and evidence are bounded. A worktree gives each worker its
|
|
||||||
own checkout while the orchestrator protects intent.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section class="pipeline">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">The tree</p>
|
|
||||||
<h2>Split at<br />the <em>seam.</em></h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel">
|
|
||||||
<strong>MAIN / ORCHESTRATOR</strong
|
|
||||||
><code
|
|
||||||
>├── agent/ui → components + visual states · ├── agent/tests → acceptance + regressions
|
|
||||||
· └── agent/docs → guide + examples · merge after each leaf returns a diff and
|
|
||||||
evidence</code
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="grid">
|
|
||||||
<article class="card">
|
|
||||||
<b>FRAME</b>
|
|
||||||
<h2>Orchestrator</h2>
|
|
||||||
<p>Owns scope, task graph, boundaries, and integration.</p>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>HAND OFF</b>
|
|
||||||
<h2>Worker</h2>
|
|
||||||
<p>Owns one coherent slice and one worktree.</p>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>PROVE</b>
|
|
||||||
<h2>Verifier</h2>
|
|
||||||
<p>Re-runs gates and reports remaining gaps.</p>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
<section class="practice">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Handoff</p>
|
|
||||||
<h2>Context that<br />can <em>travel.</em></h2>
|
|
||||||
</div>
|
|
||||||
<div class="steps">
|
|
||||||
<article>
|
|
||||||
<b>01</b>
|
|
||||||
<div>
|
|
||||||
<strong>Brief</strong
|
|
||||||
><span>Goal, owned files, dependencies, non-goals, acceptance.</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<b>02</b>
|
|
||||||
<div>
|
|
||||||
<strong>Isolation</strong><span>One branch and worktree per independent change.</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<b>03</b>
|
|
||||||
<div>
|
|
||||||
<strong>Evidence</strong
|
|
||||||
><span>Commands, result, changed files, screenshots, gaps.</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<nav class="links">
|
|
||||||
<a href="../models/">Previous: models →</a><a href="../rules/">Rules case study →</a
|
|
||||||
><a href="../hands-on/rules/">Try the rules lab →</a>
|
|
||||||
</nav>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,411 +0,0 @@
|
|||||||
const phases = {
|
|
||||||
plan: { model: { en: 'OPUS / REASONING', pt: 'OPUS / RACIOCÍNIO' }, title: { en: 'Turn ambiguity into work', pt: 'Transforme ambiguidade em trabalho' }, copy: { en: 'Inspect the repository, choose the architecture, split the request, and write acceptance criteria.', pt: 'Inspecione o repositório, escolha a arquitetura, divida o pedido e escreva critérios de aceitação.' }, code: { en: 'plan → decompose → define acceptance', pt: 'planejar → decompor → definir aceitação' } },
|
|
||||||
build: { model: { en: 'SONNET, HAIKU, OR EQUIVALENT', pt: 'SONNET, HAIKU OU EQUIVALENTE' }, title: { en: 'Execute one bounded slice', pt: 'Execute uma fatia delimitada' }, copy: { en: 'Give each worker enough context, one responsibility, and its own worktree. Less context; less collision.', pt: 'Dê a cada worker contexto suficiente, uma responsabilidade e seu próprio worktree. Menos contexto; menos colisões.' }, code: { en: 'brief + worktree → implement → test', pt: 'brief + worktree → implementar → testar' } },
|
|
||||||
review: { model: { en: 'STRONG MODEL OR HUMAN', pt: 'MODELO FORTE OU HUMANO' }, title: { en: 'Reconnect result to intent', pt: 'Reconecte o resultado à intenção' }, copy: { en: 'Check the diff against the original brief, run the checks, then merge, request changes, or discard.', pt: 'Compare o diff com o brief original, execute as verificações e então faça merge, peça mudanças ou descarte.' }, code: { en: 'diff + checks → review → merge / iterate', pt: 'diff + verificações → revisar → merge / iterar' } }
|
|
||||||
};
|
|
||||||
|
|
||||||
const handsOnPrompts = {
|
|
||||||
en: {
|
|
||||||
basic: [
|
|
||||||
'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.'
|
|
||||||
].join('\n'),
|
|
||||||
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.'
|
|
||||||
].join('\n')
|
|
||||||
},
|
|
||||||
pt: {
|
|
||||||
basic: [
|
|
||||||
'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.'
|
|
||||||
].join('\n'),
|
|
||||||
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.'
|
|
||||||
].join('\n')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const modelGuide = {
|
|
||||||
providers: {
|
|
||||||
openai: {
|
|
||||||
label: 'OpenAI', source: 'https://developers.openai.com/api/docs/guides/latest-model',
|
|
||||||
title: { en: 'Sol · Terra · Luna', pt: 'Sol · Terra · Luna' },
|
|
||||||
copy: { en: 'GPT-5.6 separates capability tier from reasoning effort. Sol is flagship, Terra balances performance and cost, and Luna targets efficient high-volume work.', pt: 'O GPT-5.6 separa o nível de capacidade do esforço de raciocínio. Sol é flagship, Terra equilibra desempenho e custo, e Luna atende trabalho eficiente em alto volume.' },
|
|
||||||
tiers: [
|
|
||||||
['STRONG', 'Sol', { en: 'orchestration + hard judgment', pt: 'orquestração + julgamento difícil' }],
|
|
||||||
['BALANCED', 'Terra', { en: 'normal implementation', pt: 'implementação normal' }],
|
|
||||||
['FAST', 'Luna', { en: 'bounded, high-volume work', pt: 'trabalho delimitado e volumoso' }]
|
|
||||||
],
|
|
||||||
config: 'reasoning: { effort: "medium" }'
|
|
||||||
},
|
|
||||||
claude: {
|
|
||||||
label: 'Claude', source: 'https://docs.anthropic.com/en/docs/claude-code/model-config',
|
|
||||||
title: { en: 'Opus · Sonnet · Haiku', pt: 'Opus · Sonnet · Haiku' },
|
|
||||||
copy: { en: 'Claude Code exposes memorable aliases. Opus handles complex reasoning, Sonnet everyday coding, and Haiku simple fast work. The opusplan alias can plan with Opus and execute with Sonnet.', pt: 'Claude Code oferece aliases fáceis de lembrar. Opus cuida de raciocínio complexo, Sonnet do código cotidiano e Haiku de trabalho simples e rápido. O alias opusplan pode planejar com Opus e executar com Sonnet.' },
|
|
||||||
tiers: [
|
|
||||||
['STRONG', 'Opus', { en: 'planning + architecture', pt: 'planejamento + arquitetura' }],
|
|
||||||
['BALANCED', 'Sonnet', { en: 'everyday coding', pt: 'código cotidiano' }],
|
|
||||||
['FAST', 'Haiku', { en: 'simple, fast tasks', pt: 'tarefas simples e rápidas' }]
|
|
||||||
],
|
|
||||||
config: '/model opus · /model sonnet · /model haiku'
|
|
||||||
},
|
|
||||||
gemini: {
|
|
||||||
label: 'Gemini', source: 'https://ai.google.dev/gemini-api/docs/thinking',
|
|
||||||
title: { en: 'Pro · Flash · Flash-Lite', pt: 'Pro · Flash · Flash-Lite' },
|
|
||||||
copy: { en: 'Gemini uses model families rather than interchangeable aliases. Pro targets complex reasoning, Flash balances capability and throughput, and Flash-Lite prioritizes latency and cost.', pt: 'Gemini usa famílias de modelos, não aliases intercambiáveis. Pro mira raciocínio complexo, Flash equilibra capacidade e throughput, e Flash-Lite prioriza latência e custo.' },
|
|
||||||
tiers: [
|
|
||||||
['STRONG', 'Pro', { en: 'complex reasoning', pt: 'raciocínio complexo' }],
|
|
||||||
['BALANCED', 'Flash', { en: 'capability + throughput', pt: 'capacidade + throughput' }],
|
|
||||||
['FAST', 'Flash-Lite', { en: 'latency + cost', pt: 'latência + custo' }]
|
|
||||||
],
|
|
||||||
config: 'thinkingConfig: { thinkingLevel: "MEDIUM" }'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
efforts: {
|
|
||||||
low: { en: ['LOW', 'Use for formatting, lookup, narrow edits, and well-specified worker tasks. Optimize for fast feedback.', 'bounded task → low'], pt: ['BAIXO', 'Use para formatação, consulta, edições estreitas e tarefas de worker bem especificadas. Otimize para feedback rápido.', 'tarefa delimitada → baixo'] },
|
|
||||||
medium: { en: ['MEDIUM', 'Balanced starting point for normal implementation, tests, and review. Measure before moving up.', 'normal build → medium'], pt: ['MÉDIO', 'Ponto inicial equilibrado para implementação normal, testes e revisão. Meça antes de subir.', 'build normal → médio'] },
|
|
||||||
high: { en: ['HIGH', 'Use for architecture, orchestration, hard debugging, and consequential review where added latency is justified.', 'ambiguity + risk → high'], pt: ['ALTO', 'Use para arquitetura, orquestração, diagnóstico difícil e revisão importante quando a latência extra se justifica.', 'ambiguidade + risco → alto'] }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const skillSources = {
|
|
||||||
ponytail: 'https://github.com/ilindaniel/ponytail-lite/blob/e7b42dc2d384a702240dea4d52a7bf5530b821b6/AGENTS.md',
|
|
||||||
caveman: 'https://github.com/JuliusBrussee/caveman/blob/3b74643f4d910f496babd4e634b1ba7168816f14/skills/caveman/SKILL.md',
|
|
||||||
unlazy: 'https://github.com/Leonxlnx/unlazy/blob/473d4b80421c36d733042434cd4b938f81a19ef1/SKILL.md',
|
|
||||||
research: 'https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/research/SKILL.md',
|
|
||||||
debug: 'https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/diagnosing-bugs/SKILL.md',
|
|
||||||
review: 'https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/code-review/SKILL.md',
|
|
||||||
tokens: 'https://github.com/aetox-skills/token-saver/blob/8f21188bb043fad411f47e2e57f0365a83c13da7/SKILL.md'
|
|
||||||
};
|
|
||||||
|
|
||||||
const skillInstallPrompts = {
|
|
||||||
en: [
|
|
||||||
'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.'
|
|
||||||
].join('\n'),
|
|
||||||
pt: [
|
|
||||||
'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.'
|
|
||||||
].join('\n')
|
|
||||||
};
|
|
||||||
|
|
||||||
const interactiveCopy = {
|
|
||||||
workers: {
|
|
||||||
ui: { en: ['Interface worker', 'Receives: component contract + visual states', 'Returns: focused diff + viewport evidence'], pt: ['Worker de interface', 'Recebe: contrato do componente + estados visuais', 'Devolve: diff focado + evidência dos viewports'] },
|
|
||||||
tests: { en: ['Verification worker', 'Receives: acceptance criteria + changed surface', 'Returns: failing case, passing checks, risk notes'], pt: ['Worker de verificação', 'Recebe: critérios de aceitação + superfície alterada', 'Devolve: caso de falha, verificações passando e riscos'] },
|
|
||||||
docs: { en: ['Documentation worker', 'Receives: reviewed behavior + audience', 'Returns: guide, examples, and migration notes'], pt: ['Worker de documentação', 'Recebe: comportamento revisado + público', 'Devolve: guia, exemplos e notas de migração'] }
|
|
||||||
},
|
|
||||||
trees: {
|
|
||||||
main: { status: 'clean', owner: { en: 'Orchestrator', pt: 'Orquestrador' }, path: './project', command: 'git worktree list', note: { en: 'Shared history and integration point. Workers never edit here.', pt: 'Histórico compartilhado e ponto de integração. Workers nunca editam aqui.' } },
|
|
||||||
ui: { status: 'working', owner: { en: 'UI worker', pt: 'Worker de UI' }, path: '../task-ui', command: 'git worktree add ../task-ui -b agent/ui', note: { en: 'Own checkout and index. Safe to change presentation files in parallel.', pt: 'Checkout e índice próprios. Seguro para alterar a apresentação em paralelo.' } },
|
|
||||||
tests: { status: 'ready', owner: { en: 'Test worker', pt: 'Worker de testes' }, path: '../task-tests', command: 'git diff main...agent/tests', note: { en: 'Checks are green. Review the diff before merging into main.', pt: 'Verificações passaram. Revise o diff antes do merge em main.' } },
|
|
||||||
docs: { status: 'review', owner: { en: 'Docs worker', pt: 'Worker de docs' }, path: '../task-docs', command: 'git merge --no-ff agent/docs', note: { en: 'Review requested. Merge, request changes, or discard without touching another checkout.', pt: 'Revisão solicitada. Faça merge, peça mudanças ou descarte sem tocar em outro checkout.' } }
|
|
||||||
},
|
|
||||||
routes: {
|
|
||||||
plan: { score: 92, label: { en: 'High ambiguity', pt: 'Alta ambiguidade' }, why: { en: 'Architecture and decomposition have a wide error surface. Spend reasoning here.', pt: 'Arquitetura e decomposição têm grande superfície de erro. Invista raciocínio aqui.' } },
|
|
||||||
build: { score: 38, label: { en: 'Bounded execution', pt: 'Execução delimitada' }, why: { en: 'The brief already removed ambiguity. Optimize for speed and tight feedback.', pt: 'O brief já removeu a ambiguidade. Otimize para velocidade e feedback curto.' } },
|
|
||||||
explore: { score: 22, label: { en: 'Read-only discovery', pt: 'Descoberta somente leitura' }, why: { en: 'Search, map, and report. A lightweight model can return facts without editing.', pt: 'Busque, mapeie e reporte. Um modelo leve devolve fatos sem editar.' } },
|
|
||||||
review: { score: 74, label: { en: 'Independent judgment', pt: 'Julgamento independente' }, why: { en: 'Reconnect the diff to intent with fresh context and adversarial attention.', pt: 'Reconecte o diff à intenção com contexto novo e atenção crítica.' } }
|
|
||||||
},
|
|
||||||
skillFiles: {
|
|
||||||
skill: { icon: '◇', title: 'SKILL.md', en: 'Trigger, procedure, constraints, and the exact evidence the agent must return.', pt: 'Gatilho, procedimento, restrições e a evidência exata que o agente deve devolver.' },
|
|
||||||
references: { icon: '≡', title: 'references/', en: 'Stable facts loaded only when the procedure needs them. Keep the main instruction lean.', pt: 'Fatos estáveis carregados apenas quando o procedimento precisa. Mantenha a instrução principal enxuta.' },
|
|
||||||
scripts: { icon: '›_', title: 'scripts/', en: 'Deterministic checks and repeated operations. Prefer executable proof over prose.', pt: 'Verificações determinísticas e operações repetidas. Prefira prova executável a prosa.' },
|
|
||||||
assets: { icon: '▧', title: 'assets/', en: 'Templates and examples the agent can copy without reinventing the expected shape.', pt: 'Templates e exemplos que o agente pode copiar sem reinventar o formato esperado.' }
|
|
||||||
},
|
|
||||||
skillWorkflow: {
|
|
||||||
observe: { number: '01', title: { en: 'Start from repeated friction', pt: 'Comece pelo atrito repetido' }, question: { en: 'Which non-obvious decision keeps being rediscovered?', pt: 'Qual decisão não óbvia continua sendo redescoberta?' }, action: { en: 'Collect two or three realistic requests. Separate durable judgment from one project’s temporary details.', pt: 'Colete dois ou três pedidos realistas. Separe julgamento durável dos detalhes temporários de um projeto.' }, output: { en: 'A narrow capability and concrete examples.', pt: 'Uma capacidade estreita e exemplos concretos.' }, proof: { en: 'Without the skill, agents repeatedly make the same avoidable mistake.', pt: 'Sem a skill, agentes repetem o mesmo erro evitável.' } },
|
|
||||||
trigger: { number: '02', title: { en: 'Make discovery precise', pt: 'Torne a descoberta precisa' }, question: { en: 'When should this load—and when should it stay out?', pt: 'Quando isto deve carregar — e quando deve ficar de fora?' }, action: { en: 'Choose a short action-oriented name. Write a discriminating description that names the task and meaningful boundary.', pt: 'Escolha um nome curto orientado à ação. Escreva uma descrição discriminante que nomeie a tarefa e seu limite.' }, output: { en: 'YAML name + description in SKILL.md.', pt: 'Nome + descrição YAML em SKILL.md.' }, proof: { en: 'Relevant prompts select it; nearby unrelated prompts do not.', pt: 'Prompts relevantes selecionam; prompts próximos mas não relacionados, não.' } },
|
|
||||||
scaffold: { number: '03', title: { en: 'Choose only useful anatomy', pt: 'Escolha apenas a anatomia útil' }, question: { en: 'What must be instructions, executable, consulted, or copied?', pt: 'O que deve ser instrução, executável, consultado ou copiado?' }, action: { en: 'Keep shared guidance in SKILL.md. Add scripts for repeated deterministic work, references for conditional facts, and assets for generated output.', pt: 'Mantenha orientação comum em SKILL.md. Adicione scripts para trabalho determinístico, referências para fatos condicionais e assets para saída.' }, output: { en: 'Smallest folder structure that supports the workflow.', pt: 'A menor estrutura de pastas que sustenta o fluxo.' }, proof: { en: 'Every file has a real caller; no placeholder directories.', pt: 'Cada arquivo tem um consumidor real; nenhuma pasta placeholder.' } },
|
|
||||||
write: { number: '04', title: { en: 'Write what changes decisions', pt: 'Escreva o que muda decisões' }, question: { en: 'What would a capable agent still get wrong?', pt: 'O que um agente capaz ainda erraria?' }, action: { en: 'State outcome, non-obvious constraints, routing, and stopping conditions. Remove generic advice, duplicate facts, and speculative rules.', pt: 'Declare resultado, restrições não óbvias, roteamento e condições de parada. Remova conselhos genéricos, fatos duplicados e regras especulativas.' }, output: { en: 'Lean SKILL.md with progressive links.', pt: 'SKILL.md enxuto com links progressivos.' }, proof: { en: 'Another agent can act correctly without loading irrelevant detail.', pt: 'Outro agente consegue agir corretamente sem carregar detalhes irrelevantes.' } },
|
|
||||||
validate: { number: '05', title: { en: 'Test behavior, then sharpen', pt: 'Teste comportamento, depois refine' }, question: { en: 'Did the skill improve a realistic outcome?', pt: 'A skill melhorou um resultado realista?' }, action: { en: 'Run structural validation, execute every new script, and forward-test realistic requests. Fix observed failures with the narrowest rule.', pt: 'Execute validação estrutural, rode cada script novo e teste pedidos realistas. Corrija falhas observadas com a regra mais estreita.' }, output: { en: 'Validated package plus evidence from real use.', pt: 'Pacote validado mais evidência de uso real.' }, proof: { en: 'quick_validate passes and behavior improves without unrelated side effects.', pt: 'quick_validate passa e o comportamento melhora sem efeitos colaterais.' } }
|
|
||||||
},
|
|
||||||
commonSkills: {
|
|
||||||
ponytail: { number: '01', kind: { en: 'SIMPLIFICATION INSTINCT', pt: 'INSTINTO DE SIMPLIFICAÇÃO' }, title: 'ponytail-lite', rule: { en: 'Stop at the first rung that holds.', pt: 'Pare no primeiro degrau que sustenta.' }, use: { en: 'Use when a request invites frameworks, dependencies, abstractions, or speculative scaffolding. It checks reuse, standard library, and native platform features before adding code.', pt: 'Use quando um pedido convida frameworks, dependências, abstrações ou scaffolding especulativo. Verifica reúso, biblioteca padrão e recursos nativos antes de adicionar código.' }, example: { en: 'Date picker? Start with <input type="date">.', pt: 'Seletor de data? Comece com <input type="date">.' }, caution: { en: 'Never simplify away security, accessibility, validation, or real edge cases.', pt: 'Nunca simplifique segurança, acessibilidade, validação ou casos extremos reais.' } },
|
|
||||||
caveman: { number: '02', kind: { en: 'COMMUNICATION STYLE', pt: 'ESTILO DE COMUNICAÇÃO' }, title: 'caveman', rule: { en: 'Signal first. Drop filler.', pt: 'Sinal primeiro. Corte o excesso.' }, use: { en: 'Use for routine status, handoffs, and technical summaries where speed matters. Short fragments make actions and evidence easy to scan.', pt: 'Use em status, handoffs e resumos técnicos rotineiros onde velocidade importa. Fragmentos curtos facilitam localizar ações e evidências.' }, example: { en: 'Built. Tests pass. Published.', pt: 'Feito. Testes passaram. Publicado.' }, caution: { en: 'Drop the style for security warnings, irreversible actions, and sequences where terse wording can be misread.', pt: 'Abandone o estilo em alertas de segurança, ações irreversíveis e sequências onde concisão pode causar erro.' } },
|
|
||||||
unlazy: { number: '03', kind: { en: 'COMPLETION DISCIPLINE', pt: 'DISCIPLINA DE CONCLUSÃO' }, title: 'unlazy', rule: { en: 'Define observable gates. Finish against evidence.', pt: 'Defina gates observáveis. Termine com evidências.' }, use: { en: 'Use for substantial autonomous builds, audits, and parallel work where quiet omissions are expensive. It turns “done” into runnable acceptance checks.', pt: 'Use em builds autônomos grandes, auditorias e trabalho paralelo onde omissões custam caro. Transforma “pronto” em verificações executáveis.' }, example: { en: 'Gate: language toggle persists. Check: browser reload. Expect: pt-BR.', pt: 'Gate: idioma persiste. Check: recarregar navegador. Esperado: pt-BR.' }, caution: { en: 'Too heavy for trivial edits or factual answers.', pt: 'Pesado demais para edições triviais ou respostas factuais.' } },
|
|
||||||
research: { number: '04', kind: { en: 'SOURCE DISCIPLINE', pt: 'DISCIPLINA DE FONTES' }, title: 'research', rule: { en: 'Trace claims to owners.', pt: 'Leve afirmações até suas fontes.' }, use: { en: 'Use when APIs, standards, architecture facts, or current behavior must be verified. Capture findings in a cited note, prioritizing primary sources.', pt: 'Use quando APIs, padrões, fatos de arquitetura ou comportamento atual precisam ser verificados. Registre achados citados, priorizando fontes primárias.' }, example: { en: 'Git behavior → git-scm.com docs, not a remembered blog summary.', pt: 'Comportamento do Git → documentação git-scm.com, não memória de um blog.' }, caution: { en: 'Practitioner articles add context; they do not override official behavior.', pt: 'Artigos de praticantes dão contexto; não substituem comportamento oficial.' } },
|
|
||||||
debug: { number: '05', kind: { en: 'DIAGNOSTIC LOOP', pt: 'CICLO DE DIAGNÓSTICO' }, title: 'diagnosing-bugs', rule: { en: 'No red-capable loop, no theory.', pt: 'Sem ciclo capaz de falhar, sem teoria.' }, use: { en: 'Use for hard bugs, flakes, and regressions. First build a fast deterministic reproduction, then minimize, rank hypotheses, instrument, and fix the root cause.', pt: 'Use para bugs difíceis, flakes e regressões. Primeiro crie reprodução rápida e determinística; depois minimize, ranqueie hipóteses, instrumente e corrija a causa raiz.' }, example: { en: 'One command reproduces the exact symptom before any fix.', pt: 'Um comando reproduz o sintoma exato antes de qualquer correção.' }, caution: { en: 'Do not jump from error message straight to a patch.', pt: 'Não pule da mensagem de erro direto para um patch.' } },
|
|
||||||
review: { number: '06', kind: { en: 'INDEPENDENT REVIEW', pt: 'REVISÃO INDEPENDENTE' }, title: 'code-review', rule: { en: 'Check standards and intent separately.', pt: 'Verifique padrões e intenção separadamente.' }, use: { en: 'Use on a branch or PR. One axis checks repository standards; another checks whether the change actually satisfies its originating specification.', pt: 'Use em branch ou PR. Um eixo verifica padrões do repositório; outro verifica se a mudança realmente satisfaz a especificação original.' }, example: { en: 'Clean code can still solve the wrong problem.', pt: 'Código limpo ainda pode resolver o problema errado.' }, caution: { en: 'Pin the comparison point and source specification before reviewing.', pt: 'Fixe o ponto de comparação e a especificação antes de revisar.' } },
|
|
||||||
tokens: { number: '07', kind: { en: 'CONTEXT ECONOMY', pt: 'ECONOMIA DE CONTEXTO' }, title: 'token-saver', rule: { en: 'Keep signal. Strip command noise.', pt: 'Mantenha sinal. Corte ruído de comandos.' }, use: { en: 'Use around verbose tests, builds, Git output, and logs. Filtering preserves context for reasoning while retaining full failure output for recovery.', pt: 'Use em testes, builds, saídas Git e logs verbosos. Filtragem preserva contexto para raciocínio e mantém falhas completas para recuperação.' }, example: { en: '200 passing-test lines → one result; failures keep their trace.', pt: '200 linhas de testes passando → um resultado; falhas mantêm o trace.' }, caution: { en: 'Read raw output when exact wording or full diffs matter.', pt: 'Leia saída bruta quando texto exato ou diffs completos importarem.' } }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const translations = {
|
|
||||||
pt: {
|
|
||||||
'.chapter-links a:nth-child(1)': '01 frota', '.chapter-links a:nth-child(2)': '02 worktrees', '.chapter-links a:nth-child(3)': '03 modelos', '.chapter-links a:nth-child(4)': '04 skills', '.chapter-links a:nth-child(5)': '05 criar', '.chapter-links a:nth-child(6)': '06 kit de campo', '.chapter-links a:nth-child(7)': '07 prática', '.edition': 'ENGENHARIA DE IA <i></i> 01 / 2026',
|
|
||||||
'.hero .eyebrow': 'Uma apresentação para quem entrega software', '.lede': '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.', '.hero-index span': 'NOTA DE CAMPO / 001', '.hero-index strong': 'Entregue o<br /><em>sistema.</em>', '.hero-index small': 'Skills · agentes · worktrees · evidências',
|
|
||||||
'.hero-stats div:nth-child(1) span': 'modelo forte<br />para ambiguidade', '.hero-stats div:nth-child(2) span': 'workers delimitados<br />em paralelo', '.hero-stats div:nth-child(3) span': 'iterações<br />com evidências', '.hero-stats p': 'Leia isto como um mapa de rota, não como uma receita de prompt.',
|
|
||||||
'.thesis span': 'REGRA ZERO', '.thesis strong': 'Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.', '.fleet .section-label span:nth-child(1)': 'Uma pequena frota', '.fleet .section-label span:nth-child(2)': 'coordenação antes do paralelismo', '.captain span': 'ORQUESTRADOR', '.captain h2': 'Decide o que<br />precisa acontecer.', '.worker-card[data-worker="ui"] strong': 'Componentes e estados visuais', '.worker-card[data-worker="tests"] strong': 'Casos de aceitação', '.worker-card[data-worker="docs"] strong': 'Guia e exemplos', '.caption': 'O orquestrador preserva a intenção, escreve pequenos contratos e reúne resultados verificáveis. Ele não precisa digitar cada linha.',
|
|
||||||
'.failure-map .section-label span:nth-child(1)': 'Por que a fronteira importa', '.failure-map .section-label span:nth-child(2)': 'uma tarefa vaga / três falhas previsíveis', '.failure-grid article:nth-child(1) strong': 'Sopa de contexto', '.failure-grid article:nth-child(1) p': 'Cada worker lê tudo. Ninguém sabe quais fatos são essenciais.', '.failure-grid article:nth-child(2) strong': 'Colisão de branches', '.failure-grid article:nth-child(2) p': 'Dois agentes usam o mesmo checkout. O caminho mais rápido vira resolução de conflitos.', '.failure-grid article:nth-child(3) strong': 'Desvio confiante', '.failure-grid article:nth-child(3) p': 'O diff parece ótimo, mas ninguém verifica se resolveu o problema original.',
|
|
||||||
'.workflow .eyebrow': 'O ciclo de subagentes', '#workflow-title': 'Clique em uma fase.<br /><em>Veja a passagem.</em>', '.workflow .copy > p:last-child': 'Delegar é mover uma tarefa delimitada para um contexto menor — não abrir mão da responsabilidade.', '.handoff .section-label span:nth-child(1)': 'O que atravessa contextos', '.handoff .section-label span:nth-child(2)': 'brief → diff → evidência', '.handoff thead th:nth-child(1)': 'Pacote', '.handoff thead th:nth-child(2)': 'Contém', '.handoff thead th:nth-child(3)': 'Por que importa',
|
|
||||||
'.worktrees .eyebrow': 'Git worktrees', '.worktrees h2': 'Uma branch<br />por <em>mão.</em>', '.worktree-intro > p:nth-of-type(2)': 'Um worktree é outro diretório ligado ao mesmo repositório. Cada agente recebe seu próprio checkout e índice; o histórico continua compartilhado.', '.worktree-intro .interaction-hint': 'Selecione um nó para inspecionar checkout, responsável e próxima ação.', '.tree-toolbar > span:first-child': 'topologia do repositório', '.tree-live': '<i></i> 4 checkouts', '.tree-node.root span': 'RAIZ', '.tree-node.ui span': 'AGENTE DE UI', '.tree-node.tests span': 'AGENTE DE TESTES', '.tree-node.docs span': 'AGENTE DE DOCS', '.tree-node.root small': '● limpo', '.tree-node.ui small': '3 arquivos · trabalhando', '.tree-node.tests small': '8 verificações · pronto', '.tree-node.docs small': '2 páginas · revisão', '.routing .eyebrow': 'Roteamento de modelos', '.routing h2': 'Não pague por<br />raciocínio onde precisa<br />de <em>ritmo.</em>', '.routing .interaction-hint': 'Escolha um trabalho para entender por que o perfil do modelo muda.', '.route-table .head span:nth-child(1)': 'Trabalho', '.route-table .head span:nth-child(2)': 'Perfil', '.route-table .head span:nth-child(3)': 'Formato do prompt', '.route-table [data-route="plan"] strong': 'Planejar', '.route-table [data-route="build"] strong': 'Construir', '.route-table [data-route="explore"] strong': 'Explorar', '.route-table [data-route="review"] strong': 'Revisar', '.skills .eyebrow': 'Skills', '.skills h2': 'Escreva do jeito certo<br /><em>uma vez.</em>', '.skills > div:first-child > p': '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.',
|
|
||||||
'.skill-principles span:nth-child(1)': '01 / defina o gatilho', '.skill-principles span:nth-child(2)': '02 / carregue detalhes sob demanda', '.skill-principles span:nth-child(3)': '03 / devolva evidências', '.skill-package > span': 'PACOTE DE SKILL', '.skill-catalog .section-label span:nth-child(1)': 'Skills comuns', '.skill-catalog .section-label span:nth-child(2)': 'escolha o comportamento antes do modelo', '.catalog-intro .eyebrow': 'O kit de campo', '.catalog-intro h2': 'Trabalhos diferentes.<br />Instintos <em>diferentes.</em>', '.catalog-intro > p': 'Uma skill muda como o agente aborda o trabalho. Algumas moldam a comunicação. Outras impõem pesquisa, diagnóstico, revisão ou disciplina de conclusão. Selecione uma para inspecionar sua regra operacional.', '[data-common-skill="ponytail"] span': 'SIMPLIFICAR', '[data-common-skill="ponytail"] small': 'código mínimo que funciona', '[data-common-skill="caveman"] span': 'COMUNICAR', '[data-common-skill="caveman"] small': 'sinal sem excesso', '[data-common-skill="unlazy"] span': 'CONCLUIR', '[data-common-skill="unlazy"] small': 'gates e evidências', '[data-common-skill="research"] span': 'INVESTIGAR', '[data-common-skill="research"] small': 'fontes primárias primeiro', '[data-common-skill="debug"] span': 'DIAGNOSTICAR', '[data-common-skill="debug"] small': 'ciclo curto de feedback', '[data-common-skill="review"] span': 'REVISAR', '[data-common-skill="review"] small': 'padrões × especificação', '[data-common-skill="tokens"] span': 'ECONOMIZAR', '[data-common-skill="tokens"] small': 'comprima saídas ruidosas', '.skill-loadout > span': 'UM LOADOUT PRÁTICO', '.skill-loadout > div': '<b>PLANEJAR</b> unlazy <i>→</i> <b>CONSTRUIR</b> ponytail-lite <i>→</i> <b>DIAGNOSTICAR</b> diagnosing-bugs <i>→</i> <b>REPORTAR</b> caveman', '.rule span': 'O PAPEL HUMANO', '.rule strong': 'O agente pode ser autônomo na execução. Intenção, limites e evidências continuam sendo seus.', '.callout span': 'COMECE AQUI', '.callout strong': 'Comece com um agente e uma skill. Adicione paralelismo apenas quando as tarefas forem realmente independentes.', '.sources .section-label span:nth-child(1)': 'Continue aprendendo', '.sources .section-label span:nth-child(2)': '12 novas leituras + documentação primária', '.sources p': 'Aprofunde com documentação oficial, casos de produção, Medium e fluxos de praticantes. <a href="rules/">Estudo de caso sobre regras e enforcement →</a> <a href="docs/references/README.md">Referências primárias →</a> <a href="docs/references/additional-reading.md">Trilha com 12 leituras →</a>'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(translations.pt, {
|
|
||||||
'.model-gearbox .section-label span:nth-child(1)': 'Câmbio de modelos', '.model-gearbox .section-label span:nth-child(2)': 'nível de capacidade × esforço de raciocínio',
|
|
||||||
'.gearbox-intro .eyebrow': 'Dois controles separados', '.gearbox-intro h2': 'Escolha o motor.<br />Depois escolha a <em>marcha.</em>',
|
|
||||||
'.gearbox-intro > p': 'Um modelo mais forte muda o teto de capacidade. Mais esforço de raciocínio dá mais espaço para esse modelo trabalhar. Comece com a combinação mais leve que passa seus checks e mova um controle por vez.',
|
|
||||||
'.effort-rail > span': 'RACIOCÍNIO / PENSAMENTO', '[data-effort="low"] b': 'BAIXO', '[data-effort="low"] small': 'delimitado + rápido', '[data-effort="medium"] b': 'MÉDIO', '[data-effort="medium"] small': 'ponto inicial', '[data-effort="high"] b': 'ALTO', '[data-effort="high"] small': 'complexo + custoso',
|
|
||||||
'.gearbox-rule span': 'REGRA DE ROTEAMENTO', '.gearbox-rule strong': 'Use modelos fortes para ambiguidade e julgamento. Use modelos leves para execução delimitada. Aumente o esforço apenas quando a avaliação mostrar ganho.',
|
|
||||||
'.skill-builder .section-label span:nth-child(1)': 'Criar uma skill',
|
|
||||||
'.skill-builder .section-label span:nth-child(2)': 'atrito repetido → julgamento reutilizável',
|
|
||||||
'.builder-intro .eyebrow': 'A forja de skills',
|
|
||||||
'.builder-intro h2': 'Ensine a decisão.<br />Mantenha o contexto <em>leve.</em>',
|
|
||||||
'.builder-intro > p': '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.',
|
|
||||||
'[data-skill-step="observe"] span': 'Observar', '[data-skill-step="observe"] small': 'encontre atrito repetido',
|
|
||||||
'[data-skill-step="trigger"] span': 'Definir gatilho', '[data-skill-step="trigger"] small': 'roteie com precisão',
|
|
||||||
'[data-skill-step="scaffold"] span': 'Escolher anatomia', '[data-skill-step="scaffold"] small': 'apenas arquivos necessários',
|
|
||||||
'[data-skill-step="write"] span': 'Escrever orientação', '[data-skill-step="write"] small': 'decisões, não trivialidades',
|
|
||||||
'[data-skill-step="validate"] span': 'Validar', '[data-skill-step="validate"] small': 'teste comportamento real',
|
|
||||||
'.artifact-head span': 'SAÍDA / PACOTE DE SKILL', '.artifact-command span': 'VALIDAR',
|
|
||||||
'.builder-loop > span': 'APÓS USO REAL',
|
|
||||||
'.builder-loop > div': '<b>observar falha</b><i>→</i><b>refinar uma regra</b><i>→</i><b>retestar comportamento</b><i>→</i><b>manter estreita</b>',
|
|
||||||
'.install-skills header span': 'PACOTE DE INSTALAÇÃO', '.install-skills header strong': 'Peça ao seu agente para verificar, instalar e validar as skills.',
|
|
||||||
'.install-skills footer': 'Revise cada fonte antes da instalação. Skills locais existentes devem ser preservadas.',
|
|
||||||
'.hands-on .section-label span:nth-child(1)': 'Prática', '.hands-on .section-label span:nth-child(2)': '10 minutos / uma feature ausente',
|
|
||||||
'.hands-intro .eyebrow': 'Laboratório Tiny Tasks', '.hands-intro h2': 'Mesma tarefa.<br />Melhor <em>sistema operacional.</em>',
|
|
||||||
'.hands-intro > div:last-child > p': '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.',
|
|
||||||
'.starter-link': 'Abrir o projeto inicial →', '.exercise-brief > span': 'A FEATURE AUSENTE',
|
|
||||||
'.exercise-brief > strong': 'Adicione filtros Todos / Abertos / Concluídos que sobrevivem reload e navegação.',
|
|
||||||
'.exercise-brief > div': '<b>STACK</b> HTML · CSS · JavaScript <b>DEPENDÊNCIAS</b> nenhuma <b>ARQUIVOS</b> 3',
|
|
||||||
'.prompt-card:first-child header strong': 'Bom prompt', '.prompt-card.enhanced header strong': 'Bom prompt + skills',
|
|
||||||
'.prompt-card:first-child footer': 'Contexto claro · restrições · aceitação · evidência', '.prompt-card.enhanced footer': 'Mesmo contrato · métodos explícitos · prova mais forte',
|
|
||||||
'.comparison-strip > span': 'COMPARE AS EXECUÇÕES', '.comparison-strip > div:nth-child(2)': '<b>01</b> Arquivos alterados', '.comparison-strip > div:nth-child(3)': '<b>02</b> Novas dependências', '.comparison-strip > div:nth-child(4)': '<b>03</b> Checks executados', '.comparison-strip > div:nth-child(5)': '<b>04</b> Evidências retornadas'
|
|
||||||
});
|
|
||||||
|
|
||||||
const panel = document.querySelector('#phase-panel');
|
|
||||||
const buttons = document.querySelectorAll('[data-phase]');
|
|
||||||
const originals = new Map();
|
|
||||||
let currentLanguage = 'en';
|
|
||||||
|
|
||||||
function setText(selector, value) {
|
|
||||||
const nodes = document.querySelectorAll(selector);
|
|
||||||
if (!nodes.length) return;
|
|
||||||
if (!originals.has(selector)) originals.set(selector, [...nodes].map((node) => node.innerHTML));
|
|
||||||
nodes.forEach((node) => { node.innerHTML = value; });
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(id) {
|
|
||||||
const phase = phases[id];
|
|
||||||
panel.innerHTML = `<div class="phase-meta"><span>${phase.model[currentLanguage]}</span><small>${currentLanguage === 'pt' ? 'contexto: isolado' : 'context: isolated'}</small></div><h3>${phase.title[currentLanguage]}</h3><p>${phase.copy[currentLanguage]}</p><code>${phase.code[currentLanguage]}</code>`;
|
|
||||||
buttons.forEach((button) => { const active = button.dataset.phase === id; button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); });
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectButtons(selector, activeValue, key) {
|
|
||||||
document.querySelectorAll(selector).forEach((button) => {
|
|
||||||
const active = button.dataset[key] === activeValue;
|
|
||||||
button.classList.toggle('active', active);
|
|
||||||
button.setAttribute(button.hasAttribute('aria-selected') ? 'aria-selected' : 'aria-pressed', String(active));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderWorker(id) {
|
|
||||||
const item = interactiveCopy.workers[id][currentLanguage];
|
|
||||||
document.querySelector('#worker-detail').innerHTML = `<span>${item[0]}</span><strong>${item[1]}</strong><small>${item[2]}</small>`;
|
|
||||||
selectButtons('[data-worker]', id, 'worker');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTree(id) {
|
|
||||||
const item = interactiveCopy.trees[id];
|
|
||||||
const language = currentLanguage;
|
|
||||||
document.querySelector('#tree-detail').innerHTML = `<div><span>${language === 'pt' ? 'RESPONSÁVEL' : 'OWNER'}</span><strong>${item.owner[language]}</strong></div><div><span>CHECKOUT</span><strong>${item.path}</strong></div><p>${item.note[language]}</p><code>${item.command}</code>`;
|
|
||||||
selectButtons('[data-tree]', id, 'tree');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderRoute(id) {
|
|
||||||
const item = interactiveCopy.routes[id];
|
|
||||||
const language = currentLanguage;
|
|
||||||
document.querySelector('#route-detail').innerHTML = `<div class="route-meter"><span style="--score:${item.score}%"></span></div><div><small>${language === 'pt' ? 'CARGA DE RACIOCÍNIO' : 'REASONING LOAD'} · ${item.score}</small><strong>${item.label[language]}</strong><p>${item.why[language]}</p></div>`;
|
|
||||||
selectButtons('[data-route]', id, 'route');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderModelProvider(id) {
|
|
||||||
const item = modelGuide.providers[id];
|
|
||||||
const language = currentLanguage;
|
|
||||||
const sourceLabel = language === 'pt' ? 'FONTE OFICIAL ↗' : 'OFFICIAL SOURCE ↗';
|
|
||||||
const kindLabels = language === 'pt' ? { STRONG: 'FORTE', BALANCED: 'EQUILÍBRIO', FAST: 'RÁPIDO' } : {};
|
|
||||||
const tiers = item.tiers.map(([kind, name, note]) => `<div><span>${kindLabels[kind] || kind}</span><strong>${name}</strong><small>${note[language]}</small></div>`).join('');
|
|
||||||
document.querySelector('#provider-detail').innerHTML = `<header><span>${item.label}</span><a href="${item.source}" target="_blank" rel="noopener">${sourceLabel}</a></header><h3>${item.title[language]}</h3><p>${item.copy[language]}</p><div class="model-ladder">${tiers}</div>`;
|
|
||||||
selectButtons('[data-model-provider]', id, 'modelProvider');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderEffort(id) {
|
|
||||||
const item = modelGuide.efforts[id][currentLanguage];
|
|
||||||
const provider = modelGuide.providers[document.querySelector('[data-model-provider].active')?.dataset.modelProvider || 'openai'];
|
|
||||||
document.querySelector('#effort-detail').innerHTML = `<span>${item[0]}</span><p>${item[1]}</p><code>${provider.config}</code>`;
|
|
||||||
selectButtons('[data-effort]', id, 'effort');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSkillFile(id) {
|
|
||||||
const item = interactiveCopy.skillFiles[id];
|
|
||||||
document.querySelector('#skill-detail').innerHTML = `<span>${item.icon}</span><div><strong>${item.title}</strong><p>${item[currentLanguage]}</p><small>${currentLanguage === 'pt' ? 'clique em outro arquivo para explorar' : 'select another file to explore'}</small></div>`;
|
|
||||||
selectButtons('[data-skill-file]', id, 'skillFile');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSkillWorkflow(id) {
|
|
||||||
const item = interactiveCopy.skillWorkflow[id];
|
|
||||||
const language = currentLanguage;
|
|
||||||
const labels = language === 'pt'
|
|
||||||
? ['PERGUNTA', 'AÇÃO', 'ARTEFATO', 'PROVA']
|
|
||||||
: ['QUESTION', 'ACTION', 'ARTIFACT', 'PROOF'];
|
|
||||||
document.querySelector('#builder-detail').innerHTML = `<header><span>${item.number}</span><small>${labels[0]}</small></header><h3>${item.title[language]}</h3><blockquote>${item.question[language]}</blockquote><div class="builder-action"><span>${labels[1]}</span><p>${item.action[language]}</p></div><footer><div><span>${labels[2]}</span><strong>${item.output[language]}</strong></div><div><span>${labels[3]}</span><strong>${item.proof[language]}</strong></div></footer>`;
|
|
||||||
selectButtons('[data-skill-step]', id, 'skillStep');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCommonSkill(id) {
|
|
||||||
const item = interactiveCopy.commonSkills[id];
|
|
||||||
const language = currentLanguage;
|
|
||||||
const labels = language === 'pt'
|
|
||||||
? ['QUANDO USAR', 'EXEMPLO', 'CUIDADO']
|
|
||||||
: ['WHEN TO USE', 'EXAMPLE', 'WATCH OUT'];
|
|
||||||
const sourceLabel = language === 'pt' ? 'FONTE NO GITHUB ↗' : 'GITHUB SOURCE ↗';
|
|
||||||
document.querySelector('#common-skill-detail').innerHTML = `<header><span>${item.number}</span><small>${item.kind[language]}</small></header><h3>${item.title}</h3><blockquote>${item.rule[language]}</blockquote><div class="common-skill-notes"><div><span>${labels[0]}</span><p>${item.use[language]}</p></div><div><span>${labels[1]}</span><p>${item.example[language]}</p></div><div><span>${labels[2]}</span><p>${item.caution[language]}</p></div></div><a class="skill-source" href="${skillSources[id]}" target="_blank" rel="noopener">${sourceLabel}</a>`;
|
|
||||||
selectButtons('[data-common-skill]', id, 'commonSkill');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderHandsOn() {
|
|
||||||
document.querySelector('#prompt-basic').textContent = handsOnPrompts[currentLanguage].basic;
|
|
||||||
document.querySelector('#prompt-skills').textContent = handsOnPrompts[currentLanguage].skills;
|
|
||||||
document.querySelector('#prompt-install-skills').textContent = skillInstallPrompts[currentLanguage];
|
|
||||||
document.querySelectorAll('[data-copy-target] span').forEach((label) => { label.textContent = currentLanguage === 'pt' ? 'COPIAR' : 'COPY'; });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyPrompt(button) {
|
|
||||||
const text = document.querySelector(`#${button.dataset.copyTarget}`).textContent;
|
|
||||||
let copied = false;
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
copied = true;
|
|
||||||
} catch (error) {
|
|
||||||
const helper = document.createElement('textarea');
|
|
||||||
helper.value = text;
|
|
||||||
helper.setAttribute('readonly', '');
|
|
||||||
helper.style.position = 'fixed';
|
|
||||||
helper.style.opacity = '0';
|
|
||||||
document.body.appendChild(helper);
|
|
||||||
helper.select();
|
|
||||||
copied = document.execCommand('copy');
|
|
||||||
helper.remove();
|
|
||||||
}
|
|
||||||
const status = document.querySelector('#copy-status');
|
|
||||||
status.textContent = copied
|
|
||||||
? (currentLanguage === 'pt' ? 'Prompt copiado. Cole em uma nova sessão de agente.' : 'Prompt copied. Paste it into a fresh agent session.')
|
|
||||||
: (currentLanguage === 'pt' ? 'Não foi possível copiar. Selecione o texto manualmente.' : 'Copy unavailable. Select the text manually.');
|
|
||||||
if (copied) {
|
|
||||||
button.classList.add('copied');
|
|
||||||
button.querySelector('span').textContent = currentLanguage === 'pt' ? 'COPIADO' : 'COPIED';
|
|
||||||
window.setTimeout(() => { button.classList.remove('copied'); button.querySelector('span').textContent = currentLanguage === 'pt' ? 'COPIAR' : 'COPY'; }, 1800);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderInteractive() {
|
|
||||||
renderWorker(document.querySelector('[data-worker].active')?.dataset.worker || 'ui');
|
|
||||||
renderTree(document.querySelector('[data-tree].active')?.dataset.tree || 'main');
|
|
||||||
renderRoute(document.querySelector('[data-route].active')?.dataset.route || 'plan');
|
|
||||||
renderModelProvider(document.querySelector('[data-model-provider].active')?.dataset.modelProvider || 'openai');
|
|
||||||
renderEffort(document.querySelector('[data-effort].active')?.dataset.effort || 'medium');
|
|
||||||
renderSkillFile(document.querySelector('[data-skill-file].active')?.dataset.skillFile || 'skill');
|
|
||||||
renderSkillWorkflow(document.querySelector('[data-skill-step].active')?.dataset.skillStep || 'observe');
|
|
||||||
renderCommonSkill(document.querySelector('[data-common-skill].active')?.dataset.commonSkill || 'ponytail');
|
|
||||||
renderHandsOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyLanguage(language) {
|
|
||||||
currentLanguage = language === 'pt' ? 'pt' : 'en';
|
|
||||||
document.documentElement.lang = currentLanguage === 'pt' ? 'pt-BR' : 'en';
|
|
||||||
if (currentLanguage === 'pt') Object.entries(translations.pt).forEach(([selector, value]) => setText(selector, value));
|
|
||||||
else originals.forEach((values, selector) => document.querySelectorAll(selector).forEach((node, index) => { node.innerHTML = values[index]; }));
|
|
||||||
document.querySelectorAll('[data-lang]').forEach((button) => { const active = button.dataset.lang === currentLanguage; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); });
|
|
||||||
render(document.querySelector('[data-phase].active')?.dataset.phase || 'plan');
|
|
||||||
renderInteractive();
|
|
||||||
try { localStorage.setItem('ai-for-dummies-language', currentLanguage); } catch (error) { /* previews may disable storage */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.forEach((button) => button.addEventListener('click', () => render(button.dataset.phase)));
|
|
||||||
document.querySelectorAll('[data-lang]').forEach((button) => button.addEventListener('click', () => applyLanguage(button.dataset.lang)));
|
|
||||||
document.querySelectorAll('[data-worker]').forEach((button) => button.addEventListener('click', () => renderWorker(button.dataset.worker)));
|
|
||||||
document.querySelectorAll('[data-tree]').forEach((button) => button.addEventListener('click', () => renderTree(button.dataset.tree)));
|
|
||||||
document.querySelectorAll('[data-route]').forEach((button) => button.addEventListener('click', () => renderRoute(button.dataset.route)));
|
|
||||||
document.querySelectorAll('[data-model-provider]').forEach((button) => button.addEventListener('click', () => { renderModelProvider(button.dataset.modelProvider); renderEffort(document.querySelector('[data-effort].active')?.dataset.effort || 'medium'); }));
|
|
||||||
document.querySelectorAll('[data-effort]').forEach((button) => button.addEventListener('click', () => renderEffort(button.dataset.effort)));
|
|
||||||
document.querySelectorAll('[data-skill-file]').forEach((button) => button.addEventListener('click', () => renderSkillFile(button.dataset.skillFile)));
|
|
||||||
document.querySelectorAll('[data-skill-step]').forEach((button) => button.addEventListener('click', () => renderSkillWorkflow(button.dataset.skillStep)));
|
|
||||||
document.querySelectorAll('[data-common-skill]').forEach((button) => button.addEventListener('click', () => renderCommonSkill(button.dataset.commonSkill)));
|
|
||||||
document.querySelectorAll('[data-copy-target]').forEach((button) => button.addEventListener('click', () => copyPrompt(button)));
|
|
||||||
window.addEventListener('scroll', () => { const height = document.documentElement.scrollHeight - window.innerHeight; document.querySelector('.reading-progress span').style.width = `${height > 0 ? (window.scrollY / height) * 100 : 0}%`; }, { passive: true });
|
|
||||||
|
|
||||||
let savedLanguage = 'en';
|
|
||||||
try { savedLanguage = localStorage.getItem('ai-for-dummies-language') || 'en'; } catch (error) { /* previews may disable storage */ }
|
|
||||||
render('plan');
|
|
||||||
applyLanguage(savedLanguage);
|
|
||||||
+13
-11
@@ -427,17 +427,19 @@ paths, and confidence notes.
|
|||||||
|
|
||||||
## Hands-on lab
|
## Hands-on lab
|
||||||
|
|
||||||
The presentation includes a dependency-free starter at `hands-on/starter/`. It
|
The presentation includes a dependency-free starter at
|
||||||
renders a small task board but intentionally omits the All / Open / Done filter.
|
`public/hands-on/starter/`. It renders a small task board but intentionally
|
||||||
|
omits the All / Open / Done filter. Attendees clone it from the `pages` branch,
|
||||||
|
where the build places it at `hands-on/starter/`.
|
||||||
|
|
||||||
Run it from the repository root:
|
Run it from the repository root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m http.server 4173
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open
|
Open
|
||||||
[http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/).
|
[http://localhost:4321/ai-for-dummies/hands-on/starter/](http://localhost:4321/ai-for-dummies/hands-on/starter/).
|
||||||
In a fresh coding-agent session, copy **Run A — Good prompt** from the
|
In a fresh coding-agent session, copy **Run A — Good prompt** from the
|
||||||
presentation. Record changed files, dependencies, checks, and evidence. Restore
|
presentation. Record changed files, dependencies, checks, and evidence. Restore
|
||||||
the starter, then repeat with **Run B — Good prompt + skills**.
|
the starter, then repeat with **Run B — Good prompt + skills**.
|
||||||
@@ -464,20 +466,20 @@ Compare:
|
|||||||
|
|
||||||
### Hands-on rules lab
|
### Hands-on rules lab
|
||||||
|
|
||||||
A second lab at `hands-on/rules/` mirrors the starter's visual system and runs
|
A second lab at `public/hands-on/rules/` mirrors the starter's visual system and
|
||||||
the same exercise against rule sources. It lists five toggleable rule sources —
|
runs the same exercise against rule sources. It lists five toggleable rule
|
||||||
`AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook, the
|
sources — `AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit`
|
||||||
`check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the **ruled**
|
hook, the `check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the
|
||||||
prompt live as each toggle flips.
|
**ruled** prompt live as each toggle flips.
|
||||||
|
|
||||||
Run it:
|
Run it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m http.server 4173
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open
|
Open
|
||||||
[http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/).
|
[http://localhost:4321/ai-for-dummies/hands-on/rules/](http://localhost:4321/ai-for-dummies/hands-on/rules/).
|
||||||
Compare the **naive** and **ruled** prompt panels. Toggle rules off to shrink
|
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
|
the prompt; toggle them on to add more guards. Copy the final prompt and run it
|
||||||
against a real coding agent.
|
against a real coding agent.
|
||||||
|
|||||||
+2
-4
@@ -6,15 +6,12 @@ export default [
|
|||||||
ignores: [
|
ignores: [
|
||||||
'.agents/**',
|
'.agents/**',
|
||||||
'.astro/**',
|
'.astro/**',
|
||||||
'app.js',
|
|
||||||
'dist/**',
|
'dist/**',
|
||||||
'full-guide/**',
|
|
||||||
'hands-on/**',
|
'hands-on/**',
|
||||||
|
'legacy/**',
|
||||||
'public/hands-on/**',
|
'public/hands-on/**',
|
||||||
'rules/**',
|
|
||||||
'scripts/**',
|
'scripts/**',
|
||||||
'skill-reviews/**',
|
'skill-reviews/**',
|
||||||
'skills-review/**',
|
|
||||||
'skills/**',
|
'skills/**',
|
||||||
'submitted-skills/**',
|
'submitted-skills/**',
|
||||||
'vote-service/**',
|
'vote-service/**',
|
||||||
@@ -26,6 +23,7 @@ export default [
|
|||||||
ignores: [
|
ignores: [
|
||||||
'dist/**',
|
'dist/**',
|
||||||
'hands-on/**',
|
'hands-on/**',
|
||||||
|
'legacy/**',
|
||||||
'public/hands-on/**',
|
'public/hands-on/**',
|
||||||
'submitted-skills/**',
|
'submitted-skills/**',
|
||||||
'skill-reviews/**',
|
'skill-reviews/**',
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="../responsive.css" />
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="description" content="AI For Dummies: a field guide to skills, models, subagents, and worktrees." />
|
|
||||||
<title>AI For Dummies — Field Guide</title>
|
|
||||||
<link rel="stylesheet" href="../styles.css" />
|
|
||||||
<link rel="stylesheet" href="audit.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="reading-progress" aria-hidden="true"><span></span></div>
|
|
||||||
<main>
|
|
||||||
<header class="topbar"><a class="brand" href="#top"><span class="mark">A</span> field guide</a><nav class="chapter-links" aria-label="Chapter sections"><a href="#fleet">01 fleet</a><a href="#worktrees">02 worktrees</a><a href="#models">03 models</a><a href="#skills">04 skills</a><a href="#create-skill">05 create</a><a href="#field-kit">06 field kit</a><a href="#hands-on">07 hands-on</a><a href="#verification">08 verify</a></nav><div class="topbar-tools"><a class="skills-review-link" href="../skills-review/">review submissions ↗</a><div class="lang-switch" aria-label="Language"><button class="active" data-lang="en" aria-pressed="true">EN</button><span>/</span><button data-lang="pt" aria-pressed="false">PT</button></div><span class="edition">AI ENGINEERING <i></i> 01 / 2026</span></div></header>
|
|
||||||
<section class="hero" id="top"><div><p class="eyebrow">A presentation for humans who ship</p><h1>AI for<br /><em>dummies.</em></h1><p class="lede">You do not need an army of models. You need a system: one mind to frame the work, several hands to execute it, and a clean boundary between every task.</p></div><aside class="hero-index"><span>FIELD NOTE / 001</span><strong>Ship the<br /><em>system.</em></strong><small>Skills · agents · worktrees · proof</small></aside></section>
|
|
||||||
<section class="hero-stats" aria-label="Chapter summary"><div><strong>01</strong><span>strong model<br />for ambiguity</span></div><div><strong>03</strong><span>bounded workers<br />in parallel</span></div><div><strong>∞</strong><span>iterations<br />with evidence</span></div><p>Read this as a route map, not a prompt recipe.</p></section>
|
|
||||||
<section class="thesis"><div><span>RULE ZERO</span><strong>Strong model for ambiguity.<br />Light model for bounded work.</strong></div><div class="signal" aria-hidden="true"><b>THINK</b><i></i><i></i><i></i><b>MAKE</b></div></section>
|
|
||||||
<section class="fleet" id="fleet"><div class="section-label"><span>A small fleet</span><span>coordination before parallelism</span></div><div class="fleet-grid"><article class="captain"><span>ORCHESTRATOR</span><h2>Decides what<br />needs to happen.</h2><code>Opus / reasoning</code></article><div class="arrow">→</div><div class="workers" role="group" aria-label="Worker agents"><button class="worker-card active" data-worker="ui" aria-pressed="true"><span>UI</span><strong>Component and visual states</strong><code>agent/ui</code></button><button class="worker-card" data-worker="tests" aria-pressed="false"><span>TEST</span><strong>Acceptance cases</strong><code>agent/tests</code></button><button class="worker-card" data-worker="docs" aria-pressed="false"><span>DOCS</span><strong>Guide and examples</strong><code>agent/docs</code></button></div></div><div class="worker-detail" id="worker-detail" aria-live="polite"></div><p class="caption">The orchestrator preserves intent, writes small contracts, and gathers results that can be verified. It does not need to type every line.</p></section>
|
|
||||||
<section class="failure-map"><div class="section-label"><span>Why the boundary matters</span><span>one vague task / three predictable failures</span></div><div class="failure-grid"><article><span>01</span><strong>Context soup</strong><p>Every worker reads everything. Nobody knows which facts are load-bearing.</p></article><article><span>02</span><strong>Branch collision</strong><p>Two agents touch the same checkout. The fastest path becomes conflict resolution.</p></article><article><span>03</span><strong>Confident drift</strong><p>The diff is polished, but no one checks whether it solved the original problem.</p></article></div></section>
|
|
||||||
<section class="workflow" aria-labelledby="workflow-title"><div class="copy"><p class="eyebrow">The subagent loop</p><h2 id="workflow-title">Click a phase.<br /><em>See the handoff.</em></h2><p>Delegation means moving one bounded task into a smaller context—not giving away responsibility.</p></div><div class="phase-tabs" role="tablist" aria-label="Workflow phases"><button class="active" data-phase="plan" role="tab" aria-selected="true"><b>01</b> PLAN</button><button data-phase="build" role="tab" aria-selected="false"><b>02</b> BUILD</button><button data-phase="review" role="tab" aria-selected="false"><b>03</b> REVIEW</button></div><article class="phase-panel" id="phase-panel" aria-live="polite"></article></section>
|
|
||||||
<section class="handoff"><div class="section-label"><span>What crosses contexts</span><span>brief → diff → evidence</span></div><table><thead><tr><th>Package</th><th>Contains</th><th>Why it matters</th></tr></thead><tbody><tr><th scope="row">Brief</th><td>goal, files, boundaries</td><td>stops the worker inventing the problem</td></tr><tr><th scope="row">Worktree</th><td>branch and isolated checkout</td><td>parallel edits do not collide</td></tr><tr><th scope="row">Checks</th><td>tests, build, criteria</td><td>turns “looks good” into evidence</td></tr><tr><th scope="row">Diff</th><td>small, reviewable change</td><td>integration and discard stay cheap</td></tr></tbody></table></section>
|
|
||||||
<section class="worktrees" id="worktrees"><div class="worktree-intro"><p class="eyebrow">Git worktrees</p><h2>One branch<br />per <em>hand.</em></h2><p>A worktree is another directory linked to the same repository. Each agent gets its own checkout and index; history remains shared.</p><p class="interaction-hint">Select a node to inspect its checkout, owner, and next action.</p></div><div class="tree-lab"><div class="tree-toolbar"><span>repository topology</span><span class="tree-live"><i></i> 4 checkouts</span></div><div class="tree-stage" role="tree" aria-label="Repository worktree topology"><svg viewBox="0 0 760 330" preserveAspectRatio="none" aria-hidden="true"><path class="tree-edge trunk" d="M380 48 V118"/><path class="tree-edge" d="M380 118 C380 170 110 150 110 224"/><path class="tree-edge" d="M380 118 V224"/><path class="tree-edge" d="M380 118 C380 170 650 150 650 224"/></svg><button class="tree-node root active" data-tree="main" role="treeitem" aria-selected="true"><span>ROOT</span><strong>main</strong><small>● clean</small></button><button class="tree-node branch ui" data-tree="ui" role="treeitem" aria-selected="false"><span>UI AGENT</span><strong>agent/ui</strong><small>3 files · working</small></button><button class="tree-node branch tests" data-tree="tests" role="treeitem" aria-selected="false"><span>TEST AGENT</span><strong>agent/tests</strong><small>8 checks · ready</small></button><button class="tree-node branch docs" data-tree="docs" role="treeitem" aria-selected="false"><span>DOCS AGENT</span><strong>agent/docs</strong><small>2 pages · review</small></button></div><article class="tree-detail" id="tree-detail" aria-live="polite"></article></div></section>
|
|
||||||
<section class="routing"><div><p class="eyebrow">Model routing</p><h2>Do not pay for<br />reasoning where<br />you need <em>rhythm.</em></h2><p class="interaction-hint">Choose a job to see why the model profile changes.</p></div><div class="route-console"><div class="route-table"><div class="head"><span>Work</span><span>Profile</span><span>Prompt shape</span></div><button class="active" data-route="plan" aria-pressed="true"><strong>Plan</strong><b>strong / broad</b><small>What changes? What can break?</small></button><button data-route="build" aria-pressed="false"><strong>Build</strong><b>fast / focused</b><small>Implement this slice. Run these checks.</small></button><button data-route="explore" aria-pressed="false"><strong>Explore</strong><b>read-only / light</b><small>Find where this contract is used.</small></button><button data-route="review" aria-pressed="false"><strong>Review</strong><b>independent</b><small>Does the diff satisfy the brief?</small></button></div><article class="route-detail" id="route-detail" aria-live="polite"></article></div></section>
|
|
||||||
<section class="model-gearbox" id="models"><div class="section-label"><span>Model gearbox</span><span>capability tier × thinking effort</span></div><div class="gearbox-intro"><div><p class="eyebrow">Two separate knobs</p><h2>Choose the engine.<br />Then choose the <em>gear.</em></h2></div><p>A stronger model changes the capability ceiling. Higher reasoning effort gives that model more room to work. Start with the lightest combination that passes your real checks, then move one knob at a time.</p></div><div class="gearbox"><div class="provider-tabs" role="tablist" aria-label="Model providers"><button class="active" data-model-provider="openai" role="tab" aria-selected="true">OPENAI</button><button data-model-provider="claude" role="tab" aria-selected="false">CLAUDE</button><button data-model-provider="gemini" role="tab" aria-selected="false">GEMINI</button></div><article class="provider-detail" id="provider-detail" aria-live="polite"></article><div class="effort-rail"><span>REASONING / THINKING</span><button data-effort="low" aria-pressed="false"><b>LOW</b><small>bounded + fast</small></button><button class="active" data-effort="medium" aria-pressed="true"><b>MEDIUM</b><small>default start</small></button><button data-effort="high" aria-pressed="false"><b>HIGH</b><small>complex + costly</small></button></div><article class="effort-detail" id="effort-detail" aria-live="polite"></article></div><div class="gearbox-rule"><span>ROUTING RULE</span><strong>Use strong models for ambiguity and judgment. Use lighter models for bounded execution. Raise effort only when evaluation shows a gain.</strong></div></section>
|
|
||||||
<section class="skills" id="skills"><div><p class="eyebrow">Skills</p><h2>Write the right way<br /><em>once.</em></h2><p>A skill is a reusable procedure. It can carry instructions, references, scripts, and assets. It is not magical memory, and it does not replace acceptance criteria.</p><div class="skill-principles"><span>01 / trigger clearly</span><span>02 / load detail on demand</span><span>03 / return evidence</span></div></div><div class="skill-explorer"><div class="skill-package" role="tree" aria-label="Skill package files"><span>SKILL PACKAGE</span><button class="active" data-skill-file="skill" role="treeitem" aria-selected="true"><code>SKILL.md</code><small>procedure and limits</small></button><button data-skill-file="references" role="treeitem" aria-selected="false"><code>references/</code><small>facts to consult</small></button><button data-skill-file="scripts" role="treeitem" aria-selected="false"><code>scripts/</code><small>repeatable checks</small></button><button data-skill-file="assets" role="treeitem" aria-selected="false"><code>assets/</code><small>templates and examples</small></button></div><article class="skill-detail" id="skill-detail" aria-live="polite"></article></div><pre><code>name: review-ui · check focus, mobile, reduced motion · run verification · return evidence</code></pre></section>
|
|
||||||
<section class="skill-builder" id="create-skill"><div class="section-label"><span>Create a skill</span><span>repeatable pain → reusable judgment</span></div><div class="builder-intro"><div><p class="eyebrow">The skill forge</p><h2>Teach the decision.<br />Keep the context <em>light.</em></h2></div><p>Do not package everything you know. Capture the non-obvious choices that repeatedly improve an outcome, then prove the skill changes behavior.</p></div><div class="builder-workbench"><nav class="builder-steps" role="tablist" aria-label="Skill creation workflow"><button class="active" data-skill-step="observe" role="tab" aria-selected="true"><b>01</b><span>Observe</span><small>find repeated friction</small></button><button data-skill-step="trigger" role="tab" aria-selected="false"><b>02</b><span>Define trigger</span><small>route precisely</small></button><button data-skill-step="scaffold" role="tab" aria-selected="false"><b>03</b><span>Choose anatomy</span><small>only needed files</small></button><button data-skill-step="write" role="tab" aria-selected="false"><b>04</b><span>Write guidance</span><small>decisions, not trivia</small></button><button data-skill-step="validate" role="tab" aria-selected="false"><b>05</b><span>Validate</span><small>test real behavior</small></button></nav><article class="builder-detail" id="builder-detail" aria-live="polite"></article><aside class="builder-artifact"><div class="artifact-head"><span>OUTPUT / SKILL PACKAGE</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>VALIDATE</span><code>quick_validate.py ./review-ui</code></div></aside></div><div class="builder-loop"><span>AFTER REAL USE</span><div><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></section>
|
|
||||||
<section class="skill-catalog" id="field-kit"><div class="section-label"><span>Common skills</span><span>choose behavior before model</span></div><div class="catalog-intro"><div><p class="eyebrow">The field kit</p><h2>Different jobs.<br />Different <em>instincts.</em></h2></div><p>A skill changes how an agent approaches work. Some shape communication. Others enforce research, debugging, review, or completion discipline. Select one to inspect its operating rule and verified source.</p></div><div class="skill-deck"><div class="skill-index" role="tablist" aria-label="Common agent skills"><button class="active" data-common-skill="ponytail" role="tab" aria-selected="true"><span>SIMPLIFY</span><strong>ponytail-lite</strong><small>minimum code that holds</small></button><button data-common-skill="caveman" role="tab" aria-selected="false"><span>COMMUNICATE</span><strong>caveman</strong><small>signal without filler</small></button><button data-common-skill="unlazy" role="tab" aria-selected="false"><span>COMPLETE</span><strong>unlazy</strong><small>gates and evidence</small></button><button data-common-skill="research" role="tab" aria-selected="false"><span>INVESTIGATE</span><strong>research</strong><small>primary sources first</small></button><button data-common-skill="debug" role="tab" aria-selected="false"><span>DIAGNOSE</span><strong>diagnosing-bugs</strong><small>tight feedback loop</small></button><button data-common-skill="review" role="tab" aria-selected="false"><span>REVIEW</span><strong>code-review</strong><small>standards × spec</small></button><button data-common-skill="tokens" role="tab" aria-selected="false"><span>ECONOMIZE</span><strong>token-saver</strong><small>compress noisy output</small></button></div><article class="common-skill-detail" id="common-skill-detail" aria-live="polite"></article></div><div class="skill-loadout"><span>ONE PRACTICAL LOADOUT</span><div><b>PLAN</b> unlazy <i>→</i> <b>BUILD</b> ponytail-lite <i>→</i> <b>DEBUG</b> diagnosing-bugs <i>→</i> <b>REPORT</b> caveman</div></div><article class="install-skills"><header><div><span>INSTALL PACK</span><strong>Ask your coding agent to verify, install, and validate the skills.</strong></div><button data-copy-target="prompt-install-skills"><span>COPY</span><i aria-hidden="true">↗</i></button></header><pre><code id="prompt-install-skills"></code></pre><footer>Review every source before installation. Existing local skills must be preserved.</footer></article></section>
|
|
||||||
<section class="hands-on" id="hands-on"><div class="section-label"><span>Hands-on</span><span>10 minutes / one missing feature</span></div><div class="hands-intro"><div><p class="eyebrow">Tiny Tasks lab</p><h2>Same task.<br />Better <em>operating system.</em></h2></div><div><p>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.</p><div class="starter-links"><div class="starter-link-group"><a href="../hands-on/starter/" class="starter-link">Open the starter →</a><a href="https://git.marcospaulo.dev.br/netcracker/ai-for-dummies/src/branch/pages/hands-on/starter" class="starter-link starter-link-source">Clone from Gitea →</a></div><div class="starter-link-group"><a href="../hands-on/rules/" class="starter-link">Open the rules lab →</a><a href="https://git.marcospaulo.dev.br/netcracker/ai-for-dummies/src/branch/pages/hands-on/rules" class="starter-link starter-link-source">Clone from Gitea →</a></div></div></div></div><div class="exercise-brief"><span>THE MISSING FEATURE</span><strong>Add All / Open / Done filters that survive reload and browser navigation.</strong><div><b>STACK</b> HTML · CSS · JavaScript <b>DEPENDENCIES</b> none <b>FILES</b> 3</div></div><div class="prompt-compare"><article class="prompt-card"><header><div><span>RUN A</span><strong>Good prompt</strong></div><button data-copy-target="prompt-basic"><span>COPY</span><i aria-hidden="true">↗</i></button></header><pre><code id="prompt-basic"></code></pre><footer>Clear context · constraints · acceptance · evidence</footer></article><article class="prompt-card enhanced"><header><div><span>RUN B</span><strong>Good prompt + skills</strong></div><button data-copy-target="prompt-skills"><span>COPY</span><i aria-hidden="true">↗</i></button></header><pre><code id="prompt-skills"></code></pre><footer>Same contract · explicit working methods · stronger proof</footer></article></div><div class="comparison-strip"><span>COMPARE THE RUNS</span><div><b>01</b> Files changed</div><div><b>02</b> New dependencies</div><div><b>03</b> Checks actually run</div><div><b>04</b> Evidence returned</div></div><p class="copy-status" id="copy-status" role="status" aria-live="polite"></p></section>
|
|
||||||
<aside class="rule"><span>THE HUMAN JOB</span><strong>The agent may be autonomous in execution. Intent, boundaries, and evidence remain yours.</strong></aside><aside class="callout"><span>START HERE</span><strong>Begin with one agent and one skill. Add parallelism only when the tasks are truly independent.</strong></aside>
|
|
||||||
<section class="verification" id="verification"><div class="section-label"><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"><article><span>01 · STATIC</span><h3>Lint and types</h3><p>Format, lint, type-check. Fast and scoped to one file. Run on every save.</p><code>pnpm lint; echo "lint=$?"
|
|
||||||
pnpm typecheck; echo "typecheck=$?"</code></article><article><span>02 · BEHAVIOR</span><h3>Unit and contract</h3><p>Tests that repeat. Run before claiming done.</p><code>pnpm test; echo "test=$?"
|
|
||||||
cd services/api && go test ./...</code></article><article><span>03 · INTEGRATION</span><h3>Real UI and API</h3><p>Drive the actual UI, API, or browser. Slower and flakier — only this catches mobile 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></div></div><article class="verify-cta"><span>RUN IT YOURSELF · two labs, under 10 minutes each</span><div class="verify-cta-grid"><a href="../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="../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>Open the rules lab →</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 class="sources"><div class="section-label"><span>Keep learning</span><span>12 new readings + primary docs</span></div><p>Go deeper with official documentation, production case studies, Medium, and practitioner workflows. <a href="../rules/">Rules and enforcement case study →</a> <a href="../skills-review/">Skills review desk →</a> <a href="../docs/references/README.md">Primary references →</a> <a href="../docs/references/additional-reading.md">12-part reading path →</a></p></section>
|
|
||||||
<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="../summary/">route map</a>, then jump directly to <a href="../models/">models</a>, <a href="../agents/">agents and worktrees</a>, <a href="../skills/">skill creation</a>, <a href="../rules/">rules</a>, or the <a href="../skills-review/">skills review desk</a>.</p></section></main><script src="../app.js" defer></script>
|
|
||||||
</body></html>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// 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();
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<!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>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
: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}}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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();
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<!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>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
: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}}
|
|
||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="AI For Dummies: a practical route map for models, agents, worktrees, skills, rules, and verification."
|
|
||||||
/>
|
|
||||||
<title>AI For Dummies — Start here</title>
|
|
||||||
<link rel="stylesheet" href="chapters.css" />
|
|
||||||
<link rel="stylesheet" href="landing.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<header class="top">
|
|
||||||
<a href="./" aria-current="page">AI FOR DUMMIES</a><span>00 / START HERE</span
|
|
||||||
><a href="skills-review/">review desk ↗</a>
|
|
||||||
</header>
|
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">The short route</p>
|
|
||||||
<h1>Ship the<br /><em>system.</em></h1>
|
|
||||||
<p>
|
|
||||||
Start with the map. Then open the one chapter that matches the decision in front of you:
|
|
||||||
model, agent, worktree, skill, rule, or proof.
|
|
||||||
</p>
|
|
||||||
<a class="guide-launch" href="full-guide/">Take the full field guide <span>→</span></a>
|
|
||||||
</section>
|
|
||||||
<section class="grid route-grid" aria-label="Guide chapters">
|
|
||||||
<article class="card">
|
|
||||||
<b>01</b>
|
|
||||||
<h2>Models</h2>
|
|
||||||
<p>Capability and effort are separate knobs.</p>
|
|
||||||
<a href="models/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>02</b>
|
|
||||||
<h2>Agents & trees</h2>
|
|
||||||
<p>Bound roles, handoffs, and worktrees.</p>
|
|
||||||
<a href="agents/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>03</b>
|
|
||||||
<h2>Skills</h2>
|
|
||||||
<p>Capture repeatable decisions in small packages.</p>
|
|
||||||
<a href="skills/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>04</b>
|
|
||||||
<h2>Rules</h2>
|
|
||||||
<p>Connect guidance to enforcement.</p>
|
|
||||||
<a href="rules/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>05</b>
|
|
||||||
<h2>Hands-on</h2>
|
|
||||||
<p>Compare a strong prompt with skill-enabled work.</p>
|
|
||||||
<a href="hands-on/starter/">Open lab →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>06</b>
|
|
||||||
<h2>Review desk</h2>
|
|
||||||
<p>Browse original packages, references, scripts, and improvements.</p>
|
|
||||||
<a href="skills-review/">Open desk →</a>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
<section class="landing-note">
|
|
||||||
<span>THE THREAD</span
|
|
||||||
><strong
|
|
||||||
>Frame uncertainty → isolate execution → preserve judgment → verify the change.</strong
|
|
||||||
>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
The route map is now the default entry. The full guide remains available whenever you want
|
|
||||||
the whole narrative.
|
|
||||||
</footer>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.guide-launch{display:inline-flex;gap:14px;align-items:center;margin-top:20px;padding:12px 16px;background:var(--ink);color:var(--paper);font:700 12px var(--font-mono);letter-spacing:.06em;text-decoration:none;text-transform:uppercase;transition:transform .2s ease,background .2s ease}.guide-launch:hover{transform:translateY(-3px);background:var(--blue)}.guide-launch span{color:var(--gold);font-size:22px;line-height:0}.route-grid .card{display:flex;min-width:0;flex-direction:column}.route-grid .card a{margin-top:auto}.landing-note{display:grid;grid-template-columns:170px minmax(0,1fr);gap:30px;margin:0 0 70px;padding:25px 0;border-top:1px solid var(--ink);border-bottom:1px solid var(--ink)}.landing-note span{color:var(--red);font:700 11px var(--font-mono);letter-spacing:.1em}.landing-note strong{font-size:clamp(22px,3.2vw,42px);line-height:1.08;letter-spacing:-.04em}@media(max-width:520px){.landing-note{grid-template-columns:1fr;gap:9px;margin-bottom:45px}}@media(prefers-reduced-motion:reduce){.guide-launch{transition:none}.guide-launch:hover{transform:none}}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<title>AI For Dummies — Models</title>
|
|
||||||
<link rel="stylesheet" href="../chapters.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<header class="top">
|
|
||||||
<a href="../summary/">← ROUTE MAP</a><span>01 / MODELS</span
|
|
||||||
><a href="../full-guide/">field guide ↗</a>
|
|
||||||
</header>
|
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">Model routing</p>
|
|
||||||
<h1>Choose the<br /><em>engine.</em></h1>
|
|
||||||
<p>
|
|
||||||
A model has a capability ceiling. Effort controls how much room it gets to reason. Route
|
|
||||||
by uncertainty and verification cost.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section class="grid">
|
|
||||||
<article class="card">
|
|
||||||
<b>LOW</b>
|
|
||||||
<h2>Bounded rhythm</h2>
|
|
||||||
<p>Lookup, small edits, formatting, and transformations with clear checks.</p>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>MEDIUM</b>
|
|
||||||
<h2>Default work</h2>
|
|
||||||
<p>Normal implementation where the contract is clear but context matters.</p>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>HIGH</b>
|
|
||||||
<h2>Ambiguity</h2>
|
|
||||||
<p>Planning, architecture, security judgment, and hard failures.</p>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
<section class="model">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Two knobs</p>
|
|
||||||
<h2>Capability<br />× effort</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel">
|
|
||||||
<strong>ROUTING RULE</strong
|
|
||||||
><code
|
|
||||||
>strong model + high effort → frame ambiguity · light model + low effort → bounded
|
|
||||||
execution · raise one knob at a time → compare evidence</code
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="practice">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Sequence</p>
|
|
||||||
<h2>Spend judgment<br />where it <em>compounds.</em></h2>
|
|
||||||
</div>
|
|
||||||
<div class="steps">
|
|
||||||
<article>
|
|
||||||
<b>01</b>
|
|
||||||
<div>
|
|
||||||
<strong>Plan</strong
|
|
||||||
><span>Strong model: scope, risks, acceptance, and worktree split.</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<b>02</b>
|
|
||||||
<div>
|
|
||||||
<strong>Build</strong
|
|
||||||
><span>Focused worker: smallest context and lightest model that can pass.</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<b>03</b>
|
|
||||||
<div>
|
|
||||||
<strong>Review</strong
|
|
||||||
><span>Independent pass when missed issues cost more than the call.</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<nav class="links">
|
|
||||||
<a href="../agents/">Next: agents & trees →</a><a href="../rules/">Rules case study →</a>
|
|
||||||
</nav>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
+1
-1
@@ -12,7 +12,6 @@
|
|||||||
"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs",
|
"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs",
|
||||||
"gate": "./.agents/scripts/gate.sh",
|
"gate": "./.agents/scripts/gate.sh",
|
||||||
"snapshot": "node .agents/scripts/snapshot-route.mjs",
|
"snapshot": "node .agents/scripts/snapshot-route.mjs",
|
||||||
"serve": "python3 -m http.server 4173",
|
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -24,6 +23,7 @@
|
|||||||
"eslint-plugin-astro": "^1",
|
"eslint-plugin-astro": "^1",
|
||||||
"husky": "^9",
|
"husky": "^9",
|
||||||
"lint-staged": "^16",
|
"lint-staged": "^16",
|
||||||
|
"playwright": "^1.62.1",
|
||||||
"prettier": "^3",
|
"prettier": "^3",
|
||||||
"prettier-plugin-astro": "^0.14",
|
"prettier-plugin-astro": "^0.14",
|
||||||
"stylelint": "^16",
|
"stylelint": "^16",
|
||||||
|
|||||||
@@ -36,7 +36,247 @@ together.
|
|||||||
|
|
||||||
## Done when
|
## Done when
|
||||||
|
|
||||||
- [ ] Screenshot diffs empty at all four widths without `responsive.css`
|
Superseded by the legacy-page amendment below. `responsive.css` cannot be
|
||||||
- [ ] Ported rules live with the component that needs them, or in `tokens.css`
|
deleted before task 20 because `full-guide/index.html` is still the live legacy
|
||||||
- [ ] `responsive.css` deleted, and nothing references it
|
route and deliberately links it.
|
||||||
- [ ] `pnpm run gate` green; 42 assertions intact
|
|
||||||
|
- [ ] Screenshot diffs reviewed at all required widths without an Astro import
|
||||||
|
of `responsive.css`
|
||||||
|
- [x] Ported rules live with the component that needs them, or in `tokens.css`
|
||||||
|
- [x] No Astro file or layout references `responsive.css`; the legacy page link
|
||||||
|
remains unchanged until task 20
|
||||||
|
- [x] `pnpm run gate` green; assertions intact
|
||||||
|
|
||||||
|
## Amendment — the screenshot step now actually works
|
||||||
|
|
||||||
|
When this brief was written, `.agents/scripts/visual-regression.mjs` threw
|
||||||
|
`playwright ... install its project dependency` for everyone: the browsers were
|
||||||
|
cached in `~/.cache/ms-playwright` but the node package was never a dependency.
|
||||||
|
Every "screenshots match" checkbox in this plan was unsatisfiable. `playwright`
|
||||||
|
is now a devDependency (`c2a046d`) and a headless screenshot has been verified
|
||||||
|
to work. **Take the screenshots.** "I read the rules and they look unused" is
|
||||||
|
not the proof this brief asks for.
|
||||||
|
|
||||||
|
All eight routes are migrated as of `edd03f9` — `index`, `models`, `agents`,
|
||||||
|
`skills`, `summary`, `rules`, `full-guide`, `skills-review`. Screenshot all of
|
||||||
|
them, not just `/full-guide/`.
|
||||||
|
|
||||||
|
## Amendment — the token-gap queue is already closed
|
||||||
|
|
||||||
|
The brief says "~190 accumulated `/* token-gap: ... */` markers … do not start".
|
||||||
|
Task 02c ran and took them from 154 to **17**. Do not reopen that work either;
|
||||||
|
the 17 that remain are deliberate.
|
||||||
|
|
||||||
|
Two things 02c got wrong that you must not repeat. It closed six markers by
|
||||||
|
pointing the value at a palette token with a _different_ value — `#5b7098` at
|
||||||
|
`var(--accent)` (`#7c78a8`), `#9eb0bb` at `var(--muted)` (`#697b89`) — after its
|
||||||
|
own marker comments had recorded "no token matches". That shipped a silent
|
||||||
|
redesign the gate could not see. `edd03f9` repaired it by adding exact on-dark
|
||||||
|
tokens: `--ink-muted`, `--ink-line`, `--ink-code`, `--accent-paper`,
|
||||||
|
`--accent-paper-active`, `--accent-surface`, and four exact-alpha overlays
|
||||||
|
`--white-14/23/25/31`. Use those when you need a colour on `--ink` or
|
||||||
|
`--accent`; the light-background palette is the wrong family there.
|
||||||
|
|
||||||
|
If you must port a colour or size that has no token, add an **exact** one to
|
||||||
|
`tokens.css` — you own it — or leave the raw value under a `token-gap:` marker.
|
||||||
|
Never round to a near neighbour.
|
||||||
|
|
||||||
|
## Amendment — how this gets verified
|
||||||
|
|
||||||
|
The gate does not read your output: all 42 assertions in `scripts/verify.mjs`
|
||||||
|
read the legacy files, so deleting `responsive.css` and breaking every mobile
|
||||||
|
layout passes it cleanly. Before you claim done, diff the **built** CSS
|
||||||
|
(`dist/_astro/*.css`) against `main` and enumerate every declaration that
|
||||||
|
disappeared. Each one is either present elsewhere or a regression.
|
||||||
|
|
||||||
|
## Attempt 1 stopped short — and the goal as written is not reachable yet
|
||||||
|
|
||||||
|
Preserved as `rejected/15e-attempt-1` (`d2312d7`). Start by reading that diff:
|
||||||
|
it ported rules into `ChapterHero`, `FleetDiagram`, `RouteTable`, `WorktreeMap`,
|
||||||
|
`LanguageToggle`, `ReadingProgress`, `agents.astro`, `full-guide.astro` and
|
||||||
|
`tokens.css`, and it added `pixelmatch`, `pngjs` and `postcss` plus a working
|
||||||
|
screenshot-compare harness. Reuse all of it. It never finished the comparison
|
||||||
|
loop and never committed on its own.
|
||||||
|
|
||||||
|
It also deleted `responsive.css` while **`full-guide/index.html` still links
|
||||||
|
it**. That page is the live site until task 20 cuts over; deleting the
|
||||||
|
stylesheet strips it. `src/components/islands/GuideSelector.astro` still
|
||||||
|
references it too.
|
||||||
|
|
||||||
|
So the "delete it" checkbox in this brief cannot be honestly ticked before the
|
||||||
|
cutover. Do not tick it, and do not delete the file. **The deliverable is
|
||||||
|
everything up to the deletion:**
|
||||||
|
|
||||||
|
- Every rule `responsive.css` provides to an Astro route lives with the
|
||||||
|
component that needs it, or in `tokens.css`.
|
||||||
|
- No `.astro` file and no Astro layout links `responsive.css` any more.
|
||||||
|
- `full-guide/index.html` still links it, unchanged, and still renders exactly
|
||||||
|
as it does today.
|
||||||
|
- A list, in this file, of the rules that remain in `responsive.css` solely for
|
||||||
|
the legacy page — that list is the deletion checklist task 20 will execute.
|
||||||
|
|
||||||
|
### Task 20 deletion checklist
|
||||||
|
|
||||||
|
`responsive.css` has no Astro consumer after this task. Its remaining consumer
|
||||||
|
is the untouched `<link rel="stylesheet" href="../responsive.css">` in
|
||||||
|
`full-guide/index.html`. Task 20 must retire that page before deleting the
|
||||||
|
stylesheet. Until then, retain these legacy-only sections exactly as they are:
|
||||||
|
|
||||||
|
- The root guide shell and all interactive section selectors (`.topbar` through
|
||||||
|
`.copy-status`), including their dark-surface colour values.
|
||||||
|
- The responsive blocks at 1600px, 2200px, 1050px, 800px, and 600px, plus the
|
||||||
|
reduced-motion block. They continue to style the legacy HTML only.
|
||||||
|
- The legacy page's `.route-meter span { height: var(--score) }` transition.
|
||||||
|
Astro uses the component-safe transform equivalent; do not modify this legacy
|
||||||
|
copy while the legacy document is live.
|
||||||
|
|
||||||
|
Amend the "Done when" boxes to match before you start, and say in your final
|
||||||
|
report that the file is intentionally still present.
|
||||||
|
|
||||||
|
### One rule to port properly while you are in there
|
||||||
|
|
||||||
|
`.route-meter span` animates `height: var(--score)` — a layout property, which
|
||||||
|
task 18 was meant to eliminate. 18 changed it in `responsive.css` and that
|
||||||
|
change was reverted (`0bca43c`) because it belongs in the component, not in a
|
||||||
|
legacy file. When you port this rule, port it as
|
||||||
|
`transform: scaleY(var(--score)); transform-origin: bottom` with `height: 100%`.
|
||||||
|
`--score` is set inline as a percentage (`--score:92%`) by `GuideSelector.astro`
|
||||||
|
and `full-guide.astro`; a percentage in `scaleY()` is valid and was verified
|
||||||
|
rendering correctly at `matrix(1, 0, 0, 0.92, 0, 0)`. Leave the legacy
|
||||||
|
`responsive.css` copy of the rule alone.
|
||||||
|
|
||||||
|
## Attempt 3 rejected: two breakpoints were replaced with dead tokens
|
||||||
|
|
||||||
|
Tagged `rejected/15e-attempt-3` (`d5f5514`). The gate passed with 84 assertions
|
||||||
|
intact, and the work is still wrong. Recover anything reusable from the tag; do
|
||||||
|
not build on it.
|
||||||
|
|
||||||
|
**The disqualifying defect.** `responsive.css` carries
|
||||||
|
`@media (max-width: 880px)` — which collapses `.verify-layers` and
|
||||||
|
`.verify-cta-grid` to one column and `.verify-intro` to a single column — and
|
||||||
|
`@media (max-width: 1050px)`, which narrows `.tree-node` to 145px, puts
|
||||||
|
`.tree-detail` on two columns, and collapses `.skill-explorer`. Neither was
|
||||||
|
ported. Both are absent from the built CSS:
|
||||||
|
|
||||||
|
```
|
||||||
|
grep -oh 'max-width:[0-9]*px' dist/_astro/*.css | sort -u
|
||||||
|
```
|
||||||
|
|
||||||
|
returns every other breakpoint and not those two. On the built site the
|
||||||
|
verification section stays multi-column down to 560px and the worktree tree
|
||||||
|
never narrows.
|
||||||
|
|
||||||
|
What made it look finished is worse than the omission. `audit-ui.mjs` compares
|
||||||
|
the built CSS against `.agents/snapshots/built-css-values.json`, so dropping
|
||||||
|
those two media queries fails the value contract. Attempt 3 answered that by
|
||||||
|
adding
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Retained as exact legacy dimensions for the built-CSS value contract;
|
||||||
|
neither is used as a media query. */
|
||||||
|
--legacy-audit-width-880: 880px;
|
||||||
|
--legacy-audit-width-1050: 1050px;
|
||||||
|
```
|
||||||
|
|
||||||
|
Two variables, referenced by nothing, whose only function is to put the strings
|
||||||
|
`880px` and `1050px` back into the built sheet so the check that exists to catch
|
||||||
|
this exact loss reports success. The comment says so outright. **Port the media
|
||||||
|
queries. Never satisfy a value contract with a value nothing uses** — if a check
|
||||||
|
is in the way, the check is telling you something.
|
||||||
|
|
||||||
|
**Three more things to fix on the next attempt.**
|
||||||
|
|
||||||
|
1. **No `--raw-<hex>` tokens.** Attempt 3 added roughly fifty of them:
|
||||||
|
`--raw-9eabb4: #9eabb4`, `--raw-ffffff2d: #ffffff2d`, and so on. A token
|
||||||
|
named after its own value carries no meaning, so it is not a token — it is a
|
||||||
|
lookup table that satisfies `check-tokens.mjs` while defeating the point of
|
||||||
|
having one. If a ported rule needs a colour that the palette has no name for,
|
||||||
|
either it is one of the existing tokens (use it) or it is a genuine gap (name
|
||||||
|
it for its role, or leave the literal and add a token-gap marker — see
|
||||||
|
`.agents/rules/theming.md`).
|
||||||
|
|
||||||
|
2. **Leave `--white-31` alone.** Attempt 3 rewrote it from
|
||||||
|
`rgb(255 255 255 / 31.3725%)` to `#ffffff50` and added a second
|
||||||
|
`--white-31-alpha` holding the original. The two are numerically equal; the
|
||||||
|
churn reverses task 02c, which set these six on-dark tokens to their exact
|
||||||
|
values on purpose, and leaves a duplicate behind.
|
||||||
|
|
||||||
|
3. **Do not commit screenshots.** Attempt 3 added 128 PNGs under
|
||||||
|
`.agents/snapshots/15e-before/` and `15e-after/`, 35 MB, to a repository
|
||||||
|
whose entire pack is under 400 KB. Take them, compare them, report the
|
||||||
|
comparison, and leave them out of the commit.
|
||||||
|
|
||||||
|
**Acceptance, restated.** Every breakpoint in `responsive.css` that serves an
|
||||||
|
Astro route appears in the built CSS, in a rule that does the same thing it did
|
||||||
|
before:
|
||||||
|
|
||||||
|
```
|
||||||
|
diff <(grep -oh '(max\|min)-width:[0-9]*px' responsive.css | sort -u) \
|
||||||
|
<(grep -ohE '(max|min)-width:[0-9]*px' dist/_astro/*.css | sort -u)
|
||||||
|
```
|
||||||
|
|
||||||
|
Anything only the legacy page needs may be missing from the built sheet — say
|
||||||
|
which, and why, in the final report.
|
||||||
|
|
||||||
|
## Attempt 4 rejected: the ported rules are in the sheet and do nothing
|
||||||
|
|
||||||
|
Tagged `rejected/15e-attempt-4` (`b6b6200`). This attempt fixed everything
|
||||||
|
attempt 3 got wrong — the 880px and 1050px media queries are ported, the tokens
|
||||||
|
are named for their roles rather than their hex values, `--white-31` is
|
||||||
|
untouched, `responsive.css` is unchanged and still linked by
|
||||||
|
`full-guide/index.html`, no `.astro` file imports it, the route-meter is
|
||||||
|
`transform: scaleY()`, the four off-scale breakpoints carry honest token-gap
|
||||||
|
markers, no screenshots were committed, and the gate passes with 84 assertions.
|
||||||
|
The acceptance test in this brief passes on it.
|
||||||
|
|
||||||
|
**It is still wrong, and the acceptance test was the problem.**
|
||||||
|
|
||||||
|
Astro scopes a component's styles. `WorktreeMap.astro`'s rules compile to
|
||||||
|
`.tree-node[data-astro-cid-lsutp3lb] { width: 180px }`, specificity 0,2,0. A
|
||||||
|
rule ported verbatim out of `responsive.css` arrives as
|
||||||
|
`@media (max-width: 1050px) { .tree-node { width: 145px } }`, specificity 0,1,0.
|
||||||
|
The breakpoint is in the built sheet, the media query matches, the selector
|
||||||
|
matches — and the declaration loses. At 1050px the node stays 180px wide.
|
||||||
|
Measured:
|
||||||
|
|
||||||
|
```
|
||||||
|
node .agents/scripts/computed-style-diff.mjs full-guide --widths 880,1050
|
||||||
|
```
|
||||||
|
|
||||||
|
56 differences on attempt 4. Diffing breakpoints between `responsive.css` and
|
||||||
|
`dist/_astro/*.css` cannot see any of it, which is my error, not the agent's: I
|
||||||
|
wrote that test.
|
||||||
|
|
||||||
|
**Port rules into the component that owns the selector**, so they compile with
|
||||||
|
the same scope as the base rule they are overriding. A rule that has no owning
|
||||||
|
component belongs in a global sheet, but then the base rule it overrides has to
|
||||||
|
be global too.
|
||||||
|
|
||||||
|
### The baseline is not zero, and that is the real finding
|
||||||
|
|
||||||
|
The same command reports **52 differences on `main`**, before this task changes
|
||||||
|
anything. `full-guide.astro` currently imports `responsive.css`, and that import
|
||||||
|
has never fully worked, for the identical reason: a global stylesheet cannot
|
||||||
|
override scoped component styles. The responsive layer has been partly inert in
|
||||||
|
the Astro build for as long as it has been imported, on `.tree-node`, `.branch`,
|
||||||
|
`.worker-card`, `.tree-stage`, `.tree-lab`, `.hero`, `.fleet`, `.chapter-links`
|
||||||
|
and more.
|
||||||
|
|
||||||
|
So this task is not "keep parity". Parity is already broken, and porting the
|
||||||
|
rules properly is what fixes it.
|
||||||
|
|
||||||
|
### Acceptance, restated again
|
||||||
|
|
||||||
|
```
|
||||||
|
node .agents/scripts/computed-style-diff.mjs full-guide
|
||||||
|
```
|
||||||
|
|
||||||
|
- Must report **fewer than 52** differences at 880px and 1050px — that is the
|
||||||
|
`main` baseline, and anything above it is a regression.
|
||||||
|
- Drive it to **zero** where you can. Every difference you leave must be listed
|
||||||
|
in your final report with the reason it is not fixable inside this task's
|
||||||
|
scope.
|
||||||
|
- Keep everything attempt 4 got right; the list above is not a set of problems,
|
||||||
|
it is the standard to match.
|
||||||
|
- The breakpoint diff against the built CSS stays as a _necessary_ check. It is
|
||||||
|
no longer a _sufficient_ one.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -43,3 +43,175 @@ a written reason.
|
|||||||
|
|
||||||
Do not weaken an assertion to make it pass. If it cannot pass, something is
|
Do not weaken an assertion to make it pass. If it cannot pass, something is
|
||||||
broken — that is the assertion doing its job.
|
broken — that is the assertion doing its job.
|
||||||
|
|
||||||
|
## Amendment — this is now the highest-value task in the plan
|
||||||
|
|
||||||
|
Written before the migration ran; what follows is what the migration taught.
|
||||||
|
|
||||||
|
Every one of the 42 assertions still reads a **legacy** file:
|
||||||
|
`grep -cE 'dist/|src/pages|src/content' scripts/verify.mjs` returns 0. Nothing
|
||||||
|
in the gate looks at what the Astro pages render. Two tasks shipped invisible
|
||||||
|
regressions straight through a green gate:
|
||||||
|
|
||||||
|
- **02b** deleted the `:root` palette blocks from the four legacy stylesheets,
|
||||||
|
reasoning `src/styles/tokens.css` is the single source of truth. It is — for
|
||||||
|
Astro pages. The legacy pages link those stylesheets standalone and never load
|
||||||
|
`tokens.css`, so every `var(--paper)` / `var(--ink)` / `var(--gold)` on the
|
||||||
|
live site resolved to nothing. Eight pages, colourless. Gate green.
|
||||||
|
- **15d attempt 1** built `/full-guide/` by importing
|
||||||
|
`full-guide/index.html?raw` and `set:html`-ing the `<main>` out of it. Zero
|
||||||
|
`data-language-content` attributes, zero `.pt` reads: Portuguese gone from the
|
||||||
|
largest page on a bilingual site. Gate green. Tagged `rejected/15d-attempt-1`.
|
||||||
|
|
||||||
|
The gate is doing its job — it is a legacy-content contract. Your job is to make
|
||||||
|
it an output contract too. Until you land, "gate green" means nothing about the
|
||||||
|
new site.
|
||||||
|
|
||||||
|
### Three checks to build in, each of which caught a real regression
|
||||||
|
|
||||||
|
1. **Bilingual coverage of the built HTML.** The strongest check found is a full
|
||||||
|
inversion of the legacy mechanism: parse the `translations.pt` object out of
|
||||||
|
`app.js` (brace-match it, then evaluate it), and assert every PT string
|
||||||
|
appears in the corresponding `dist/**/index.html`. Flatten tags and collapse
|
||||||
|
whitespace on **both** sides before comparing — a needle stripped of `<br />`
|
||||||
|
will not match a haystack that still has it, and that false negative cost an
|
||||||
|
afternoon. This check moved `/full-guide/` from 17 to 102 of 102 PT strings
|
||||||
|
present, and only the last pass revealed that all seven common-skill buttons
|
||||||
|
were rendering the wrong _English_ too. `translations.pt` is the truth for
|
||||||
|
`/full-guide/` and `/rules/`; the other six routes are English-only today and
|
||||||
|
must stay that way.
|
||||||
|
|
||||||
|
2. **Every `var()` resolves.** For each legacy page, collect the stylesheets it
|
||||||
|
actually links, and assert every `var(--x)` it uses is defined by that set.
|
||||||
|
`--score` is the only legitimate miss — `app.js:289` sets it inline. This is
|
||||||
|
the check that would have caught 02b in seconds.
|
||||||
|
|
||||||
|
3. **Built-CSS diff against `main`.** Build, then enumerate every colour and
|
||||||
|
size declaration in `dist/_astro/*.css` that exists on `main` and not on the
|
||||||
|
branch. This caught 02c pointing six on-dark colours at palette tokens with
|
||||||
|
different values. Expect notation-only differences: `#ffffff24` is exactly
|
||||||
|
`rgb(255 255 255 / 14.1176%)`, and `32px`/`48px` now resolve through
|
||||||
|
`--step-32`/`--step-48`.
|
||||||
|
|
||||||
|
A working implementation of (1) exists as a scratchpad script; rewrite it
|
||||||
|
properly rather than porting it — it was throwaway.
|
||||||
|
|
||||||
|
### On the assertion count
|
||||||
|
|
||||||
|
Baseline is 42 and the gate enforces it. Re-pointing should _raise_ it, not hold
|
||||||
|
it: the snapshot assertions in step 3 are additive. If you find yourself needing
|
||||||
|
to remove one, that is the escalation path, not the workaround.
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
`playwright` is now a devDependency (`c2a046d`) and
|
||||||
|
`.agents/scripts/visual-regression.mjs` runs. It used to throw for everyone,
|
||||||
|
which is why no task in this plan ever produced the captures its brief asked
|
||||||
|
for.
|
||||||
|
|
||||||
|
## Attempt 1 was rejected — count parity is not coverage
|
||||||
|
|
||||||
|
`d15c301`, tagged `rejected/19-attempt-1`. It did good work: `verify.mjs` now
|
||||||
|
reads `dist/`, the 102 Portuguese strings are checked against the built
|
||||||
|
full-guide, `audit-ui.mjs` gained per-page CSS-variable resolution and a
|
||||||
|
built-CSS baseline (`.agents/snapshots/built-css-values.json`). Keep all of
|
||||||
|
that.
|
||||||
|
|
||||||
|
It was rejected because it **replaced all 42 assertion messages with 42 new
|
||||||
|
ones**. Not one of the originals survives. The count check passed, and the count
|
||||||
|
check is the weakest thing in the gate: ten cheap rendered-text snapshot
|
||||||
|
assertions were added while roughly a dozen specific contracts were deleted with
|
||||||
|
no reason written anywhere.
|
||||||
|
|
||||||
|
Contracts present on `main` and absent from the branch, by token count in
|
||||||
|
`scripts/verify.mjs` (main → attempt 1):
|
||||||
|
|
||||||
|
| Contract | main | attempt 1 |
|
||||||
|
| ------------------------------------- | ---- | --------- |
|
||||||
|
| `vote-service` IP one-vote-per-source | 2 | 0 |
|
||||||
|
| `skillSources` pinned commit URLs | 3 | 0 |
|
||||||
|
| review desk change-lens | 6 | 0 |
|
||||||
|
| review desk file-mode | 2 | 0 |
|
||||||
|
| review desk markdown preview | 13 | 1 |
|
||||||
|
| responsive / `max-width` contracts | 5 | 0 |
|
||||||
|
| secret-safety handling in the catalog | 1 | 0 |
|
||||||
|
| catalog coverage | 10 | 2 |
|
||||||
|
|
||||||
|
`scripts/audit-ui.mjs` also went from 6 assertions to 5.
|
||||||
|
|
||||||
|
A rendered-text snapshot does not replace these. "The vote service enforces one
|
||||||
|
vote per source IP" is not a string in any HTML file; deleting that assertion
|
||||||
|
deletes the contract. Same for the pinned skill-source commits and the
|
||||||
|
review-desk interaction modes.
|
||||||
|
|
||||||
|
### What attempt 2 must produce
|
||||||
|
|
||||||
|
1. **A mapping table in this file**, one row per original assertion: its message
|
||||||
|
on `main`, the assertion that now pins the same fact, and — only where
|
||||||
|
genuinely obsolete — a one-line reason. Build it from
|
||||||
|
`git show rejected/19-attempt-1^:scripts/verify.mjs | grep -oP 'throw new Error\(\s*\K.*'`
|
||||||
|
so no row is missed. 42 rows, no gaps.
|
||||||
|
2. **Every fact still pinned.** Re-point it at `dist/` where the fact is visible
|
||||||
|
in rendered output; keep reading the legacy or source file where it is not.
|
||||||
|
`vote-service/` and the pinned skill sources are not part of the Astro output
|
||||||
|
and their assertions should keep reading what they read today — re-pointing
|
||||||
|
is not the goal, coverage is.
|
||||||
|
3. **Raise the baseline.** The snapshot assertions are additive, so the final
|
||||||
|
count is well above 42. Update the baseline in the gate to the new number in
|
||||||
|
the same commit, and say what it is in this file. A task that ends at exactly
|
||||||
|
42 after adding ten assertions has removed ten.
|
||||||
|
4. Keep `audit-ui.mjs` at 6 or more.
|
||||||
|
|
||||||
|
Start from `rejected/19-attempt-1` rather than from scratch — the `dist/`
|
||||||
|
re-point and the Portuguese coverage check are worth keeping.
|
||||||
|
|
||||||
|
## Attempt 2 contract mapping
|
||||||
|
|
||||||
|
Baseline: 42 original assertions. `scripts/verify.mjs` now has 84 assertion
|
||||||
|
sites: the 42 output/snapshot assertions from attempt 1 plus the 42 contracts
|
||||||
|
below. No assertion was removed. `gate.sh` enforces 84 as the new floor.
|
||||||
|
|
||||||
|
| Original assertion message | Current assertion that pins the same fact | Removal reason |
|
||||||
|
| --------------------------------------------------------------- | ------------------------------------------------------------ | -------------- |
|
||||||
|
| `missing reference` | `missing reference contract` | — |
|
||||||
|
| `missing content` | `missing full-guide content contract` | — |
|
||||||
|
| `missing interaction` | `missing full-guide interaction contract` | — |
|
||||||
|
| `missing starter content` | `missing starter content contract` | — |
|
||||||
|
| `missing starter behavior` | `missing starter behavior contract` | — |
|
||||||
|
| `missing additional source` | `missing additional-reading source contract` | — |
|
||||||
|
| `fewer than five additional readings` | `additional-reading coverage fell below five entries` | — |
|
||||||
|
| `missing pinned skill source` | `missing pinned skill-source contract` | — |
|
||||||
|
| `missing model source` | `missing model-source contract` | — |
|
||||||
|
| `external runtime dependency found` | `full-guide has an external runtime dependency` | — |
|
||||||
|
| `missing rules content` | `missing rules content contract` | — |
|
||||||
|
| `missing rules interaction` | `missing rules interaction contract` | — |
|
||||||
|
| `missing rules control` | `missing rules-control contract` | — |
|
||||||
|
| `main presentation does not link to rules page` | `full-guide no longer links to rules` | — |
|
||||||
|
| `main presentation does not link to skills review page` | `full-guide no longer links to skills review` | — |
|
||||||
|
| `main presentation missing chapter route` | `full-guide is missing a chapter route` | — |
|
||||||
|
| `rules page has an external runtime dependency` | `rules page has an external runtime dependency` | — |
|
||||||
|
| `missing rules responsive contract` | `missing rules responsive contract` | — |
|
||||||
|
| `missing review page content` | `missing review page-content contract` | — |
|
||||||
|
| `missing review interaction` | `missing review interaction contract` | — |
|
||||||
|
| `missing review file manifest` | `missing review file-manifest contract` | — |
|
||||||
|
| `review catalog does not cover all submissions` | `review catalog no longer covers all submissions` | — |
|
||||||
|
| `review catalog does not record secret safety handling` | `review catalog lost its secret-safety contract` | — |
|
||||||
|
| `chapter page missing shared navigation` | `chapter page missing shared navigation` | — |
|
||||||
|
| `missing chapter responsive contract` | `missing chapter responsive contract` | — |
|
||||||
|
| `landing page missing` | `landing page missing a route-map destination` | — |
|
||||||
|
| `landing page should remain a fast, static route map` | `landing page is no longer a static route map` | — |
|
||||||
|
| `skills anatomy missing` | `skills anatomy missing package contract` | — |
|
||||||
|
| `skills anatomy interaction missing` | `skills anatomy lost its interaction module` | — |
|
||||||
|
| `skills anatomy responsive contract missing` | `skills anatomy lost responsive contract` | — |
|
||||||
|
| `operational submission privacy leak` | `operational submission privacy leak` | — |
|
||||||
|
| `operational submission identity remains in review desk source` | `operational submission identity remains in review output` | — |
|
||||||
|
| `operational submission redaction contract missing` | `operational submission redaction contract missing` | — |
|
||||||
|
| `review file-mode contract missing` | `review file-mode contract missing` | — |
|
||||||
|
| `file selection still resets improved mode` | `review file selection no longer preserves preview mode` | — |
|
||||||
|
| `review change-lens contract missing` | `review change-lens contract missing` | — |
|
||||||
|
| `review change-lens CSS missing` | `review change-lens CSS contract missing` | — |
|
||||||
|
| `review markdown preview contract missing` | `review markdown-preview contract missing` | — |
|
||||||
|
| `review vote widget wiring missing` | `review vote-widget island is not wired to a hashed asset` | — |
|
||||||
|
| `review vote widget contract missing` | `review vote-widget contract missing` | — |
|
||||||
|
| `review vote widget CSS missing` | `review vote-widget CSS contract missing` | — |
|
||||||
|
| `vote-service missing IP-based one-vote-per-source contract` | `vote-service missing IP-based one-vote-per-source contract` | — |
|
||||||
|
|||||||
@@ -57,3 +57,81 @@ git rev-parse origin/pages # write it down
|
|||||||
- [ ] Old files deleted; `pnpm run gate` green
|
- [ ] Old files deleted; `pnpm run gate` green
|
||||||
- [ ] Docs match reality
|
- [ ] Docs match reality
|
||||||
- [ ] Previous `pages` SHA recorded for rollback
|
- [ ] Previous `pages` SHA recorded for rollback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cutover record
|
||||||
|
|
||||||
|
`origin/pages` before cutover: **`37a1e480c6bffb0bff77ad3854ec068688c17d7b`**
|
||||||
|
("Merge branch 'main' into pages"). Reset `pages` to this SHA to roll back.
|
||||||
|
|
||||||
|
### Step 1 — verification before deletion
|
||||||
|
|
||||||
|
`rendered-text-diff.mjs` compares the _rendered_ DOM of each legacy page against
|
||||||
|
its Astro replacement. Deleting the legacy files makes that comparison
|
||||||
|
impossible, so the full sweep was taken first, against the post-cutover build:
|
||||||
|
|
||||||
|
| Route | Legacy spans | Astro spans | Missing | Extra |
|
||||||
|
| ----------------- | -----------: | ----------: | ------: | ----: |
|
||||||
|
| `/` | 36 | 36 | 0 | 0 |
|
||||||
|
| `/full-guide/` | 432 | 432 | 0 | 0 |
|
||||||
|
| `/summary/` | 34 | 34 | 0 | 0 |
|
||||||
|
| `/models/` | 36 | 36 | 0 | 0 |
|
||||||
|
| `/agents/` | 39 | 39 | 0 | 0 |
|
||||||
|
| `/skills/` | 48 | 48 | 0 | 0 |
|
||||||
|
| `/rules/` | 119 | 119 | 0 | 0 |
|
||||||
|
| `/skills-review/` | 178 | 178 | 0 | 0 |
|
||||||
|
| `/full-guide/` pt | 431 | 431 | 0 | 0 |
|
||||||
|
| `/rules/` pt | 119 | 119 | 0 | 0 |
|
||||||
|
|
||||||
|
`computed-style-diff.mjs full-guide --widths 880,1050` → 32 differences,
|
||||||
|
unchanged from the task 15e result, so the file moves below are style-neutral.
|
||||||
|
|
||||||
|
### Step 2 — what could not be deleted
|
||||||
|
|
||||||
|
The brief assumed every legacy file had been superseded. Seven had not: the
|
||||||
|
Astro build still imports them, and `astro build` fails outright without them.
|
||||||
|
|
||||||
|
| File | Imported by |
|
||||||
|
| ------------------------------- | --------------------------------- |
|
||||||
|
| `styles.css` | `src/pages/full-guide.astro` |
|
||||||
|
| `full-guide/audit.css` | `src/pages/full-guide.astro` |
|
||||||
|
| `chapters.css` | `src/layouts/ChapterLayout.astro` |
|
||||||
|
| `skills/styles.css` | `src/pages/skills.astro` |
|
||||||
|
| `skills-review/styles.css` | `src/pages/skills-review.astro` |
|
||||||
|
| `skills-review/change-lens.css` | `src/pages/skills-review.astro` |
|
||||||
|
| `skills-review/app.js` | `src/pages/skills-review.astro` |
|
||||||
|
|
||||||
|
`app.js` pulls in `catalog.js`, `files.js`, and `vote.js`, which pull in
|
||||||
|
`submitted-catalog.js` and `submitted-files.js` — twelve files in all.
|
||||||
|
|
||||||
|
They were moved to `legacy/`, not into `src/`. `check-tokens.mjs` sweeps `src`,
|
||||||
|
and these files are full of raw hex and unnamed breakpoints; moving one into
|
||||||
|
`src/` should mean migrating it to tokens in the same change, never adding a
|
||||||
|
scan exclusion. `legacy/` keeps the checker's scope honest and stops the files
|
||||||
|
sitting at route-shaped paths next to the routes they no longer serve.
|
||||||
|
|
||||||
|
Deleted outright (32 files): `app.js`, `responsive.css`, `landing.css`,
|
||||||
|
`rules/app.js`, `rules/styles.css`, `skills/app.js`, all ten `index.html` files,
|
||||||
|
and the root `hands-on/` copy — byte-identical to `public/hands-on/`, which is
|
||||||
|
what the build ships.
|
||||||
|
|
||||||
|
### Step 3 — verify.mjs off the legacy source
|
||||||
|
|
||||||
|
Two assertions read `app.js` to parse `translations.pt`. The 102 strings were
|
||||||
|
extracted verbatim before deletion into `.agents/snapshots/full-guide-pt.json` —
|
||||||
|
a legacy capture, not a snapshot of the Astro build, so the check still asserts
|
||||||
|
against an independent source.
|
||||||
|
|
||||||
|
The brace-matching helper went with the file, taking one assertion. It was
|
||||||
|
replaced by a check that no snapshot entry is empty: without it, trimming the
|
||||||
|
snapshot would make the "every string is present" assertion pass vacuously.
|
||||||
|
Count stays at 84.
|
||||||
|
|
||||||
|
`audit-ui.mjs` now reads the ten pages from `dist/`, resolving Astro's
|
||||||
|
base-absolute `/ai-for-dummies/...` hrefs against `dist/`.
|
||||||
|
|
||||||
|
### Not done here
|
||||||
|
|
||||||
|
Steps 3, 4, and 6 — publish, verify on the real host, fast-forward `pages` — are
|
||||||
|
untouched. They need a human present.
|
||||||
|
|||||||
Generated
+29
@@ -37,6 +37,9 @@ importers:
|
|||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: ^16
|
specifier: ^16
|
||||||
version: 16.4.0
|
version: 16.4.0
|
||||||
|
playwright:
|
||||||
|
specifier: ^1.62.1
|
||||||
|
version: 1.62.1
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3
|
specifier: ^3
|
||||||
version: 3.9.6
|
version: 3.9.6
|
||||||
@@ -1306,6 +1309,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
|
resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@@ -1856,6 +1864,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
|
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
playwright-core@1.62.1:
|
||||||
|
resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
playwright@1.62.1:
|
||||||
|
resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
postcss-resolve-nested-selector@0.1.6:
|
postcss-resolve-nested-selector@0.1.6:
|
||||||
resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==}
|
resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==}
|
||||||
|
|
||||||
@@ -3844,6 +3862,9 @@ snapshots:
|
|||||||
|
|
||||||
flattie@1.1.1: {}
|
flattie@1.1.1: {}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -4592,6 +4613,14 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@4.0.7: {}
|
picomatch@4.0.7: {}
|
||||||
|
|
||||||
|
playwright-core@1.62.1: {}
|
||||||
|
|
||||||
|
playwright@1.62.1:
|
||||||
|
dependencies:
|
||||||
|
playwright-core: 1.62.1
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.2
|
||||||
|
|
||||||
postcss-resolve-nested-selector@0.1.6: {}
|
postcss-resolve-nested-selector@0.1.6: {}
|
||||||
|
|
||||||
postcss-safe-parser@7.0.1(postcss@8.5.28):
|
postcss-safe-parser@7.0.1(postcss@8.5.28):
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
.topbar-tools{display:flex;align-items:center;gap:24px}
|
|
||||||
.skills-review-link{color:var(--blue);font:700 9px var(--font-mono);letter-spacing:.06em;text-decoration:none;text-transform:uppercase;white-space:nowrap}.skills-review-link:hover{color:var(--accent)}
|
|
||||||
.lang-switch{display:flex;align-items:center;gap:6px;color:var(--muted);font:500 10px var(--font-mono);letter-spacing:.1em}
|
|
||||||
.lang-switch button{padding:0;border:0;color:inherit;background:transparent;font:inherit;cursor:pointer}
|
|
||||||
.lang-switch button.active{color:var(--ink);font-weight:700}
|
|
||||||
.lang-switch button:focus-visible{outline:2px solid var(--accent);outline-offset:4px}
|
|
||||||
@media(max-width:800px){.topbar-tools{margin-left:auto}}
|
|
||||||
@media(max-width:600px){.topbar-tools .edition{display:none}}
|
|
||||||
|
|
||||||
/* Interactive operations map */
|
|
||||||
.interaction-hint{margin-top:24px!important;padding-left:18px;border-left:3px solid var(--gold);font:500 11px/1.6 var(--font-mono)!important;color:var(--muted)!important}
|
|
||||||
button{font-family:inherit}
|
|
||||||
.worker-card{display:grid;align-content:space-between;gap:20px;min-height:180px;padding:22px;border:0;color:var(--paper);background:var(--ink);text-align:left;cursor:pointer;transition:background .2s ease,transform .2s ease}
|
|
||||||
.worker-card span{color:#9eabb4;font:500 10px var(--font-mono);letter-spacing:.1em}
|
|
||||||
.worker-card strong{font-size:16px;line-height:1.2}.worker-card code{color:var(--gold);font:11px var(--font-mono)}
|
|
||||||
.worker-card:hover,.worker-card.active{background:#244760}.worker-card.active{box-shadow:inset 0 -4px 0 var(--gold)}.worker-card:active{transform:translateY(2px)}
|
|
||||||
.worker-detail{display:grid;grid-template-columns:.55fr 1.1fr 1.2fr;gap:1px;margin-top:1px;background:var(--line)}
|
|
||||||
.worker-detail>*{margin:0;padding:16px 20px;background:#edf0f1}.worker-detail span,.worker-detail small{font:500 10px/1.5 var(--font-mono);letter-spacing:.06em;text-transform:uppercase}.worker-detail span{color:var(--accent)}.worker-detail strong{font-size:13px}.worker-detail small{color:var(--muted);text-transform:none}
|
|
||||||
|
|
||||||
.tree-lab{min-width:0;border:1px solid #41596b;background:#0b1b27;box-shadow:18px 18px 0 #081621}
|
|
||||||
.tree-toolbar{display:flex;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #41596b;color:#9eabb4;font:500 9px var(--font-mono);letter-spacing:.1em;text-transform:uppercase}
|
|
||||||
.tree-live{display:flex;align-items:center;gap:8px}.tree-live i{display:block;width:7px;height:7px;border-radius:50%;background:#80c69a;box-shadow:0 0 0 4px #80c69a22}
|
|
||||||
.tree-stage{position:relative;height:330px;overflow:hidden;background-image:linear-gradient(#ffffff06 1px,transparent 1px),linear-gradient(90deg,#ffffff06 1px,transparent 1px);background-size:24px 24px}
|
|
||||||
.tree-stage svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}.tree-edge{fill:none;stroke:#527f9f;stroke-width:2;stroke-dasharray:5 5;vector-effect:non-scaling-stroke}.tree-edge.trunk{stroke:var(--gold);stroke-dasharray:none;stroke-width:3}
|
|
||||||
.tree-node{position:absolute;z-index:2;display:grid;gap:6px;width:164px;padding:13px 15px;border:1px solid #527085;color:var(--paper);background:#112a3b;text-align:left;cursor:pointer;transition:border-color .2s ease,background .2s ease,transform .2s ease,box-shadow .2s ease}
|
|
||||||
.tree-node span,.tree-node small{font:500 8px var(--font-mono);letter-spacing:.08em}.tree-node span{color:#8ca1af}.tree-node strong{font:600 12px var(--font-mono)}.tree-node small{color:#a9b6be}
|
|
||||||
.tree-node.root{top:25px;left:50%;transform:translateX(-50%);border-color:var(--gold)}.tree-node.branch{top:220px}.tree-node.ui{left:3%}.tree-node.tests{left:50%;transform:translateX(-50%)}.tree-node.docs{right:3%}
|
|
||||||
.tree-node:hover,.tree-node.active{border-color:var(--gold);background:#1c425a;box-shadow:0 0 0 4px #efc76b18}.tree-node.root:hover,.tree-node.root.active{transform:translateX(-50%) translateY(-3px)}.tree-node.tests:hover,.tree-node.tests.active{transform:translateX(-50%) translateY(-3px)}.tree-node.ui:hover,.tree-node.ui.active,.tree-node.docs:hover,.tree-node.docs.active{transform:translateY(-3px)}
|
|
||||||
.tree-detail{display:grid;grid-template-columns:.7fr 1fr 1.6fr;gap:1px;border-top:1px solid #41596b;background:#41596b}.tree-detail>div,.tree-detail>p,.tree-detail>code{margin:0;padding:18px;background:#102536}.tree-detail div{display:grid;gap:8px}.tree-detail span{color:var(--accent);font:500 8px var(--font-mono);letter-spacing:.1em}.tree-detail strong{color:var(--paper);font:500 11px var(--font-mono)}.tree-detail p{color:#aebbc3;font-size:11px;line-height:1.55}.tree-detail code{grid-column:1/-1;color:var(--gold);font:11px var(--font-mono)}
|
|
||||||
|
|
||||||
.route-console{display:grid;gap:14px}.route-table button{display:grid;grid-template-columns:.8fr .9fr 1.4fr;width:100%;padding:0;border:0;color:inherit;background:transparent;text-align:left;cursor:pointer}.route-table button>*{padding:15px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}.route-table button:hover,.route-table button.active{background:#e8ecee}.route-table button.active strong{box-shadow:inset 4px 0 0 var(--gold)}
|
|
||||||
.route-detail{display:grid;grid-template-columns:100px 1fr;gap:22px;align-items:center;padding:22px;color:var(--paper);background:var(--deep)}.route-meter{display:grid;align-items:end;width:76px;height:76px;padding:7px;border:1px solid #496274}.route-meter span{display:block;width:100%;height:var(--score);background:var(--gold);transition:height .35s ease}.route-detail>div:last-child{display:grid;gap:7px}.route-detail small{color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.08em}.route-detail strong{font-size:17px}.route-detail p{margin:0;color:#b5c0c7;font-size:12px;line-height:1.5}
|
|
||||||
|
|
||||||
.skill-explorer{display:grid;grid-template-columns:.9fr 1.1fr;min-height:290px;background:var(--blue)}.skill-package button{display:grid;grid-template-columns:1fr 1fr;gap:15px;padding:17px 20px;border:0;border-bottom:1px solid #ffffff40;color:var(--paper);background:transparent;text-align:left;cursor:pointer}.skill-package button:hover,.skill-package button.active{background:#315f80}.skill-package button.active{box-shadow:inset 4px 0 0 var(--gold)}.skill-package button code{font:12px var(--font-mono)}.skill-package button small{opacity:.7}
|
|
||||||
.skill-detail{display:grid;grid-template-columns:auto 1fr;gap:18px;align-content:center;padding:28px;color:var(--paper);background:#244760}.skill-detail>span{color:var(--gold);font:42px Georgia,serif}.skill-detail>div{display:grid;gap:13px}.skill-detail strong{font:600 15px var(--font-mono)}.skill-detail p{margin:0;color:#c4cdd3;font-size:12px;line-height:1.65}.skill-detail small{color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.06em;text-transform:uppercase}
|
|
||||||
|
|
||||||
.worker-card:focus-visible,.tree-node:focus-visible,.route-table button:focus-visible,.skill-package button:focus-visible{outline:3px solid var(--gold);outline-offset:-3px}
|
|
||||||
|
|
||||||
/* Model and effort gearbox */
|
|
||||||
.model-gearbox{margin-bottom:150px;padding-top:80px;border-top:1px solid var(--line)}
|
|
||||||
.gearbox-intro{display:grid;grid-template-columns:.9fr 1.1fr;gap:70px;align-items:end;margin:45px 0}.gearbox-intro h2{margin-bottom:0}.gearbox-intro>p{max-width:650px;margin:0;color:var(--muted);font-size:14px;line-height:1.75}
|
|
||||||
.gearbox{display:grid;grid-template-columns:150px minmax(0,1.35fr) minmax(230px,.65fr);grid-template-rows:minmax(410px,auto) auto;border:1px solid var(--line);background:var(--line);gap:1px}
|
|
||||||
.provider-tabs{display:grid;grid-template-rows:repeat(3,1fr);gap:1px;background:var(--line)}.provider-tabs button{border:0;padding:18px;color:var(--ink);background:var(--paper);font:700 10px var(--font-mono);letter-spacing:.08em;cursor:pointer;writing-mode:vertical-rl;transform:rotate(180deg)}.provider-tabs button:hover{background:#eceff0}.provider-tabs button.active{color:var(--paper);background:var(--blue);box-shadow:inset -5px 0 0 var(--gold)}
|
|
||||||
.provider-detail{display:grid;align-content:start;padding:42px clamp(28px,4vw,58px);color:var(--paper);background:var(--deep)}.provider-detail header{display:flex;justify-content:space-between;gap:20px;align-items:center}.provider-detail header span{color:var(--gold);font:500 9px var(--font-mono);letter-spacing:.09em}.provider-detail header a{color:#cbd9e1;font:500 9px var(--font-mono)}.provider-detail h3{margin:34px 0 12px;font-size:clamp(34px,4vw,62px);letter-spacing:-.06em}.provider-detail>p{max-width:730px;margin:0;color:#b7c7d1;font-size:13px;line-height:1.7}.model-ladder{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;margin-top:34px;background:#ffffff2b}.model-ladder div{padding:18px;background:#18364a}.model-ladder span{display:block;color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.08em}.model-ladder strong{display:block;margin-top:10px;font-size:13px}.model-ladder small{display:block;margin-top:7px;color:#aebfc9;font-size:10px;line-height:1.4}
|
|
||||||
.effort-rail{display:grid;grid-template-rows:auto repeat(3,1fr);background:#e9ecee}.effort-rail>span{padding:17px;color:var(--accent);font:700 8px var(--font-mono);letter-spacing:.08em}.effort-rail button{display:grid;align-content:center;gap:8px;padding:22px;border:0;border-top:1px solid var(--line);color:var(--ink);background:var(--paper);text-align:left;cursor:pointer}.effort-rail button b{font:700 18px var(--font-mono)}.effort-rail button small{color:var(--muted);font-size:10px}.effort-rail button:hover{background:#eceff0}.effort-rail button.active{color:var(--paper);background:var(--accent);box-shadow:inset 5px 0 0 var(--gold)}.effort-rail button.active small{color:#eeedf6}
|
|
||||||
.effort-detail{grid-column:1/-1;display:grid;grid-template-columns:150px 1fr auto;gap:25px;align-items:center;padding:22px 28px;color:var(--paper);background:#132b3b}.effort-detail>span{color:var(--gold);font:500 9px var(--font-mono);letter-spacing:.08em}.effort-detail p{margin:0;font-size:12px;line-height:1.55}.effort-detail code{padding:10px 12px;color:var(--gold);background:#081621;font:10px var(--font-mono)}.gearbox-rule{display:grid;grid-template-columns:150px 1fr;gap:25px;padding:24px 28px;color:var(--ink);background:var(--gold)}.gearbox-rule span{color:var(--accent);font:700 9px var(--font-mono);letter-spacing:.08em}.gearbox-rule strong{font-size:13px;line-height:1.5}.provider-tabs button:focus-visible,.effort-rail button:focus-visible,.provider-detail a:focus-visible{outline:3px solid var(--gold);outline-offset:-3px}
|
|
||||||
|
|
||||||
/* Skill shelf */
|
|
||||||
.skill-builder{margin-bottom:150px;padding-top:80px;border-top:1px solid var(--line)}
|
|
||||||
.builder-intro{display:grid;grid-template-columns:.95fr 1.05fr;gap:70px;align-items:end;margin:45px 0}.builder-intro h2{margin-bottom:0}.builder-intro>p{max-width:620px;margin:0;color:var(--muted);font-size:14px;line-height:1.75}
|
|
||||||
.builder-workbench{display:grid;grid-template-columns:minmax(220px,.65fr) minmax(380px,1.35fr) minmax(270px,.85fr);min-height:570px;border:1px solid var(--line);background:var(--line)}
|
|
||||||
.builder-steps{display:grid;grid-template-rows:repeat(5,1fr);gap:1px;background:var(--line)}.builder-steps button{display:grid;grid-template-columns:42px 1fr;grid-template-rows:auto auto;align-content:center;column-gap:13px;padding:18px;border:0;color:var(--ink);background:var(--paper);text-align:left;cursor:pointer}.builder-steps button b{grid-row:1/-1;align-self:center;color:var(--accent);font:500 11px var(--font-mono)}.builder-steps button span{font:700 12px var(--font-mono)}.builder-steps button small{margin-top:5px;color:var(--muted);font-size:10px}.builder-steps button:hover{background:#eceff0}.builder-steps button.active{color:var(--paper);background:var(--blue);box-shadow:inset 5px 0 0 var(--gold)}.builder-steps button.active b,.builder-steps button.active small{color:var(--gold)}
|
|
||||||
.builder-detail{display:grid;grid-template-rows:auto auto auto 1fr auto;align-content:start;padding:38px clamp(28px,4vw,58px);color:var(--paper);background:var(--deep)}.builder-detail header{display:flex;justify-content:space-between;align-items:center}.builder-detail header span{color:#ffffff30;font:500 54px var(--font-mono)}.builder-detail header small,.builder-action span,.builder-detail footer span{color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.1em}.builder-detail h3{margin:27px 0 14px;font-size:clamp(26px,3vw,44px);line-height:1.05;letter-spacing:-.05em}.builder-detail blockquote{margin:0;padding:0;color:#a9bcc8;font:italic 20px/1.35 Georgia,serif}.builder-action{align-self:center;margin:34px 0}.builder-action p{margin:11px 0 0;color:#d5dde2;font-size:13px;line-height:1.7}.builder-detail footer{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#ffffff2b}.builder-detail footer>div{display:grid;gap:10px;padding:17px;background:#18364a}.builder-detail footer strong{font-size:11px;line-height:1.5}
|
|
||||||
.builder-artifact{display:grid;grid-template-rows:auto 1fr auto;color:var(--paper);background:#0b1b27}.artifact-head{display:flex;justify-content:space-between;padding:17px;border-bottom:1px solid #344c5d;font:500 8px var(--font-mono);letter-spacing:.09em}.artifact-head i{width:8px;height:8px;border-radius:50%;background:#80c69a;box-shadow:0 0 0 4px #80c69a20}.builder-artifact pre{display:grid;align-items:center;margin:0;padding:28px;overflow:auto;color:#bed0dc;background-image:linear-gradient(#ffffff05 1px,transparent 1px),linear-gradient(90deg,#ffffff05 1px,transparent 1px);background-size:22px 22px}.builder-artifact pre code{font:12px/1.9 var(--font-mono)}.artifact-command{display:grid;gap:9px;padding:18px;border-top:1px solid #344c5d}.artifact-command span{color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.09em}.artifact-command code{font:10px var(--font-mono)}
|
|
||||||
.builder-loop{display:grid;grid-template-columns:200px 1fr;gap:25px;padding:23px 28px;color:var(--ink);background:var(--gold)}.builder-loop>span{font:700 9px var(--font-mono);letter-spacing:.09em}.builder-loop>div{font:600 11px var(--font-mono)}.builder-loop i{margin:0 12px;color:var(--accent);font-style:normal}.builder-steps button:focus-visible{position:relative;z-index:2;outline:3px solid var(--gold);outline-offset:-3px}
|
|
||||||
|
|
||||||
.skill-catalog{margin-bottom:130px;padding-top:80px;border-top:1px solid var(--line)}.verification{padding-top:80px;border-top:1px solid var(--line);margin-bottom:130px}.verify-intro{display:grid;grid-template-columns:.9fr 1.1fr;gap:70px;align-items:end;margin:45px 0}.verify-intro h2{margin-bottom:0}.verify-intro>p{max-width:650px;margin:0;color:var(--muted);font-size:14px;line-height:1.75}.verify-layers{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;margin-top:1px;background:var(--line);border:1px solid var(--line)}.verify-layers article{background:var(--paper);padding:24px 22px;display:grid;gap:12px;grid-template-rows:auto auto 1fr auto}.verify-layers article>span{font:500 9px var(--font-mono);letter-spacing:.09em;color:var(--accent)}.verify-layers article h3{margin:0;font-size:18px;letter-spacing:-.02em}.verify-layers article p{margin:0;color:var(--muted);font-size:13px;line-height:1.55}.verify-layers article code{font:10.5px var(--font-mono);color:var(--ink);background:var(--paper);border:1px solid var(--line);padding:10px 12px;white-space:pre-wrap;word-break:break-word;line-height:1.55}.verify-antipatterns{margin-top:55px}.verify-antipatterns>span{font:500 9px var(--font-mono);letter-spacing:.09em;color:var(--muted)}.ap-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;margin-top:14px;background:var(--line);border:1px solid var(--line)}.ap-grid article{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;align-items:start;background:var(--paper);padding:18px 20px}.ap-grid article b{font:500 22px var(--font-mono);color:var(--accent);line-height:1;align-self:center}.ap-grid article strong{font-size:15px;letter-spacing:-.01em}.ap-grid article p{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.ap-grid article code{font:11px var(--font-mono);color:var(--ink);background:var(--paper);padding:1px 4px;border:1px solid var(--line)}.verify-cta{margin-top:55px;padding:26px 28px;color:var(--paper);background:var(--ink)}.verify-cta>span{font:500 9px var(--font-mono);letter-spacing:.09em;color:var(--gold)}.verify-cta-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;margin-top:18px;background:#ffffff1f}.verify-card{display:grid;gap:6px;background:var(--ink);padding:20px 22px;color:var(--paper);text-decoration:none;border-bottom:2px solid var(--gold)}.verify-card strong{font-size:17px;letter-spacing:-.01em}.verify-card p{margin:0;color:#bfccd4;font-size:13px;line-height:1.55}.verify-card p code{font:11px var(--font-mono);color:var(--gold);background:#0f2230;padding:1px 5px;border:1px solid #2a4150}.verify-card small{font:500 10px var(--font-mono);color:var(--gold);letter-spacing:.09em}@media(max-width:880px){.verify-layers,.verify-cta-grid{grid-template-columns:1fr}.verify-intro{grid-template-columns:1fr}}@media(max-width:560px){.verify-intro{display:block}.verify-intro>p{margin-top:18px}.ap-grid{grid-template-columns:1fr}.verify-cta{padding:22px 20px}}
|
|
||||||
.catalog-intro{display:grid;grid-template-columns:.9fr 1.1fr;gap:70px;align-items:end;margin:45px 0}.catalog-intro h2{margin-bottom:0}.catalog-intro>p{max-width:620px;margin:0;color:var(--muted);font-size:14px;line-height:1.75}
|
|
||||||
.skill-deck{display:grid;grid-template-columns:minmax(270px,.8fr) minmax(0,1.7fr);min-height:620px;border:1px solid var(--line);background:var(--line)}
|
|
||||||
.skill-index{display:grid;grid-template-rows:repeat(7,1fr);gap:1px;background:var(--line)}
|
|
||||||
.skill-index button{display:grid;grid-template-columns:95px 1fr;grid-template-rows:auto auto;align-content:center;column-gap:18px;padding:16px 20px;border:0;color:var(--ink);background:var(--paper);text-align:left;cursor:pointer;transition:background .2s ease,color .2s ease}
|
|
||||||
.skill-index button span{grid-row:1/-1;align-self:center;color:var(--accent);font:500 8px var(--font-mono);letter-spacing:.08em}.skill-index button strong{font:600 13px var(--font-mono)}.skill-index button small{margin-top:5px;color:var(--muted);font-size:10px}.skill-index button:hover{background:#eceff0}.skill-index button.active{color:var(--paper);background:var(--deep);box-shadow:inset 5px 0 0 var(--gold)}.skill-index button.active span,.skill-index button.active small{color:var(--gold)}
|
|
||||||
.common-skill-detail{display:grid;grid-template-rows:auto auto auto 1fr;align-content:start;padding:44px clamp(30px,5vw,78px);color:var(--paper);background:var(--accent);overflow:hidden}.common-skill-detail header{display:flex;justify-content:space-between;align-items:center;padding-bottom:18px;border-bottom:1px solid #ffffff42}.common-skill-detail header span{font:500 48px var(--font-mono);opacity:.34}.common-skill-detail header small{font:500 9px var(--font-mono);letter-spacing:.1em}.common-skill-detail h3{margin:38px 0 18px;font-size:clamp(30px,4vw,60px);letter-spacing:-.06em}.common-skill-detail blockquote{max-width:720px;margin:0 0 38px;padding:0;border:0;color:var(--gold);font:400 clamp(20px,2.5vw,34px)/1.15 Georgia,serif;font-style:italic}.common-skill-notes{display:grid;grid-template-columns:1.45fr .9fr .9fr;gap:1px;align-self:end;background:#ffffff42}.common-skill-notes>div{padding:20px;background:#6c6898}.common-skill-notes span{color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.08em}.common-skill-notes p{margin:12px 0 0;color:#f1f0f7;font-size:12px;line-height:1.55}.common-skill-notes div:nth-child(2) p{font-family:var(--font-mono);font-size:10px}
|
|
||||||
.skill-loadout{display:grid;grid-template-columns:210px 1fr;gap:25px;padding:24px 28px;color:var(--paper);background:var(--ink)}.skill-loadout>span{color:var(--gold);font:500 9px var(--font-mono);letter-spacing:.09em}.skill-loadout>div{font:500 11px var(--font-mono)}.skill-loadout b{color:var(--accent);font-size:9px}.skill-loadout i{margin:0 10px;color:var(--gold);font-style:normal}.skill-index button:focus-visible{position:relative;z-index:2;outline:3px solid var(--gold);outline-offset:-3px}
|
|
||||||
.skill-source{display:inline-block;margin-top:22px;color:var(--gold);font:700 9px var(--font-mono);letter-spacing:.07em;text-decoration:none;border-bottom:1px solid currentColor}.skill-source:focus-visible{outline:3px solid var(--gold);outline-offset:4px}
|
|
||||||
.install-skills{display:grid;grid-template-rows:auto 1fr auto;margin-top:24px;color:var(--paper);background:var(--deep);border-left:7px solid var(--gold)}.install-skills header{display:flex;justify-content:space-between;align-items:center;gap:24px;padding:20px 24px;border-bottom:1px solid #ffffff2d}.install-skills header>div{display:grid;gap:7px}.install-skills header span,.install-skills footer{color:var(--gold);font:500 8px var(--font-mono);letter-spacing:.08em}.install-skills header strong{font-size:15px}.install-skills button{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid #ffffff50;color:var(--paper);background:transparent;cursor:pointer}.install-skills button:hover,.install-skills button.copied{color:var(--ink);border-color:var(--gold);background:var(--gold)}.install-skills button i{font-style:normal}.install-skills pre{max-height:360px;margin:0;padding:24px;overflow:auto;white-space:pre-wrap;background:#0b1b27}.install-skills pre code{font:10px/1.7 var(--font-mono)}.install-skills footer{padding:16px 24px;color:#b9c8d1;border-top:1px solid #ffffff2d}.install-skills button:focus-visible{outline:3px solid var(--gold);outline-offset:3px}
|
|
||||||
|
|
||||||
/* Copy-ready hands-on lab */
|
|
||||||
.hands-on{margin-bottom:130px;padding-top:80px;border-top:1px solid var(--line)}.hands-intro{display:grid;grid-template-columns:.9fr 1.1fr;gap:70px;align-items:end;margin:45px 0}.hands-intro h2{margin-bottom:0}.hands-intro>div:last-child>p{max-width:650px;margin:0;color:var(--muted);font-size:14px;line-height:1.75}.starter-link{display:inline-block;margin-top:18px;color:var(--blue);font:700 11px var(--font-mono);text-decoration:none;border-bottom:2px solid var(--gold)}.starter-links{display:flex;flex-wrap:wrap;gap:14px 28px;margin-top:18px}.starter-links .starter-link{margin-top:0}.starter-link-group{display:flex;flex-wrap:wrap;gap:6px 18px;align-items:baseline}.starter-link-source{font-weight:500!important;color:var(--muted)!important;border-bottom-color:transparent!important}.verify-card-source{display:block;margin-top:8px;color:#bfccd4;font-size:10px!important;letter-spacing:.05em;text-transform:none;font-weight:500}.verify-card-source span{font-family:var(--font-mono);color:var(--gold)}
|
|
||||||
.exercise-brief{display:grid;grid-template-columns:190px 1fr;gap:22px;padding:26px 30px;color:var(--paper);background:var(--deep)}.exercise-brief>span{color:var(--gold);font:500 9px var(--font-mono);letter-spacing:.09em}.exercise-brief>strong{font-size:clamp(20px,2.6vw,34px);line-height:1.12}.exercise-brief>div{grid-column:2;color:#afbec7;font:500 9px var(--font-mono);letter-spacing:.05em}.exercise-brief b{margin-left:18px;color:var(--accent)}.exercise-brief b:first-child{margin-left:0}
|
|
||||||
.prompt-compare{display:grid;grid-template-columns:1fr 1fr;gap:1px;margin-top:1px;background:var(--line)}.prompt-card{display:grid;grid-template-rows:auto 1fr auto;min-width:0;min-height:600px;color:var(--paper);background:#19364a}.prompt-card.enhanced{background:#596f9a}.prompt-card header{display:flex;justify-content:space-between;align-items:center;padding:20px 22px;border-bottom:1px solid #ffffff32}.prompt-card header>div{display:grid;gap:6px}.prompt-card header span,.prompt-card footer{font:500 8px var(--font-mono);letter-spacing:.09em}.prompt-card header>div span{color:var(--gold)}.prompt-card header strong{font-size:17px}.prompt-card button{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid #ffffff50;color:var(--paper);background:transparent;cursor:pointer}.prompt-card button:hover,.prompt-card button.copied{color:var(--ink);border-color:var(--gold);background:var(--gold)}.prompt-card button i{font-style:normal}.prompt-card pre{margin:0;padding:25px;overflow:auto;white-space:pre-wrap}.prompt-card pre code{font:11px/1.72 var(--font-mono)}.prompt-card footer{padding:17px 22px;color:#bfccd4;border-top:1px solid #ffffff32}.prompt-card.enhanced footer{color:#e5e3ef}.prompt-card button:focus-visible,.starter-link:focus-visible{outline:3px solid var(--gold);outline-offset:3px}
|
|
||||||
.comparison-strip{display:grid;grid-template-columns:190px repeat(4,1fr);gap:1px;background:var(--line)}.comparison-strip>*{padding:18px;background:var(--paper)}.comparison-strip>span{color:var(--accent);font:700 9px var(--font-mono);letter-spacing:.08em}.comparison-strip>div{color:var(--muted);font:500 10px var(--font-mono)}.comparison-strip b{margin-right:8px;color:var(--blue)}.copy-status{min-height:20px;margin:15px 0 0;color:var(--blue);font:600 10px var(--font-mono);text-align:right}
|
|
||||||
@media(min-width:1600px){.tree-stage{height:390px}.tree-stage svg{height:330px;top:30px}.tree-node.root{top:45px}.tree-node.branch{top:255px}.tree-node{width:190px;padding:17px}.tree-detail{grid-template-columns:.6fr .8fr 1.8fr}.worker-card{min-height:210px;padding:28px}}
|
|
||||||
@media(min-width:2200px){main{max-width:2880px;padding-inline:clamp(140px,7vw,280px)}.hero{max-width:1420px}.hero h1{font-size:clamp(150px,7vw,220px)}.tree-stage{height:460px}.tree-stage svg{height:380px;top:45px}.tree-node.root{top:65px}.tree-node.branch{top:305px}.tree-node{width:240px;padding:22px}.tree-node strong{font-size:15px}.tree-detail>*{font-size:14px!important}.fleet,.failure-map{margin-bottom:170px}.workflow,.routing,.skills{margin-bottom:180px}}
|
|
||||||
@media(max-width:1100px){.chapter-links{display:none}.builder-workbench{grid-template-columns:minmax(210px,.65fr) minmax(0,1.35fr)}.builder-artifact{grid-column:1/-1;grid-template-columns:1fr 1fr;grid-template-rows:auto}.builder-artifact .artifact-head{grid-column:1/-1}.artifact-command{align-content:center;border-top:0;border-left:1px solid #344c5d}}
|
|
||||||
@media(max-width:1050px){.tree-node{width:145px}.tree-detail{grid-template-columns:1fr 1fr}.tree-detail p{grid-column:1/-1}.skill-explorer{grid-template-columns:1fr}}
|
|
||||||
@media(max-width:800px){.worker-detail{grid-template-columns:1fr}.worktrees{display:block}.worktree-intro{margin-bottom:40px}.tree-lab{box-shadow:9px 9px 0 #081621}.route-table button{min-width:620px}.gearbox-intro,.builder-intro,.catalog-intro,.hands-intro{grid-template-columns:1fr;gap:20px}.gearbox{grid-template-columns:1fr 210px;grid-template-rows:auto auto}.provider-tabs{grid-column:1/-1;grid-template-columns:repeat(3,1fr);grid-template-rows:none}.provider-tabs button{writing-mode:horizontal-tb;transform:none}.provider-tabs button.active{box-shadow:inset 0 -5px 0 var(--gold)}.effort-detail{grid-template-columns:110px 1fr}.effort-detail code{grid-column:2}.skill-explorer{grid-template-columns:1fr 1fr}.builder-workbench{grid-template-columns:1fr}.builder-steps{grid-template-columns:1fr 1fr;grid-template-rows:none}.builder-artifact{grid-column:auto}.skill-deck{grid-template-columns:1fr}.skill-index{grid-template-columns:1fr 1fr;grid-template-rows:none}.common-skill-detail{min-height:600px}.common-skill-notes{grid-template-columns:1fr 1fr}.common-skill-notes>div:first-child{grid-column:1/-1}.prompt-compare{grid-template-columns:1fr}.comparison-strip{grid-template-columns:1fr 1fr}.comparison-strip>span{grid-column:1/-1}.exercise-brief{grid-template-columns:1fr}.exercise-brief>div{grid-column:auto}}
|
|
||||||
@media(max-width:600px){.tree-stage{height:auto;min-height:560px;padding:24px}.tree-stage svg{display:none}.tree-node,.tree-node.root,.tree-node.branch,.tree-node.ui,.tree-node.tests,.tree-node.docs{position:relative;top:auto;right:auto;left:auto;width:100%;margin:0 0 34px;transform:none}.tree-node:not(:last-child)::after{content:'↓';position:absolute;left:50%;bottom:-28px;color:var(--gold)}.tree-node:hover,.tree-node.active,.tree-node.root:hover,.tree-node.root.active,.tree-node.tests:hover,.tree-node.tests.active{transform:translateY(-2px)}.tree-detail{grid-template-columns:1fr}.tree-detail p,.tree-detail code{grid-column:auto}.gearbox{grid-template-columns:1fr}.provider-detail,.effort-rail{grid-column:1}.model-ladder{grid-template-columns:1fr}.effort-rail{grid-template-columns:repeat(3,1fr);grid-template-rows:auto auto}.effort-rail>span{grid-column:1/-1}.effort-rail button{text-align:center;border-top:1px solid var(--line);border-left:1px solid var(--line)}.effort-rail button.active{box-shadow:inset 0 -5px 0 var(--gold)}.effort-detail{grid-template-columns:1fr}.effort-detail code{grid-column:auto;overflow:auto}.gearbox-rule{grid-template-columns:1fr}.skill-explorer{grid-template-columns:1fr}.route-detail{grid-template-columns:70px 1fr}.route-meter{width:56px;height:70px}.builder-steps{grid-template-columns:1fr}.builder-steps button{min-height:78px}.builder-detail{padding:28px 24px}.builder-detail footer{grid-template-columns:1fr}.builder-artifact{grid-template-columns:1fr}.builder-artifact .artifact-head{grid-column:auto}.artifact-command{border-left:0;border-top:1px solid #344c5d}.builder-loop{grid-template-columns:1fr}.builder-loop>div{line-height:2}.builder-loop i{margin-inline:4px}.skill-index{grid-template-columns:1fr}.skill-index button{min-height:78px}.common-skill-detail{min-height:0;padding:30px 24px}.common-skill-notes{grid-template-columns:1fr}.common-skill-notes>div:first-child{grid-column:auto}.skill-loadout{grid-template-columns:1fr}.skill-loadout>div{line-height:2}.skill-loadout i{margin-inline:4px}.install-skills header{align-items:flex-start}.install-skills header strong{font-size:12px}.install-skills pre{max-height:500px}.exercise-brief{padding:22px}.exercise-brief>div{line-height:2}.exercise-brief b{margin-left:7px}.prompt-card{min-height:0}.prompt-card pre{max-height:560px}.comparison-strip{grid-template-columns:1fr}.comparison-strip>span{grid-column:auto}.copy-status{text-align:left}}
|
|
||||||
@media(prefers-reduced-motion:reduce){.worker-card,.tree-node,.route-meter span{transition:none}.reading-progress span{transition:none}}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
const languageCopy = {
|
|
||||||
en: {
|
|
||||||
back: 'field guide', navPipeline: 'Pipeline', navExamples: 'Examples', stageContext: 'CONTEXT', stageReview: 'REVIEW', heroEyebrow: 'A real repository case study', heroTitle: 'Rules that<br />survive the <em>prompt.</em>',
|
|
||||||
heroText: 'Prompts ask for behavior. Repositories preserve it. The interview project combines written context, reusable skills, executable checks, commit hooks, and independent review so the rule is still present when the conversation is gone.',
|
|
||||||
heroAside: '8 skills · 3 agents · 4 enforcement layers', thesisLabel: 'THE SHORT VERSION', thesis: 'A prompt is advice for one run. A repository rule is reusable context plus an executable boundary.',
|
|
||||||
pipelineLabel: 'Enforcement pipeline', pipelineMeta: 'select a checkpoint', pipelineEyebrow: 'From intent to evidence', pipelineTitle: 'Five places<br />a rule can <em>hold.</em>', pipelineText: 'Not every rule belongs in a hook. Put guidance where an agent can discover it, deterministic policy in a command, cheap checks at commit time, and independent judgment at review.',
|
|
||||||
skillsLabel: 'Project-local skills', skillsMeta: 'procedures born from repeated friction', skillsEyebrow: 'Small instruction packages', skillsTitle: 'Teach the trap.<br />Name the <em>trigger.</em>', skillsText: 'These skills are not downloaded magic. They are repository-specific procedures under <code>.agents/skills/</code>, distilled from mistakes, commands, and architectural decisions that kept recurring.', skillGate: 'prove green is real', skillParallel: 'worktree per task', skillRepo: 'query before crawling', skillDebt: 'separate line of work', skillWriter: 'repeat twice, encode once', skillArea: 'stack-specific traps',
|
|
||||||
examplesLabel: 'Concrete examples', examplesMeta: 'open the source, then adapt', ratchetLabel: 'CLI RATCHET', ratchetTitle: 'Debt may go down.<br />Never silently up.', hookTitle: 'Fast checks before history.', commitLabel: 'COMMIT MESSAGE', commitTitle: 'Intent has a grammar.', reviewLabel: 'INDEPENDENT REVIEW', reviewTitle: 'A second reader checks intent.', readChecker: 'Read the checker →', readHook: 'Read the hook →', readCommit: 'Read commitlint config →', readReview: 'Read review policy →',
|
|
||||||
copyLabel: 'COPY / ADAPT', copyTitle: 'Ask your agent to map the enforcement stack.', copyText: 'Use this in the interview repository or adapt the path names to another project.', copyButton: 'COPY PROMPT', deeperLabel: 'GO DEEPER', deeperTitle: 'Read the implementation, not just this summary.', deepContext: 'Repository context', deepSkills: 'Skill catalog', deepAgents: 'Specialist agents', deepPolicy: 'Staged-file policy'
|
|
||||||
},
|
|
||||||
pt: {
|
|
||||||
back: 'guia de campo', navPipeline: 'Pipeline', navExamples: 'Exemplos', stageContext: 'CONTEXTO', stageReview: 'REVISÃO', heroEyebrow: 'Um estudo de caso de repositório real', heroTitle: 'Regras que<br />sobrevivem ao <em>prompt.</em>',
|
|
||||||
heroText: 'Prompts pedem comportamento. Repositórios o preservam. O projeto interview combina contexto escrito, skills reutilizáveis, verificações executáveis, hooks de commit e revisão independente para que a regra continue existindo quando a conversa terminar.',
|
|
||||||
heroAside: '8 skills · 3 agentes · 4 camadas de enforcement', thesisLabel: 'A VERSÃO CURTA', thesis: 'Um prompt orienta uma execução. Uma regra de repositório é contexto reutilizável mais uma fronteira executável.',
|
|
||||||
pipelineLabel: 'Pipeline de enforcement', pipelineMeta: 'selecione um checkpoint', pipelineEyebrow: 'Da intenção à evidência', pipelineTitle: 'Cinco lugares<br />onde a regra <em>segura.</em>', pipelineText: 'Nem toda regra pertence a um hook. Coloque orientação onde o agente descobre, política determinística em um comando, checks baratos no commit e julgamento independente na revisão.',
|
|
||||||
skillsLabel: 'Skills locais do projeto', skillsMeta: 'procedimentos nascidos de atrito repetido', skillsEyebrow: 'Pequenos pacotes de instrução', skillsTitle: 'Ensine a armadilha.<br />Nomeie o <em>gatilho.</em>', skillsText: 'Estas skills não são mágica baixada. São procedimentos específicos do repositório em <code>.agents/skills/</code>, extraídos de erros, comandos e decisões arquiteturais recorrentes.', skillGate: 'prove que o verde é real', skillParallel: 'um worktree por tarefa', skillRepo: 'consulte antes de explorar', skillDebt: 'linha de trabalho separada', skillWriter: 'repita duas vezes, codifique uma', skillArea: 'armadilhas da stack',
|
|
||||||
examplesLabel: 'Exemplos concretos', examplesMeta: 'abra a fonte, depois adapte', ratchetLabel: 'CATRACA CLI', ratchetTitle: 'A dívida pode cair.<br />Nunca subir em silêncio.', hookTitle: 'Checks rápidos antes do histórico.', commitLabel: 'MENSAGEM DE COMMIT', commitTitle: 'A intenção tem gramática.', reviewLabel: 'REVISÃO INDEPENDENTE', reviewTitle: 'Um segundo leitor verifica a intenção.', readChecker: 'Ler o checker →', readHook: 'Ler o hook →', readCommit: 'Ler config do commitlint →', readReview: 'Ler política de revisão →',
|
|
||||||
copyLabel: 'COPIAR / ADAPTAR', copyTitle: 'Peça ao agente para mapear o enforcement.', copyText: 'Use isto no repositório interview ou adapte os caminhos para outro projeto.', copyButton: 'COPIAR PROMPT', deeperLabel: 'APROFUNDE', deeperTitle: 'Leia a implementação, não apenas este resumo.', deepContext: 'Contexto do repositório', deepSkills: 'Catálogo de skills', deepAgents: 'Agentes especialistas', deepPolicy: 'Política dos arquivos staged'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stages = {
|
|
||||||
context: { number: '01', file: 'AGENTS.md', title: { en: 'Give every agent the same map', pt: 'Dê o mesmo mapa a cada agente' }, text: { en: 'Stack, commands, product boundaries, and known traps load before implementation. This is discoverable guidance—not an executable guarantee.', pt: 'Stack, comandos, limites do produto e armadilhas conhecidas carregam antes da implementação. É orientação descobrível — não garantia executável.' }, code: 'Read AGENTS.md\n→ query .agents/db\n→ load area skill', link: 'https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/AGENTS.md' },
|
|
||||||
skills: { number: '02', file: '.agents/skills/', title: { en: 'Load only the relevant procedure', pt: 'Carregue apenas o procedimento relevante' }, text: { en: 'Frontend, Go API, gate discipline, parallel agents, repo databases, issues, skill writing, and tech debt each have a narrow trigger.', pt: 'Frontend, API Go, disciplina de gates, agentes paralelos, bancos do repo, issues, escrita de skills e dívida técnica têm gatilhos estreitos.' }, code: 'request + description\n→ matching SKILL.md\n→ focused workflow', link: 'https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.agents/skills' },
|
|
||||||
cli: { number: '03', file: 'scripts/check-ui-contract.mjs', title: { en: 'Turn measurable policy into a ratchet', pt: 'Transforme política mensurável em catraca' }, text: { en: 'The UI checker counts known violations and fails only when a count rises. Existing debt can be reduced, but a new change cannot quietly increase it.', pt: 'O checker de UI conta violações conhecidas e falha quando o total sobe. Dívida existente pode cair, mas uma mudança não pode aumentá-la em silêncio.' }, code: 'pnpm check:ui\ncurrent ≤ baseline → pass\ncurrent > baseline → fail', link: 'https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/scripts/check-ui-contract.mjs' },
|
|
||||||
commit: { number: '04', file: '.husky/pre-commit', title: { en: 'Block cheap mistakes at the boundary', pt: 'Bloqueie erros baratos na fronteira' }, text: { en: 'Husky runs lint-staged plus the whole-tree UI ratchet. Commitlint separately enforces Conventional Commit messages.', pt: 'Husky executa lint-staged mais a catraca de UI da árvore inteira. Commitlint aplica Conventional Commits separadamente.' }, code: 'git commit\n├─ lint-staged\n├─ check:ui\n└─ commitlint', link: 'https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.husky/pre-commit' },
|
|
||||||
review: { number: '05', file: '.pr-review.json', title: { en: 'Reserve judgment for the PR', pt: 'Reserve julgamento para o PR' }, text: { en: 'An AI reviewer checks security, generated-code discipline, test quality, scope, and repository-specific traps. A verifier independently reruns gates before merge.', pt: 'Um revisor de IA verifica segurança, código gerado, qualidade dos testes, escopo e armadilhas do repositório. Um verifier roda os gates novamente antes do merge.' }, code: 'diff + house rules + prior review\n→ findings + risks\n→ human merge decision', link: 'https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.pr-review.json' }
|
|
||||||
};
|
|
||||||
|
|
||||||
const skills = {
|
|
||||||
gate: { name: 'gate-discipline', trigger: { en: 'Before commit, PR, or merge.', pt: 'Antes de commit, PR ou merge.' }, lesson: { en: 'Run canonical gates separately, inspect exit codes, distrust convenient cache hits, and prove generated code is current.', pt: 'Rode gates separadamente, leia exit codes, desconfie de cache conveniente e prove que código gerado está atual.' }, example: 'pnpm lint; echo "lint=$?"', path: '.agents/skills/gate-discipline/SKILL.md' },
|
|
||||||
parallel: { name: 'parallel-agents', trigger: { en: 'When independent tasks can run concurrently.', pt: 'Quando tarefas independentes podem rodar juntas.' }, lesson: { en: 'One worktree and file ownership per task; prior-art scout first, verifier last, merge sequentially.', pt: 'Um worktree e ownership por tarefa; prior-art scout primeiro, verifier por último, merges sequenciais.' }, example: 'git worktree add .claude/worktrees/task -b feat/task', path: '.agents/skills/parallel-agents/SKILL.md' },
|
|
||||||
repo: { name: 'repo-db', trigger: { en: 'At task start and before debugging.', pt: 'No início da tarefa e antes de diagnosticar.' }, lesson: { en: 'Query the repo map, canonical commands, and known-issues ledger before crawling thousands of files.', pt: 'Consulte mapa, comandos canônicos e known-issues antes de vasculhar milhares de arquivos.' }, example: "jq -r '.verify[]' .agents/db/commands.json", path: '.agents/skills/repo-db/SKILL.md' },
|
|
||||||
debt: { name: 'tech-debt', trigger: { en: 'When unrelated debt appears mid-task.', pt: 'Quando dívida não relacionada aparece no meio.' }, lesson: { en: 'Do not smuggle cleanup into a feature. Record it, give it its own branch, and finish the underlying condition.', pt: 'Não esconda limpeza em uma feature. Registre, dê uma branch própria e finalize a condição original.' }, example: 'feature diff ≠ debt cleanup', path: '.agents/skills/tech-debt/SKILL.md' },
|
|
||||||
writer: { name: 'skill-writer', trigger: { en: 'When a workflow is explained twice.', pt: 'Quando um fluxo é explicado duas vezes.' }, lesson: { en: 'Procedures become skills; isolated facts become known-issues entries. Keep the body short and the trigger discriminating.', pt: 'Procedimentos viram skills; fatos isolados viram known-issues. Corpo curto e gatilho discriminante.' }, example: '.agents/skills/<name>/SKILL.md', path: '.agents/skills/skill-writer/SKILL.md' },
|
|
||||||
area: { name: 'frontend / go-api', trigger: { en: 'When changing the matching subsystem.', pt: 'Ao alterar o subsistema correspondente.' }, lesson: { en: 'Encode version-specific traps close to the work: Next/React/AntD boundaries, Connect APIs, sqlc types, migrations, and code generation.', pt: 'Codifique armadilhas de versão perto do trabalho: Next/React/AntD, APIs Connect, tipos sqlc, migrations e codegen.' }, example: 'area → local skill → canonical checks', path: '.agents/skills/' }
|
|
||||||
};
|
|
||||||
|
|
||||||
const prompts = {
|
|
||||||
en: `Inspect this repository's enforcement stack before changing code.\n\n1. Read AGENTS.md.\n2. List .agents/skills and select only skills whose descriptions match the task.\n3. Query .agents/db/commands.json and .agents/db/known-issues.json.\n4. Explain what is guidance versus mechanically enforced by scripts, Husky, commitlint, CI, and PR review.\n5. For each relevant rule, cite the source path and the command that proves it.\n6. Identify gaps where documentation claims enforcement but no executable check exists.\n\nDo not modify files. Return a compact map: rule → source → enforcement point → verification command → remaining gap.`,
|
|
||||||
pt: `Inspecione a stack de enforcement deste repositório antes de alterar código.\n\n1. Leia AGENTS.md.\n2. Liste .agents/skills e selecione apenas skills cuja descrição corresponda à tarefa.\n3. Consulte .agents/db/commands.json e .agents/db/known-issues.json.\n4. Explique o que é orientação e o que é imposto mecanicamente por scripts, Husky, commitlint, CI e revisão de PR.\n5. Para cada regra relevante, cite o caminho fonte e o comando que a comprova.\n6. Identifique lacunas onde a documentação promete enforcement sem check executável.\n\nNão modifique arquivos. Retorne um mapa compacto: regra → fonte → ponto de enforcement → comando de verificação → lacuna restante.`
|
|
||||||
};
|
|
||||||
|
|
||||||
let language = localStorage.getItem('rules-language') === 'pt' ? 'pt' : 'en';
|
|
||||||
|
|
||||||
function select(selector, value, key) {
|
|
||||||
document.querySelectorAll(selector).forEach((button) => {
|
|
||||||
const active = button.dataset[key] === value;
|
|
||||||
button.classList.toggle('active', active);
|
|
||||||
button.setAttribute('aria-selected', String(active));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStage(id) {
|
|
||||||
const item = stages[id];
|
|
||||||
document.querySelector('#stage-detail').innerHTML = `<div class="stage-number">${item.number}</div><div><span>${item.file}</span><h3>${item.title[language]}</h3><p>${item.text[language]}</p><a href="${item.link}">${language === 'pt' ? 'ABRIR FONTE ↗' : 'OPEN SOURCE ↗'}</a></div><pre><code>${item.code}</code></pre>`;
|
|
||||||
select('[data-stage]', id, 'stage');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSkill(id) {
|
|
||||||
const item = skills[id];
|
|
||||||
document.querySelector('#skill-detail').innerHTML = `<header><span>${language === 'pt' ? 'GATILHO' : 'TRIGGER'}</span><strong>${item.trigger[language]}</strong></header><h3>${item.name}</h3><p>${item.lesson[language]}</p><pre><code>${item.example}</code></pre><a href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/${item.path}">${language === 'pt' ? 'LER SKILL ↗' : 'READ SKILL ↗'}</a>`;
|
|
||||||
select('[data-skill]', id, 'skill');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLanguage(next) {
|
|
||||||
language = next;
|
|
||||||
document.documentElement.lang = language === 'pt' ? 'pt-BR' : 'en';
|
|
||||||
document.querySelectorAll('[data-copy]').forEach((node) => { node.innerHTML = languageCopy[language][node.dataset.copy]; });
|
|
||||||
document.querySelector('#explore-prompt').textContent = prompts[language];
|
|
||||||
document.querySelectorAll('[data-lang]').forEach((button) => { const active = button.dataset.lang === language; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); });
|
|
||||||
renderStage(document.querySelector('[data-stage].active')?.dataset.stage || 'context');
|
|
||||||
renderSkill(document.querySelector('[data-skill].active')?.dataset.skill || 'gate');
|
|
||||||
localStorage.setItem('rules-language', language);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyPrompt() {
|
|
||||||
const value = prompts[language];
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(value);
|
|
||||||
document.querySelector('#copy-status').textContent = language === 'pt' ? 'Prompt copiado.' : 'Prompt copied.';
|
|
||||||
} catch {
|
|
||||||
document.querySelector('#copy-status').textContent = language === 'pt' ? 'Selecione o texto manualmente.' : 'Select the text manually.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-stage]').forEach((button) => button.addEventListener('click', () => renderStage(button.dataset.stage)));
|
|
||||||
document.querySelectorAll('[data-skill]').forEach((button) => button.addEventListener('click', () => renderSkill(button.dataset.skill)));
|
|
||||||
document.querySelectorAll('[data-lang]').forEach((button) => button.addEventListener('click', () => renderLanguage(button.dataset.lang)));
|
|
||||||
document.querySelector('[data-copy-prompt]').addEventListener('click', copyPrompt);
|
|
||||||
window.addEventListener('scroll', () => { const height = document.documentElement.scrollHeight - innerHeight; document.querySelector('.progress span').style.width = `${height > 0 ? scrollY / height * 100 : 0}%`; }, { passive: true });
|
|
||||||
renderLanguage(language);
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="description" content="A concise case study of agent skills, CLI ratchets, Husky hooks, and PR review in netcracker/interview." />
|
|
||||||
<title>Rules That Survive the Prompt — AI For Dummies</title>
|
|
||||||
<link rel="stylesheet" href="styles.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="progress" aria-hidden="true"><span></span></div>
|
|
||||||
<header class="topbar">
|
|
||||||
<a class="back" href="../"><b>A</b><span data-copy="back">field guide</span></a>
|
|
||||||
<nav aria-label="Page sections"><a href="#pipeline" data-copy="navPipeline">Pipeline</a><a href="#skills">Skills</a><a href="#examples" data-copy="navExamples">Examples</a></nav>
|
|
||||||
<div class="languages" aria-label="Language"><button class="active" data-lang="en" aria-pressed="true">EN</button><span>/</span><button data-lang="pt" aria-pressed="false">PT</button></div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow" data-copy="heroEyebrow">A real repository case study</p>
|
|
||||||
<h1 data-copy="heroTitle">Rules that<br />survive the <em>prompt.</em></h1>
|
|
||||||
<div class="hero-foot">
|
|
||||||
<p data-copy="heroText">Prompts ask for behavior. Repositories preserve it. The interview project combines written context, reusable skills, executable checks, commit hooks, and independent review so the rule is still present when the conversation is gone.</p>
|
|
||||||
<aside><span>CASE / NETCRACKER</span><strong>interview</strong><small data-copy="heroAside">8 skills · 3 agents · 4 enforcement layers</small></aside>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="thesis">
|
|
||||||
<span data-copy="thesisLabel">THE SHORT VERSION</span>
|
|
||||||
<strong data-copy="thesis">A prompt is advice for one run. A repository rule is reusable context plus an executable boundary.</strong>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="pipeline-section" id="pipeline">
|
|
||||||
<div class="section-label"><span data-copy="pipelineLabel">Enforcement pipeline</span><span data-copy="pipelineMeta">select a checkpoint</span></div>
|
|
||||||
<div class="intro"><div><p class="eyebrow" data-copy="pipelineEyebrow">From intent to evidence</p><h2 data-copy="pipelineTitle">Five places<br />a rule can <em>hold.</em></h2></div><p data-copy="pipelineText">Not every rule belongs in a hook. Put guidance where an agent can discover it, deterministic policy in a command, cheap checks at commit time, and independent judgment at review.</p></div>
|
|
||||||
<div class="pipeline" role="tablist" aria-label="Rule enforcement stages">
|
|
||||||
<button class="active" data-stage="context" role="tab" aria-selected="true"><span>01</span><strong data-copy="stageContext">CONTEXT</strong><small>AGENTS.md</small></button>
|
|
||||||
<i>→</i><button data-stage="skills" role="tab" aria-selected="false"><span>02</span><strong>SKILLS</strong><small>.agents/skills</small></button>
|
|
||||||
<i>→</i><button data-stage="cli" role="tab" aria-selected="false"><span>03</span><strong>CLI</strong><small>check:ui</small></button>
|
|
||||||
<i>→</i><button data-stage="commit" role="tab" aria-selected="false"><span>04</span><strong>COMMIT</strong><small>Husky</small></button>
|
|
||||||
<i>→</i><button data-stage="review" role="tab" aria-selected="false"><span>05</span><strong data-copy="stageReview">REVIEW</strong><small>pragent</small></button>
|
|
||||||
</div>
|
|
||||||
<article class="stage-detail" id="stage-detail" aria-live="polite"></article>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="skill-section" id="skills">
|
|
||||||
<div class="section-label"><span data-copy="skillsLabel">Project-local skills</span><span data-copy="skillsMeta">procedures born from repeated friction</span></div>
|
|
||||||
<div class="intro"><div><p class="eyebrow" data-copy="skillsEyebrow">Small instruction packages</p><h2 data-copy="skillsTitle">Teach the trap.<br />Name the <em>trigger.</em></h2></div><p data-copy="skillsText">These skills are not downloaded magic. They are repository-specific procedures under <code>.agents/skills/</code>, distilled from mistakes, commands, and architectural decisions that kept recurring.</p></div>
|
|
||||||
<div class="skill-console">
|
|
||||||
<div class="skill-list" role="tablist" aria-label="Repository skills">
|
|
||||||
<button class="active" data-skill="gate" role="tab" aria-selected="true"><strong>gate-discipline</strong><small data-copy="skillGate">prove green is real</small></button>
|
|
||||||
<button data-skill="parallel" role="tab" aria-selected="false"><strong>parallel-agents</strong><small data-copy="skillParallel">worktree per task</small></button>
|
|
||||||
<button data-skill="repo" role="tab" aria-selected="false"><strong>repo-db</strong><small data-copy="skillRepo">query before crawling</small></button>
|
|
||||||
<button data-skill="debt" role="tab" aria-selected="false"><strong>tech-debt</strong><small data-copy="skillDebt">separate line of work</small></button>
|
|
||||||
<button data-skill="writer" role="tab" aria-selected="false"><strong>skill-writer</strong><small data-copy="skillWriter">repeat twice, encode once</small></button>
|
|
||||||
<button data-skill="area" role="tab" aria-selected="false"><strong>frontend / go-api</strong><small data-copy="skillArea">stack-specific traps</small></button>
|
|
||||||
</div>
|
|
||||||
<article class="skill-detail" id="skill-detail" aria-live="polite"></article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="examples" id="examples">
|
|
||||||
<div class="section-label"><span data-copy="examplesLabel">Concrete examples</span><span data-copy="examplesMeta">open the source, then adapt</span></div>
|
|
||||||
<div class="example-grid">
|
|
||||||
<article><span data-copy="ratchetLabel">CLI RATCHET</span><h3 data-copy="ratchetTitle">Debt may go down.<br />Never silently up.</h3><pre><code>pnpm check:ui
|
|
||||||
# raw buttons
|
|
||||||
# swallowed catches
|
|
||||||
# pages without h1
|
|
||||||
# hardcoded colours</code></pre><a data-copy="readChecker" data-source="ratchet" href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/scripts/check-ui-contract.mjs">Read the checker →</a></article>
|
|
||||||
<article><span>HUSKY / PRE-COMMIT</span><h3 data-copy="hookTitle">Fast checks before history.</h3><pre><code>pnpm exec lint-staged
|
|
||||||
node scripts/check-ui-contract.mjs</code></pre><a data-copy="readHook" data-source="hooks" href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.husky/pre-commit">Read the hook →</a></article>
|
|
||||||
<article><span data-copy="commitLabel">COMMIT MESSAGE</span><h3 data-copy="commitTitle">Intent has a grammar.</h3><pre><code>pnpm exec commitlint --edit $1
|
|
||||||
|
|
||||||
feat: add interview timer
|
|
||||||
fix(api): scope session query</code></pre><a data-copy="readCommit" data-source="commit" href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/commitlint.config.cjs">Read commitlint config →</a></article>
|
|
||||||
<article><span data-copy="reviewLabel">INDEPENDENT REVIEW</span><h3 data-copy="reviewTitle">A second reader checks intent.</h3><pre><code>.pr-review.json
|
|
||||||
├── focus
|
|
||||||
├── exclude_paths
|
|
||||||
├── languages
|
|
||||||
└── instructions</code></pre><a data-copy="readReview" data-source="review" href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.pr-review.json">Read review policy →</a></article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="copy-lab">
|
|
||||||
<div><span data-copy="copyLabel">COPY / ADAPT</span><h2 data-copy="copyTitle">Ask your agent to map the enforcement stack.</h2><p data-copy="copyText">Use this in the interview repository or adapt the path names to another project.</p></div>
|
|
||||||
<article><button data-copy-prompt><span data-copy="copyButton">COPY PROMPT</span><b>↗</b></button><pre><code id="explore-prompt"></code></pre><p id="copy-status" role="status" aria-live="polite"></p></article>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="deeper">
|
|
||||||
<div><span data-copy="deeperLabel">GO DEEPER</span><strong data-copy="deeperTitle">Read the implementation, not just this summary.</strong></div>
|
|
||||||
<nav>
|
|
||||||
<a href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/AGENTS.md"><span>01</span><b data-copy="deepContext">Repository context</b><small>AGENTS.md</small></a>
|
|
||||||
<a href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.agents/skills"><span>02</span><b data-copy="deepSkills">Skill catalog</b><small>.agents/skills/</small></a>
|
|
||||||
<a href="../skills/"><span>03</span><b>Design skills</b><small>skills/</small></a>
|
|
||||||
<a href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.claude/agents"><span>03</span><b data-copy="deepAgents">Specialist agents</b><small>.claude/agents/</small></a>
|
|
||||||
<a href="https://git.marcospaulo.dev.br/netcracker/interview/src/branch/main/.lintstagedrc.cjs"><span>04</span><b data-copy="deepPolicy">Staged-file policy</b><small>.lintstagedrc.cjs</small></a>
|
|
||||||
</nav>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<script src="app.js" defer></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because one or more lines are too long
+79
-28
@@ -1,39 +1,90 @@
|
|||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
|
||||||
|
const base = '/ai-for-dummies/';
|
||||||
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
||||||
|
// The legacy hand-written pages are gone; the built output is the site now.
|
||||||
const pages = [
|
const pages = [
|
||||||
'index.html',
|
'dist/index.html',
|
||||||
'full-guide/index.html',
|
'dist/full-guide/index.html',
|
||||||
'summary/index.html',
|
'dist/summary/index.html',
|
||||||
'models/index.html',
|
'dist/models/index.html',
|
||||||
'agents/index.html',
|
'dist/agents/index.html',
|
||||||
'skills/index.html',
|
'dist/skills/index.html',
|
||||||
'rules/index.html',
|
'dist/rules/index.html',
|
||||||
'skills-review/index.html',
|
'dist/skills-review/index.html',
|
||||||
'hands-on/starter/index.html',
|
'dist/hands-on/starter/index.html',
|
||||||
'hands-on/rules/index.html',
|
'dist/hands-on/rules/index.html',
|
||||||
];
|
];
|
||||||
|
const walk = (path) =>
|
||||||
|
readdirSync(path).flatMap((name) => {
|
||||||
|
const child = join(path, name);
|
||||||
|
return statSync(child).isDirectory() ? walk(child) : [child];
|
||||||
|
});
|
||||||
|
const definitions = (css) => new Set([...css.matchAll(/(--[\w-]+)\s*:/g)].map(([, name]) => name));
|
||||||
|
const uses = (css) => new Set([...css.matchAll(/var\((--[\w-]+)/g)].map(([, name]) => name));
|
||||||
|
const builtCss = walk('dist/_astro')
|
||||||
|
.filter((path) => path.endsWith('.css'))
|
||||||
|
.map((path) => readFileSync(path, 'utf8'))
|
||||||
|
.join('\n');
|
||||||
|
// A declaration nothing references cannot style anything. Dropping the dead ones
|
||||||
|
// before the value scan is what stops a lost value from being "restored" with an
|
||||||
|
// unreferenced token that exists only to put the string back into the sheet.
|
||||||
|
const referenced = uses(builtCss);
|
||||||
|
const liveCss = builtCss.replace(/(--[\w-]+)\s*:[^;}]*[;}]/g, (declaration, name) =>
|
||||||
|
referenced.has(name) ? declaration : '',
|
||||||
|
);
|
||||||
|
const colorPattern = /#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\([^)]*\)/g;
|
||||||
|
const sizePattern = /\b\d+(?:\.\d+)?(?:px|rem|em|vw|vh)\b/g;
|
||||||
|
// Only the media prelude, so a breakpoint has to be an actual query condition.
|
||||||
|
const mediaPreludes = (builtCss.match(/@media[^{]+/g) || []).join('\n');
|
||||||
|
const builtValues = {
|
||||||
|
colors: new Set(liveCss.match(colorPattern) || []),
|
||||||
|
sizes: new Set(liveCss.match(sizePattern) || []),
|
||||||
|
breakpoints: new Set(mediaPreludes.match(sizePattern) || []),
|
||||||
|
};
|
||||||
|
if (!builtCss) throw new Error('Astro build emitted no CSS assets to audit');
|
||||||
|
// Captured from origin/main after its Astro build. This catches a value that is
|
||||||
|
// silently changed to a nearby token: source token checks cannot see that drift.
|
||||||
|
const baselineValues = JSON.parse(read('.agents/snapshots/built-css-values.json'));
|
||||||
|
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
const html = read(page);
|
const html = read(page);
|
||||||
if (!html.includes('name="viewport"')) throw new Error(`${page} lacks a viewport declaration`);
|
if (!html.includes('name="viewport"')) throw new Error(`${page} lacks a viewport declaration`);
|
||||||
if (html.match(/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i))
|
if (html.match(/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i))
|
||||||
throw new Error(`${page} has an external runtime dependency`);
|
throw new Error(`${page} has an external runtime dependency`);
|
||||||
}
|
}
|
||||||
const shared = read('chapters.css');
|
for (const file of walk('dist').filter((path) => path.endsWith('.css'))) {
|
||||||
const skills = read('skills/styles.css');
|
const css = readFileSync(file, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
|
||||||
const guide = read('full-guide/index.html');
|
if (/@import|src:\s*url\(['"]?https?:|url\(['"]?https?:/i.test(css))
|
||||||
const guideAudit = read('full-guide/audit.css');
|
throw new Error(`${file} has an external CSS dependency`);
|
||||||
for (const token of ['@media(max-width:800px)', '@media(max-width:520px)'])
|
}
|
||||||
if (!shared.includes(token)) throw new Error(`shared chapter CSS lacks ${token}`);
|
for (const [kind, values] of Object.entries(baselineValues)) {
|
||||||
for (const token of [
|
const missing = values.filter((value) => !builtValues[kind].has(value));
|
||||||
'minmax(0,1.3fr)',
|
if (missing.length) throw new Error(`built CSS lost ${kind}: ${missing.join(', ')}`);
|
||||||
'overflow-wrap:anywhere',
|
}
|
||||||
'@media(max-width:800px)',
|
// The hands-on labs still ship their own stylesheet rather than going through
|
||||||
'prefers-reduced-motion',
|
// Astro. Resolve every var() from the stylesheets each page actually links, so
|
||||||
])
|
// Astro's tokens cannot mask a broken standalone page.
|
||||||
if (!skills.includes(token)) throw new Error(`skills package CSS lacks ${token}`);
|
// Astro emits base-absolute hrefs (`/ai-for-dummies/_astro/x.css`); those are
|
||||||
if (!guide.includes('href="audit.css"'))
|
// rooted at `dist/`, not at the page's own directory.
|
||||||
throw new Error('full guide does not load its responsive audit overrides');
|
const resolveHref = (page, href) =>
|
||||||
for (const token of ['.handoff table', '.route-table button', 'overflow-wrap:anywhere'])
|
href.startsWith(base) ? join('dist', href.slice(base.length)) : join(dirname(page), href);
|
||||||
if (!guideAudit.includes(token)) throw new Error(`full guide audit CSS lacks ${token}`);
|
for (const page of pages) {
|
||||||
|
const html = read(page);
|
||||||
|
const linked = [...html.matchAll(/<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"/gi)].map(
|
||||||
|
([, href]) => href,
|
||||||
|
);
|
||||||
|
const styles = linked
|
||||||
|
.filter((href) => !/^https?:/i.test(href))
|
||||||
|
.map((href) => read(resolveHref(page, href)));
|
||||||
|
const defined = new Set(styles.flatMap((css) => [...definitions(css)]));
|
||||||
|
const unresolved = new Set(
|
||||||
|
styles.flatMap((css) =>
|
||||||
|
[...uses(css)].filter((name) => name !== '--score' && !defined.has(name)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (unresolved.size)
|
||||||
|
throw new Error(`${page} has unresolved CSS variables: ${[...unresolved].join(', ')}`);
|
||||||
|
}
|
||||||
console.log('responsive UI audit passed');
|
console.log('responsive UI audit passed');
|
||||||
|
|||||||
+427
-385
@@ -1,397 +1,439 @@
|
|||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync, readdirSync } from 'node:fs';
|
||||||
|
|
||||||
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
||||||
const landingHtml = read('index.html');
|
const built = (path) => read(`dist/${path}`);
|
||||||
const html = read('full-guide/index.html');
|
const rendered = (value) =>
|
||||||
const js = read('app.js');
|
value
|
||||||
const refs = read('docs/references/README.md');
|
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
||||||
const additional = read('docs/references/additional-reading.md');
|
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
||||||
const starterHtml = read('hands-on/starter/index.html');
|
.replace(/<!--[\s\S]*?-->/g, '')
|
||||||
const starterJs = read('hands-on/starter/app.js');
|
.replace(/<[^>]+>/g, ' ')
|
||||||
const skillSources = read('docs/references/skill-sources.md');
|
.replace(/&/g, '&')
|
||||||
const modelRouting = read('docs/references/model-routing.md');
|
.replace(
|
||||||
const rulesHtml = read('rules/index.html');
|
/&(lt|gt|quot|nbsp);/g,
|
||||||
const rulesJs = read('rules/app.js');
|
(_, entity) => ({ lt: '<', gt: '>', quot: '"', nbsp: ' ' })[entity],
|
||||||
const rulesCss = read('rules/styles.css');
|
)
|
||||||
const reviewHtml = read('skills-review/index.html');
|
.replace(/�?39;/g, "'")
|
||||||
const reviewJs = read('skills-review/app.js');
|
.replace(/\s+/g, ' ')
|
||||||
const reviewFiles = read('skills-review/files.js');
|
.trim();
|
||||||
const reviewLensCss = read('skills-review/change-lens.css');
|
const normalize = (value) => value.replace(/\s+/g, ' ').trim();
|
||||||
const chaptersCss = read('chapters.css');
|
|
||||||
const summaryHtml = read('summary/index.html');
|
const html = {
|
||||||
const modelsHtml = read('models/index.html');
|
landing: built('index.html'),
|
||||||
const agentsHtml = read('agents/index.html');
|
guide: built('full-guide/index.html'),
|
||||||
const skillsHtml = read('skills/index.html');
|
summary: built('summary/index.html'),
|
||||||
const reviewCatalog = read('skills-review/catalog.js');
|
models: built('models/index.html'),
|
||||||
const reviewVoteJs = read('skills-review/vote.js');
|
agents: built('agents/index.html'),
|
||||||
const voteService = read('vote-service/main.go');
|
skills: built('skills/index.html'),
|
||||||
for (const url of [
|
rules: built('rules/index.html'),
|
||||||
'https://code.claude.com/docs/en/sub-agents',
|
review: built('skills-review/index.html'),
|
||||||
'https://code.claude.com/docs/en/skills',
|
starter: built('hands-on/starter/index.html'),
|
||||||
'https://code.claude.com/docs/en/worktrees',
|
labRules: built('hands-on/rules/index.html'),
|
||||||
'https://git-scm.com/docs/git-worktree.html',
|
};
|
||||||
'https://developers.openai.com/codex/skills',
|
const snapshot = (path) => normalize(read(`.agents/snapshots/${path}`));
|
||||||
])
|
const reviewAssetSrc = html.review.match(/src="([^"]+\.[a-zA-Z0-9_-]{8,}\.js)"/)?.[1];
|
||||||
if (!refs.includes(url)) throw new Error(`missing reference ${url}`);
|
const reviewAsset = reviewAssetSrc ? read(`dist/_astro/${reviewAssetSrc.split('/').at(-1)}`) : '';
|
||||||
console.log('content verification passed');
|
const builtCss = readdirSync(new URL('../dist/_astro/', import.meta.url))
|
||||||
for (const token of [
|
.filter((file) => file.endsWith('.css'))
|
||||||
'data-phase="plan"',
|
.map((file) => read(`dist/_astro/${file}`))
|
||||||
'data-phase="build"',
|
.join('\n');
|
||||||
'data-phase="review"',
|
const source = {
|
||||||
'data-tree="main"',
|
references: read('docs/references/README.md'),
|
||||||
'data-tree="ui"',
|
additionalReading: read('docs/references/additional-reading.md'),
|
||||||
'data-worker="ui"',
|
skillSources: read('src/content/skillSources/ponytail.json'),
|
||||||
'data-route="plan"',
|
providers: [
|
||||||
'data-model-provider="openai"',
|
read('src/content/providers/openai.json'),
|
||||||
'data-model-provider="claude"',
|
read('src/content/providers/claude.json'),
|
||||||
'data-model-provider="gemini"',
|
read('src/content/providers/gemini.json'),
|
||||||
'data-effort="low"',
|
].join('\n'),
|
||||||
'data-effort="medium"',
|
starter: read('public/hands-on/starter/app.js'),
|
||||||
'data-effort="high"',
|
voteService: read('vote-service/main.go'),
|
||||||
'data-skill-file="skill"',
|
ndoReview: read('src/content/reviews/ndo-repro.md'),
|
||||||
'data-skill-step="observe"',
|
};
|
||||||
'data-skill-step="validate"',
|
|
||||||
'data-common-skill="ponytail"',
|
// Rendered prose catches a deleted paragraph that string-token checks miss.
|
||||||
'data-common-skill="caveman"',
|
if (rendered(html.landing) !== snapshot('index.txt'))
|
||||||
'data-common-skill="unlazy"',
|
throw new Error('landing rendered-text snapshot changed');
|
||||||
'id="hands-on"',
|
if (rendered(html.guide) !== snapshot('full-guide.txt'))
|
||||||
'data-copy-target="prompt-install-skills"',
|
throw new Error('full-guide rendered-text snapshot changed');
|
||||||
'data-copy-target="prompt-basic"',
|
if (rendered(html.summary) !== snapshot('summary.txt'))
|
||||||
'data-copy-target="prompt-skills"',
|
throw new Error('summary rendered-text snapshot changed');
|
||||||
'hands-on/starter/',
|
if (rendered(html.models) !== snapshot('models.txt'))
|
||||||
'additional-reading.md',
|
throw new Error('models rendered-text snapshot changed');
|
||||||
'role="tablist"',
|
if (rendered(html.agents) !== snapshot('agents.txt'))
|
||||||
'<table',
|
throw new Error('agents rendered-text snapshot changed');
|
||||||
])
|
if (rendered(html.skills) !== snapshot('skills.txt'))
|
||||||
if (!html.includes(token)) throw new Error(`missing content ${token}`);
|
throw new Error('skills rendered-text snapshot changed');
|
||||||
for (const token of [
|
if (rendered(html.rules) !== snapshot('rules.txt'))
|
||||||
'const phases',
|
throw new Error('rules rendered-text snapshot changed');
|
||||||
'const handsOnPrompts',
|
if (rendered(html.review) !== snapshot('skills-review.txt'))
|
||||||
'const modelGuide',
|
throw new Error('skills-review rendered-text snapshot changed');
|
||||||
'const skillSources',
|
if (rendered(html.starter) !== snapshot('hands-on/starter.txt'))
|
||||||
'const skillInstallPrompts',
|
throw new Error('starter rendered-text snapshot changed');
|
||||||
'addEventListener',
|
if (rendered(html.labRules) !== snapshot('hands-on/rules.txt'))
|
||||||
"render('plan')",
|
throw new Error('rules lab rendered-text snapshot changed');
|
||||||
'renderTree',
|
|
||||||
'renderWorker',
|
// The 102 Portuguese strings were extracted verbatim from the legacy
|
||||||
'renderRoute',
|
// `app.js` `translations.pt` object at cutover, before that file was deleted.
|
||||||
'renderModelProvider',
|
// This is a legacy capture, not a snapshot of the Astro build: it still asserts
|
||||||
'renderEffort',
|
// against an independent source, which is the whole point of the check.
|
||||||
'renderSkillFile',
|
const portuguese = JSON.parse(read('.agents/snapshots/full-guide-pt.json')).map(rendered);
|
||||||
'renderSkillWorkflow',
|
if (portuguese.length !== 102)
|
||||||
'renderCommonSkill',
|
throw new Error('full-guide Portuguese snapshot no longer has 102 translated strings');
|
||||||
'renderHandsOn',
|
// Without this, trimming the snapshot would make the check below pass vacuously.
|
||||||
'copyPrompt',
|
if (portuguese.some((value) => !value.trim()))
|
||||||
])
|
throw new Error('full-guide Portuguese snapshot has an empty entry');
|
||||||
if (!js.includes(token)) throw new Error(`missing interaction ${token}`);
|
if (!portuguese.every((value) => rendered(html.guide).includes(value)))
|
||||||
for (const token of ['id="task-list"', 'id="task-count"'])
|
throw new Error('built full-guide lost a Portuguese translation');
|
||||||
if (!starterHtml.includes(token)) throw new Error(`missing starter content ${token}`);
|
|
||||||
for (const token of ['const tasks', 'renderTasks()'])
|
const has = (page, tokens) => tokens.every((token) => page.includes(token));
|
||||||
if (!starterJs.includes(token)) throw new Error(`missing starter behavior ${token}`);
|
if (!has(html.guide, ['data-phase="plan"', 'data-phase="build"', 'data-phase="review"']))
|
||||||
for (const token of [
|
throw new Error('built full-guide lost its phase selector');
|
||||||
'medium.com',
|
if (!has(html.guide, ['data-tree="main"', 'data-tree="ui"', 'data-worker="ui"']))
|
||||||
'anthropic.com/engineering',
|
throw new Error('built full-guide lost its worker or worktree selector');
|
||||||
'openai.com/business',
|
|
||||||
'git-scm.com/docs/git-worktree',
|
|
||||||
])
|
|
||||||
if (!additional.includes(token)) throw new Error(`missing additional source ${token}`);
|
|
||||||
if ((additional.match(/^### \d+\./gm) || []).length < 5)
|
|
||||||
throw new Error('fewer than five additional readings');
|
|
||||||
for (const token of [
|
|
||||||
'e7b42dc2d384a702240dea4d52a7bf5530b821b6',
|
|
||||||
'6654f6b60cd9d5be8b54c6fafe44346dabeb3b76',
|
|
||||||
'53048666b05b4799081517d00e09e0a2dd688678',
|
|
||||||
])
|
|
||||||
if (!skillSources.includes(token) || !js.includes(token))
|
|
||||||
throw new Error(`missing pinned skill source ${token}`);
|
|
||||||
for (const token of [
|
|
||||||
'developers.openai.com/api/docs/guides/latest-model',
|
|
||||||
'docs.anthropic.com/en/docs/claude-code/model-config',
|
|
||||||
'ai.google.dev/gemini-api/docs/thinking',
|
|
||||||
])
|
|
||||||
if (!modelRouting.includes(token) || !js.includes(token))
|
|
||||||
throw new Error(`missing model source ${token}`);
|
|
||||||
console.log('interaction verification passed');
|
|
||||||
if (html.match(/<(script|link)[^>]+(src|href)="https?:[^"]+"/i))
|
|
||||||
throw new Error('external runtime dependency found');
|
|
||||||
console.log('standalone verification passed');
|
|
||||||
for (const token of [
|
|
||||||
'id="pipeline"',
|
|
||||||
'id="skills"',
|
|
||||||
'id="examples"',
|
|
||||||
'data-stage="context"',
|
|
||||||
'data-stage="cli"',
|
|
||||||
'data-stage="commit"',
|
|
||||||
'data-stage="review"',
|
|
||||||
'gate-discipline',
|
|
||||||
'parallel-agents',
|
|
||||||
'repo-db',
|
|
||||||
'tech-debt',
|
|
||||||
'skill-writer',
|
|
||||||
'scripts/check-ui-contract.mjs',
|
|
||||||
'.husky/pre-commit',
|
|
||||||
'.pr-review.json',
|
|
||||||
'.agents/skills',
|
|
||||||
'netcracker/interview',
|
|
||||||
])
|
|
||||||
if (!rulesHtml.includes(token)) throw new Error(`missing rules content ${token}`);
|
|
||||||
console.log('rules content verification passed');
|
|
||||||
for (const token of [
|
|
||||||
'const languageCopy',
|
|
||||||
'const stages',
|
|
||||||
'const skills',
|
|
||||||
'const prompts',
|
|
||||||
'renderStage',
|
|
||||||
'renderSkill',
|
|
||||||
'renderLanguage',
|
|
||||||
'copyPrompt',
|
|
||||||
'addEventListener',
|
|
||||||
])
|
|
||||||
if (!rulesJs.includes(token)) throw new Error(`missing rules interaction ${token}`);
|
|
||||||
for (const token of [
|
|
||||||
'data-lang="en"',
|
|
||||||
'data-lang="pt"',
|
|
||||||
'data-copy-prompt',
|
|
||||||
'aria-live="polite"',
|
|
||||||
'role="tablist"',
|
|
||||||
])
|
|
||||||
if (!rulesHtml.includes(token)) throw new Error(`missing rules control ${token}`);
|
|
||||||
console.log('rules interaction verification passed');
|
|
||||||
if (!html.includes('href="../rules/"'))
|
|
||||||
throw new Error('main presentation does not link to rules page');
|
|
||||||
if (!html.includes('href="../skills-review/"'))
|
|
||||||
throw new Error('main presentation does not link to skills review page');
|
|
||||||
for (const token of ['../summary/', '../models/', '../agents/', '../skills/', 'chapter-route'])
|
|
||||||
if (!html.includes(token)) throw new Error(`main presentation missing chapter route ${token}`);
|
|
||||||
if (rulesHtml.includes('script src="http') || rulesHtml.includes('rel="stylesheet" href="http'))
|
|
||||||
throw new Error('rules page has an external runtime dependency');
|
|
||||||
for (const token of [
|
|
||||||
'@media(min-width:2200px)',
|
|
||||||
'@media(max-width:900px)',
|
|
||||||
'@media(max-width:600px)',
|
|
||||||
'prefers-reduced-motion',
|
|
||||||
])
|
|
||||||
if (!rulesCss.includes(token)) throw new Error(`missing rules responsive contract ${token}`);
|
|
||||||
console.log('rules standalone verification passed');
|
|
||||||
for (const token of [
|
|
||||||
'id="catalog"',
|
|
||||||
'id="skill-filter"',
|
|
||||||
'id="skill-list"',
|
|
||||||
'id="detail"',
|
|
||||||
'Preview Markdown',
|
|
||||||
'styles.css?v=20260904-vote-widget',
|
|
||||||
'change-lens.css?v=20260904-vote-widget',
|
|
||||||
'app.js?v=20260904-vote-widget',
|
|
||||||
'?author=Name&skill=skill-id&view=improved',
|
|
||||||
'SKILLS_REVIEW_VOTE_API',
|
|
||||||
])
|
|
||||||
if (!reviewHtml.includes(token)) throw new Error(`missing review page content ${token}`);
|
|
||||||
for (const token of [
|
|
||||||
"from './catalog.js'",
|
|
||||||
"from './files.js'",
|
|
||||||
'function renderList',
|
|
||||||
'function renderDetail',
|
|
||||||
'selectSkill',
|
|
||||||
'packageSummary',
|
|
||||||
'markdownHeadings',
|
|
||||||
'markdownToc',
|
|
||||||
"document.addEventListener('keydown'",
|
|
||||||
'loadSelectedFile',
|
|
||||||
'schedulePackageSearch',
|
|
||||||
'fetchSource',
|
|
||||||
'packageSearchText',
|
|
||||||
'diffMarkup',
|
|
||||||
'diffRows',
|
|
||||||
'data-diff',
|
|
||||||
"searchParams.set('compare'",
|
|
||||||
'markdownMarkup',
|
|
||||||
'data-render',
|
|
||||||
'preview-markdown',
|
|
||||||
'Preview Markdown',
|
|
||||||
'View source',
|
|
||||||
'FILE PREVIEW',
|
|
||||||
"searchParams.set('render'",
|
|
||||||
'AUTHOR ·',
|
|
||||||
'SKILL ·',
|
|
||||||
'function selectFromUrl',
|
|
||||||
'function syncUrl',
|
|
||||||
'URLSearchParams',
|
|
||||||
'navigator.clipboard',
|
|
||||||
'document.execCommand',
|
|
||||||
'download',
|
|
||||||
'data-file',
|
|
||||||
"searchParams.set('file'",
|
|
||||||
])
|
|
||||||
if (!reviewJs.includes(token)) throw new Error(`missing review interaction ${token}`);
|
|
||||||
for (const token of [
|
|
||||||
'ndo-repro',
|
|
||||||
'gfiber-logging',
|
|
||||||
'confluence-page',
|
|
||||||
'diagram-plantuml',
|
|
||||||
'page-reviewer',
|
|
||||||
'unslop',
|
|
||||||
'spanish-naturalizer',
|
|
||||||
'draft-mr',
|
|
||||||
'semantic-diff-review',
|
|
||||||
'reference.md',
|
|
||||||
'files =',
|
|
||||||
])
|
|
||||||
if (!`${reviewFiles}\n${read('skills-review/submitted-files.js')}`.includes(token))
|
|
||||||
throw new Error(`missing review file manifest ${token}`);
|
|
||||||
if (
|
if (
|
||||||
(reviewCatalog.match(/id:'/g) || []).length +
|
!has(html.guide, [
|
||||||
(read('skills-review/submitted-catalog.js').match(/id:'/g) || []).length !==
|
'data-route="plan"',
|
||||||
24
|
'data-model-provider="openai"',
|
||||||
|
'data-model-provider="claude"',
|
||||||
|
'data-model-provider="gemini"',
|
||||||
|
])
|
||||||
)
|
)
|
||||||
throw new Error('review catalog does not cover all submissions');
|
throw new Error('built full-guide lost its model-routing controls');
|
||||||
|
if (!has(html.guide, ['data-effort="low"', 'data-effort="medium"', 'data-effort="high"']))
|
||||||
|
throw new Error('built full-guide lost its reasoning-effort controls');
|
||||||
if (
|
if (
|
||||||
!reviewCatalog.includes('hardcoded password') ||
|
!has(html.guide, [
|
||||||
!reviewCatalog.includes('safety-redacted') ||
|
'data-skill-file="skill"',
|
||||||
!reviewJs.includes('[REDACTED]') ||
|
'data-skill-step="observe"',
|
||||||
!reviewJs.includes('[REDACTED LOCAL USER]') ||
|
'data-skill-step="validate"',
|
||||||
!reviewJs.includes('[REDACTED USER]')
|
])
|
||||||
)
|
)
|
||||||
throw new Error('review catalog does not record secret safety handling');
|
throw new Error('built full-guide lost its skill anatomy controls');
|
||||||
console.log('skills review verification passed');
|
if (
|
||||||
for (const page of [summaryHtml, modelsHtml, agentsHtml, skillsHtml])
|
!has(html.guide, [
|
||||||
if (!page.includes('../chapters.css') || !page.includes('ROUTE MAP'))
|
'data-common-skill="ponytail"',
|
||||||
throw new Error('chapter page missing shared navigation');
|
'data-common-skill="caveman"',
|
||||||
for (const token of ['--ink', '@media(max-width:800px)', '@media(max-width:520px)'])
|
'data-common-skill="unlazy"',
|
||||||
if (!chaptersCss.includes(token)) throw new Error(`missing chapter responsive contract ${token}`);
|
])
|
||||||
console.log('chapter route verification passed');
|
)
|
||||||
for (const token of [
|
throw new Error('built full-guide lost its common-skill controls');
|
||||||
'The short route',
|
if (
|
||||||
'full-guide/',
|
!has(html.guide, [
|
||||||
'models/',
|
'id="hands-on"',
|
||||||
'agents/',
|
'data-copy-target="prompt-install-skills"',
|
||||||
'skills/',
|
'data-copy-target="prompt-basic"',
|
||||||
'rules/',
|
'data-copy-target="prompt-skills"',
|
||||||
'hands-on/starter/',
|
])
|
||||||
'skills-review/',
|
)
|
||||||
])
|
throw new Error('built full-guide lost its hands-on prompts');
|
||||||
if (!landingHtml.includes(token)) throw new Error(`landing page missing ${token}`);
|
if (!has(html.guide, ['hands-on/starter/', 'additional-reading.md', 'role="tablist"', '<table']))
|
||||||
if (landingHtml.includes('app.js'))
|
throw new Error('built full-guide lost its guide navigation or semantics');
|
||||||
throw new Error('landing page should remain a fast, static route map');
|
if (!html.guide.includes('<script type="module">'))
|
||||||
console.log('landing route verification passed');
|
throw new Error('built full-guide lost its interaction module');
|
||||||
for (const token of [
|
|
||||||
'data-package-file="skill"',
|
if (
|
||||||
'data-package-file="references"',
|
!has(html.rules, [
|
||||||
'data-package-file="scripts"',
|
'id="pipeline"',
|
||||||
'data-package-file="assets"',
|
'id="skills"',
|
||||||
'id="package-preview"',
|
'id="examples"',
|
||||||
'script src="app.js"',
|
'data-stage="context"',
|
||||||
])
|
'data-stage="cli"',
|
||||||
if (!skillsHtml.includes(token)) throw new Error(`skills anatomy missing ${token}`);
|
'data-stage="commit"',
|
||||||
const skillsApp = read('skills/app.js');
|
'data-stage="review"',
|
||||||
const skillsCss = read('skills/styles.css');
|
])
|
||||||
for (const token of [
|
)
|
||||||
'const packageFiles',
|
throw new Error('built rules page lost its enforcement pipeline');
|
||||||
'function renderPackage',
|
if (
|
||||||
'addEventListener',
|
!has(html.rules, [
|
||||||
"renderPackage('skill')",
|
'gate-discipline',
|
||||||
])
|
'parallel-agents',
|
||||||
if (!skillsApp.includes(token)) throw new Error(`skills anatomy interaction missing ${token}`);
|
'repo-db',
|
||||||
for (const token of [
|
'tech-debt',
|
||||||
'grid-template-columns:minmax(190px',
|
'skill-writer',
|
||||||
'overflow-wrap:anywhere',
|
'scripts/check-ui-contract.mjs',
|
||||||
'@media(max-width:800px)',
|
'.husky/pre-commit',
|
||||||
'prefers-reduced-motion',
|
'.pr-review.json',
|
||||||
])
|
'.agents/skills',
|
||||||
if (!skillsCss.includes(token))
|
'netcracker/interview',
|
||||||
throw new Error(`skills anatomy responsive contract missing ${token}`);
|
])
|
||||||
console.log('skills anatomy verification passed');
|
)
|
||||||
console.log('presentation verification passed');
|
throw new Error('built rules page lost its case-study content');
|
||||||
const legacyName = String.fromCharCode(80, 101, 100, 114, 111, 32, 65, 114, 97, 110, 104, 97);
|
if (
|
||||||
const legacyHandle = legacyName.toLowerCase().replace(' ', '.');
|
!has(html.rules, [
|
||||||
for (const path of [
|
'data-lang="en"',
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/SKILL.md',
|
'data-lang="pt"',
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/envs.tsv',
|
'data-copy-prompt',
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/lib/env.sh',
|
'aria-live="polite"',
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/ndo-api.sh',
|
'role="tablist"',
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/ndo-ship.sh',
|
])
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/reference/bom-Dockerfile_local.example',
|
)
|
||||||
'submitted-skills/Anonymous Operational Submission/skills/ndo-repro/reference/dockerfile-local.md',
|
throw new Error('built rules page lost its accessible controls');
|
||||||
'skill-reviews/improved/ndo-repro/SKILL.md',
|
if (!html.rules.includes('data-rules-copy') || !html.rules.includes('"pt"'))
|
||||||
]) {
|
throw new Error('built rules page lost its Portuguese interaction data');
|
||||||
const source = read(path);
|
|
||||||
if (
|
if (
|
||||||
new RegExp(
|
!has(html.review, [
|
||||||
`${legacyName}|${legacyHandle}|https?:\\/\\/|git\\.netcracker\\.com|artifactorycn|managed\\.netcracker\\.cloud`,
|
'id="catalog"',
|
||||||
'i',
|
'id="skill-filter"',
|
||||||
).test(source)
|
'id="skill-list"',
|
||||||
|
'id="detail"',
|
||||||
|
'Preview Markdown',
|
||||||
|
'?author=Name&skill=skill-id&view=improved',
|
||||||
|
'SKILLS_REVIEW_VOTE_API',
|
||||||
|
])
|
||||||
|
)
|
||||||
|
throw new Error('built review desk lost its catalog or deep-link contract');
|
||||||
|
if (
|
||||||
|
!has(html.review, [
|
||||||
|
'ndo-repro',
|
||||||
|
'gfiber-logging',
|
||||||
|
'confluence-page',
|
||||||
|
'diagram-plantuml',
|
||||||
|
'page-reviewer',
|
||||||
|
'unslop',
|
||||||
|
'spanish-naturalizer',
|
||||||
|
'draft-mr',
|
||||||
|
'semantic-diff-review',
|
||||||
|
])
|
||||||
|
)
|
||||||
|
throw new Error('built review desk lost submitted-skill coverage');
|
||||||
|
if (
|
||||||
|
!html.review.includes('Anonymous%20Operational%20Submission') ||
|
||||||
|
!has(reviewAsset, ['[REDACTED]', '[REDACTED LOCAL USER]', '[REDACTED USER]'])
|
||||||
|
)
|
||||||
|
throw new Error('built review desk lost operational-submission privacy redactions');
|
||||||
|
if (!reviewAssetSrc) throw new Error('built review desk does not load its hashed island asset');
|
||||||
|
|
||||||
|
if (
|
||||||
|
![html.summary, html.models, html.agents, html.skills].every((page) => page.includes('ROUTE MAP'))
|
||||||
|
)
|
||||||
|
throw new Error('built chapter pages lost shared navigation');
|
||||||
|
if (
|
||||||
|
!has(html.landing, [
|
||||||
|
'The short route',
|
||||||
|
'full-guide/',
|
||||||
|
'models/',
|
||||||
|
'agents/',
|
||||||
|
'skills/',
|
||||||
|
'rules/',
|
||||||
|
'hands-on/starter/',
|
||||||
|
'skills-review/',
|
||||||
|
])
|
||||||
|
)
|
||||||
|
throw new Error('built landing page lost a route-map destination');
|
||||||
|
if (/<script\b/i.test(html.landing))
|
||||||
|
throw new Error('built landing page is no longer a static route map');
|
||||||
|
if (
|
||||||
|
!has(html.skills, [
|
||||||
|
'data-skill-file="skill"',
|
||||||
|
'data-skill-file="references"',
|
||||||
|
'data-skill-file="scripts"',
|
||||||
|
'data-skill-file="assets"',
|
||||||
|
'id="package-preview"',
|
||||||
|
])
|
||||||
|
)
|
||||||
|
throw new Error('built skills page lost its package explorer');
|
||||||
|
if (!has(html.starter, ['id="task-list"', 'id="task-count"']))
|
||||||
|
throw new Error('starter lab lost its task-list contract');
|
||||||
|
|
||||||
|
const allPages = Object.entries(html);
|
||||||
|
if (!allPages.every(([, page]) => page.includes('name="viewport"')))
|
||||||
|
throw new Error('a built route lacks a viewport declaration');
|
||||||
|
if (allPages.some(([, page]) => /<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(page)))
|
||||||
|
throw new Error('a built route has an external runtime dependency');
|
||||||
|
if (
|
||||||
|
!read('package.json').includes(
|
||||||
|
'"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs"',
|
||||||
)
|
)
|
||||||
throw new Error(`operational submission privacy leak in ${path}`);
|
)
|
||||||
}
|
throw new Error('pnpm verify no longer runs audit-ui and check-tokens');
|
||||||
for (const source of [reviewCatalog, reviewFiles, reviewJs])
|
if (!read('.agents/scripts/gate.sh').includes('pnpm run verify'))
|
||||||
if (source.includes(legacyName) || source.includes(legacyName.replace(' ', '%20')))
|
throw new Error('the gate no longer runs the output contract');
|
||||||
throw new Error('operational submission identity remains in review desk source');
|
if (!read('docs/references/README.md').includes('https://developers.openai.com/codex/skills'))
|
||||||
|
throw new Error('primary reference list lost Codex skills');
|
||||||
|
if ((read('docs/references/additional-reading.md').match(/^### \d+\./gm) || []).length < 5)
|
||||||
|
throw new Error('additional reading list has fewer than five entries');
|
||||||
if (
|
if (
|
||||||
!reviewFiles.includes('Anonymous%20Operational%20Submission') ||
|
/Pedro\s+Aranha|pedro\.aranha|git\.netcracker\.com|artifactorycn|managed\.netcracker\.cloud/i.test(
|
||||||
!reviewJs.includes("entry.id === 'ndo-repro'")
|
read('src/content/reviews/ndo-repro.md'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('operational review content has a privacy leak');
|
||||||
|
|
||||||
|
// These retain the pre-Astro contract one fact at a time. Visible facts read
|
||||||
|
// from dist; source-only contracts remain at their authoritative boundary.
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'https://code.claude.com/docs/en/sub-agents',
|
||||||
|
'https://code.claude.com/docs/en/skills',
|
||||||
|
'https://code.claude.com/docs/en/worktrees',
|
||||||
|
'https://git-scm.com/docs/git-worktree.html',
|
||||||
|
'https://developers.openai.com/codex/skills',
|
||||||
|
].every((url) => source.references.includes(url))
|
||||||
|
)
|
||||||
|
throw new Error('missing reference contract');
|
||||||
|
if (!has(html.guide, ['data-phase="plan"', 'data-copy-target="prompt-basic"']))
|
||||||
|
throw new Error('missing full-guide content contract');
|
||||||
|
if (!has(html.guide, ['data-route="plan"', 'data-skill-file="skill"', '<script type="module">']))
|
||||||
|
throw new Error('missing full-guide interaction contract');
|
||||||
|
if (!has(html.starter, ['id="task-list"', 'id="task-count"']))
|
||||||
|
throw new Error('missing starter content contract');
|
||||||
|
if (!['const tasks', 'renderTasks()'].every((token) => source.starter.includes(token)))
|
||||||
|
throw new Error('missing starter behavior contract');
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'medium.com',
|
||||||
|
'anthropic.com/engineering',
|
||||||
|
'openai.com/business',
|
||||||
|
'git-scm.com/docs/git-worktree',
|
||||||
|
].every((url) => source.additionalReading.includes(url))
|
||||||
|
)
|
||||||
|
throw new Error('missing additional-reading source contract');
|
||||||
|
if ((source.additionalReading.match(/^### \d+\./gm) || []).length < 5)
|
||||||
|
throw new Error('additional-reading coverage fell below five entries');
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'e7b42dc2d384a702240dea4d52a7bf5530b821b6',
|
||||||
|
'6654f6b60cd9d5be8b54c6fafe44346dabeb3b76',
|
||||||
|
'53048666b05b4799081517d00e09e0a2dd688678',
|
||||||
|
].every((sha) => `${source.skillSources}\n${html.guide}`.includes(sha))
|
||||||
|
)
|
||||||
|
throw new Error('missing pinned skill-source contract');
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'developers.openai.com/api/docs/guides/latest-model',
|
||||||
|
'docs.anthropic.com/en/docs/claude-code/model-config',
|
||||||
|
'ai.google.dev/gemini-api/docs/thinking',
|
||||||
|
].every((url) => `${source.providers}\n${html.guide}`.includes(url))
|
||||||
|
)
|
||||||
|
throw new Error('missing model-source contract');
|
||||||
|
if (/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(html.guide))
|
||||||
|
throw new Error('full-guide has an external runtime dependency');
|
||||||
|
if (!has(html.rules, ['id="pipeline"', 'id="skills"', 'id="examples"']))
|
||||||
|
throw new Error('missing rules content contract');
|
||||||
|
if (!has(html.rules, ['data-stage="context"', 'data-copy-prompt', '<script>']))
|
||||||
|
throw new Error('missing rules interaction contract');
|
||||||
|
if (!has(html.rules, ['data-lang="en"', 'data-lang="pt"', 'aria-live="polite"']))
|
||||||
|
throw new Error('missing rules-control contract');
|
||||||
|
if (!html.guide.includes('href="/ai-for-dummies/rules/"'))
|
||||||
|
throw new Error('full-guide no longer links to rules');
|
||||||
|
if (!html.guide.includes('href="/ai-for-dummies/skills-review/"'))
|
||||||
|
throw new Error('full-guide no longer links to skills review');
|
||||||
|
if (!['summary/', 'models/', 'agents/', 'skills/'].every((route) => html.guide.includes(route)))
|
||||||
|
throw new Error('full-guide is missing a chapter route');
|
||||||
|
if (/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(html.rules))
|
||||||
|
throw new Error('rules page has an external runtime dependency');
|
||||||
|
if (
|
||||||
|
!['@media(max-width:800px)', '@media(min-width:2200px)', 'prefers-reduced-motion'].every(
|
||||||
|
(token) => builtCss.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('missing rules responsive contract');
|
||||||
|
if (!has(html.review, ['id="catalog"', 'id="skill-filter"', 'id="skill-list"', 'id="detail"']))
|
||||||
|
throw new Error('missing review page-content contract');
|
||||||
|
if (
|
||||||
|
!['data-render', 'preview-markdown', 'data-file', 'URLSearchParams', 'navigator.clipboard'].every(
|
||||||
|
(token) => reviewAsset.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('missing review interaction contract');
|
||||||
|
if (
|
||||||
|
!['reference.md', 'ndo-repro', 'semantic-diff-review'].every((token) =>
|
||||||
|
html.review.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('missing review file-manifest contract');
|
||||||
|
// The desk renders its cards from an inline JSON payload, so `data-skill-id`
|
||||||
|
// only exists after the island runs. Count the entries in the payload instead.
|
||||||
|
if ((html.review.match(/\\"id\\":/g) || []).length !== 24)
|
||||||
|
throw new Error('review catalog no longer covers all submissions');
|
||||||
|
if (!source.ndoReview.includes('safety-redacted') || !reviewAsset.includes('[REDACTED]'))
|
||||||
|
throw new Error('review catalog lost its secret-safety contract');
|
||||||
|
if (
|
||||||
|
![html.summary, html.models, html.agents, html.skills].every((page) => page.includes('ROUTE MAP'))
|
||||||
|
)
|
||||||
|
throw new Error('chapter page missing shared navigation');
|
||||||
|
if (
|
||||||
|
!['@media(max-width:800px)', '@media(max-width:520px)', '--ink'].every((token) =>
|
||||||
|
builtCss.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('missing chapter responsive contract');
|
||||||
|
if (!has(html.landing, ['The short route', 'full-guide/', 'skills-review/']))
|
||||||
|
throw new Error('landing page missing a route-map destination');
|
||||||
|
if (/<script\b/i.test(html.landing))
|
||||||
|
throw new Error('landing page is no longer a static route map');
|
||||||
|
if (
|
||||||
|
!has(html.skills, [
|
||||||
|
'data-skill-file="skill"',
|
||||||
|
'data-skill-file="references"',
|
||||||
|
'id="package-preview"',
|
||||||
|
])
|
||||||
|
)
|
||||||
|
throw new Error('skills anatomy missing package contract');
|
||||||
|
if (!html.skills.includes('<script>'))
|
||||||
|
throw new Error('skills anatomy lost its interaction module');
|
||||||
|
if (
|
||||||
|
!['overflow-wrap:anywhere', '@media(max-width:800px)', 'prefers-reduced-motion'].every((token) =>
|
||||||
|
builtCss.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('skills anatomy lost responsive contract');
|
||||||
|
if (
|
||||||
|
/Pedro\s+Aranha|pedro\.aranha|git\.netcracker\.com|artifactorycn|managed\.netcracker\.cloud/i.test(
|
||||||
|
source.ndoReview,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('operational submission privacy leak');
|
||||||
|
if (html.review.includes('Pedro%20Aranha') || html.review.includes('pedro.aranha'))
|
||||||
|
throw new Error('operational submission identity remains in review output');
|
||||||
|
if (
|
||||||
|
!html.review.includes('Anonymous%20Operational%20Submission') ||
|
||||||
|
!reviewAsset.includes('[REDACTED LOCAL USER]')
|
||||||
)
|
)
|
||||||
throw new Error('operational submission redaction contract missing');
|
throw new Error('operational submission redaction contract missing');
|
||||||
console.log('review privacy verification passed');
|
if (!['dataset.preview', 'dataset.file'].every((token) => reviewAsset.includes(token)))
|
||||||
for (const token of [
|
throw new Error('review file-mode contract missing');
|
||||||
'currentContent',
|
if (!reviewAsset.includes('preview-markdown'))
|
||||||
'unchangedDraft',
|
throw new Error('review file selection no longer preserves preview mode');
|
||||||
'state.preview = button.dataset.preview',
|
if (!['data-lens', 'CHANGE LENS', 'What changed'].every((token) => reviewAsset.includes(token)))
|
||||||
'state.file = available.find',
|
throw new Error('review change-lens contract missing');
|
||||||
'loadSelectedFile',
|
|
||||||
])
|
|
||||||
if (!reviewJs.includes(token)) throw new Error(`review file-mode contract missing ${token}`);
|
|
||||||
if (reviewJs.includes("state.preview = 'original'; syncUrl(); renderDetail(); loadSelectedFile();"))
|
|
||||||
throw new Error('file selection still resets improved mode');
|
|
||||||
console.log('review file-mode verification passed');
|
|
||||||
for (const token of [
|
|
||||||
'const changeRows',
|
|
||||||
'function lensMarkup',
|
|
||||||
'data-lens',
|
|
||||||
'CHANGE LENS',
|
|
||||||
'What changed — and why.',
|
|
||||||
])
|
|
||||||
if (!reviewJs.includes(token)) throw new Error(`review change-lens contract missing ${token}`);
|
|
||||||
for (const token of [
|
|
||||||
'.change-lens',
|
|
||||||
'.change-rows',
|
|
||||||
'.skill-diff',
|
|
||||||
'.diff-lines',
|
|
||||||
'@media(max-width:620px)',
|
|
||||||
'prefers-reduced-motion',
|
|
||||||
])
|
|
||||||
if (!reviewLensCss.includes(token)) throw new Error(`review change-lens CSS missing ${token}`);
|
|
||||||
console.log('review change-lens verification passed');
|
|
||||||
for (const token of [
|
|
||||||
'.markdown-preview',
|
|
||||||
'max-height:540px',
|
|
||||||
'.markdown-table-wrap',
|
|
||||||
'.markdown-frontmatter',
|
|
||||||
'.markdown-toc',
|
|
||||||
'.preview-title',
|
|
||||||
'.preview-markdown',
|
|
||||||
'grid-template-columns:minmax(0,1fr)',
|
|
||||||
'height:120px',
|
|
||||||
'-webkit-line-clamp:2',
|
|
||||||
])
|
|
||||||
if (!read('skills-review/styles.css').includes(token))
|
|
||||||
throw new Error(`review markdown preview contract missing ${token}`);
|
|
||||||
console.log('review markdown preview verification passed');
|
|
||||||
for (const token of ["from './vote.js'", "renderVoteWidget($('#vote-widget'"])
|
|
||||||
if (!reviewJs.includes(token)) throw new Error(`review vote widget wiring missing ${token}`);
|
|
||||||
for (const token of [
|
|
||||||
'id="vote-widget"',
|
|
||||||
'function renderVoteWidget',
|
|
||||||
'X-Voter-Id',
|
|
||||||
'/api/votes',
|
|
||||||
'Voting is offline',
|
|
||||||
])
|
|
||||||
if (!reviewVoteJs.includes(token) && !reviewJs.includes(token))
|
|
||||||
throw new Error(`review vote widget contract missing ${token}`);
|
|
||||||
for (const token of ['.vote-widget', '.vote-buttons', '[aria-pressed="true"]'])
|
|
||||||
if (!read('skills-review/styles.css').includes(token))
|
|
||||||
throw new Error(`review vote widget CSS missing ${token}`);
|
|
||||||
if (
|
if (
|
||||||
!voteService.includes('X-Forwarded-For') ||
|
!['.change-lens', '.change-rows', '.skill-diff', '@media(max-width:620px)'].every((token) =>
|
||||||
(!voteService.includes('one active vote per skill') &&
|
builtCss.includes(token),
|
||||||
!voteService.includes('at most one active vote'))
|
)
|
||||||
|
)
|
||||||
|
throw new Error('review change-lens CSS contract missing');
|
||||||
|
if (
|
||||||
|
!['.markdown-preview', 'max-height:540px', '.markdown-table-wrap', '.markdown-toc'].every(
|
||||||
|
(token) => builtCss.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('review markdown-preview contract missing');
|
||||||
|
if (
|
||||||
|
!html.review.includes(
|
||||||
|
'src="/ai-for-dummies/_astro/skills-review.astro_astro_type_script_index_0_lang.',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('review vote-widget island is not wired to a hashed asset');
|
||||||
|
if (
|
||||||
|
!['id="vote-widget"', 'X-Voter-Id', '/api/votes', 'Voting is offline'].every((token) =>
|
||||||
|
`${html.review}\n${reviewAsset}`.includes(token),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error('review vote-widget contract missing');
|
||||||
|
// The CSS minifier drops the attribute value's quotes, so the built sheet
|
||||||
|
// carries `[aria-pressed=true]`. Match either form.
|
||||||
|
if (
|
||||||
|
!['.vote-widget', '.vote-buttons'].every((token) => builtCss.includes(token)) ||
|
||||||
|
!/\[aria-pressed=["']?true["']?\]/.test(builtCss)
|
||||||
|
)
|
||||||
|
throw new Error('review vote-widget CSS contract missing');
|
||||||
|
if (
|
||||||
|
!source.voteService.includes('X-Forwarded-For') ||
|
||||||
|
!source.voteService.includes('one active vote per skill')
|
||||||
)
|
)
|
||||||
throw new Error('vote-service missing IP-based one-vote-per-source contract');
|
throw new Error('vote-service missing IP-based one-vote-per-source contract');
|
||||||
console.log('review vote widget verification passed');
|
|
||||||
|
console.log('built output verification passed');
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="description" content="Friendly reviews and improved drafts for submitted Agent Skills." />
|
|
||||||
<title>Submitted Skills — Review Desk</title>
|
|
||||||
<!-- Bump all review asset versions together when this interface changes. -->
|
|
||||||
<link rel="stylesheet" href="styles.css?v=20260904-vote-widget" />
|
|
||||||
<link rel="stylesheet" href="change-lens.css?v=20260904-vote-widget" />
|
|
||||||
<!-- Vote API origin: set after vote-service is deployed (see /vote-service). Empty = widget shows "voting offline". -->
|
|
||||||
<script>window.SKILLS_REVIEW_VOTE_API = 'https://ai-for-dummies-vote.marcospaulo.dev.br';</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<header class="topbar"><a href="../" class="back">← field guide</a><span>SUBMITTED SKILLS / REVIEW DESK</span><a href="#catalog" id="submission-count">submissions</a></header>
|
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">A friendly path from draft to dependable</p>
|
|
||||||
<h1>Every skill deserves<br><em>a clear job.</em></h1>
|
|
||||||
<p>Read the original, understand what already works, and compare a safer, leaner draft. Nothing here overwrites a submission; revisions live in their own review output.</p>
|
|
||||||
</section>
|
|
||||||
<section class="principles" aria-label="Review principles">
|
|
||||||
<article><b>01</b><strong>Discoverable</strong><span>A precise description tells an agent when to load the skill.</span></article>
|
|
||||||
<article><b>02</b><strong>Useful in context</strong><span>Core workflow stays short; conditional detail loads only when needed.</span></article>
|
|
||||||
<article><b>03</b><strong>Safe by design</strong><span>Commands, secrets, and shared systems have explicit boundaries.</span></article>
|
|
||||||
<article><b>04</b><strong>Proven in use</strong><span>Real prompts and observable checks turn a draft into a reliable tool.</span></article>
|
|
||||||
</section>
|
|
||||||
<section class="method">
|
|
||||||
<div><p class="eyebrow">How to use this desk</p><h2>Compare.<br><em>Then choose.</em></h2></div>
|
|
||||||
<ol><li>Select a submission, or open an author URL.</li><li>Read the gentle review before judging the draft.</li><li>Choose <strong>Preview Markdown</strong> in the file toolbar to render either version.</li><li>Copy or download the version you want, then vote for the draft you would ship.</li></ol>
|
|
||||||
</section>
|
|
||||||
<section class="catalog" id="catalog">
|
|
||||||
<aside><p class="eyebrow">The catalog</p><label for="skill-filter">Find a skill</label><input id="skill-filter" type="search" placeholder="author, skill, topic" autocomplete="off"><p class="count" id="count"></p><div id="skill-list" role="listbox" aria-label="Submitted skills"></div></aside>
|
|
||||||
<article class="detail" id="detail" aria-live="polite"></article>
|
|
||||||
</section>
|
|
||||||
<section class="research">
|
|
||||||
<p class="eyebrow">Why these reviews look this way</p>
|
|
||||||
<p>The recommendations follow the open Agent Skills format: valid frontmatter for discovery, progressive disclosure for context economy, deterministic scripts for fragile repeated mechanics, and behavioral evaluation rather than a checklist of pretty headings.</p>
|
|
||||||
<div><a href="https://agentskills.io/specification" target="_blank" rel="noreferrer">Format specification ↗</a><a href="https://agentskills.io/skill-creation/best-practices" target="_blank" rel="noreferrer">Writing practices ↗</a><a href="https://agentskills.io/skill-creation/evaluating-skills" target="_blank" rel="noreferrer">Evaluation loop ↗</a><a href="https://agentskills.io/skill-creation/using-scripts" target="_blank" rel="noreferrer">Scripts guide ↗</a></div>
|
|
||||||
</section>
|
|
||||||
<footer>Share an author with <code>?author=Name</code>, or one review with <code>?author=Name&skill=skill-id&view=improved</code>. To add a submission later: drop a package under <code>submitted-skills/</code>, add an entry under <code>src/content/reviews/{new-id}.md</code> (and mirror it into <code>skills-review/catalog.js</code> which the desk still reads), then run <code>node scripts/build-skill-review.mjs</code>. Votes call a separate service — see <code>vote-service/</code> — one per visitor, tracked by network source.</footer>
|
|
||||||
</main>
|
|
||||||
<script type="module" src="app.js?v=20260904-vote-widget"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
const packageFiles = {
|
|
||||||
skill: { label: 'SKILL.md', title: 'The operating contract', body: 'The one file that should always be loaded. Define the exact trigger, the ordered workflow, safety limits, and the evidence the agent returns.', code: '---\nname: review-ui\ndescription: Review a changed UI for focus, reflow, and motion.\n---\n\n1. Inspect the changed interaction.\n2. Run the UI checks.\n3. Return findings with evidence.' },
|
|
||||||
references: { label: 'references/', title: 'Facts, only when needed', body: 'Keep conditional detail out of the main instruction. A dialog pattern, framework caveat, or accessibility checklist belongs here when it is not needed for every review.', code: 'references/\n└── accessibility.md\n ├── keyboard interaction patterns\n └── focus and reflow checklist' },
|
|
||||||
scripts: { label: 'scripts/', title: 'Mechanics that should not depend on memory', body: 'Turn deterministic checks into runnable tools. The agent still judges the result, but it should not have to recreate a viewport test or filename rule by hand.', code: 'scripts/\n└── check-reflow.mjs\n └── checks 320px, 1280px, and 4K widths' },
|
|
||||||
assets: { label: 'assets/', title: 'Starting material, not hidden instructions', body: 'Use assets for templates and examples a person or agent can copy. Keep them clearly named so package readers can choose the right starting point.', code: 'assets/\n├── review-report.md\n└── focus-test-fixture.html' }
|
|
||||||
};
|
|
||||||
|
|
||||||
const preview = document.querySelector('#package-preview');
|
|
||||||
const buttons = [...document.querySelectorAll('[data-package-file]')];
|
|
||||||
function renderPackage(file) {
|
|
||||||
const item = packageFiles[file];
|
|
||||||
preview.classList.remove('is-swapping');
|
|
||||||
void preview.offsetWidth;
|
|
||||||
preview.classList.add('is-swapping');
|
|
||||||
preview.innerHTML = `<span>SELECTED / ${item.label}</span><h3>${item.title}</h3><p>${item.body}</p><pre><code>${item.code}</code></pre>`;
|
|
||||||
buttons.forEach((button) => {
|
|
||||||
const selected = button.dataset.packageFile === file;
|
|
||||||
button.classList.toggle('active', selected);
|
|
||||||
button.setAttribute('aria-selected', String(selected));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
buttons.forEach((button) => button.addEventListener('click', () => renderPackage(button.dataset.packageFile)));
|
|
||||||
renderPackage('skill');
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
||||||
<meta name="description" content="A practical guide to creating compact, reusable AI coding skills.">
|
|
||||||
<title>AI For Dummies — Skills</title>
|
|
||||||
<link rel="stylesheet" href="../chapters.css">
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<header class="top"><a href="../summary/">← ROUTE MAP</a><span>03 / SKILLS</span><a href="../skills-review/">review desk ↗</a></header>
|
|
||||||
<section class="hero"><p class="eyebrow">Reusable judgment</p><h1>Teach the<br><em>decision.</em></h1><p>A skill changes behavior. Keep the trigger precise, put the workflow in <code>SKILL.md</code>, and move conditional facts, scripts, and examples into focused files.</p></section>
|
|
||||||
<section class="pipeline package-anatomy">
|
|
||||||
<div><p class="eyebrow">Package anatomy</p><h2>One job.<br>More than<br>one <em>file.</em></h2><p class="package-hint">Choose a file to see why it belongs in the package.</p></div>
|
|
||||||
<div class="package-workbench" data-package-workbench>
|
|
||||||
<div class="package-tree" role="tablist" aria-label="Files in the review-ui skill package">
|
|
||||||
<p>REVIEW-UI / SKILL PACKAGE</p>
|
|
||||||
<button class="active" data-package-file="skill" role="tab" aria-selected="true"><code>├── SKILL.md</code><small>trigger + workflow</small></button>
|
|
||||||
<button data-package-file="references" role="tab" aria-selected="false"><code>├── references/</code><small>conditional facts</small></button>
|
|
||||||
<button data-package-file="scripts" role="tab" aria-selected="false"><code>├── scripts/</code><small>deterministic checks</small></button>
|
|
||||||
<button data-package-file="assets" role="tab" aria-selected="false"><code>└── assets/</code><small>templates + examples</small></button>
|
|
||||||
</div>
|
|
||||||
<article class="package-preview" id="package-preview" aria-live="polite"></article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="practice"><div><p class="eyebrow">Create a skill</p><h2>Observe →<br>trigger →<br>validate</h2></div><div class="steps"><article><b>01</b><div><strong>Observe friction</strong><span>Find a repeated decision or failure.</span></div></article><article><b>02</b><div><strong>Define the trigger</strong><span>Say when it should load and when it should stay out.</span></div></article><article><b>03</b><div><strong>Choose anatomy</strong><span>Use references for facts and scripts for deterministic mechanics.</span></div></article><article><b>04</b><div><strong>Evaluate behavior</strong><span>Test realistic prompts, edge cases, safety, and evidence.</span></div></article></div></section>
|
|
||||||
<nav class="links"><a href="../agents/">Agents & trees →</a><a href="../rules/">Rules case study →</a><a href="../skills-review/">Review submitted skills →</a><a href="../full-guide/#create-skill">Full guide: skill forge →</a></nav>
|
|
||||||
</main>
|
|
||||||
<script src="app.js" defer></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -137,17 +137,16 @@ const isDiff = mode === 'diff';
|
|||||||
.skill-diff {
|
.skill-diff {
|
||||||
border: 1px solid var(--ink);
|
border: 1px solid var(--ink);
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
animation: lens-enter 0.28s ease both;
|
/* purpose: change lens reveal (state change) */
|
||||||
|
animation: lens-enter 200ms cubic-bezier(0.2, 0, 0, 1) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-lens {
|
.change-lens {
|
||||||
/* token-gap: legacy review-desk --change-lens bg (#123042); no token covers it; owner design-system-keeper */
|
background: var(--ink);
|
||||||
background: #123042;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.skill-diff {
|
.skill-diff {
|
||||||
/* token-gap: legacy review-desk --skill-diff bg (#102b3a); --deep here is #102536; owner design-system-keeper */
|
background: var(--deep);
|
||||||
background: #102b3a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-lens > header,
|
.change-lens > header,
|
||||||
@@ -157,8 +156,7 @@ const isDiff = mode === 'diff';
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
padding: 22px 24px;
|
padding: 22px 24px;
|
||||||
/* token-gap: legacy review-desk header rule (#466274); --line here is #d8dee2; owner design-system-keeper */
|
border-bottom: 1px solid var(--line);
|
||||||
border-bottom: 1px solid #466274;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-lens span,
|
.change-lens span,
|
||||||
@@ -184,8 +182,7 @@ const isDiff = mode === 'diff';
|
|||||||
padding: 9px 11px;
|
padding: 9px 11px;
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
/* token-gap: legacy review-desk button border (#557080); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #557080;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font:
|
font:
|
||||||
700 10px ui-monospace,
|
700 10px ui-monospace,
|
||||||
@@ -203,8 +200,7 @@ const isDiff = mode === 'diff';
|
|||||||
.skill-diff > p {
|
.skill-diff > p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 17px 24px;
|
padding: 17px 24px;
|
||||||
/* token-gap: legacy review-desk body text (#c6d2d7); --muted here is #697b89; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #c6d2d7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The change-rows grid: a 1px-gap "fake border" trick (house style) over
|
/* The change-rows grid: a 1px-gap "fake border" trick (house style) over
|
||||||
@@ -212,24 +208,21 @@ const isDiff = mode === 'diff';
|
|||||||
.change-rows {
|
.change-rows {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
/* token-gap: legacy review-desk grid rule (#466274); --line here is #d8dee2; owner design-system-keeper */
|
background: var(--line);
|
||||||
background: #466274;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-rows article {
|
.change-rows article {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 120px minmax(0, 1fr) minmax(0, 1fr) minmax(220px, 0.85fr);
|
grid-template-columns: 120px minmax(0, 1fr) minmax(0, 1fr) minmax(220px, 0.85fr);
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
/* token-gap: legacy review-desk article rule (#466274); --line here is #d8dee2; owner design-system-keeper */
|
background: var(--line);
|
||||||
background: #466274;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-rows article > * {
|
.change-rows article > * {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 17px;
|
padding: 17px;
|
||||||
/* token-gap: legacy review-desk cell bg (#173b4f); --deep here is #102536; owner design-system-keeper */
|
background: var(--deep);
|
||||||
background: #173b4f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-rows article > span {
|
.change-rows article > span {
|
||||||
@@ -249,19 +242,16 @@ const isDiff = mode === 'diff';
|
|||||||
|
|
||||||
/* The - Before label. */
|
/* The - Before label. */
|
||||||
.change-rows div:first-of-type b {
|
.change-rows div:first-of-type b {
|
||||||
/* token-gap: legacy review-desk before-label (#e89a8e); --red here is #a7483f; owner design-system-keeper */
|
color: var(--red);
|
||||||
color: #e89a8e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The + After label. */
|
/* The + After label. */
|
||||||
.change-rows div:nth-of-type(2) b {
|
.change-rows div:nth-of-type(2) b {
|
||||||
/* token-gap: legacy review-desk after-label (#9bcba7); --accent here is #7c78a8; owner design-system-keeper */
|
color: var(--accent);
|
||||||
color: #9bcba7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-rows aside {
|
.change-rows aside {
|
||||||
/* token-gap: legacy review-desk aside bg (#1d455b); --deep here is #102536; owner design-system-keeper */
|
background: var(--deep);
|
||||||
background: #1d455b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-rows aside b {
|
.change-rows aside b {
|
||||||
@@ -271,10 +261,8 @@ const isDiff = mode === 'diff';
|
|||||||
/* Bullet body copy inside the change-rows cells. */
|
/* Bullet body copy inside the change-rows cells. */
|
||||||
.change-rows p {
|
.change-rows p {
|
||||||
margin: 7px 0 0;
|
margin: 7px 0 0;
|
||||||
/* token-gap: legacy review-desk cell text (#d4dfe3); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #d4dfe3;
|
font-size: var(--step-12);
|
||||||
/* token-gap: no --step-* covers 12px; owner design-system-keeper */
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,8 +271,7 @@ const isDiff = mode === 'diff';
|
|||||||
.diff-lines {
|
.diff-lines {
|
||||||
max-height: 540px;
|
max-height: 540px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
/* token-gap: legacy review-desk diff top rule (#466274); --line here is #d8dee2; owner design-system-keeper */
|
border-top: 1px solid var(--line);
|
||||||
border-top: 1px solid #466274;
|
|
||||||
font:
|
font:
|
||||||
12px / 1.55 ui-monospace,
|
12px / 1.55 ui-monospace,
|
||||||
monospace;
|
monospace;
|
||||||
@@ -302,34 +289,27 @@ const isDiff = mode === 'diff';
|
|||||||
|
|
||||||
/* Line numbers in the gutter. */
|
/* Line numbers in the gutter. */
|
||||||
.diff-lines span {
|
.diff-lines span {
|
||||||
/* token-gap: legacy review-desk gutter (#91aab7); --muted here is #697b89; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #91aab7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Added lines: a soft green pair signals "new" against the dark surface. */
|
/* Added lines: a soft green pair signals "new" against the dark surface. */
|
||||||
.diff-lines .added {
|
.diff-lines .added {
|
||||||
/* token-gap: legacy review-desk added text (#d5f1d6); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #d5f1d6;
|
background: var(--deep);
|
||||||
/* token-gap: legacy review-desk added bg (#1a4b42); --deep here is #102536; owner design-system-keeper */
|
|
||||||
background: #1a4b42;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-lines .added span {
|
.diff-lines .added span {
|
||||||
/* token-gap: legacy review-desk added gutter (#a9e3ae); --accent here is #7c78a8; owner design-system-keeper */
|
color: var(--accent);
|
||||||
color: #a9e3ae;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Removed lines: a warm red pair, opposite side of the diff. */
|
/* Removed lines: a warm red pair, opposite side of the diff. */
|
||||||
.diff-lines .removed {
|
.diff-lines .removed {
|
||||||
/* token-gap: legacy review-desk removed text (#ffd7d0); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #ffd7d0;
|
background: var(--deep);
|
||||||
/* token-gap: legacy review-desk removed bg (#572f32); --deep here is #102536; owner design-system-keeper */
|
|
||||||
background: #572f32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-lines .removed span {
|
.diff-lines .removed span {
|
||||||
/* token-gap: legacy review-desk removed gutter (#ffb5a8); --red here is #a7483f; owner design-system-keeper */
|
color: var(--red);
|
||||||
color: #ffb5a8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes lens-enter {
|
@keyframes lens-enter {
|
||||||
@@ -343,8 +323,7 @@ const isDiff = mode === 'diff';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: 1000px is not a named breakpoint; owner design-system-keeper */
|
@media (max-width: 1100px) {
|
||||||
@media (max-width: 1000px) {
|
|
||||||
.change-rows article {
|
.change-rows article {
|
||||||
grid-template-columns: 100px 1fr 1fr;
|
grid-template-columns: 100px 1fr 1fr;
|
||||||
}
|
}
|
||||||
@@ -354,8 +333,7 @@ const isDiff = mode === 'diff';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: 620px is not a named breakpoint; owner design-system-keeper */
|
@media (max-width: 560px) {
|
||||||
@media (max-width: 620px) {
|
|
||||||
.change-lens > header,
|
.change-lens > header,
|
||||||
.skill-diff > header {
|
.skill-diff > header {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -389,8 +367,7 @@ const isDiff = mode === 'diff';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.change-rows p {
|
.change-rows p {
|
||||||
/* token-gap: no --step-* covers 13px; owner design-system-keeper */
|
font-size: var(--step-13);
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-lines p {
|
.diff-lines p {
|
||||||
|
|||||||
@@ -54,10 +54,8 @@ const { files, currentFile, ariaLabel = 'Skill package files' } = Astro.props;
|
|||||||
gap: 1px;
|
gap: 1px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
/* token-gap: legacy review-desk --ink (#122534); --deep here is #102536; owner design-system-keeper */
|
background: var(--deep);
|
||||||
background: #122534;
|
border-bottom: 1px solid var(--line);
|
||||||
/* token-gap: legacy review-desk border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
|
||||||
border-bottom: 1px solid #486175;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tabs button {
|
.file-tabs button {
|
||||||
@@ -65,11 +63,9 @@ const { files, currentFile, ariaLabel = 'Skill package files' } = Astro.props;
|
|||||||
gap: 1px;
|
gap: 1px;
|
||||||
min-width: max-content;
|
min-width: max-content;
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
/* token-gap: legacy review-desk muted code (#d6e1e4); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #d6e1e4;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
/* token-gap: legacy review-desk border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font:
|
font:
|
||||||
@@ -78,25 +74,20 @@ const { files, currentFile, ariaLabel = 'Skill package files' } = Astro.props;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-tabs button span {
|
.file-tabs button span {
|
||||||
/* token-gap: legacy review-desk gold (#ebbf58); --gold here is #efc76b; owner design-system-keeper */
|
color: var(--gold);
|
||||||
color: #ebbf58;
|
font-size: var(--step-09);
|
||||||
/* token-gap: no --step-* covers 9px; owner design-system-keeper */
|
|
||||||
font-size: 9px;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tabs button.active,
|
.file-tabs button.active,
|
||||||
.file-tabs button:hover {
|
.file-tabs button:hover {
|
||||||
/* token-gap: legacy review-desk --ink (#122534); --deep here is #102536; owner design-system-keeper */
|
color: var(--deep);
|
||||||
color: #122534;
|
background: var(--gold);
|
||||||
/* token-gap: legacy review-desk gold (#ebbf58); --gold here is #efc76b; owner design-system-keeper */
|
|
||||||
background: #ebbf58;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tabs button.active span,
|
.file-tabs button.active span,
|
||||||
.file-tabs button:hover span {
|
.file-tabs button:hover span {
|
||||||
/* token-gap: legacy review-desk --ink (#122534); --deep here is #102536; owner design-system-keeper */
|
color: var(--deep);
|
||||||
color: #122534;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tabs button:focus-visible {
|
.file-tabs button:focus-visible {
|
||||||
|
|||||||
@@ -148,16 +148,14 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props;
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
/* token-gap: source uses 30px; no --step-* covers 30px; owner design-system-keeper */
|
font-size: var(--step-30);
|
||||||
font-size: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.workers {
|
.workers {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
/* token-gap: source seam colour is #41596b for the gap:1px hairline trick; no token matches; owner design-system-keeper */
|
background: var(--muted);
|
||||||
background: #41596b;
|
|
||||||
}
|
}
|
||||||
.worker-card {
|
.worker-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -172,8 +170,7 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props;
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.worker-card span {
|
.worker-card span {
|
||||||
/* token-gap: source uses #9eabb4; no token matches; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #9eabb4;
|
|
||||||
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
font:
|
font:
|
||||||
500 10px 'DM Mono',
|
500 10px 'DM Mono',
|
||||||
@@ -182,8 +179,7 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props;
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.worker-card strong {
|
.worker-card strong {
|
||||||
/* token-gap: source uses 16px; no --step-* covers 16px; owner design-system-keeper */
|
font-size: var(--step-16);
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
.worker-card code {
|
.worker-card code {
|
||||||
@@ -208,4 +204,11 @@ const { orchestrator, workers, initial = workers[0]?.id } = Astro.props;
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1600px) {
|
||||||
|
.worker-card {
|
||||||
|
min-height: 210px;
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -157,8 +157,7 @@ const { columns, rows } = Astro.props;
|
|||||||
}
|
}
|
||||||
.handoff-table td {
|
.handoff-table td {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
/* token-gap: source uses 13px; no --step-* covers 13px; owner design-system-keeper */
|
font-size: var(--step-13);
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
action bar from the tab strip from the body. */
|
action bar from the tab strip from the body. */
|
||||||
.preview {
|
.preview {
|
||||||
border: 1px solid var(--ink);
|
border: 1px solid var(--ink);
|
||||||
/* token-gap: legacy review-desk --ink (#122534); --deep here is #102536; owner design-system-keeper */
|
background: var(--deep);
|
||||||
background: #122534;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview > header {
|
.preview > header {
|
||||||
@@ -75,8 +74,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
/* token-gap: legacy review-desk border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border-bottom: 1px solid var(--line);
|
||||||
border-bottom: 1px solid #486175;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Title cluster: eyebrow line + subtitle. */
|
/* Title cluster: eyebrow line + subtitle. */
|
||||||
@@ -95,8 +93,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
|
|
||||||
/* Subtitle below the eyebrow. */
|
/* Subtitle below the eyebrow. */
|
||||||
.preview-title small {
|
.preview-title small {
|
||||||
/* token-gap: legacy review-desk subtitle (#c1d1d8); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #c1d1d8;
|
|
||||||
/* token-gap: no --step-* covers 9px; owner design-system-keeper */
|
/* token-gap: no --step-* covers 9px; owner design-system-keeper */
|
||||||
font:
|
font:
|
||||||
9px / 1.35 ui-monospace,
|
9px / 1.35 ui-monospace,
|
||||||
@@ -116,8 +113,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
padding: 9px 11px;
|
padding: 9px 11px;
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
/* token-gap: legacy review-desk border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font:
|
font:
|
||||||
700 10px ui-monospace,
|
700 10px ui-monospace,
|
||||||
@@ -125,8 +121,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preview button:hover {
|
.preview button:hover {
|
||||||
/* token-gap: legacy review-desk button hover (#29455a); no token covers it; owner design-system-keeper */
|
background: var(--muted);
|
||||||
background: #29455a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The Markdown toggle is deliberately distinct from the other actions. */
|
/* The Markdown toggle is deliberately distinct from the other actions. */
|
||||||
@@ -139,10 +134,8 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
.preview button.preview-markdown:hover,
|
.preview button.preview-markdown:hover,
|
||||||
.preview button.preview-markdown[aria-pressed='true'] {
|
.preview button.preview-markdown[aria-pressed='true'] {
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
/* token-gap: legacy review-desk markdown toggle pressed (#a7483f); --red here is #a7483f but a token rename changed that — owner design-system-keeper */
|
background: var(--red);
|
||||||
background: #a7483f;
|
border-color: var(--red);
|
||||||
/* token-gap: legacy review-desk markdown toggle pressed (#a7483f); --red here is #a7483f but a token rename changed that — owner design-system-keeper */
|
|
||||||
border-color: #a7483f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Source body: bounded scroll, monospaced, the canonical dark code
|
/* Source body: bounded scroll, monospaced, the canonical dark code
|
||||||
@@ -151,10 +144,8 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
max-height: 540px;
|
max-height: 540px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
/* token-gap: legacy review-desk pre text (#d6e1e4); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #d6e1e4;
|
background: var(--ink);
|
||||||
/* token-gap: legacy review-desk pre bg (#0c1a25); --ink here is #172f42; owner design-system-keeper */
|
|
||||||
background: #0c1a25;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Code text inside the source surface. */
|
/* Code text inside the source surface. */
|
||||||
@@ -170,10 +161,8 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
max-height: 540px;
|
max-height: 540px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
/* token-gap: legacy review-desk markdown text (#d6e1e4); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #d6e1e4;
|
background: var(--ink);
|
||||||
/* token-gap: legacy review-desk markdown bg (#0c1a25); --ink here is #172f42; owner design-system-keeper */
|
|
||||||
background: #0c1a25;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview > :first-child {
|
.markdown-preview > :first-child {
|
||||||
@@ -187,8 +176,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
.markdown-preview :global(h5),
|
.markdown-preview :global(h5),
|
||||||
.markdown-preview :global(h6) {
|
.markdown-preview :global(h6) {
|
||||||
margin: 1.5em 0 0.5em;
|
margin: 1.5em 0 0.5em;
|
||||||
/* token-gap: legacy review-desk heading colour (#fff); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #fff;
|
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +207,8 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
|
|
||||||
.markdown-preview :global(code) {
|
.markdown-preview :global(code) {
|
||||||
padding: 0.12em 0.3em;
|
padding: 0.12em 0.3em;
|
||||||
/* token-gap: legacy review-desk inline code colour (#fff); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #fff;
|
background: var(--muted);
|
||||||
/* token-gap: legacy review-desk inline code bg (#29455a); no token covers it; owner design-system-keeper */
|
|
||||||
background: #29455a;
|
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +216,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
max-height: none;
|
max-height: none;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
/* token-gap: legacy review-desk pre border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview :global(pre code) {
|
.markdown-preview :global(pre code) {
|
||||||
@@ -243,14 +228,12 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
padding: 0.3em 1em;
|
padding: 0.3em 1em;
|
||||||
border-left: 3px solid var(--gold);
|
border-left: 3px solid var(--gold);
|
||||||
/* token-gap: legacy review-desk blockquote text (#b9c8d0); --muted here is #697b89; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #b9c8d0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview :global(hr) {
|
.markdown-preview :global(hr) {
|
||||||
border: 0;
|
border: 0;
|
||||||
/* token-gap: legacy review-desk hr (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border-top: 1px solid var(--line);
|
||||||
border-top: 1px solid #486175;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Frontmatter: the two-column key/value grid that opens the rendered
|
/* Frontmatter: the two-column key/value grid that opens the rendered
|
||||||
@@ -261,8 +244,7 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
gap: 3px 14px;
|
gap: 3px 14px;
|
||||||
margin: 0 0 24px;
|
margin: 0 0 24px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
/* token-gap: legacy review-desk frontmatter border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
|
||||||
font:
|
font:
|
||||||
11px/1.5 ui-monospace,
|
11px/1.5 ui-monospace,
|
||||||
monospace;
|
monospace;
|
||||||
@@ -281,31 +263,27 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
/* token-gap: legacy review-desk table border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview :global(table) {
|
.markdown-preview :global(table) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 460px;
|
min-width: 460px;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
/* token-gap: no --step-* covers 13px; owner design-system-keeper */
|
font-size: var(--step-13);
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview :global(th),
|
.markdown-preview :global(th),
|
||||||
.markdown-preview :global(td) {
|
.markdown-preview :global(td) {
|
||||||
padding: 9px 11px;
|
padding: 9px 11px;
|
||||||
/* token-gap: legacy review-desk cell border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview :global(th) {
|
.markdown-preview :global(th) {
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
/* token-gap: legacy review-desk table header bg (#173046); --deep here is #102536; owner design-system-keeper */
|
background: var(--deep);
|
||||||
background: #173046;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table of contents: the "ON THIS PAGE" nav that opens the body when
|
/* Table of contents: the "ON THIS PAGE" nav that opens the body when
|
||||||
@@ -313,10 +291,8 @@ const { title, subtitle, rendered } = Astro.props;
|
|||||||
.markdown-preview :global(.markdown-toc) {
|
.markdown-preview :global(.markdown-toc) {
|
||||||
margin: 0 0 24px;
|
margin: 0 0 24px;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
/* token-gap: legacy review-desk toc border (#486175); --line here is #d8dee2; owner design-system-keeper */
|
border: 1px solid var(--line);
|
||||||
border: 1px solid #486175;
|
background: var(--deep);
|
||||||
/* token-gap: legacy review-desk toc bg (#102b3a); --deep here is #102536; owner design-system-keeper */
|
|
||||||
background: #102b3a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview :global(.markdown-toc > span) {
|
.markdown-preview :global(.markdown-toc > span) {
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
.detail {
|
.detail {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 38px;
|
padding: 38px;
|
||||||
/* token-gap: legacy review-desk --paper (#f6f3ed); --paper here is #f5f4f1; owner design-system-keeper */
|
background: var(--paper);
|
||||||
background: #f6f3ed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header row: title cluster on the left, version switcher on the right. */
|
/* Header row: title cluster on the left, version switcher on the right. */
|
||||||
@@ -128,8 +127,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
|
|
||||||
.detail > header p {
|
.detail > header p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/* token-gap: legacy review-desk --muted (#65717a); --muted here is #697b89; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #65717a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* author-link and share-link are NEW elements not in legacy stylesheets;
|
/* author-link and share-link are NEW elements not in legacy stylesheets;
|
||||||
@@ -157,10 +155,8 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
|
|
||||||
.switch button.active,
|
.switch button.active,
|
||||||
.switch button[aria-pressed='true'] {
|
.switch button[aria-pressed='true'] {
|
||||||
/* token-gap: legacy review-desk switch text (#f6f3ed); --paper here is #f5f4f1; owner design-system-keeper */
|
color: var(--paper);
|
||||||
color: #f6f3ed;
|
background: var(--ink);
|
||||||
/* token-gap: legacy review-desk --ink (#122534); --ink here is #172f42; owner design-system-keeper */
|
|
||||||
background: #122534;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch button:focus-visible {
|
.switch button:focus-visible {
|
||||||
@@ -175,8 +171,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin: 45px 0 20px;
|
margin: 45px 0 20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
/* token-gap: legacy review-desk --gold (#ebbf58); --gold here is #efc76b; owner design-system-keeper */
|
background: var(--gold);
|
||||||
background: #ebbf58;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.purpose span {
|
.purpose span {
|
||||||
@@ -187,8 +182,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
|
|
||||||
.purpose p {
|
.purpose p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/* token-gap: no --step-* covers 18px; owner design-system-keeper */
|
font-size: var(--step-18);
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,8 +197,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
|
|
||||||
.review-grid section {
|
.review-grid section {
|
||||||
padding: 22px;
|
padding: 22px;
|
||||||
/* token-gap: legacy review-desk --paper (#f6f3ed); --paper here is #f5f4f1; owner design-system-keeper */
|
background: var(--paper);
|
||||||
background: #f6f3ed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-grid span {
|
.review-grid span {
|
||||||
@@ -226,19 +219,15 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
.extras {
|
.extras {
|
||||||
margin: 1px 0 25px;
|
margin: 1px 0 25px;
|
||||||
padding: 18px 22px;
|
padding: 18px 22px;
|
||||||
/* token-gap: legacy review-desk extras text (#122534); --ink here is #172f42; owner design-system-keeper */
|
color: var(--ink);
|
||||||
color: #122534;
|
background: var(--paper);
|
||||||
/* token-gap: legacy review-desk extras bg (#e5eeeb); --paper here is #f5f4f1; owner design-system-keeper */
|
border-left: 4px solid var(--blue);
|
||||||
background: #e5eeeb;
|
|
||||||
/* token-gap: legacy review-desk --blue (#215675); --blue here is #527f9f; owner design-system-keeper */
|
|
||||||
border-left: 4px solid #215675;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.extras span {
|
.extras span {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
/* token-gap: legacy review-desk --blue (#215675); --blue here is #527f9f; owner design-system-keeper */
|
color: var(--blue);
|
||||||
color: #215675;
|
|
||||||
font: 700 10px monospace;
|
font: 700 10px monospace;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
}
|
}
|
||||||
@@ -247,8 +236,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: 850px is not a named breakpoint; owner design-system-keeper */
|
@media (max-width: 800px) {
|
||||||
@media (max-width: 850px) {
|
|
||||||
.detail {
|
.detail {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
@@ -258,8 +246,7 @@ const authorHref = `?author=${encodeURIComponent(entry.author)}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: 530px is not a named breakpoint; owner design-system-keeper */
|
@media (max-width: 560px) {
|
||||||
@media (max-width: 530px) {
|
|
||||||
.detail > header {
|
.detail > header {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,15 +53,13 @@ const { number, title, summary, href, cta = 'Open chapter →' } = Astro.props;
|
|||||||
|
|
||||||
.card b {
|
.card b {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
/* token-gap: legacy 24px fixed from chapters.css .card b; no --step-* covers 24px; owner design-system-keeper */
|
font-size: var(--step-24);
|
||||||
font-size: 24px;
|
|
||||||
font-family: ui-monospace, monospace;
|
font-family: ui-monospace, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card h2 {
|
.card h2 {
|
||||||
margin: 18px 0 8px;
|
margin: 18px 0 8px;
|
||||||
/* token-gap: legacy 25px fixed from chapters.css .card h2; --step-5 is clamp(24px,3vw,38px) and grows at wide viewports; owner design-system-keeper */
|
font-size: var(--step-25);
|
||||||
font-size: 25px;
|
|
||||||
letter-spacing: -0.04em;
|
letter-spacing: -0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,8 +167,7 @@ const {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.route-row strong {
|
.route-row strong {
|
||||||
/* token-gap: source uses 14px; no --step-* covers 14px; owner design-system-keeper */
|
font-size: var(--step-14);
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
.route-row b {
|
.route-row b {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
@@ -177,8 +176,7 @@ const {
|
|||||||
}
|
}
|
||||||
.route-row small {
|
.route-row small {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
/* token-gap: source uses 12px; no --step-* covers 12px; owner design-system-keeper */
|
font-size: var(--step-12);
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
.route-row.active {
|
.route-row.active {
|
||||||
background: var(--line);
|
background: var(--line);
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ const { entries, selectedId } = Astro.props;
|
|||||||
|
|
||||||
/* Title: two-line clamp so a long title doesn't push other rows. */
|
/* Title: two-line clamp so a long title doesn't push other rows. */
|
||||||
#skill-list button strong {
|
#skill-list button strong {
|
||||||
/* token-gap: no --step-* covers 13px; owner design-system-keeper */
|
font-size: var(--step-13);
|
||||||
font-size: 13px;
|
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
max-height: 36px;
|
max-height: 36px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ const { files, initial = files[0]?.id ?? 'skill', packageLabel = 'SKILL PACKAGE'
|
|||||||
.skill-package-label {
|
.skill-package-label {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
/* token-gap: source uses 1px solid #ffffff40 over the blue background; no token matches; owner design-system-keeper */
|
border-bottom: 1px solid var(--white-25);
|
||||||
border-bottom: 1px solid #ffffff40;
|
|
||||||
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
font:
|
font:
|
||||||
500 10px 'DM Mono',
|
500 10px 'DM Mono',
|
||||||
@@ -94,8 +93,7 @@ const { files, initial = files[0]?.id ?? 'skill', packageLabel = 'SKILL PACKAGE'
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
padding: 17px 20px;
|
padding: 17px 20px;
|
||||||
border: 0;
|
border: 0;
|
||||||
/* token-gap: source uses 1px solid #ffffff40 over the blue background; no token matches; owner design-system-keeper */
|
border-bottom: 1px solid var(--white-25);
|
||||||
border-bottom: 1px solid #ffffff40;
|
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|||||||
@@ -77,14 +77,12 @@ const share = (count: number) => (total ? Math.round((count / total) * 100) : 0)
|
|||||||
margin: 1px 0 25px;
|
margin: 1px 0 25px;
|
||||||
padding: 18px 22px;
|
padding: 18px 22px;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
/* token-gap: legacy review-desk vote bg (#e5eeeb); --paper here is #f5f4f1; owner design-system-keeper */
|
background: var(--paper);
|
||||||
background: #e5eeeb;
|
|
||||||
border-left: 4px solid var(--gold);
|
border-left: 4px solid var(--gold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vote-widget > span {
|
.vote-widget > span {
|
||||||
/* token-gap: legacy review-desk --blue (#215675); --blue here is #527f9f; owner design-system-keeper */
|
color: var(--blue);
|
||||||
color: #215675;
|
|
||||||
font: 700 10px monospace;
|
font: 700 10px monospace;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
}
|
}
|
||||||
@@ -133,14 +131,35 @@ const share = (count: number) => (total ? Math.round((count / total) * 100) : 0)
|
|||||||
.vote-note {
|
.vote-note {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
/* token-gap: no --step-* covers 12px; owner design-system-keeper */
|
font-size: var(--step-12);
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: 530px is not a named breakpoint; owner design-system-keeper */
|
@media (max-width: 560px) {
|
||||||
@media (max-width: 530px) {
|
|
||||||
.vote-buttons {
|
.vote-buttons {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vote-buttons button b {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
:global(.is-updating) {
|
||||||
|
/* purpose: vote widget tally update (feedback) */
|
||||||
|
animation: tally-pop 200ms cubic-bezier(0.2, 0, 0, 1) both;
|
||||||
|
}
|
||||||
|
@keyframes tally-pop {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
:global(.is-updating) {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -151,8 +151,7 @@ const { branches, initial = 'main', rootLabel = 'ROOT', rootSmall = '● clean'
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: var(--ink);
|
background: var(--ink);
|
||||||
/* token-gap: source uses 1px solid #41596b over the ink background; no token matches; owner design-system-keeper */
|
border: 1px solid var(--muted);
|
||||||
border: 1px solid #41596b;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -177,13 +176,11 @@ const { branches, initial = 'main', rootLabel = 'ROOT', rootSmall = '● clean'
|
|||||||
}
|
}
|
||||||
.tree-node small {
|
.tree-node small {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
/* token-gap: source uses #9eabb4; no token matches; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #9eabb4;
|
|
||||||
font-size: var(--step-0);
|
font-size: var(--step-0);
|
||||||
}
|
}
|
||||||
.tree-node.root small {
|
.tree-node.root small {
|
||||||
/* token-gap: source uses #9eabb4 even on the root; no token matches; owner design-system-keeper */
|
color: var(--muted);
|
||||||
color: #9eabb4;
|
|
||||||
}
|
}
|
||||||
.tree-node.active {
|
.tree-node.active {
|
||||||
box-shadow: inset 4px 0 0 var(--gold);
|
box-shadow: inset 4px 0 0 var(--gold);
|
||||||
@@ -217,4 +214,111 @@ const { branches, initial = 'main', rootLabel = 'ROOT', rootSmall = '● clean'
|
|||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* token-gap: preserve the legacy worktree layout threshold; this behavior is required by task 15e */
|
||||||
|
@media (max-width: 1050px) {
|
||||||
|
.tree-stage {
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tree-node,
|
||||||
|
.tree-node.root {
|
||||||
|
min-height: 0;
|
||||||
|
width: 145px;
|
||||||
|
padding: 13px 15px;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.tree-node span,
|
||||||
|
.tree-node small {
|
||||||
|
font: 500 var(--step-08) var(--font-mono);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
.tree-node strong {
|
||||||
|
font: 600 var(--step-12) var(--font-mono);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1600px) {
|
||||||
|
.tree-stage {
|
||||||
|
height: 390px;
|
||||||
|
}
|
||||||
|
.tree-stage svg {
|
||||||
|
height: 330px;
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
.tree-node.root {
|
||||||
|
top: 45px;
|
||||||
|
}
|
||||||
|
.tree-node.branch {
|
||||||
|
top: 255px;
|
||||||
|
}
|
||||||
|
.tree-node {
|
||||||
|
width: 190px;
|
||||||
|
padding: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 2200px) {
|
||||||
|
.tree-stage {
|
||||||
|
height: 460px;
|
||||||
|
}
|
||||||
|
.tree-stage svg {
|
||||||
|
height: 380px;
|
||||||
|
top: 45px;
|
||||||
|
}
|
||||||
|
.tree-node.root {
|
||||||
|
top: 65px;
|
||||||
|
}
|
||||||
|
.tree-node.branch {
|
||||||
|
top: 305px;
|
||||||
|
}
|
||||||
|
.tree-node {
|
||||||
|
width: 240px;
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
.tree-node strong {
|
||||||
|
font-size: var(--step-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* token-gap: preserve the legacy full-guide layout threshold; responsive.css is gone, so this rule is now the only definition of it */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.tree-stage {
|
||||||
|
height: auto;
|
||||||
|
min-height: 560px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
.tree-stage svg {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tree-node,
|
||||||
|
.tree-node.root,
|
||||||
|
.tree-node.branch,
|
||||||
|
.tree-node.ui,
|
||||||
|
.tree-node.tests,
|
||||||
|
.tree-node.docs {
|
||||||
|
position: relative;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
left: auto;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 34px;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.tree-node:not(:last-child)::after {
|
||||||
|
content: '↓';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -28px;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
.tree-node:hover,
|
||||||
|
.tree-node.active,
|
||||||
|
.tree-node.root:hover,
|
||||||
|
.tree-node.root.active,
|
||||||
|
.tree-node.tests:hover,
|
||||||
|
.tree-node.tests.active {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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' &&
|
||||||
|
|||||||
@@ -99,7 +99,11 @@ const { rootSelector, data } = Astro.props;
|
|||||||
|
|
||||||
function replace(target, markup) {
|
function replace(target, markup) {
|
||||||
const panel = root.querySelector(target);
|
const panel = root.querySelector(target);
|
||||||
if (panel) panel.innerHTML = markup;
|
if (!panel) return;
|
||||||
|
panel.innerHTML = markup;
|
||||||
|
panel.classList.remove('is-swapping');
|
||||||
|
void panel.offsetWidth;
|
||||||
|
panel.classList.add('is-swapping');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names deliberately match legacy functions until task 19 updates its checks.
|
// Names deliberately match legacy functions until task 19 updates its checks.
|
||||||
@@ -291,4 +295,24 @@ const { rootSelector, data } = Astro.props;
|
|||||||
outline: 3px solid var(--gold);
|
outline: 3px solid var(--gold);
|
||||||
outline-offset: -3px;
|
outline-offset: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.is-swapping > *) {
|
||||||
|
/* purpose: tab panel change in the guide (state change) */
|
||||||
|
animation: guide-swap 200ms cubic-bezier(0.2, 0, 0, 1) both;
|
||||||
|
}
|
||||||
|
@keyframes guide-swap {
|
||||||
|
from {
|
||||||
|
opacity: 0.15;
|
||||||
|
transform: translateY(5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
:global(.is-swapping > *) {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const { target = '.reading-progress span' } = Astro.props;
|
|||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
const height = document.documentElement.scrollHeight - window.innerHeight;
|
const height = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
span.style.width = (height > 0 ? (window.scrollY / height) * 100 : 0) + '%';
|
span.style.transform = `scaleX(${height > 0 ? window.scrollY / height : 0})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `{ passive: true }` is non-negotiable. The legacy app.js binds
|
// `{ passive: true }` is non-negotiable. The legacy app.js binds
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -487,24 +492,6 @@ fix(api): scope session query</code></pre>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Token-gap escape hatch for legacy values without a token yet — see
|
|
||||||
.agents/scripts/check-tokens.mjs and the rules/astro.md token contract.
|
|
||||||
Owner: design-system-keeper. */
|
|
||||||
/* token-gap: --accent/#7c78a8 etc. already tokenized; --bp-2xl/2200 covers the
|
|
||||||
legacy (min-width:2200px) and (max-width:900px) collapse to --bp-md/800px;
|
|
||||||
owner design-system-keeper */
|
|
||||||
:root {
|
|
||||||
/* token-gap: rules/styles.css values copied verbatim; design-system-keeper */
|
|
||||||
--paper: #f5f4f1;
|
|
||||||
--ink: #172f42; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
--muted: #697b89; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
--accent: #7c78a8; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
--blue: #527f9f; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
--gold: #efc76b; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
--line: #d8dee2; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
--deep: #102536; /* token-gap: rules/styles.css; design-system-keeper */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rules-root is the only element Astro scopes styles to. The legacy
|
/* rules-root is the only element Astro scopes styles to. The legacy
|
||||||
page's rules/styles.css put everything on body-level selectors, so we
|
page's rules/styles.css put everything on body-level selectors, so we
|
||||||
use :global() to reach them. */
|
use :global() to reach them. */
|
||||||
@@ -569,7 +556,7 @@ fix(api): scope session query</code></pre>
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
font-size: 15px; /* token-gap: source 15px; design-system-keeper */
|
font-size: var(--step-1);
|
||||||
}
|
}
|
||||||
.topbar nav {
|
.topbar nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -646,7 +633,7 @@ fix(api): scope session query</code></pre>
|
|||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 16px; /* token-gap: source 16px; design-system-keeper */
|
font-size: var(--step-16);
|
||||||
line-height: 1.75;
|
line-height: 1.75;
|
||||||
}
|
}
|
||||||
.hero-foot aside {
|
.hero-foot aside {
|
||||||
@@ -666,7 +653,7 @@ fix(api): scope session query</code></pre>
|
|||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
.hero-foot aside strong {
|
.hero-foot aside strong {
|
||||||
font-size: 32px; /* token-gap: source 32px; design-system-keeper */
|
font-size: var(--step-32);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thesis {
|
.thesis {
|
||||||
@@ -715,7 +702,7 @@ fix(api): scope session query</code></pre>
|
|||||||
max-width: 650px;
|
max-width: 650px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 14px; /* token-gap: source 14px; design-system-keeper */
|
font-size: var(--step-14);
|
||||||
line-height: 1.75;
|
line-height: 1.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,7 +766,7 @@ fix(api): scope session query</code></pre>
|
|||||||
background: var(--deep);
|
background: var(--deep);
|
||||||
}
|
}
|
||||||
.stage-number {
|
.stage-number {
|
||||||
color: #ffffff24; /* token-gap: stage number 12% white on --deep; design-system-keeper */
|
color: var(--white-14);
|
||||||
font:
|
font:
|
||||||
500 76px 'DM Mono',
|
500 76px 'DM Mono',
|
||||||
monospace;
|
monospace;
|
||||||
@@ -801,8 +788,8 @@ fix(api): scope session query</code></pre>
|
|||||||
.stage-detail p,
|
.stage-detail p,
|
||||||
.skill-detail p {
|
.skill-detail p {
|
||||||
max-width: 650px;
|
max-width: 650px;
|
||||||
color: #b8c8d2; /* token-gap: lighter than --muted on dark bg; design-system-keeper */
|
color: var(--ink-line);
|
||||||
font-size: 13px; /* token-gap: source 13px; design-system-keeper */
|
font-size: var(--step-13);
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
.stage-detail a,
|
.stage-detail a,
|
||||||
@@ -822,7 +809,7 @@ fix(api): scope session query</code></pre>
|
|||||||
.copy-lab pre {
|
.copy-lab pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: #081621; /* token-gap: darker than --deep for pre block; design-system-keeper */
|
background: var(--deep);
|
||||||
}
|
}
|
||||||
.stage-detail pre {
|
.stage-detail pre {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
@@ -832,7 +819,7 @@ fix(api): scope session query</code></pre>
|
|||||||
.skill-detail code,
|
.skill-detail code,
|
||||||
.example-grid code,
|
.example-grid code,
|
||||||
.copy-lab code {
|
.copy-lab code {
|
||||||
color: #c9d5dc; /* token-gap: code text on dark bg; design-system-keeper */
|
color: var(--ink-code);
|
||||||
font:
|
font:
|
||||||
11px/1.7 'DM Mono',
|
11px/1.7 'DM Mono',
|
||||||
monospace;
|
monospace;
|
||||||
@@ -870,7 +857,7 @@ fix(api): scope session query</code></pre>
|
|||||||
}
|
}
|
||||||
.skill-list button small {
|
.skill-list button small {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 10px; /* token-gap: source 10px; design-system-keeper */
|
font-size: var(--step-00);
|
||||||
}
|
}
|
||||||
.skill-list button.active {
|
.skill-list button.active {
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
@@ -878,23 +865,23 @@ fix(api): scope session query</code></pre>
|
|||||||
box-shadow: inset 6px 0 var(--gold);
|
box-shadow: inset 6px 0 var(--gold);
|
||||||
}
|
}
|
||||||
.skill-list button.active small {
|
.skill-list button.active small {
|
||||||
color: #f0eef8; /* token-gap: near-white text on --accent active state; design-system-keeper */
|
color: var(--accent-paper-active);
|
||||||
}
|
}
|
||||||
.skill-detail {
|
.skill-detail {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
padding: 50px clamp(30px, 5vw, 80px);
|
padding: 50px clamp(30px, 5vw, 80px);
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: #5b7098; /* token-gap: skill detail panel bg, between --blue and --accent; design-system-keeper */
|
background: var(--accent-surface);
|
||||||
}
|
}
|
||||||
.skill-detail header {
|
.skill-detail header {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 9px;
|
gap: 9px;
|
||||||
padding-bottom: 22px;
|
padding-bottom: 22px;
|
||||||
border-bottom: 1px solid #ffffff3a; /* token-gap: 23% white over skill detail; design-system-keeper */
|
border-bottom: 1px solid var(--white-23);
|
||||||
}
|
}
|
||||||
.skill-detail header strong {
|
.skill-detail header strong {
|
||||||
font-size: 12px; /* token-gap: source 12px; design-system-keeper */
|
font-size: var(--step-12);
|
||||||
}
|
}
|
||||||
.skill-detail pre {
|
.skill-detail pre {
|
||||||
margin: 20px 0 0;
|
margin: 20px 0 0;
|
||||||
@@ -959,7 +946,7 @@ fix(api): scope session query</code></pre>
|
|||||||
}
|
}
|
||||||
.copy-lab > div p {
|
.copy-lab > div p {
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
color: #eceaf5; /* token-gap: near-white text on --accent; design-system-keeper */
|
color: var(--accent-paper);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
.copy-lab article {
|
.copy-lab article {
|
||||||
@@ -974,7 +961,7 @@ fix(api): scope session query</code></pre>
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
padding: 11px 14px;
|
padding: 11px 14px;
|
||||||
border: 1px solid #ffffff50; /* token-gap: 31% white copy button border; design-system-keeper */
|
border: 1px solid var(--white-31);
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font:
|
font:
|
||||||
@@ -1021,7 +1008,7 @@ fix(api): scope session query</code></pre>
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
background: #132b3b; /* token-gap: deeper.nav grid rule color; design-system-keeper */
|
background: var(--ink);
|
||||||
}
|
}
|
||||||
.deeper nav a {
|
.deeper nav a {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -1039,10 +1026,10 @@ fix(api): scope session query</code></pre>
|
|||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
.deeper nav b {
|
.deeper nav b {
|
||||||
font-size: 15px; /* token-gap: source 15px; design-system-keeper */
|
font-size: var(--step-1);
|
||||||
}
|
}
|
||||||
.deeper nav small {
|
.deeper nav small {
|
||||||
color: #9eb0bb; /* token-gap: muted small text on --ink; design-system-keeper */
|
color: var(--ink-muted);
|
||||||
font:
|
font:
|
||||||
9px 'DM Mono',
|
9px 'DM Mono',
|
||||||
monospace;
|
monospace;
|
||||||
@@ -1080,8 +1067,7 @@ fix(api): scope session query</code></pre>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: legacy breakpoint from rules/styles.css; design-system-keeper */
|
@media (max-width: 800px) {
|
||||||
@media (max-width: 900px) {
|
|
||||||
.topbar {
|
.topbar {
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
}
|
}
|
||||||
@@ -1126,8 +1112,7 @@ fix(api): scope session query</code></pre>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: legacy breakpoint from rules/styles.css; design-system-keeper */
|
@media (max-width: 560px) {
|
||||||
@media (max-width: 600px) {
|
|
||||||
main {
|
main {
|
||||||
padding-inline: 18px;
|
padding-inline: 18px;
|
||||||
}
|
}
|
||||||
@@ -1171,7 +1156,7 @@ fix(api): scope session query</code></pre>
|
|||||||
padding: 28px 22px;
|
padding: 28px 22px;
|
||||||
}
|
}
|
||||||
.stage-number {
|
.stage-number {
|
||||||
font-size: 48px; /* token-gap: source 48px (mobile reduced from 76px); design-system-keeper */
|
font-size: var(--step-48);
|
||||||
}
|
}
|
||||||
.stage-detail pre {
|
.stage-detail pre {
|
||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
|
|||||||
@@ -140,8 +140,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
|
|
||||||
.package-tree {
|
.package-tree {
|
||||||
padding: 22px 16px;
|
padding: 22px 16px;
|
||||||
/* token-gap: legacy 1px solid #426070 over --ink; no token matches; owner design-system-keeper */
|
border-right: 1px solid var(--muted);
|
||||||
border-right: 1px solid #426070;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +148,8 @@ const packageFiles: PackageFile[] = [
|
|||||||
.package-preview > span {
|
.package-preview > span {
|
||||||
margin: 0 0 14px;
|
margin: 0 0 14px;
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
/* token-gap: source uses 11px monospace; no --step-* covers 11px on this surface; design-system-keeper */
|
|
||||||
font:
|
font:
|
||||||
700 11px / 1.35 ui-monospace,
|
700 var(--step-0) / 1.35 ui-monospace,
|
||||||
monospace;
|
monospace;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
}
|
}
|
||||||
@@ -166,8 +164,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
border: 0;
|
border: 0;
|
||||||
border-left: 2px solid transparent;
|
border-left: 2px solid transparent;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
/* token-gap: legacy #d6e1e4 over --ink tree buttons; no token matches; design-system-keeper */
|
color: var(--line);
|
||||||
color: #d6e1e4;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
transition:
|
||||||
@@ -180,8 +177,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
.package-tree button:focus-visible,
|
.package-tree button:focus-visible,
|
||||||
.package-tree button.active {
|
.package-tree button.active {
|
||||||
border-left-color: var(--gold);
|
border-left-color: var(--gold);
|
||||||
/* token-gap: legacy #1f3a4b active/hover on --ink tree; no token matches; design-system-keeper */
|
background: var(--blue);
|
||||||
background: #1f3a4b;
|
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,8 +194,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
.package-tree small {
|
.package-tree small {
|
||||||
/* token-gap: legacy #aebfc7 muted text on --ink tree; no token matches; design-system-keeper */
|
color: var(--muted);
|
||||||
color: #aebfc7;
|
|
||||||
font:
|
font:
|
||||||
11px / 1.25 Arial,
|
11px / 1.25 Arial,
|
||||||
sans-serif;
|
sans-serif;
|
||||||
@@ -209,8 +204,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
.package-preview {
|
.package-preview {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 26px;
|
padding: 26px;
|
||||||
/* token-gap: legacy #173245 preview surface (between --ink and --blue); no token matches; design-system-keeper */
|
background: var(--ink);
|
||||||
background: #173245;
|
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,8 +218,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
.package-preview p {
|
.package-preview p {
|
||||||
max-width: 52ch;
|
max-width: 52ch;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/* token-gap: legacy #d6e1e4 muted text on #173245 preview; no token matches; design-system-keeper */
|
color: var(--line);
|
||||||
color: #d6e1e4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.package-preview pre {
|
.package-preview pre {
|
||||||
@@ -233,19 +226,17 @@ const packageFiles: PackageFile[] = [
|
|||||||
margin: 20px 0 0;
|
margin: 20px 0 0;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
/* token-gap: legacy #466274 rule on preview pre border; no token matches; design-system-keeper */
|
border: 1px solid var(--muted);
|
||||||
border: 1px solid #466274;
|
background: var(--deep);
|
||||||
/* token-gap: legacy #102837 fill on preview pre background; no token matches; design-system-keeper */
|
color: var(--line);
|
||||||
background: #102837;
|
|
||||||
/* token-gap: legacy #d6e1e4 code text on #102837; no token matches; design-system-keeper */
|
|
||||||
color: #d6e1e4;
|
|
||||||
font:
|
font:
|
||||||
12px / 1.55 ui-monospace,
|
12px / 1.55 ui-monospace,
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.package-preview.is-swapping {
|
.package-preview.is-swapping {
|
||||||
animation: package-preview-in 0.34s ease both;
|
/* purpose: review-desk detail swap on selection (state change) */
|
||||||
|
animation: package-preview-in 200ms cubic-bezier(0.2, 0, 0, 1) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes package-preview-in {
|
@keyframes package-preview-in {
|
||||||
@@ -265,16 +256,15 @@ const packageFiles: PackageFile[] = [
|
|||||||
}
|
}
|
||||||
.package-tree {
|
.package-tree {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
/* token-gap: legacy #426070 mobile layout rule over --ink; no token matches; design-system-keeper */
|
/* token-gap: legacy var(--muted) mobile layout rule over --ink; no token matches; design-system-keeper */
|
||||||
border-bottom: 1px solid #426070;
|
border-bottom: 1px solid var(--muted);
|
||||||
}
|
}
|
||||||
.package-preview {
|
.package-preview {
|
||||||
padding: 22px;
|
padding: 22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: legacy 520px breakpoint from skills/styles.css (mobile phone), not in named set 560/800/1100/1600/2200; design-system-keeper */
|
@media (max-width: 560px) {
|
||||||
@media (max-width: 520px) {
|
|
||||||
.package-tree {
|
.package-tree {
|
||||||
padding: 18px 10px;
|
padding: 18px 10px;
|
||||||
}
|
}
|
||||||
@@ -288,8 +278,7 @@ const packageFiles: PackageFile[] = [
|
|||||||
padding: 18px;
|
padding: 18px;
|
||||||
}
|
}
|
||||||
.package-preview pre {
|
.package-preview pre {
|
||||||
/* token-gap: legacy 11px mobile font-size from skills/styles.css; no --step-* covers 11px; design-system-keeper */
|
font-size: var(--step-0);
|
||||||
font-size: 11px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,7 @@
|
|||||||
"en": "Drop the style for security warnings, irreversible actions, and sequences where terse wording can be misread.",
|
"en": "Drop the style for security warnings, irreversible actions, and sequences where terse wording can be misread.",
|
||||||
"pt": "Abandone o estilo em alertas de segurança, ações irreversíveis e sequências onde concisão pode causar erro."
|
"pt": "Abandone o estilo em alertas de segurança, ações irreversíveis e sequências onde concisão pode causar erro."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/JuliusBrussee/caveman/blob/3b74643f4d910f496babd4e634b1ba7168816f14/skills/caveman/SKILL.md"
|
"source": "https://github.com/JuliusBrussee/caveman/blob/3b74643f4d910f496babd4e634b1ba7168816f14/skills/caveman/SKILL.md",
|
||||||
|
"label": { "en": "COMMUNICATE", "pt": "COMUNICAR" },
|
||||||
|
"tagline": { "en": "signal without filler", "pt": "sinal sem excesso" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,7 @@
|
|||||||
"en": "Do not jump from error message straight to a patch.",
|
"en": "Do not jump from error message straight to a patch.",
|
||||||
"pt": "Não pule da mensagem de erro direto para um patch."
|
"pt": "Não pule da mensagem de erro direto para um patch."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/diagnosing-bugs/SKILL.md"
|
"source": "https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/diagnosing-bugs/SKILL.md",
|
||||||
|
"label": { "en": "DIAGNOSE", "pt": "DIAGNOSTICAR" },
|
||||||
|
"tagline": { "en": "tight feedback loop", "pt": "ciclo curto de feedback" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,7 @@
|
|||||||
"en": "Never simplify away security, accessibility, validation, or real edge cases.",
|
"en": "Never simplify away security, accessibility, validation, or real edge cases.",
|
||||||
"pt": "Nunca simplifique segurança, acessibilidade, validação ou casos extremos reais."
|
"pt": "Nunca simplifique segurança, acessibilidade, validação ou casos extremos reais."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/ilindaniel/ponytail-lite/blob/e7b42dc2d384a702240dea4d52a7bf5530b821b6/AGENTS.md"
|
"source": "https://github.com/ilindaniel/ponytail-lite/blob/e7b42dc2d384a702240dea4d52a7bf5530b821b6/AGENTS.md",
|
||||||
|
"label": { "en": "SIMPLIFY", "pt": "SIMPLIFICAR" },
|
||||||
|
"tagline": { "en": "minimum code that holds", "pt": "código mínimo que funciona" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,5 +16,7 @@
|
|||||||
"en": "Practitioner articles add context; they do not override official behavior.",
|
"en": "Practitioner articles add context; they do not override official behavior.",
|
||||||
"pt": "Artigos de praticantes dão contexto; não substituem comportamento oficial."
|
"pt": "Artigos de praticantes dão contexto; não substituem comportamento oficial."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/research/SKILL.md"
|
"source": "https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/research/SKILL.md",
|
||||||
|
"label": { "en": "INVESTIGATE", "pt": "INVESTIGAR" },
|
||||||
|
"tagline": { "en": "primary sources first", "pt": "fontes primárias primeiro" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,7 @@
|
|||||||
"en": "Pin the comparison point and source specification before reviewing.",
|
"en": "Pin the comparison point and source specification before reviewing.",
|
||||||
"pt": "Fixe o ponto de comparação e a especificação antes de revisar."
|
"pt": "Fixe o ponto de comparação e a especificação antes de revisar."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/code-review/SKILL.md"
|
"source": "https://github.com/mattpocock/skills/blob/6654f6b60cd9d5be8b54c6fafe44346dabeb3b76/skills/engineering/code-review/SKILL.md",
|
||||||
|
"label": { "en": "REVIEW", "pt": "REVISAR" },
|
||||||
|
"tagline": { "en": "standards × spec", "pt": "padrões × especificação" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,7 @@
|
|||||||
"en": "Read raw output when exact wording or full diffs matter.",
|
"en": "Read raw output when exact wording or full diffs matter.",
|
||||||
"pt": "Leia saída bruta quando texto exato ou diffs completos importarem."
|
"pt": "Leia saída bruta quando texto exato ou diffs completos importarem."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/aetox-skills/token-saver/blob/8f21188bb043fad411f47e2e57f0365a83c13da7/SKILL.md"
|
"source": "https://github.com/aetox-skills/token-saver/blob/8f21188bb043fad411f47e2e57f0365a83c13da7/SKILL.md",
|
||||||
|
"label": { "en": "ECONOMIZE", "pt": "ECONOMIZAR" },
|
||||||
|
"tagline": { "en": "compress noisy output", "pt": "comprima saídas ruidosas" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,7 @@
|
|||||||
"en": "Too heavy for trivial edits or factual answers.",
|
"en": "Too heavy for trivial edits or factual answers.",
|
||||||
"pt": "Pesado demais para edições triviais ou respostas factuais."
|
"pt": "Pesado demais para edições triviais ou respostas factuais."
|
||||||
},
|
},
|
||||||
"source": "https://github.com/Leonxlnx/unlazy/blob/473d4b80421c36d733042434cd4b938f81a19ef1/SKILL.md"
|
"source": "https://github.com/Leonxlnx/unlazy/blob/473d4b80421c36d733042434cd4b938f81a19ef1/SKILL.md",
|
||||||
|
"label": { "en": "COMPLETE", "pt": "CONCLUIR" },
|
||||||
|
"tagline": { "en": "gates and evidence", "pt": "gates e evidências" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,12 +235,22 @@ const skillWorkflow = defineCollection({
|
|||||||
// render time; here it is embedded to match the GuideSelectorData
|
// render time; here it is embedded to match the GuideSelectorData
|
||||||
// contract. If the URL changes, both this entry and the matching
|
// contract. If the URL changes, both this entry and the matching
|
||||||
// `skillSources/<id>.json` must be updated.
|
// `skillSources/<id>.json` must be updated.
|
||||||
|
//
|
||||||
|
// `label` and `tagline` are the selector *button*'s two lines and are
|
||||||
|
// distinct from `kind` and `use`, which belong to the detail panel:
|
||||||
|
// ponytail's button reads "SIMPLIFY" / "minimum code that holds" while its
|
||||||
|
// panel reads "SIMPLIFICATION INSTINCT" and a paragraph of prose. They are
|
||||||
|
// not in `interactiveCopy` — the English lives in the `full-guide/index.html`
|
||||||
|
// markup and the Portuguese in `translations.pt` — so task 05b had nothing
|
||||||
|
// to migrate them from and the page rendered `kind`/`use` on the buttons.
|
||||||
const commonSkills = defineCollection({
|
const commonSkills = defineCollection({
|
||||||
type: 'data',
|
type: 'data',
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
number: z.string(),
|
number: z.string(),
|
||||||
kind: localized,
|
kind: localized,
|
||||||
|
label: localized,
|
||||||
|
tagline: localized,
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
rule: localized,
|
rule: localized,
|
||||||
use: localized,
|
use: localized,
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
import BaseLayout from './BaseLayout.astro';
|
import BaseLayout from './BaseLayout.astro';
|
||||||
import TopBar from '../components/blocks/TopBar.astro';
|
import TopBar from '../components/blocks/TopBar.astro';
|
||||||
import SiteFooter from '../components/blocks/SiteFooter.astro';
|
import SiteFooter from '../components/blocks/SiteFooter.astro';
|
||||||
import chaptersStylesheet from '../../chapters.css?url';
|
import chaptersStylesheet from '../../legacy/styles/chapters.css?url';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
+2231
-58
File diff suppressed because it is too large
Load Diff
+5
-14
@@ -84,10 +84,8 @@ const cards = data.cards ?? [];
|
|||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background: var(--ink);
|
background: var(--ink);
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
/* token-gap: legacy 12px fixed from landing.css; --step-0 is 11px;
|
|
||||||
no token matches 12px; owner design-system-keeper */
|
|
||||||
font:
|
font:
|
||||||
700 12px ui-monospace,
|
700 var(--step-12) ui-monospace,
|
||||||
monospace;
|
monospace;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -104,8 +102,7 @@ const cards = data.cards ?? [];
|
|||||||
|
|
||||||
.guide-launch span {
|
.guide-launch span {
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
/* token-gap: legacy 22px arrow from landing.css; no --step-* covers 22px; owner design-system-keeper */
|
font-size: var(--step-22);
|
||||||
font-size: 22px;
|
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,25 +118,19 @@ const cards = data.cards ?? [];
|
|||||||
|
|
||||||
.landing-note span {
|
.landing-note span {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
/* token-gap: legacy 11px monospace eyebrow (no DM Mono in stack);
|
|
||||||
--step-0 also resolves to 11px but with different font weight;
|
|
||||||
preserved verbatim from landing.css; owner design-system-keeper */
|
|
||||||
font:
|
font:
|
||||||
700 11px ui-monospace,
|
700 var(--step-0) ui-monospace,
|
||||||
monospace;
|
monospace;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-note strong {
|
.landing-note strong {
|
||||||
/* token-gap: clamp(22px,3.2vw,42px) is specific to this strip; no
|
font-size: var(--step-4);
|
||||||
--step-* ramp covers both endpoints; owner design-system-keeper */
|
|
||||||
font-size: clamp(22px, 3.2vw, 42px);
|
|
||||||
line-height: 1.08;
|
line-height: 1.08;
|
||||||
letter-spacing: -0.04em;
|
letter-spacing: -0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* token-gap: 520px collapse from landing.css (was bottom-bar threshold); owner design-system-keeper */
|
@media (max-width: 560px) {
|
||||||
@media (max-width: 520px) {
|
|
||||||
.landing-note {
|
.landing-note {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 9px;
|
gap: 9px;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import reviewStylesheet from '../../skills-review/styles.css?url';
|
import reviewStylesheet from '../../legacy/styles/skills-review.css?url';
|
||||||
import changeLensStylesheet from '../../skills-review/change-lens.css?url';
|
import changeLensStylesheet from '../../legacy/styles/change-lens.css?url';
|
||||||
|
|
||||||
const reviews = await getCollection('reviews');
|
const reviews = await getCollection('reviews');
|
||||||
const reviewOrder = [
|
const reviewOrder = [
|
||||||
@@ -152,6 +152,6 @@ const serializedCatalog = JSON.stringify(catalog).replace(/</g, '\\u003c');
|
|||||||
window.__SKILLS_REVIEW_CATALOG = JSON.parse(serializedCatalog);
|
window.__SKILLS_REVIEW_CATALOG = JSON.parse(serializedCatalog);
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
import '../../skills-review/app.js';
|
import '../../legacy/skills-review/app.js';
|
||||||
</script>
|
</script>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { getEntry } from 'astro:content';
|
|||||||
import ChapterLayout from '../layouts/ChapterLayout.astro';
|
import ChapterLayout from '../layouts/ChapterLayout.astro';
|
||||||
import ChapterHero from '../components/blocks/ChapterHero.astro';
|
import ChapterHero from '../components/blocks/ChapterHero.astro';
|
||||||
import SkillPackageExplorer from '../components/islands/SkillPackageExplorer.astro';
|
import SkillPackageExplorer from '../components/islands/SkillPackageExplorer.astro';
|
||||||
import skillsStylesheet from '../../skills/styles.css?url';
|
import skillsStylesheet from '../../legacy/styles/skills.css?url';
|
||||||
|
|
||||||
// Required-field guard. The chapters schema marks section eyebrow /
|
// Required-field guard. The chapters schema marks section eyebrow /
|
||||||
// panelLabel / panelCode / steps / copy as optional because the schema
|
// panelLabel / panelCode / steps / copy as optional because the schema
|
||||||
|
|||||||
@@ -20,8 +20,106 @@
|
|||||||
--step-6: clamp(36px, 5vw, 65px);
|
--step-6: clamp(36px, 5vw, 65px);
|
||||||
--step-5: clamp(24px, 3vw, 38px);
|
--step-5: clamp(24px, 3vw, 38px);
|
||||||
--step-4: clamp(22px, 3vw, 36px);
|
--step-4: clamp(22px, 3vw, 36px);
|
||||||
|
--step-48: 48px;
|
||||||
|
--step-32: 32px;
|
||||||
|
--step-30: 30px;
|
||||||
|
--step-25: 25px;
|
||||||
|
--step-24: 24px;
|
||||||
|
--step-22: 22px;
|
||||||
|
--step-20: 20px;
|
||||||
|
--step-18: 18px;
|
||||||
|
--step-17: 17px;
|
||||||
|
--step-16: 16px;
|
||||||
--step-1: 15px;
|
--step-1: 15px;
|
||||||
|
--step-14: 14px;
|
||||||
|
--step-13: 13px;
|
||||||
|
--step-12: 12px;
|
||||||
--step-0: 11px;
|
--step-0: 11px;
|
||||||
|
--step-00: 10px;
|
||||||
|
--step-09: 9px;
|
||||||
|
--step-08: 8px;
|
||||||
|
--step-42: 42px;
|
||||||
|
--step-code: 10.5px;
|
||||||
|
--step-exercise-title: clamp(20px, 2.6vw, 34px);
|
||||||
|
--step-skill-title: clamp(30px, 4vw, 60px);
|
||||||
|
--step-pull-quote: clamp(20px, 2.5vw, 34px);
|
||||||
|
--step-display-wide: clamp(150px, 7vw, 220px);
|
||||||
|
|
||||||
|
/* On-dark palette. These are the values the source actually uses on the
|
||||||
|
--ink and --accent surfaces. They are not near-misses of the palette
|
||||||
|
above: --muted is a dark-on-light value and is illegible on --ink, and
|
||||||
|
--paper is warm where the text over --accent is violet-tinted. Each one
|
||||||
|
is the exact hex the legacy stylesheets ship. */
|
||||||
|
--ink-muted: #9eb0bb;
|
||||||
|
--ink-line: #b8c8d2;
|
||||||
|
--ink-code: #c9d5dc;
|
||||||
|
--accent-paper: #eceaf5;
|
||||||
|
--accent-paper-active: #f0eef8;
|
||||||
|
--accent-surface: #5b7098;
|
||||||
|
|
||||||
|
/* Overlays. The percentages are the exact 8-bit alphas the source ships
|
||||||
|
(#ffffff24, #ffffff3a, #ffffff40, #ffffff50), not rounded to whole
|
||||||
|
numbers — the names are rounded, the values are not. */
|
||||||
|
--white-14: rgb(255 255 255 / 14.1176%);
|
||||||
|
--white-23: rgb(255 255 255 / 22.7451%);
|
||||||
|
--white-25: rgb(255 255 255 / 25.098%);
|
||||||
|
--white-31: rgb(255 255 255 / 31.3725%);
|
||||||
|
|
||||||
|
/* Full-guide supporting surfaces. These preserve the exact values from the
|
||||||
|
legacy responsive layer; they are deliberately not substitutions from the
|
||||||
|
light-background palette. */
|
||||||
|
--guide-toolbar-text: #9eabb4;
|
||||||
|
--guide-panel-blue: #244760;
|
||||||
|
--guide-worker-detail-surface: #edf0f1;
|
||||||
|
--guide-tree-rule: #41596b;
|
||||||
|
--guide-code-surface: #0b1b27;
|
||||||
|
--guide-tree-shadow: #081621;
|
||||||
|
--guide-live: #80c69a;
|
||||||
|
--guide-live-glow: #80c69a22;
|
||||||
|
--guide-grid-line: #ffffff06;
|
||||||
|
--guide-tree-border: #527085;
|
||||||
|
--guide-tree-node: #112a3b;
|
||||||
|
--guide-tree-node-label: #8ca1af;
|
||||||
|
--guide-tree-node-muted: #a9b6be;
|
||||||
|
--guide-tree-node-hover: #1c425a;
|
||||||
|
--guide-gold-glow: #efc76b18;
|
||||||
|
--guide-tree-detail-text: #aebbc3;
|
||||||
|
--guide-phase-hover: #e8ecee;
|
||||||
|
--guide-meter-border: #496274;
|
||||||
|
--guide-route-copy: #b5c0c7;
|
||||||
|
--guide-skill-hover: #315f80;
|
||||||
|
--guide-skill-copy: #c4cdd3;
|
||||||
|
--guide-skill-index-hover: #eceff0;
|
||||||
|
--guide-provider-copy: #cbd9e1;
|
||||||
|
--guide-provider-detail-copy: #b7c7d1;
|
||||||
|
--guide-provider-rule: #ffffff2b;
|
||||||
|
--guide-effort-surface: #18364a;
|
||||||
|
--guide-effort-copy: #aebfc9;
|
||||||
|
--guide-effort-active: #e9ecee;
|
||||||
|
--guide-effort-active-copy: #eeedf6;
|
||||||
|
--guide-builder-surface: #132b3b;
|
||||||
|
--guide-builder-rule: #ffffff30;
|
||||||
|
--guide-builder-action-copy: #d5dde2;
|
||||||
|
--guide-builder-copy: #a9bcc8;
|
||||||
|
--guide-builder-border: #344c5d;
|
||||||
|
--guide-builder-live-glow: #80c69a20;
|
||||||
|
--guide-builder-code: #bed0dc;
|
||||||
|
--guide-builder-grid: #ffffff05;
|
||||||
|
--guide-verify-rule: #ffffff1f;
|
||||||
|
--guide-verify-copy: #bfccd4;
|
||||||
|
--guide-verify-code: #0f2230;
|
||||||
|
--guide-verify-code-border: #2a4150;
|
||||||
|
--guide-accent-rule: #ffffff42;
|
||||||
|
--guide-accent-note: #6c6898;
|
||||||
|
--guide-accent-copy: #f1f0f7;
|
||||||
|
--guide-install-rule: #ffffff2d;
|
||||||
|
--guide-install-copy: #b9c8d1;
|
||||||
|
--guide-control-border: #ffffff50;
|
||||||
|
--guide-exercise-copy: #afbec7;
|
||||||
|
--guide-prompt-surface: #19364a;
|
||||||
|
--guide-prompt-enhanced: #596f9a;
|
||||||
|
--guide-prompt-rule: #ffffff32;
|
||||||
|
--guide-prompt-enhanced-copy: #e5e3ef;
|
||||||
|
|
||||||
/* Breakpoints */
|
/* Breakpoints */
|
||||||
--bp-sm: 560px;
|
--bp-sm: 560px;
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<title>AI For Dummies — Route map</title>
|
|
||||||
<link rel="stylesheet" href="../chapters.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<header class="top">
|
|
||||||
<a href="../full-guide/">← AI FOR DUMMIES</a><span>00 / ROUTE MAP</span
|
|
||||||
><a href="../skills-review/">review desk ↗</a>
|
|
||||||
</header>
|
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">Start here</p>
|
|
||||||
<h1>Ship the<br /><em>system.</em></h1>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section class="grid">
|
|
||||||
<article class="card">
|
|
||||||
<b>01</b>
|
|
||||||
<h2>Models</h2>
|
|
||||||
<p>Capability and effort are separate knobs.</p>
|
|
||||||
<a href="../models/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>02</b>
|
|
||||||
<h2>Agents & trees</h2>
|
|
||||||
<p>Bound roles, handoffs, and worktrees.</p>
|
|
||||||
<a href="../agents/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>03</b>
|
|
||||||
<h2>Skills</h2>
|
|
||||||
<p>Capture repeatable decisions.</p>
|
|
||||||
<a href="../skills/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>04</b>
|
|
||||||
<h2>Rules</h2>
|
|
||||||
<p>Connect guidance to enforcement.</p>
|
|
||||||
<a href="../rules/">Open chapter →</a>
|
|
||||||
</article>
|
|
||||||
<article class="card">
|
|
||||||
<b>05</b>
|
|
||||||
<h2>Practice</h2>
|
|
||||||
<p>Compare prompts and skill-enabled runs.</p>
|
|
||||||
<a href="../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="../skills-review/">Open desk →</a>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
<nav class="links">
|
|
||||||
<a href="../full-guide/">Full field guide</a
|
|
||||||
><a href="../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>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user