diff --git a/.agents/snapshots/summary.txt b/.agents/snapshots/summary.txt new file mode 100644 index 0000000..ef6a6e4 --- /dev/null +++ b/.agents/snapshots/summary.txt @@ -0,0 +1,35 @@ +AI For Dummies — Route map +← AI FOR DUMMIES +00 / ROUTE MAP +review desk ↗ +Start here +Ship the +system. +This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result. +01 +Models +Capability and effort are separate knobs. +Open chapter → +02 +Agents & trees +Bound roles, handoffs, and worktrees. +Open chapter → +03 +Skills +Capture repeatable decisions. +Open chapter → +04 +Rules +Connect guidance to enforcement. +Open chapter → +05 +Practice +Compare prompts and skill-enabled runs. +Open lab → +06 +Review desk +Browse original files and improved drafts. +Open desk → +Full field guide +Operations guide +Each chapter stands alone; the order follows a real task becoming a reliable change. diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml new file mode 100644 index 0000000..f425742 --- /dev/null +++ b/.gitea/workflows/verify.yml @@ -0,0 +1,55 @@ +# Tier 3 gate and publication. The act-runner registration is stored in an +# emptyDir: after a runner pod restart, re-register it before debugging CI. +name: verify-and-publish + +on: + push: + branches: [main] + pull_request: + +jobs: + gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm install --package-lock=false --prefer-offline + - run: npm run lint + - run: ./.agents/scripts/gate.sh + - name: visual regression + run: | + npx playwright install --with-deps chromium + node .agents/scripts/visual-regression.mjs + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots + path: .agents/snapshots/diff/ + + publish: + if: github.ref == 'refs/heads/main' + needs: gate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm install --package-lock=false --prefer-offline + - run: npm run build + - name: Publish generated site to pages + run: | + git config user.name 'gitea-actions[bot]' + git config user.email 'gitea-actions[bot]@users.noreply.local' + git switch --orphan pages + git rm -rf . + cp -a dist/. . + git add --all + git commit -m 'chore: publish site' + git push --force origin HEAD:pages diff --git a/.lintstagedrc.json b/.lintstagedrc.json index dbaa4bf..cb3fa14 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,16 +1,6 @@ { - "*.{js,mjs,ts,astro}": [ - "prettier --write", - "eslint --fix --max-warnings=0" - ], - "*.css": [ - "prettier --write", - "stylelint --fix --max-warnings=0" - ], - "*.{astro,css}": [ - "node .agents/scripts/check-tokens.mjs" - ], - "*.{json,md,yml,yaml}": [ - "prettier --write" - ] + "*.{js,mjs,ts,astro}": ["prettier --write", "eslint --fix --max-warnings=0 --no-warn-ignored"], + "*.css": ["prettier --write", "stylelint --fix --max-warnings=0"], + "src/**/*.{astro,css}": ["node .agents/scripts/check-tokens.mjs"], + "*.{json,md,yml,yaml}": ["prettier --write"] } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..4bbd663 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +dist +node_modules +public/hands-on +submitted-skills +skill-reviews +vote-service +.agents/snapshots diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..596e8f0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,11 @@ +{ + "printWidth": 100, + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "plugins": ["prettier-plugin-astro"], + "overrides": [ + { "files": "*.astro", "options": { "parser": "astro" } }, + { "files": "*.md", "options": { "proseWrap": "always", "printWidth": 80 } } + ] +} diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..53cc03d --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,21 @@ +{ + "extends": ["stylelint-config-standard"], + "ignoreFiles": [ + "dist/**", + "hands-on/**", + "public/hands-on/**", + "submitted-skills/**", + "skill-reviews/**", + "vote-service/**" + ], + "rules": { + "custom-property-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$", + "declaration-property-value-disallowed-list": { + "/^transition/": ["/width/", "/height/", "/^top/", "/^left/", "/margin/"], + "/^animation/": ["/width/", "/height/"] + }, + "media-feature-name-no-unknown": true, + "no-descending-specificity": null, + "selector-class-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" + } +} diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..da12024 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,6 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + base: '/ai-for-dummies', + trailingSlash: 'always', +}); diff --git a/docs/operations-guide.md b/docs/operations-guide.md index 0162b53..6fa6ea8 100644 --- a/docs/operations-guide.md +++ b/docs/operations-guide.md @@ -18,45 +18,45 @@ worktree practices taught by the presentation fit together. ## Quick links -| Resource | Location | -| :--- | :--- | -| Live presentation | [https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/](https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/) | -| Gitea repository | [https://git.marcospaulo.dev.br/netcracker/ai-for-dummies](https://git.marcospaulo.dev.br/netcracker/ai-for-dummies) | -| Local checkout | `/home/marcos/Projects/ai-for-dummies` | -| Source branch | `main` | -| Published branch | `pages` | -| Local verification | `npm run verify` | -| SilverBullet page | `Guides/AI For Dummies Presentation` | -| Skills-review vote API | `vote-service/` — separate pod, see `vote-service/README.md` | +| Resource | Location | +| :--------------------- | :------------------------------------------------------------------------------------------------------------------------- | +| Live presentation | [https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/](https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/) | +| Gitea repository | [https://git.marcospaulo.dev.br/netcracker/ai-for-dummies](https://git.marcospaulo.dev.br/netcracker/ai-for-dummies) | +| Local checkout | `/home/marcos/Projects/ai-for-dummies` | +| Source branch | `main` | +| Published branch | `pages` | +| Local verification | `npm run verify` | +| SilverBullet page | `Guides/AI For Dummies Presentation` | +| Skills-review vote API | `vote-service/` — separate pod, see `vote-service/README.md` | ## How the site is built -The presentation is deliberately dependency-free. Gitea Pages serves the -repository files directly; there is no bundler or generated `dist/` folder. +The presentation is authored in Astro. `main` holds source; the Gitea Pages +Server still serves a branch directly, so the `pages` branch holds generated +`dist/` output and is not a source branch. -| File | Responsibility | -| :--- | :--- | -| `index.html` | Default route map and focused chapter navigation | -| `full-guide/index.html` | Complete bilingual field guide, controls, labels, and English source copy | -| `styles.css` | Base editorial visual system | -| `responsive.css` | Interactive diagrams and Full HD, 4K, tablet, and mobile adaptations | -| `app.js` | Interactions, state, and Portuguese translations | -| `scripts/verify.mjs` | Content and interaction contract checks | -| `docs/references/` | Primary documentation and additional reading | +| File | Responsibility | +| :---------------------------- | :--------------------------------------------------------- | +| `src/` | Astro routes, layouts, components, and source styles | +| `public/hands-on/` | Verbatim vanilla lab fixtures; Astro does not process them | +| `astro.config.mjs` | Static build with `base: '/ai-for-dummies'` | +| `scripts/verify.mjs` | Content and interaction contract checks | +| `.gitea/workflows/verify.yml` | Gate, build, and machine-owned publication to `pages` | -The English HTML is the fallback when JavaScript is unavailable. Portuguese -copy is applied by `app.js`; the language preference is stored in -`localStorage`, and the document language changes to `pt-BR`. +Astro ships no JavaScript by default. Interactive islands opt in per component; +the bilingual behaviour remains a client-side concern until its dedicated +migration task. ## Normal edit and publish workflow ```mermaid flowchart LR - E[Edit main] --> V[npm run verify] + E[Edit main] --> V[npm run gate] V --> C[Commit] - C --> M[Push main] - M --> P[Fast-forward pages] - P --> S[Gitea Pages Server] + C --> P[Push main] + P --> G[Gitea Actions: gate + build] + G --> B[Force-push dist to pages] + B --> S[Gitea Pages Server] S --> L[Live URL] ``` @@ -69,41 +69,36 @@ git pull --ff-only git status --short --branch ``` -Do not overwrite unrelated local changes. The untracked -`scripts/inspect.py` and `scripts/__pycache__/` are local visual-test artifacts -and are intentionally not part of the published site. +Do not overwrite unrelated local changes. The untracked `scripts/inspect.py` and +`scripts/__pycache__/` are local visual-test artifacts and are intentionally not +part of the published site. ### 2. Preview locally ```bash -python3 -m http.server 4173 +npm run dev ``` -Open [http://localhost:4173](http://localhost:4173). Check English and -Portuguese, keyboard focus, the interactive panels, and at least one desktop -and one mobile viewport. +Open +[http://localhost:4321/ai-for-dummies/](http://localhost:4321/ai-for-dummies/). +Check English and Portuguese, keyboard focus, the interactive panels, and at +least one desktop and one mobile viewport. The production site is under the +`/ai-for-dummies/` base path, so do not test only root-relative URLs. ### 3. Verify before committing ```bash -npm run verify -node --check app.js -node scripts/audit-ui.mjs -git diff --check +npm run gate ``` -Expected project verifier output: - -```text -content verification passed -interaction verification passed -standalone verification passed -``` +The gate runs Astro types and build checks, content contracts, the runtime +dependency audit, and the token check. It also refuses a reduced count of +`verify.mjs` assertions. ### 4. Commit and push the source branch ```bash -git add README.md app.js index.html full-guide/ styles.css responsive.css scripts/verify.mjs scripts/audit-ui.mjs docs/ +git add git commit -m "feat: describe the change" git push origin main ``` @@ -111,54 +106,44 @@ git push origin main Stage only files that belong to the change. Review `git status --short` before committing. -### 5. Fast-forward the published branch +### 5. CI publication and its manual fallback -Use a temporary worktree so the current checkout stays on `main`: +On a successful `main` push, Gitea Actions builds `dist/` and force-pushes it to +`pages`. This force-push is intentional: `pages` is machine-owned generated +output, and no person or other workflow may write it. + +This Gitea's act-runner registration is kept in an `emptyDir`. A pod restart +silently removes the registration; if a site does not update, check and +re-register the runner before changing the workflow. + +If the runner is unavailable, use the following manual fallback from a clean +`main` checkout. It deliberately replaces the generated branch tree only: ```bash -git worktree add /tmp/ai-for-dummies-pages pages -git -C /tmp/ai-for-dummies-pages merge --ff-only origin/pages # local pages is often stale -git -C /tmp/ai-for-dummies-pages merge --no-edit main -git -C /tmp/ai-for-dummies-pages push origin pages +npm install --package-lock=false --prefer-offline +npm run build +git worktree add /tmp/ai-for-dummies-pages --detach pages +git -C /tmp/ai-for-dummies-pages rm -rf . +cp -a dist/. /tmp/ai-for-dummies-pages/ +git -C /tmp/ai-for-dummies-pages add --all +git -C /tmp/ai-for-dummies-pages commit -m "chore: publish site" +git -C /tmp/ai-for-dummies-pages push --force origin HEAD:pages git worktree remove /tmp/ai-for-dummies-pages ``` -The `pages` branch should represent the exact published source. Avoid editing -it directly and avoid force-pushing it. - -The two histories have diverged — `pages` carries merge commits and -cherry-picked duplicates of `main` commits — so `merge --ff-only main` fails -with `Not possible to fast-forward`. A normal merge is correct here, and -matches the `Merge branch 'main' into pages` commits already on the branch. -The invariant worth checking is the *tree*, not the history: - -```bash -git rev-parse main^{tree} pages^{tree} # must print the same hash twice -``` - -If the merge conflicts (duplicated commits touching the same lines will do -it), resolve by taking `main` wholesale, since `main` is the source of truth -for published content: - -```bash -git -C /tmp/ai-for-dummies-pages checkout main -- . -git -C /tmp/ai-for-dummies-pages add -A -git -C /tmp/ai-for-dummies-pages diff --cached main --stat # must be empty -git -C /tmp/ai-for-dummies-pages commit --no-edit -``` - ### 6. Verify the deployment ```bash -curl -I https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/ +curl -sS -o /dev/null -w '%{http_code}\n' \ + "https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/summary/?v=$(git rev-parse --short HEAD)" ``` -If the edge still shows an older page, retry with the current commit as a -cache-busting query: +Also check a nested static asset; base-path problems usually appear on assets +first: ```bash -git rev-parse --short HEAD -curl -I "https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/?v=COMMIT" +curl -sS -o /dev/null -w '%{http_code}\n' \ + "https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/hands-on/starter/?v=$(git rev-parse --short HEAD)" ``` The correct URL pattern is **owner subdomain + repository path**: @@ -173,12 +158,12 @@ certificate. ## Skills-review vote service -`skills-review/` is served by the same static Pages Server as the rest of -this site, so it cannot itself remember votes. `vote-service/` is a separate -Go API on its own pod for that: one JSON file as the store, one vote per -visitor enforced by IP (a MAC address never reaches a server across the -internet). It is deployed independently of `main`/`pages` — the site can be -republished without touching it, and vice versa. +`skills-review/` is served by the same static Pages Server as the rest of this +site, so it cannot itself remember votes. `vote-service/` is a separate Go API +on its own pod for that: one JSON file as the store, one vote per visitor +enforced by IP (a MAC address never reaches a server across the internet). It is +deployed independently of `main`/`pages` — the site can be republished without +touching it, and vice versa. ```bash cd vote-service @@ -197,23 +182,22 @@ microk8s kubectl apply -f deploy/ingress.yaml microk8s kubectl -n ai-for-dummies rollout restart deploy ai-for-dummies-vote ``` -Namespace `ai-for-dummies`, `ingressClassName: public`, no per-ingress TLS. -The Deployment is pinned to node `kubernets` with a `nodeSelector`: the +Namespace `ai-for-dummies`, `ingressClassName: public`, no per-ingress TLS. The +Deployment is pinned to node `kubernets` with a `nodeSelector`: the `microk8s-hostpath` PV carries a `nodeAffinity` for whichever node first binds it, so scheduling and storage have to agree on one node. The vote widget's browser-side `fetch` calls must reach the API over the public -internet — a cluster-internal-only Service would be unreachable from a -visitor's browser even if the Pages Server happens to run on the same -network. Exposure is therefore public, terminated by **Caddy on the Oracle VPS -over Tailscale** (the same path as every other public host here, not the -cloudflared tunnel), with `ALLOWED_ORIGIN`/CORS as the boundary that restricts -which site's script may call it. After deploying, keep -`window.SKILLS_REVIEW_VOTE_API` in `skills-review/index.html` in sync with -`ALLOWED_ORIGIN` on the service. +internet — a cluster-internal-only Service would be unreachable from a visitor's +browser even if the Pages Server happens to run on the same network. Exposure is +therefore public, terminated by **Caddy on the Oracle VPS over Tailscale** (the +same path as every other public host here, not the cloudflared tunnel), with +`ALLOWED_ORIGIN`/CORS as the boundary that restricts which site's script may +call it. After deploying, keep `window.SKILLS_REVIEW_VOTE_API` in +`skills-review/index.html` in sync with `ALLOWED_ORIGIN` on the service. One cluster-wide gotcha worth knowing before reading the vote code: the ingress -controller runs with `use-forwarded-headers` off, so nginx *overwrites* +controller runs with `use-forwarded-headers` off, so nginx _overwrites_ `X-Forwarded-For`/`X-Real-IP` with the VPS's tailnet address. Caddy stamps the true client address into `X-Client-IP` instead. Full rationale, the Caddy block, and the anti-abuse design are in @@ -221,7 +205,8 @@ and the anti-abuse design are in ## Adding or changing a presentation section -1. Add semantic HTML and stable `data-*` hooks in the focused chapter or `full-guide/index.html`; keep `index.html` as the short route map. +1. Add semantic HTML and stable `data-*` hooks in the focused chapter or + `full-guide/index.html`; keep `index.html` as the short route map. 2. Put interactive content in a data object inside `app.js`. 3. Add one focused render function and bind its controls once. 4. Add Portuguese static copy to `translations.pt` and dynamic copy to the @@ -251,8 +236,8 @@ flowchart TD Use a strong model where ambiguity dominates: repository inspection, architecture, decomposition, risk analysis, and review. Use faster or cheaper -models for bounded implementation only after the brief defines the goal, -files, constraints, and checks. +models for bounded implementation only after the brief defines the goal, files, +constraints, and checks. Every worker should return: @@ -266,8 +251,8 @@ coordination cost, context cost, and integration risk. ## Worktree-per-worker model -A Git branch isolates history; a Git worktree also isolates the active files -and index. Give each editing agent one task, one branch, and one worktree. +A Git branch isolates history; a Git worktree also isolates the active files and +index. Give each editing agent one task, one branch, and one worktree. ```bash git worktree add ../task-ui -b agent/ui @@ -285,14 +270,14 @@ Recommended lifecycle: 5. Merge, request changes, or discard. 6. Remove the finished worktree with `git worktree remove PATH`. -Worktrees prevent agents from changing the same checkout underneath each -other. They do not eliminate semantic merge conflicts; task ownership and -review still matter. +Worktrees prevent agents from changing the same checkout underneath each other. +They do not eliminate semantic merge conflicts; task ownership and review still +matter. ## What a skill is -A skill is a reusable procedure that changes how an agent makes decisions. It -is not magical memory and does not replace a task brief or acceptance criteria. +A skill is a reusable procedure that changes how an agent makes decisions. It is +not magical memory and does not replace a task brief or acceptance criteria. ```text skill-name/ @@ -309,8 +294,8 @@ Progressive disclosure keeps context light: 2. **SKILL.md** loads when the skill applies. 3. **References, scripts, and assets** load only when the workflow needs them. -Do not create empty resource directories. Every file should have a real -consumer and should improve a decision or repeatable operation. +Do not create empty resource directories. Every file should have a real consumer +and should improve a decision or repeatable operation. ## Skill-creation workflow @@ -329,14 +314,17 @@ likely false activation. ```yaml --- name: review-ui -description: Review frontend changes for focus, responsive layout, and reduced-motion behavior. +description: + Review frontend changes for focus, responsive layout, and reduced-motion + behavior. --- ``` ### 3. Choose the smallest anatomy - Put shared workflow and constraints in `SKILL.md`. -- Add `scripts/` when deterministic execution prevents repeated reimplementation. +- Add `scripts/` when deterministic execution prevents repeated + reimplementation. - Add `references/` for details needed only in certain modes. - Add `assets/` for templates or generated-output inputs. - Add `agents/openai.yaml` only when UI metadata or invocation policy is useful. @@ -360,33 +348,37 @@ accumulating universal instructions. ## Common skills and when to use them -| Skill | Use it for | Core rule | Avoid when | -| :--- | :--- | :--- | :--- | -| `ponytail-lite` | Requests inviting unnecessary frameworks or abstractions | Stop at the first sufficient solution: reuse, standard library, native platform, existing dependency, then minimum new code | Simplification would remove validation, security, accessibility, or real edge cases | -| `caveman` | Routine status, handoffs, and technical summaries | Put signal first and remove filler | Security warnings, irreversible actions, or sequences where terse wording can be misread | -| `unlazy` | Substantial builds, audits, and parallel work | Define observable gates and finish against evidence | Trivial edits or factual answers | -| `research` | APIs, standards, current behavior, and architecture facts | Trace claims to primary sources and save cited findings | The answer is already stable and locally proven | -| `diagnosing-bugs` | Hard bugs, flakes, and regressions | Build a fast red-capable feedback loop before theorizing | Simple known fixes with an existing regression test | -| `code-review` | Branch or PR review | Check repository standards and original specification as separate axes | No comparison point or review request exists | -| `token-saver` | Verbose tests, builds, logs, and Git output | Preserve signal and retain full failure output for recovery | Exact raw wording or full diff context is required | -| `webapp-testing` | Frontend interaction and responsive verification | Drive the real UI and assert on DOM, console, and screenshots | Static structure checks are already decisive | +| Skill | Use it for | Core rule | Avoid when | +| :---------------- | :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | +| `ponytail-lite` | Requests inviting unnecessary frameworks or abstractions | Stop at the first sufficient solution: reuse, standard library, native platform, existing dependency, then minimum new code | Simplification would remove validation, security, accessibility, or real edge cases | +| `caveman` | Routine status, handoffs, and technical summaries | Put signal first and remove filler | Security warnings, irreversible actions, or sequences where terse wording can be misread | +| `unlazy` | Substantial builds, audits, and parallel work | Define observable gates and finish against evidence | Trivial edits or factual answers | +| `research` | APIs, standards, current behavior, and architecture facts | Trace claims to primary sources and save cited findings | The answer is already stable and locally proven | +| `diagnosing-bugs` | Hard bugs, flakes, and regressions | Build a fast red-capable feedback loop before theorizing | Simple known fixes with an existing regression test | +| `code-review` | Branch or PR review | Check repository standards and original specification as separate axes | No comparison point or review request exists | +| `token-saver` | Verbose tests, builds, logs, and Git output | Preserve signal and retain full failure output for recovery | Exact raw wording or full diff context is required | +| `webapp-testing` | Frontend interaction and responsive verification | Drive the real UI and assert on DOM, console, and screenshots | Static structure checks are already decisive | Useful compositions: -- **Large feature:** `unlazy` → `ponytail-lite` → implementation → `code-review`. -- **Hard regression:** `diagnosing-bugs` → fix → `code-review` → `caveman` handoff. -- **Documentation with unstable facts:** `research` → writing → cited verification. -- **Interactive presentation:** `frontend-design` → `webapp-testing` → responsive evidence. +- **Large feature:** `unlazy` → `ponytail-lite` → implementation → + `code-review`. +- **Hard regression:** `diagnosing-bugs` → fix → `code-review` → `caveman` + handoff. +- **Documentation with unstable facts:** `research` → writing → cited + verification. +- **Interactive presentation:** `frontend-design` → `webapp-testing` → + responsive evidence. ## Model and effort routing Treat model tier and reasoning effort as separate controls: -| Work shape | Capability tier | Effort baseline | -| :--- | :--- | :--- | -| Formatting, lookup, narrow edit | Luna / Haiku / Flash-Lite | Low or minimal where supported | -| Normal implementation and tests | Terra / Sonnet / Flash | Medium | -| Architecture, orchestration, hard debugging | Sol / Opus / Pro | High | +| Work shape | Capability tier | Effort baseline | +| :------------------------------------------ | :------------------------ | :----------------------------- | +| Formatting, lookup, narrow edit | Luna / Haiku / Flash-Lite | Low or minimal where supported | +| Normal implementation and tests | Terra / Sonnet / Flash | Medium | +| Architecture, orchestration, hard debugging | Sol / Opus / Pro | High | For Claude Code, `/model opus`, `/model sonnet`, and `/model haiku` switch the model alias; `opusplan` can use Opus while planning and Sonnet while executing. @@ -414,14 +406,14 @@ includes a copy-ready installation request that tells the coding agent to: Important exceptions: `ponytail-lite` is published as `AGENTS.md`, not a conventional skill package; `token-saver` expects a separate RTK binary; and `unlazy` includes optional hooks. The prompt does not install binaries or enable -hooks without separate approval. See [skill-sources.md](references/skill-sources.md) -for exact commits, package paths, and confidence notes. +hooks without separate approval. See +[skill-sources.md](references/skill-sources.md) for exact commits, package +paths, and confidence notes. ## Hands-on lab -The presentation includes a dependency-free starter at -`hands-on/starter/`. It renders a small task board but intentionally omits the -All / Open / Done filter. +The presentation includes a dependency-free starter at `hands-on/starter/`. It +renders a small task board but intentionally omits the All / Open / Done filter. Run it from the repository root: @@ -429,7 +421,8 @@ Run it from the repository root: python3 -m http.server 4173 ``` -Open [http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/). +Open +[http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/). In a fresh coding-agent session, copy **Run A — Good prompt** from the presentation. Record changed files, dependencies, checks, and evidence. Restore the starter, then repeat with **Run B — Good prompt + skills**. @@ -440,27 +433,27 @@ The skill-enabled prompt invokes only two working methods: - `$webapp-testing` verifies filters, URL state, history navigation, accessibility state, empty state, and mobile layout. -The goal is not to prove that a longer prompt is better. Both prompts define -the same task contract. Run B adds reusable operating discipline without -repeating those skill instructions inside the prompt. +The goal is not to prove that a longer prompt is better. Both prompts define the +same task contract. Run B adds reusable operating discipline without repeating +those skill instructions inside the prompt. Compare: -| Signal | Useful question | -| :--- | :--- | -| Files changed | Did the agent stay inside `hands-on/starter/`? | -| Dependencies | Did it add a library where native APIs were enough? | -| Verification | Did it actually exercise URL reload and browser history? | -| Evidence | Did the final response name checks and results? | -| Complexity | Is the solution proportionate to three tasks and three filters? | +| Signal | Useful question | +| :------------ | :-------------------------------------------------------------- | +| Files changed | Did the agent stay inside `hands-on/starter/`? | +| Dependencies | Did it add a library where native APIs were enough? | +| Verification | Did it actually exercise URL reload and browser history? | +| Evidence | Did the final response name checks and results? | +| Complexity | Is the solution proportionate to three tasks and three filters? | ### Hands-on rules lab A second lab at `hands-on/rules/` mirrors the starter's visual system and runs -the same exercise against rule sources. It lists five toggleable rule sources -— `AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook, -the `check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the -**ruled** prompt live as each toggle flips. +the same exercise against rule sources. It lists five toggleable rule sources — +`AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook, the +`check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the **ruled** +prompt live as each toggle flips. Run it: @@ -468,15 +461,16 @@ Run it: python3 -m http.server 4173 ``` -Open [http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/). +Open +[http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/). Compare the **naive** and **ruled** prompt panels. Toggle rules off to shrink -the prompt; toggle them on to add more guards. Copy the final prompt and run -it against a real coding agent. +the prompt; toggle them on to add more guards. Copy the final prompt and run it +against a real coding agent. ## Rules and enforcement case study -The separate `/rules/` page uses `netcracker/interview` as a concrete example -of repository-level control. Its interactive pipeline shows five layers: +The separate `/rules/` page uses `netcracker/interview` as a concrete example of +repository-level control. Its interactive pipeline shows five layers: 1. `AGENTS.md` gives every agent the same product and toolchain context. 2. `.agents/skills/` loads narrow procedures for frontend, Go API, gates, @@ -494,19 +488,20 @@ together when the underlying interview workflow changes. ## Troubleshooting -| Symptom | Check | Fix | -| :--- | :--- | :--- | -| Live page is old | Compare `main`, `pages`, and remote SHAs | Fast-forward and push `pages`; retry with `?v=COMMIT` | -| `ERR_SSL_PROTOCOL_ERROR` | Confirm the hostname | Use `netcracker.pages.marcospaulo.dev.br/ai-for-dummies/` | -| Portuguese copy is missing | Inspect `translations.pt` and dynamic interaction data | Add both static and dynamic translations; reload after clearing saved language if needed | -| New control does nothing | Check `data-*` hook, event binding, and render function | Add the hook to `scripts/verify.mjs`; run `node --check app.js` | -| Desktop works, mobile breaks | Inspect the section below 800 px and 600 px | Add an explicit stacking rule and preserve focus visibility | -| Worktree creation says branch is checked out | Run `git worktree list` | Reuse or remove the existing worktree; do not force it | -| Pages push is rejected | Fetch and inspect remote branch state | Reconcile normally; never force-push without an explicit recovery decision | +| Symptom | Check | Fix | +| :------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------- | +| Live page is old | Compare `main`, `pages`, and remote SHAs | Fast-forward and push `pages`; retry with `?v=COMMIT` | +| `ERR_SSL_PROTOCOL_ERROR` | Confirm the hostname | Use `netcracker.pages.marcospaulo.dev.br/ai-for-dummies/` | +| Portuguese copy is missing | Inspect `translations.pt` and dynamic interaction data | Add both static and dynamic translations; reload after clearing saved language if needed | +| New control does nothing | Check `data-*` hook, event binding, and render function | Add the hook to `scripts/verify.mjs`; run `node --check app.js` | +| Desktop works, mobile breaks | Inspect the section below 800 px and 600 px | Add an explicit stacking rule and preserve focus visibility | +| Worktree creation says branch is checked out | Run `git worktree list` | Reuse or remove the existing worktree; do not force it | +| Pages push is rejected | Fetch and inspect remote branch state | Reconcile normally; never force-push without an explicit recovery decision | ## Safe rollback -Prefer a normal revert so history and the `pages` branch remain fast-forwardable: +Prefer a normal revert so history and the `pages` branch remain +fast-forwardable: ```bash git switch main @@ -532,7 +527,8 @@ ordinary content recovery. - [ ] `main` is pushed. - [ ] `pages` fast-forwards to the same commit. - [ ] Live endpoint returns HTTP 200 and contains the new section. -- [ ] Research links and this SilverBullet guide are updated when the workflow changes. +- [ ] Research links and this SilverBullet guide are updated when the workflow + changes. ## Resumo rápido em português @@ -542,6 +538,6 @@ Gitea Pages publica diretamente essa branch. Use o endereço com `netcracker.pages.../ai-for-dummies/`; o formato inverso quebra o TLS. Para agentes: modelo forte planeja e revisa; workers delimitados implementam em -worktrees separados; evidências voltam ao orquestrador. Para skills: capture -uma decisão repetida, defina um gatilho preciso, crie apenas os recursos úteis, +worktrees separados; evidências voltam ao orquestrador. Para skills: capture uma +decisão repetida, defina um gatilho preciso, crie apenas os recursos úteis, escreva orientação que muda decisões e valide estrutura **e** comportamento. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..2dffe80 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,43 @@ +import js from '@eslint/js'; +import astro from 'eslint-plugin-astro'; + +export default [ + { + ignores: [ + '.agents/**', + '.astro/**', + 'app.js', + 'dist/**', + 'full-guide/**', + 'hands-on/**', + 'public/hands-on/**', + 'rules/**', + 'scripts/**', + 'skill-reviews/**', + 'skills-review/**', + 'skills/**', + 'submitted-skills/**', + 'vote-service/**', + ], + }, + js.configs.recommended, + ...astro.configs.recommended, + { + ignores: [ + 'dist/**', + 'hands-on/**', + 'public/hands-on/**', + 'submitted-skills/**', + 'skill-reviews/**', + 'vote-service/**', + ], + }, + { + rules: { + 'no-console': ['warn', { allow: ['warn', 'error'] }], + eqeqeq: ['error', 'always'], + 'no-var': 'error', + 'prefer-const': 'error', + }, + }, +]; diff --git a/package.json b/package.json index dc21720..c748d55 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,38 @@ { "name": "ai-for-dummies", "private": true, - "scripts": { "verify": "node scripts/verify.mjs", "serve": "python3 -m http.server 4173" } + "type": "module", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "check": "astro check", + "lint": "eslint . --max-warnings=0 && stylelint --allow-empty-input 'src/**/*.css' --max-warnings=0", + "format": "prettier --write .", + "verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs", + "gate": "./.agents/scripts/gate.sh", + "snapshot": "node .agents/scripts/snapshot-route.mjs", + "serve": "python3 -m http.server 4173", + "prepare": "husky" + }, + "devDependencies": { + "@astrojs/check": "0.9.4", + "@eslint/js": "^9", + "@typescript-eslint/parser": "^8", + "astro": "5.5.0", + "eslint": "^9", + "eslint-plugin-astro": "^1", + "husky": "^9", + "lint-staged": "^16", + "prettier": "^3", + "prettier-plugin-astro": "^0.14", + "stylelint": "^16", + "stylelint-config-standard": "^39", + "typescript": "^5.8.3" + }, + "overrides": { + "@astrojs/language-server": "2.15.0", + "defu": "6.1.7", + "vite": "6.2.1" + } } diff --git a/public/hands-on/rules/README.md b/public/hands-on/rules/README.md new file mode 100644 index 0000000..d5cc0e2 --- /dev/null +++ b/public/hands-on/rules/README.md @@ -0,0 +1,28 @@ +# Hands-on · Rules + +A tiny, zero-dependency demo showing how rule sources reshape the same prompt. + +## Run + +Open `index.html` directly. No build, no server, no `npm install`. + +## What it shows + +- Five rule sources, taken from a real monorepo (`netcracker/interview`): + - `AGENTS.md` — repo-wide instruction file. + - `.agents/skills/gate-discipline/SKILL.md` — skill body loaded on demand. + - `.husky/pre-commit` — git hook that runs other enforcers. + - `scripts/check-ui-contract.mjs` — custom CLI enforcer (ratchet). + - `commitlint.config.cjs` — commit-msg linter. +- Each rule has an on/off switch. Toggling a rule prepends its body to the **ruled** prompt. +- EN ↔ PT toggle keeps both languages useful. +- "Copy ruled prompt" copies the current ruled-prompt text to clipboard. +- Responsive on mobile, Full HD, and 4K (one column under 720 px). + +## Mirror of `/hands-on/starter` + +Same visual system as the starter (`--paper`, `--ink`, `--blue`, `--gold`). Drop-in replacement under `hands-on/rules/`. + +## Token budget + +Page weight: ~5 KB total, no framework, no fetch. diff --git a/public/hands-on/rules/app.js b/public/hands-on/rules/app.js new file mode 100644 index 0000000..06f1904 --- /dev/null +++ b/public/hands-on/rules/app.js @@ -0,0 +1,124 @@ +// Five rule sources lifted from netcracker/interview. +// Toggling a rule injects its body into the ruled prompt. +const RULES = [ + { + id: 'agents', + name: 'AGENTS.md', + kind: 'Repo-wide instruction', + path: 'AGENTS.md', + en: 'Read AGENTS.md before touching this repo. Stack: pnpm + turbo monorepo, Go API, Next.js apps. Gates run from the monorepo root: pnpm lint, typecheck, test.', + pt: 'Leia AGENTS.md antes de tocar neste repo. Stack: pnpm + turbo monorepo, API em Go, apps Next.js. Gates rodam da raiz: pnpm lint, typecheck, test.' + }, + { + id: 'skill', + name: 'gate-discipline skill', + kind: 'Skill body', + path: '.agents/skills/gate-discipline/SKILL.md', + en: 'Skill `gate-discipline`: every gate runs separately with `$?`. No `| tail`. `TURBO_FORCE=true` if a pass looks too cheap. Generated code is regenerated, never hand-edited.', + pt: 'Skill `gate-discipline`: cada gate roda separado com `$?`. Nada de `| tail`. `TURBO_FORCE=true` se o passar for bom demais. Código gerado se regenera, nunca se edita.' + }, + { + id: 'husky', + name: 'Husky pre-commit', + kind: 'Git hook (commit time)', + path: '.husky/pre-commit', + en: 'Pre-commit runs `pnpm exec lint-staged`, then `node scripts/check-ui-contract.mjs` (UI ratchet), then commitlint. Counts in the baseline may only go DOWN.', + pt: 'Pre-commit roda `pnpm exec lint-staged`, depois `node scripts/check-ui-contract.mjs` (ratchet de UI), depois commitlint. Contadores do baseline só podem DIMINUIR.' + }, + { + id: 'enforcer', + name: 'check-ui-contract.mjs', + kind: 'Custom enforcer (CLI)', + path: 'scripts/check-ui-contract.mjs', + en: 'Enforcer scans for raw + + `).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(); diff --git a/public/hands-on/rules/index.html b/public/hands-on/rules/index.html new file mode 100644 index 0000000..8d3976f --- /dev/null +++ b/public/hands-on/rules/index.html @@ -0,0 +1,43 @@ + + + + + + Guardrails — Hands-on Rules + + + +
+
+
HANDS-ON / RULES

Guardrails

+

Toggle rules. Same task, different coverage.

+
+ +
+

Rule sources

0 / 5 active
+
+
+ +
+

Prompt diff

+
+ + +
+
+
+
+
NAIVE

Plain prompt

+

+        
+
+
RULED

With guardrails

+

+          
+        
+
+
+
+ + + diff --git a/public/hands-on/rules/styles.css b/public/hands-on/rules/styles.css new file mode 100644 index 0000000..898f13e --- /dev/null +++ b/public/hands-on/rules/styles.css @@ -0,0 +1 @@ +:root{--paper:#f4f3ef;--ink:#173044;--muted:#687d8c;--blue:#5683a1;--gold:#efc86d;--line:#d5dde1}*{box-sizing:border-box}body{margin:0;min-width:320px;background:var(--paper);color:var(--ink);font-family:Arial,sans-serif}main{width:min(880px,calc(100% - 40px));margin:0 auto;padding:70px 0}header,.section-head{display:flex;justify-content:space-between;gap:30px;align-items:end}header{padding-bottom:50px;border-bottom:1px solid var(--line)}header span,.prompt-card>header span{font:10px monospace;letter-spacing:.08em}h1{margin:12px 0 0;font-size:clamp(48px,10vw,100px);letter-spacing:-.08em}header p{max-width:220px;color:var(--muted);line-height:1.5}section{padding-top:45px}h2{font-size:24px}.section-head>span{color:var(--blue);font:11px monospace}.rule-list,.prompt-grid{display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}.rule{display:grid;grid-template-columns:1fr auto auto;gap:20px;padding:22px;background:var(--paper);align-items:center}.rule h3{margin:0 0 6px;font-size:17px}.rule p{margin:0;color:var(--muted);font-size:13px;line-height:1.45}.rule code{font:11px ui-monospace,monospace;color:var(--ink);background:var(--paper);padding:1px 5px;border:1px solid var(--line)}.rule-tag{font:10px monospace;letter-spacing:.08em;color:var(--muted);text-transform:uppercase}.toggle{appearance:none;width:44px;height:24px;border:1px solid var(--line);background:var(--paper);border-radius:12px;position:relative;cursor:pointer;transition:background .15s ease}.toggle::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;background:var(--ink);border-radius:50%;transition:transform .15s ease,background .15s ease}.toggle[aria-pressed="true"]{background:var(--blue);border-color:var(--blue)}.toggle[aria-pressed="true"]::after{transform:translateX(20px);background:var(--gold)}.prompt-grid{grid-template-columns:1fr 1fr;margin-top:18px}.prompt-card{background:var(--paper);padding:22px;display:flex;flex-direction:column;gap:14px}.prompt-card>header{display:flex;justify-content:space-between;align-items:end;padding-bottom:0;border-bottom:0}.prompt-card h3{margin:0;font-size:17px}.prompt-card pre{margin:0;font:12px ui-monospace,monospace;white-space:pre-wrap;word-break:break-word;color:var(--ink);background:var(--paper);border:1px solid var(--line);padding:14px;min-height:160px;line-height:1.5}#copy-btn{align-self:flex-start;appearance:none;border:1px solid var(--ink);background:var(--ink);color:var(--paper);font:11px monospace;letter-spacing:.08em;padding:9px 14px;cursor:pointer;text-transform:uppercase}#copy-btn[data-copied="true"]{background:var(--blue);border-color:var(--blue)}.lang-switch{display:flex;gap:1px;border:1px solid var(--line)}.lang-switch button{appearance:none;border:0;background:var(--paper);color:var(--muted);font:11px monospace;letter-spacing:.08em;padding:6px 10px;cursor:pointer;text-transform:uppercase}.lang-switch button[aria-pressed="true"]{background:var(--ink);color:var(--paper)}@media(max-width:720px){.prompt-grid{grid-template-columns:1fr}.rule{grid-template-columns:1fr}.rule-tag{display:none}}@media(max-width:560px){header{display:block}header p{margin-top:24px}.lang-switch{flex:1}.lang-switch button{flex:1}} diff --git a/public/hands-on/starter/README.md b/public/hands-on/starter/README.md new file mode 100644 index 0000000..b1358d4 --- /dev/null +++ b/public/hands-on/starter/README.md @@ -0,0 +1,13 @@ +# Tiny Tasks hands-on starter + +A dependency-free HTML/CSS/JavaScript exercise used by the AI For Dummies +presentation. The task list renders; status filtering is intentionally absent. + +Run from the repository root: + +```bash +python3 -m http.server 4173 +``` + +Open and paste either prompt from the +presentation into a fresh coding-agent session rooted at this repository. diff --git a/public/hands-on/starter/app.js b/public/hands-on/starter/app.js new file mode 100644 index 0000000..c744210 --- /dev/null +++ b/public/hands-on/starter/app.js @@ -0,0 +1,15 @@ +const tasks = [ + { title: 'Review pull request', owner: 'Maya', status: 'open' }, + { title: 'Write release notes', owner: 'Theo', status: 'done' }, + { title: 'Check mobile layout', owner: 'Lina', status: 'open' } +]; + +const list = document.querySelector('#task-list'); +const count = document.querySelector('#task-count'); + +function renderTasks() { + list.innerHTML = tasks.map((task) => `

${task.title}

Owner: ${task.owner}

${task.status}
`).join(''); + count.textContent = `${tasks.length} tasks`; +} + +renderTasks(); diff --git a/public/hands-on/starter/index.html b/public/hands-on/starter/index.html new file mode 100644 index 0000000..6543a3a --- /dev/null +++ b/public/hands-on/starter/index.html @@ -0,0 +1,22 @@ + + + + + + Tiny Tasks — Hands-on Starter + + + +
+
+
HANDS-ON / STARTER

Tiny Tasks

+

Three tasks. One missing filter.

+
+
+

Today

+
+
+
+ + + diff --git a/public/hands-on/starter/styles.css b/public/hands-on/starter/styles.css new file mode 100644 index 0000000..b00a2bf --- /dev/null +++ b/public/hands-on/starter/styles.css @@ -0,0 +1 @@ +:root{--paper:#f4f3ef;--ink:#173044;--muted:#687d8c;--blue:#5683a1;--gold:#efc86d;--line:#d5dde1}*{box-sizing:border-box}body{margin:0;min-width:320px;background:var(--paper);color:var(--ink);font-family:Arial,sans-serif}main{width:min(880px,calc(100% - 40px));margin:0 auto;padding:70px 0}header,.section-head{display:flex;justify-content:space-between;gap:30px;align-items:end}header{padding-bottom:50px;border-bottom:1px solid var(--line)}header span,.task-meta{font:10px monospace;letter-spacing:.08em}h1{margin:12px 0 0;font-size:clamp(48px,10vw,100px);letter-spacing:-.08em}header p{max-width:220px;color:var(--muted);line-height:1.5}section{padding-top:45px}h2{font-size:24px}.section-head>span{color:var(--blue);font:11px monospace}.task-list{display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}.task{display:grid;grid-template-columns:1fr auto;gap:20px;padding:22px;background:var(--paper)}.task h3{margin:0 0 8px;font-size:17px}.task p{margin:0;color:var(--muted);font-size:13px}.task-meta{align-self:center;padding:7px 9px;color:var(--ink);background:var(--gold)}.task[data-status="done"] .task-meta{color:var(--paper);background:var(--blue)}@media(max-width:560px){header{display:block}header p{margin-top:24px}.task{grid-template-columns:1fr}.task-meta{justify-self:start}} diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro new file mode 100644 index 0000000..7ebe952 --- /dev/null +++ b/src/layouts/BaseLayout.astro @@ -0,0 +1,23 @@ +--- +interface Props { + title: string; + description: string; + lang?: string; +} + +const { title, description, lang = 'en' } = Astro.props; +--- + + + + + + + {title} + + + + + + + diff --git a/src/pages/summary.astro b/src/pages/summary.astro new file mode 100644 index 0000000..e15be22 --- /dev/null +++ b/src/pages/summary.astro @@ -0,0 +1,63 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import chaptersStylesheet from '../../chapters.css?url'; + +const base = import.meta.env.BASE_URL; +const introduction = + 'This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result.'; +--- + + + +
+
+ ← AI FOR DUMMIES + 00 / ROUTE MAP + review desk ↗ +
+
+

Start here

+

Ship the
system.

+

{introduction}

+
+
+ + + + +
+ 05

Practice

Compare prompts and skill-enabled runs.

Open lab → +
+
+ 06

Review desk

Browse original files and improved drafts.

Open desk → +
+
+ + +
+
diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +}