{"owner":"vinta","repo":"pangu.js","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":4714,"categories":["anthropic-skill","root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".claude/rules/chrome-extension.md","AGENTS.md","CLAUDE.md",".claude/skills/verify-before-npm-publish/SKILL.md"],"skills":{".claude/rules/chrome-extension.md":"---\npaths:\n  - 'browser-extensions/chrome/**'\n  - 'src/browser/**'\n---\n\n# Chrome Extension\n\n- The Chrome floor is declared three times and all must agree: `minimum_chrome_version` in `manifest.json`, `build.target` in `browser-extensions/vite.config.extension.ts`, and the `available` year of `baseline-js/use-baseline` in `eslint.config.js`. A bump also drops the fallbacks it makes dead and gets a CHANGELOG line.\n- Chrome's shipped initial value for `text-autospace` is `no-autospace` (verified in Chrome 150 on 2026-07-23), whatever chromestatus and the blink-dev intent say about default-on. The `html { text-autospace: normal }` content-script CSS is the permanent opt-in on every Chrome version, never redundant. A computed `normal` on a page proves the extension CSS injected.\n- `requestIdleCallback` never fires in a hidden document (background tab, minimized window), and the `timeout` option does not rescue it. Everything routed through `BrowserPangu.schedule()` waits until the tab is visible again. Focus does not matter, only visibility.\n  - `claude-in-chrome` opens tabs as background tabs, so they are hidden until the user selects one. A logged label with an unchanged page in such a tab means the late fix is queued, not broken. Verify scheduled work in a visible tab.\n","AGENTS.md":"# AGENTS.md\n\n## Constraints\n\n- Generated files: `dist/` and `browser-extensions/chrome/dist/` are build or test output; avoid hand-editing them unless the task explicitly targets generated artifacts.\n- Preserve the zero-runtime-dependency package design unless the user explicitly accepts a new dependency and its maintenance cost.\n- Keep exact dependency versions in `package.json`; do not introduce `^` or `~` ranges.\n- Use `node:` prefixes for Node.js built-in modules.\n- Avoid hard-wrapping Markdown prose; keep paragraphs and list items on single logical lines unless a format requires manual line breaks.\n- Do not wrap code comments early. Fill each comment line up to Prettier's `printWidth` (200, in `.prettierrc.json`) and break only when the line would exceed it. Prettier never reflows comment text, so an early-wrapped comment stays narrow forever.\n\n## Workflow\n\n- For multi-step, ambiguous, broad, or high-risk work, state a short 3–5 item plan before editing, covering approach, files, verification, and consequential open questions. Implement narrow, well-scoped fixes directly after gathering context.\n- For model-assisted prompt experiments, use [prompt-experiments](.agents/skills/prompt-experiments/SKILL.md). Ordinary deterministic spacing fixes use the checks below.\n- Before writing code that uses dependencies, tools, browser or extension APIs, or release machinery, invoke `find-docs` and check current documentation. Use local source as needed; do not rely on training data for API or configuration details. For Context7 lookups, use the pre-resolved IDs below.\n\n## Verification\n\n- Run `npm run build` before `npm run lint` or `npm run typecheck`; these checks depend on generated package outputs. The `test` and `test:*` scripts already run the full build.\n- Run the smallest meaningful check for the files changed, then broaden only when risk or failures require it.\n- For shared spacing changes, run `npm run test:shared`; add or update tests in `tests/shared/` when behavior changes.\n- For Node API or CLI changes, run `npm run test:node`; use focused fixture coverage for file-processing behavior.\n- For browser DOM behavior, run `npm run test:browser` or a focused Playwright test when possible.\n- For extension source changes, run `npm run build:extension`; run broader tests if shared or browser library behavior is affected.\n- For build, packaging, or export changes, run `npm run build` and the relevant package or smoke check.\n- For docs/config-only changes, at minimum run `git diff --check`.\n- Do not leave new warnings behind. If a meaningful check cannot run, state exactly what was skipped, why, and the next best check.\n\n## Publishing And Releases\n\n- Do not run `npm publish` locally. npm publishing is handled by GitHub Actions through the `v*` tag workflow and Trusted Publishing.\n- Use `npm run bump-version <version>` only when the task explicitly asks for a release/version flow.\n- Before changing release or packaging scripts, verify their generated output paths.\n\n## Context7 Library IDs\n\nPre-resolved IDs for the `find-docs` skill. Pass directly to `ctx7 docs`, skipping the `ctx7 library` step:\n\n| Tool              | `libraryId`                                    |\n| ----------------- | ---------------------------------------------- |\n| attw              | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome Extensions | `/websites/developer_chrome_extensions`        |\n| ESLint            | `/eslint/eslint`                               |\n| MDN Web Docs      | `/mdn/content`                                 |\n| Node.js           | `/nodejs/node`                                 |\n| npm               | `/websites/npmjs`                              |\n| Playwright        | `/microsoft/playwright`                        |\n| Prettier          | `/prettier/prettier`                           |\n| publint           | `/publint/publint`                             |\n| Shields.io        | `/websites/shields_io_badges`                  |\n| TypeScript        | `/websites/typescriptlang`                     |\n| typescript-eslint | `/typescript-eslint/typescript-eslint`         |\n| Vite              | `/vitejs/vite`                                 |\n| Vitest            | `/vitest-dev/vitest`                           |\n","CLAUDE.md":"# CLAUDE.md\n\n## Spacing Rules\n\n- `CONTEXT.md` is the glossary of terms the spacing rules, docs, and code share. Read it before touching spacing rules, and use its terms, never the synonyms it lists under _Avoid_.\n- Decisions that changed a spacing contract are ADRs in `docs/adr/`. Check them before proposing a rule change that reverses one.\n- When tweaking spacing rules, if the simpler rule is blocked only by rare test cases (typo-shaped input, degenerate shapes, nothing a real user reported), challenge the user to drop those cases in favor of the simpler rule instead of complicating the rule to preserve them. Show the candidate rule and exactly which expectations it breaks, then recommend dropping. Comment dropped cases out in place with `// Rare cases, ignore`, and record reversals of documented contracts as an ADR (precedent: ADR 0007). This licenses dropping rare-case contracts, not pruning tests in general.\n- When fixing a spacing issue, first try absorbing it into an existing rule (widen a character class, adjust a lookahead, rename the rule if its name stops matching). Add a separate rule when the tweak would overcomplicate the existing one, such as forcing one regex to serve two unrelated readings: optimize for total complexity of the rule set, not rule count.\n- Before changing a spacing rule, prototype every candidate on a patched copy of the built engine (`dist/shared/index.js`) and generate a per-case table with one block per case: input, rules output, extension output. Cover the symbol's test file plus real-world shapes, and decide on the diff between candidates, never on argument.\n- Example inputs use real-world text only. Never invent a name, term, or sentence to show an unlisted or boundary case; made-up input proves nothing about real text. A synthetic token is allowed only for a regex-boundary unit test, and says so.\n- A per-line reading (pipe, plus, hyphen) must test its contact before an earlier rule spaces that contact away. Plus reading missed every `CJK+A` line until it moved ahead of the operator rules, while the glossary promised the flip all along. When a glossary promise fails on a shape, check rule order before the regex.\n\n## Releasing\n\n```bash\nnpm run bump-version 1.2.3      # Bumps package.json, extension manifest, src/shared/index.ts, examples/package.json, then builds and packs the extension zip. Does NOT commit or tag.\n```\n\nWhen the release won't ship a new extension to the Chrome Web Store, restore the manifest version with `git restore browser-extensions/chrome/manifest.json` before committing, so the manifest doesn't claim an extension release that never happens. `/verify-before-npm-publish` decides: it fails when extension build inputs changed since the last store release but the manifest wasn't bumped, or didn't change but the manifest was.\n\n1. **Verify**: `/verify-before-npm-publish` must print `VERIFY OK` on the exact HEAD being tagged, so rerun it whenever HEAD moves after a pass.\n2. **Tag**: tag (`git tag -a vX.Y.Z -m \"\"`) and push master plus the tag only after the user says \"publish\".\n3. **npm publishing** runs in GitHub Actions (`publish.yml`) on a pushed `v*` tag, using npm Trusted Publishing (OIDC). Never run `npm publish` locally. ADR 0014 records why publishing is one job, not a pack/publish split.\n4. **Confirm**: `npm install pangu@X.Y.Z --min-release-age=0` in a scratch directory.\n\n## Gotcha\n\n- When adding or editing a glossary entry, a definition uses only bold glossary terms, platform names as the platform spells them (`Text` node, string, line, element), and ordinary English in its ordinary sense. One word carries one sense: a word needed in a technical sense gets its own entry or gets replaced. A term matches the code identifier for the same concept; when they diverge, define the code's word or rename the code.\n- Never blanket find-replace a domain term: pangu vocabulary doubles as ordinary verbs (space, settle). After a rename, grep the changed files for the old word's remaining forms, identifiers included, and read each hit for noun versus verb.\n- Write code comments in English with ANS characters only. Never paste CJK sample text from tests into a comment; describe the shape generically (`CJK | CJK`, `A+CJK`) and use `\\uXXXX` escape notation when a specific character matters.\n- In `tests/shared/`, write each `spaceText` case as its own literal `expect(...).toBe(...)` line, never a loop or `it.each`, so a failure names its input.\n- Keep every `// FIXME` line and `FIXME:` prefix in tests, even beside `it.fails` or `test.fail`: the user greps FIXME to find pending work.\n- Prefix a console call with `[pangu]` only in code that runs in an ordinary web page's console (content scripts, `src/shared/`), where the prefix is the only filter separating our logs from the site's. Options page, popup, and service worker logs stay unprefixed.\n- `fixtures/` has no `.prettierignore` on purpose. A byte-sensitive fixture carries a leading `<!-- prettier-ignore -->` pragma that `loadFixture()` strips before browser tests byte-compare `innerHTML` against the `.expected.html` files. The pragma guards only the next node, so many fixtures fail `prettier --check`. Never format fixtures in bulk (a repo-wide `prettier --write` breaks browser tests), and leave unreferenced fixtures alone.\n\n## External Tool Documentation\n\nPre-resolved Context7 IDs for the `find-docs` skill. Pass them to `ctx7 docs` and skip `ctx7 library`:\n\n| Tool               | `libraryId`                                    |\n| ------------------ | ---------------------------------------------- |\n| attw               | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome built-in AI | `/websites/developer_chrome_ai`                |\n| Chrome Extensions  | `/websites/developer_chrome_extensions`        |\n| ESLint             | `/eslint/eslint`                               |\n| GitHub Actions     | `/websites/github_en_actions`                  |\n| MDN Web Docs       | `/mdn/content`                                 |\n| Node.js            | `/nodejs/node`                                 |\n| npm                | `/websites/npmjs`                              |\n| Playwright         | `/microsoft/playwright`                        |\n| Prettier           | `/prettier/prettier`                           |\n| publint            | `/publint/publint`                             |\n| Shields.io         | `/websites/shields_io_badges`                  |\n| TypeScript         | `/websites/typescriptlang`                     |\n| typescript-eslint  | `/typescript-eslint/typescript-eslint`         |\n| Vite               | `/vitejs/vite`                                 |\n| Vitest             | `/vitest-dev/vitest`                           |\n",".claude/skills/verify-before-npm-publish/SKILL.md":"---\nname: verify-before-npm-publish\ndescription: (project) Before publishing pangu, check the release is ready, pack it, and run the examples app against that tarball to prove the about-to-ship build is consumable by a real install\ndisable-model-invocation: true\nallowed-tools:\n  - Bash(npm:*)\n  - Bash(node:*)\n  - Bash(mktemp:*)\n  - Bash(cp:*)\n  - Bash(ls:*)\n  - Bash(rm:*)\n  - Bash(tar:*)\n  - Bash(git diff:*)\n  - Bash(git show:*)\n  - Bash(git describe:*)\n  - Bash(sort:*)\n  - Bash(comm:*)\n---\n\n# Verify before publish\n\nThe `examples/` app is a real consumer: its own `package.json` pins a published `pangu`, its own `tsconfig.json` resolves under `NodeNext`, and its checks reach pangu through `node_modules` and the `exports` map. That is exactly the path pangu's own build never takes, because the build resolves `src/` directly, sibling file to sibling file. So a `.d.ts` with a bare relative import, or a file the `files` list forgot to ship, reaches npm green.\n\nThis skill briefly repoints that consumer at the **freshly packed tarball** instead of its published pin, runs every example check against it, and restores the pin. The packed tarball is the only honest artifact. Repointing is also what lets the check run before publish at all: pangu's next version is not on the registry yet, so the pin cannot resolve until the tarball stands in for it.\n\n## Run the verification\n\nEverything below runs as one command so the `trap` restores `examples/package.json` whether the checks pass, fail, or error midway. Never split it.\n\nThe checks capture their exit code explicitly instead of leaning on `set -e` to abort. Some runners (Claude Code's Bash tool among them) invoke this in a shell where `set -e` does not stop the script, and a bare `echo \"VERIFY OK\"` after an unguarded `npm run verify` then prints a pass over a real failure. Report the `VERIFY OK` / `VERIFY FAILED` line, never the tool's own exit status.\n\n```bash\nset -eu\nfail() { echo \"VERIFY FAILED ($1)\"; exit 1; }\n\n# Pushing the tag publishes the tagged commit without running test.yml, so it must pass the suite here and carry an unused version\nVERSION=\"$(node -p \"require('./package.json').version\")\"\n[ -z \"$(git status --porcelain)\" ] || fail \"dirty working tree\"\n[ \"$(git branch --show-current)\" = master ] || fail \"not on master\"\nif git rev-parse -q --verify \"refs/tags/v$VERSION\" >/dev/null; then fail \"tag v$VERSION exists locally\"; fi\n[ -z \"$(npm view \"pangu@$VERSION\" version)\" ] || fail \"pangu@$VERSION already on npm\"\n{ npm test && npm run lint && npm run typecheck; } || fail \"tests, lint, or typecheck\"\n\n# Build and pack the exact bytes that would go to npm\nnpm run build\nTARBALL_DIR=\"$(mktemp -d)\"\nnpm pack --pack-destination \"$TARBALL_DIR\"\nTGZ=\"$(ls \"$TARBALL_DIR\"/pangu-*.tgz)\"\n\n# Every file under dist/ lives in an entry folder. A file directly in dist/ is a code-split chunk that a build config change let back in\nif tar -tzf \"$TGZ\" | grep -E '^package/dist/[^/]+$'; then echo \"VERIFY FAILED (stray file in dist/)\"; exit 1; fi\n\n# bump-version writes every version copy, so a mismatch means a hand edit missed one\ntar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"\n[ \"$(node -p \"require('$TARBALL_DIR/package/package.json').version\")\" = \"$VERSION\" ] || fail \"tarball package.json version\"\n[ \"$(node --input-type=module -e \"import { pangu } from '$TARBALL_DIR/package/dist/shared/index.js'; console.log(pangu.version)\")\" = \"$VERSION\" ] || fail \"pangu.version\"\n# The last store release is the manifest version at the latest tag, so any change in extension build inputs since then needs a manifest bump, and no change needs a restored manifest\nMANIFEST=\"$(node -p \"require('./browser-extensions/chrome/manifest.json').version\")\"\nSTORE=\"$(git show \"$(git describe --tags --abbrev=0):browser-extensions/chrome/manifest.json\" | node -p \"JSON.parse(require('fs').readFileSync(0,'utf8')).version\")\"\nif git diff --quiet -I \"readonly version: string\" \"v$STORE\" HEAD -- src/browser src/shared browser-extensions/chrome browser-extensions/vite.config.extension.ts ':!browser-extensions/chrome/manifest.json' ':!browser-extensions/chrome/tests'; then\n  [ \"$MANIFEST\" != \"$VERSION\" ] || fail \"extension unchanged since v$STORE, restore the manifest version\"\nelse\n  [ \"$MANIFEST\" = \"$VERSION\" ] || fail \"extension changed since v$STORE, manifest version\"\nfi\n[ \"$(node -p \"require('./examples/package.json').dependencies.pangu\")\" = \"$VERSION\" ] || fail \"examples pin\"\ngrep -m1 '^## v' CHANGELOG.md | grep -q \"^## v$VERSION \" || fail \"CHANGELOG top heading\"\n\n# CDN links reach any shipped path, so a file dropped since the published version breaks them\nPUBLISHED=\"$(npm view pangu version)\"\n# A user-level min-release-age would refuse a version published days ago\nnpm pack \"pangu@$PUBLISHED\" --min-release-age=0 --pack-destination \"$TARBALL_DIR\"\ntar -tzf \"$TARBALL_DIR/pangu-$PUBLISHED.tgz\" | sort > \"$TARBALL_DIR/published.txt\"\ntar -tzf \"$TGZ\" | sort > \"$TARBALL_DIR/packed.txt\"\nREMOVED=\"$(comm -23 \"$TARBALL_DIR/published.txt\" \"$TARBALL_DIR/packed.txt\")\"\n[ -z \"$REMOVED\" ] || { echo \"$REMOVED\"; fail \"files removed since $PUBLISHED\"; }\n\n# From here the examples pin is modified, so guarantee its restoration on any exit\ncp examples/package.json \"$TARBALL_DIR/package.json.orig\"\ntrap 'cp \"$TARBALL_DIR/package.json.orig\" examples/package.json; rm -rf \"$TARBALL_DIR\" examples/node_modules examples/package-lock.json' EXIT\n\n# Repoint the consumer at the tarball, install, and run every example check against it\nnode -e \"const fs=require('fs'),f='examples/package.json',p=JSON.parse(fs.readFileSync(f));p.dependencies.pangu='file:'+process.argv[1];fs.writeFileSync(f,JSON.stringify(p,null,2)+'\\n')\" \"$TGZ\"\nnpm install --prefix examples --no-audit --no-fund\n\nrc=0\nnpm run verify --prefix examples || rc=$?\nif [ \"$rc\" -eq 0 ]; then echo \"VERIFY OK\"; else echo \"VERIFY FAILED (exit $rc)\"; fi\nexit \"$rc\"\n```\n\n## Reading the result\n\n- `VERIFY OK` on the last line → the tarball is clear to publish.\n- `VERIFY FAILED` → do not publish. The `trap` has already restored the pin and dropped the throwaway install; diagnose from the message or the table below, fix, and rerun.\n\nEither way, confirm the pin came back before moving on: `git diff --quiet examples/package.json` must exit 0. A dirty `examples/package.json` means the run was interrupted before the trap fired; restore it with `git checkout -- examples/package.json`.\n\n## What each failure means\n\n| Symptom                          | Cause                                                                                    |\n| -------------------------------- | ---------------------------------------------------------------------------------------- |\n| `TS2834` in a `dist/**/*.d.ts`   | a relative import in `src/` is missing its `.js` extension                               |\n| `TS2339` on an inherited method  | a base-class `.d.ts` import failed to resolve, collapsing the subclass to an error type  |\n| `Cannot find module` at run time | `package.json` `files` doesn't ship the referenced file, or an `exports` path is wrong   |\n| `stray file in dist/`            | the `esm` environment emitted a hashed shared chunk; keep `output.preserveModules` on it |\n| `files removed since X.Y.Z`      | a rename or build change dropped a shipped path; restore it or release as a major        |\n\nTo read the shipped artifact directly, unpack the tarball: `tar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"` exposes `package/dist/**/*.d.ts` and `package/package.json`.\n\n## What the run exercises\n\n`npm run verify` in `examples/` chains the runtime entrypoints (`verify:commonjs`, `verify:esm`, `verify:cli`), the browser page (`verify:browser`, which serves `verify-browser.html` and drives the UMD `<script>` tag and the ESM `import` in headless Chromium), and the type check (`typecheck`, whose `tsconfig.json` checks `verify-types.ts` under the `require` condition and `verify-types.mts` under the `import` condition, covering both branches of the dual-package `exports` map). Every one runs against the installed tarball, not the published pin.\n"},"files":{".claude/rules/chrome-extension.md":"---\npaths:\n  - 'browser-extensions/chrome/**'\n  - 'src/browser/**'\n---\n\n# Chrome Extension\n\n- The Chrome floor is declared three times and all must agree: `minimum_chrome_version` in `manifest.json`, `build.target` in `browser-extensions/vite.config.extension.ts`, and the `available` year of `baseline-js/use-baseline` in `eslint.config.js`. A bump also drops the fallbacks it makes dead and gets a CHANGELOG line.\n- Chrome's shipped initial value for `text-autospace` is `no-autospace` (verified in Chrome 150 on 2026-07-23), whatever chromestatus and the blink-dev intent say about default-on. The `html { text-autospace: normal }` content-script CSS is the permanent opt-in on every Chrome version, never redundant. A computed `normal` on a page proves the extension CSS injected.\n- `requestIdleCallback` never fires in a hidden document (background tab, minimized window), and the `timeout` option does not rescue it. Everything routed through `BrowserPangu.schedule()` waits until the tab is visible again. Focus does not matter, only visibility.\n  - `claude-in-chrome` opens tabs as background tabs, so they are hidden until the user selects one. A logged label with an unchanged page in such a tab means the late fix is queued, not broken. Verify scheduled work in a visible tab.\n","AGENTS.md":"# AGENTS.md\n\n## Constraints\n\n- Generated files: `dist/` and `browser-extensions/chrome/dist/` are build or test output; avoid hand-editing them unless the task explicitly targets generated artifacts.\n- Preserve the zero-runtime-dependency package design unless the user explicitly accepts a new dependency and its maintenance cost.\n- Keep exact dependency versions in `package.json`; do not introduce `^` or `~` ranges.\n- Use `node:` prefixes for Node.js built-in modules.\n- Avoid hard-wrapping Markdown prose; keep paragraphs and list items on single logical lines unless a format requires manual line breaks.\n- Do not wrap code comments early. Fill each comment line up to Prettier's `printWidth` (200, in `.prettierrc.json`) and break only when the line would exceed it. Prettier never reflows comment text, so an early-wrapped comment stays narrow forever.\n\n## Workflow\n\n- For multi-step, ambiguous, broad, or high-risk work, state a short 3–5 item plan before editing, covering approach, files, verification, and consequential open questions. Implement narrow, well-scoped fixes directly after gathering context.\n- For model-assisted prompt experiments, use [prompt-experiments](.agents/skills/prompt-experiments/SKILL.md). Ordinary deterministic spacing fixes use the checks below.\n- Before writing code that uses dependencies, tools, browser or extension APIs, or release machinery, invoke `find-docs` and check current documentation. Use local source as needed; do not rely on training data for API or configuration details. For Context7 lookups, use the pre-resolved IDs below.\n\n## Verification\n\n- Run `npm run build` before `npm run lint` or `npm run typecheck`; these checks depend on generated package outputs. The `test` and `test:*` scripts already run the full build.\n- Run the smallest meaningful check for the files changed, then broaden only when risk or failures require it.\n- For shared spacing changes, run `npm run test:shared`; add or update tests in `tests/shared/` when behavior changes.\n- For Node API or CLI changes, run `npm run test:node`; use focused fixture coverage for file-processing behavior.\n- For browser DOM behavior, run `npm run test:browser` or a focused Playwright test when possible.\n- For extension source changes, run `npm run build:extension`; run broader tests if shared or browser library behavior is affected.\n- For build, packaging, or export changes, run `npm run build` and the relevant package or smoke check.\n- For docs/config-only changes, at minimum run `git diff --check`.\n- Do not leave new warnings behind. If a meaningful check cannot run, state exactly what was skipped, why, and the next best check.\n\n## Publishing And Releases\n\n- Do not run `npm publish` locally. npm publishing is handled by GitHub Actions through the `v*` tag workflow and Trusted Publishing.\n- Use `npm run bump-version <version>` only when the task explicitly asks for a release/version flow.\n- Before changing release or packaging scripts, verify their generated output paths.\n\n## Context7 Library IDs\n\nPre-resolved IDs for the `find-docs` skill. Pass directly to `ctx7 docs`, skipping the `ctx7 library` step:\n\n| Tool              | `libraryId`                                    |\n| ----------------- | ---------------------------------------------- |\n| attw              | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome Extensions | `/websites/developer_chrome_extensions`        |\n| ESLint            | `/eslint/eslint`                               |\n| MDN Web Docs      | `/mdn/content`                                 |\n| Node.js           | `/nodejs/node`                                 |\n| npm               | `/websites/npmjs`                              |\n| Playwright        | `/microsoft/playwright`                        |\n| Prettier          | `/prettier/prettier`                           |\n| publint           | `/publint/publint`                             |\n| Shields.io        | `/websites/shields_io_badges`                  |\n| TypeScript        | `/websites/typescriptlang`                     |\n| typescript-eslint | `/typescript-eslint/typescript-eslint`         |\n| Vite              | `/vitejs/vite`                                 |\n| Vitest            | `/vitest-dev/vitest`                           |\n","CLAUDE.md":"# CLAUDE.md\n\n## Spacing Rules\n\n- `CONTEXT.md` is the glossary of terms the spacing rules, docs, and code share. Read it before touching spacing rules, and use its terms, never the synonyms it lists under _Avoid_.\n- Decisions that changed a spacing contract are ADRs in `docs/adr/`. Check them before proposing a rule change that reverses one.\n- When tweaking spacing rules, if the simpler rule is blocked only by rare test cases (typo-shaped input, degenerate shapes, nothing a real user reported), challenge the user to drop those cases in favor of the simpler rule instead of complicating the rule to preserve them. Show the candidate rule and exactly which expectations it breaks, then recommend dropping. Comment dropped cases out in place with `// Rare cases, ignore`, and record reversals of documented contracts as an ADR (precedent: ADR 0007). This licenses dropping rare-case contracts, not pruning tests in general.\n- When fixing a spacing issue, first try absorbing it into an existing rule (widen a character class, adjust a lookahead, rename the rule if its name stops matching). Add a separate rule when the tweak would overcomplicate the existing one, such as forcing one regex to serve two unrelated readings: optimize for total complexity of the rule set, not rule count.\n- Before changing a spacing rule, prototype every candidate on a patched copy of the built engine (`dist/shared/index.js`) and generate a per-case table with one block per case: input, rules output, extension output. Cover the symbol's test file plus real-world shapes, and decide on the diff between candidates, never on argument.\n- Example inputs use real-world text only. Never invent a name, term, or sentence to show an unlisted or boundary case; made-up input proves nothing about real text. A synthetic token is allowed only for a regex-boundary unit test, and says so.\n- A per-line reading (pipe, plus, hyphen) must test its contact before an earlier rule spaces that contact away. Plus reading missed every `CJK+A` line until it moved ahead of the operator rules, while the glossary promised the flip all along. When a glossary promise fails on a shape, check rule order before the regex.\n\n## Releasing\n\n```bash\nnpm run bump-version 1.2.3      # Bumps package.json, extension manifest, src/shared/index.ts, examples/package.json, then builds and packs the extension zip. Does NOT commit or tag.\n```\n\nWhen the release won't ship a new extension to the Chrome Web Store, restore the manifest version with `git restore browser-extensions/chrome/manifest.json` before committing, so the manifest doesn't claim an extension release that never happens. `/verify-before-npm-publish` decides: it fails when extension build inputs changed since the last store release but the manifest wasn't bumped, or didn't change but the manifest was.\n\n1. **Verify**: `/verify-before-npm-publish` must print `VERIFY OK` on the exact HEAD being tagged, so rerun it whenever HEAD moves after a pass.\n2. **Tag**: tag (`git tag -a vX.Y.Z -m \"\"`) and push master plus the tag only after the user says \"publish\".\n3. **npm publishing** runs in GitHub Actions (`publish.yml`) on a pushed `v*` tag, using npm Trusted Publishing (OIDC). Never run `npm publish` locally. ADR 0014 records why publishing is one job, not a pack/publish split.\n4. **Confirm**: `npm install pangu@X.Y.Z --min-release-age=0` in a scratch directory.\n\n## Gotcha\n\n- When adding or editing a glossary entry, a definition uses only bold glossary terms, platform names as the platform spells them (`Text` node, string, line, element), and ordinary English in its ordinary sense. One word carries one sense: a word needed in a technical sense gets its own entry or gets replaced. A term matches the code identifier for the same concept; when they diverge, define the code's word or rename the code.\n- Never blanket find-replace a domain term: pangu vocabulary doubles as ordinary verbs (space, settle). After a rename, grep the changed files for the old word's remaining forms, identifiers included, and read each hit for noun versus verb.\n- Write code comments in English with ANS characters only. Never paste CJK sample text from tests into a comment; describe the shape generically (`CJK | CJK`, `A+CJK`) and use `\\uXXXX` escape notation when a specific character matters.\n- In `tests/shared/`, write each `spaceText` case as its own literal `expect(...).toBe(...)` line, never a loop or `it.each`, so a failure names its input.\n- Keep every `// FIXME` line and `FIXME:` prefix in tests, even beside `it.fails` or `test.fail`: the user greps FIXME to find pending work.\n- Prefix a console call with `[pangu]` only in code that runs in an ordinary web page's console (content scripts, `src/shared/`), where the prefix is the only filter separating our logs from the site's. Options page, popup, and service worker logs stay unprefixed.\n- `fixtures/` has no `.prettierignore` on purpose. A byte-sensitive fixture carries a leading `<!-- prettier-ignore -->` pragma that `loadFixture()` strips before browser tests byte-compare `innerHTML` against the `.expected.html` files. The pragma guards only the next node, so many fixtures fail `prettier --check`. Never format fixtures in bulk (a repo-wide `prettier --write` breaks browser tests), and leave unreferenced fixtures alone.\n\n## External Tool Documentation\n\nPre-resolved Context7 IDs for the `find-docs` skill. Pass them to `ctx7 docs` and skip `ctx7 library`:\n\n| Tool               | `libraryId`                                    |\n| ------------------ | ---------------------------------------------- |\n| attw               | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome built-in AI | `/websites/developer_chrome_ai`                |\n| Chrome Extensions  | `/websites/developer_chrome_extensions`        |\n| ESLint             | `/eslint/eslint`                               |\n| GitHub Actions     | `/websites/github_en_actions`                  |\n| MDN Web Docs       | `/mdn/content`                                 |\n| Node.js            | `/nodejs/node`                                 |\n| npm                | `/websites/npmjs`                              |\n| Playwright         | `/microsoft/playwright`                        |\n| Prettier           | `/prettier/prettier`                           |\n| publint            | `/publint/publint`                             |\n| Shields.io         | `/websites/shields_io_badges`                  |\n| TypeScript         | `/websites/typescriptlang`                     |\n| typescript-eslint  | `/typescript-eslint/typescript-eslint`         |\n| Vite               | `/vitejs/vite`                                 |\n| Vitest             | `/vitest-dev/vitest`                           |\n",".claude/skills/verify-before-npm-publish/SKILL.md":"---\nname: verify-before-npm-publish\ndescription: (project) Before publishing pangu, check the release is ready, pack it, and run the examples app against that tarball to prove the about-to-ship build is consumable by a real install\ndisable-model-invocation: true\nallowed-tools:\n  - Bash(npm:*)\n  - Bash(node:*)\n  - Bash(mktemp:*)\n  - Bash(cp:*)\n  - Bash(ls:*)\n  - Bash(rm:*)\n  - Bash(tar:*)\n  - Bash(git diff:*)\n  - Bash(git show:*)\n  - Bash(git describe:*)\n  - Bash(sort:*)\n  - Bash(comm:*)\n---\n\n# Verify before publish\n\nThe `examples/` app is a real consumer: its own `package.json` pins a published `pangu`, its own `tsconfig.json` resolves under `NodeNext`, and its checks reach pangu through `node_modules` and the `exports` map. That is exactly the path pangu's own build never takes, because the build resolves `src/` directly, sibling file to sibling file. So a `.d.ts` with a bare relative import, or a file the `files` list forgot to ship, reaches npm green.\n\nThis skill briefly repoints that consumer at the **freshly packed tarball** instead of its published pin, runs every example check against it, and restores the pin. The packed tarball is the only honest artifact. Repointing is also what lets the check run before publish at all: pangu's next version is not on the registry yet, so the pin cannot resolve until the tarball stands in for it.\n\n## Run the verification\n\nEverything below runs as one command so the `trap` restores `examples/package.json` whether the checks pass, fail, or error midway. Never split it.\n\nThe checks capture their exit code explicitly instead of leaning on `set -e` to abort. Some runners (Claude Code's Bash tool among them) invoke this in a shell where `set -e` does not stop the script, and a bare `echo \"VERIFY OK\"` after an unguarded `npm run verify` then prints a pass over a real failure. Report the `VERIFY OK` / `VERIFY FAILED` line, never the tool's own exit status.\n\n```bash\nset -eu\nfail() { echo \"VERIFY FAILED ($1)\"; exit 1; }\n\n# Pushing the tag publishes the tagged commit without running test.yml, so it must pass the suite here and carry an unused version\nVERSION=\"$(node -p \"require('./package.json').version\")\"\n[ -z \"$(git status --porcelain)\" ] || fail \"dirty working tree\"\n[ \"$(git branch --show-current)\" = master ] || fail \"not on master\"\nif git rev-parse -q --verify \"refs/tags/v$VERSION\" >/dev/null; then fail \"tag v$VERSION exists locally\"; fi\n[ -z \"$(npm view \"pangu@$VERSION\" version)\" ] || fail \"pangu@$VERSION already on npm\"\n{ npm test && npm run lint && npm run typecheck; } || fail \"tests, lint, or typecheck\"\n\n# Build and pack the exact bytes that would go to npm\nnpm run build\nTARBALL_DIR=\"$(mktemp -d)\"\nnpm pack --pack-destination \"$TARBALL_DIR\"\nTGZ=\"$(ls \"$TARBALL_DIR\"/pangu-*.tgz)\"\n\n# Every file under dist/ lives in an entry folder. A file directly in dist/ is a code-split chunk that a build config change let back in\nif tar -tzf \"$TGZ\" | grep -E '^package/dist/[^/]+$'; then echo \"VERIFY FAILED (stray file in dist/)\"; exit 1; fi\n\n# bump-version writes every version copy, so a mismatch means a hand edit missed one\ntar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"\n[ \"$(node -p \"require('$TARBALL_DIR/package/package.json').version\")\" = \"$VERSION\" ] || fail \"tarball package.json version\"\n[ \"$(node --input-type=module -e \"import { pangu } from '$TARBALL_DIR/package/dist/shared/index.js'; console.log(pangu.version)\")\" = \"$VERSION\" ] || fail \"pangu.version\"\n# The last store release is the manifest version at the latest tag, so any change in extension build inputs since then needs a manifest bump, and no change needs a restored manifest\nMANIFEST=\"$(node -p \"require('./browser-extensions/chrome/manifest.json').version\")\"\nSTORE=\"$(git show \"$(git describe --tags --abbrev=0):browser-extensions/chrome/manifest.json\" | node -p \"JSON.parse(require('fs').readFileSync(0,'utf8')).version\")\"\nif git diff --quiet -I \"readonly version: string\" \"v$STORE\" HEAD -- src/browser src/shared browser-extensions/chrome browser-extensions/vite.config.extension.ts ':!browser-extensions/chrome/manifest.json' ':!browser-extensions/chrome/tests'; then\n  [ \"$MANIFEST\" != \"$VERSION\" ] || fail \"extension unchanged since v$STORE, restore the manifest version\"\nelse\n  [ \"$MANIFEST\" = \"$VERSION\" ] || fail \"extension changed since v$STORE, manifest version\"\nfi\n[ \"$(node -p \"require('./examples/package.json').dependencies.pangu\")\" = \"$VERSION\" ] || fail \"examples pin\"\ngrep -m1 '^## v' CHANGELOG.md | grep -q \"^## v$VERSION \" || fail \"CHANGELOG top heading\"\n\n# CDN links reach any shipped path, so a file dropped since the published version breaks them\nPUBLISHED=\"$(npm view pangu version)\"\n# A user-level min-release-age would refuse a version published days ago\nnpm pack \"pangu@$PUBLISHED\" --min-release-age=0 --pack-destination \"$TARBALL_DIR\"\ntar -tzf \"$TARBALL_DIR/pangu-$PUBLISHED.tgz\" | sort > \"$TARBALL_DIR/published.txt\"\ntar -tzf \"$TGZ\" | sort > \"$TARBALL_DIR/packed.txt\"\nREMOVED=\"$(comm -23 \"$TARBALL_DIR/published.txt\" \"$TARBALL_DIR/packed.txt\")\"\n[ -z \"$REMOVED\" ] || { echo \"$REMOVED\"; fail \"files removed since $PUBLISHED\"; }\n\n# From here the examples pin is modified, so guarantee its restoration on any exit\ncp examples/package.json \"$TARBALL_DIR/package.json.orig\"\ntrap 'cp \"$TARBALL_DIR/package.json.orig\" examples/package.json; rm -rf \"$TARBALL_DIR\" examples/node_modules examples/package-lock.json' EXIT\n\n# Repoint the consumer at the tarball, install, and run every example check against it\nnode -e \"const fs=require('fs'),f='examples/package.json',p=JSON.parse(fs.readFileSync(f));p.dependencies.pangu='file:'+process.argv[1];fs.writeFileSync(f,JSON.stringify(p,null,2)+'\\n')\" \"$TGZ\"\nnpm install --prefix examples --no-audit --no-fund\n\nrc=0\nnpm run verify --prefix examples || rc=$?\nif [ \"$rc\" -eq 0 ]; then echo \"VERIFY OK\"; else echo \"VERIFY FAILED (exit $rc)\"; fi\nexit \"$rc\"\n```\n\n## Reading the result\n\n- `VERIFY OK` on the last line → the tarball is clear to publish.\n- `VERIFY FAILED` → do not publish. The `trap` has already restored the pin and dropped the throwaway install; diagnose from the message or the table below, fix, and rerun.\n\nEither way, confirm the pin came back before moving on: `git diff --quiet examples/package.json` must exit 0. A dirty `examples/package.json` means the run was interrupted before the trap fired; restore it with `git checkout -- examples/package.json`.\n\n## What each failure means\n\n| Symptom                          | Cause                                                                                    |\n| -------------------------------- | ---------------------------------------------------------------------------------------- |\n| `TS2834` in a `dist/**/*.d.ts`   | a relative import in `src/` is missing its `.js` extension                               |\n| `TS2339` on an inherited method  | a base-class `.d.ts` import failed to resolve, collapsing the subclass to an error type  |\n| `Cannot find module` at run time | `package.json` `files` doesn't ship the referenced file, or an `exports` path is wrong   |\n| `stray file in dist/`            | the `esm` environment emitted a hashed shared chunk; keep `output.preserveModules` on it |\n| `files removed since X.Y.Z`      | a rename or build change dropped a shipped path; restore it or release as a major        |\n\nTo read the shipped artifact directly, unpack the tarball: `tar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"` exposes `package/dist/**/*.d.ts` and `package/package.json`.\n\n## What the run exercises\n\n`npm run verify` in `examples/` chains the runtime entrypoints (`verify:commonjs`, `verify:esm`, `verify:cli`), the browser page (`verify:browser`, which serves `verify-browser.html` and drives the UMD `<script>` tag and the ESM `import` in headless Chromium), and the type check (`typecheck`, whose `tsconfig.json` checks `verify-types.ts` under the `require` condition and `verify-types.mts` under the `import` condition, covering both branches of the dual-package `exports` map). Every one runs against the installed tarball, not the published pin.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/vinta/pangu.js/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\n## Constraints\n\n- Generated files: `dist/` and `browser-extensions/chrome/dist/` are build or test output; avoid hand-editing them unless the task explicitly targets generated artifacts.\n- Preserve the zero-runtime-dependency package design unless the user explicitly accepts a new dependency and its maintenance cost.\n- Keep exact dependency versions in `package.json`; do not introduce `^` or `~` ranges.\n- Use `node:` prefixes for Node.js built-in modules.\n- Avoid hard-wrapping Markdown prose; keep paragraphs and list items on single logical lines unless a format requires manual line breaks.\n- Do not wrap code comments early. Fill each comment line up to Prettier's `printWidth` (200, in `.prettierrc.json`) and break only when the line would exceed it. Prettier never reflows comment text, so an early-wrapped comment stays narrow forever.\n\n## Workflow\n\n- For multi-step, ambiguous, broad, or high-risk work, state a short 3–5 item plan before editing, covering approach, files, verification, and consequential open questions. Implement narrow, well-scoped fixes directly after gathering context.\n- For model-assisted prompt experiments, use [prompt-experiments](.agents/skills/prompt-experiments/SKILL.md). Ordinary deterministic spacing fixes use the checks below.\n- Before writing code that uses dependencies, tools, browser or extension APIs, or release machinery, invoke `find-docs` and check current documentation. Use local source as needed; do not rely on training data for API or configuration details. For Context7 lookups, use the pre-resolved IDs below.\n\n## Verification\n\n- Run `npm run build` before `npm run lint` or `npm run typecheck`; these checks depend on generated package outputs. The `test` and `test:*` scripts already run the full build.\n- Run the smallest meaningful check for the files changed, then broaden only when risk or failures require it.\n- For shared spacing changes, run `npm run test:shared`; add or update tests in `tests/shared/` when behavior changes.\n- For Node API or CLI changes, run `npm run test:node`; use focused fixture coverage for file-processing behavior.\n- For browser DOM behavior, run `npm run test:browser` or a focused Playwright test when possible.\n- For extension source changes, run `npm run build:extension`; run broader tests if shared or browser library behavior is affected.\n- For build, packaging, or export changes, run `npm run build` and the relevant package or smoke check.\n- For docs/config-only changes, at minimum run `git diff --check`.\n- Do not leave new warnings behind. If a meaningful check cannot run, state exactly what was skipped, why, and the next best check.\n\n## Publishing And Releases\n\n- Do not run `npm publish` locally. npm publishing is handled by GitHub Actions through the `v*` tag workflow and Trusted Publishing.\n- Use `npm run bump-version <version>` only when the task explicitly asks for a release/version flow.\n- Before changing release or packaging scripts, verify their generated output paths.\n\n## Context7 Library IDs\n\nPre-resolved IDs for the `find-docs` skill. Pass directly to `ctx7 docs`, skipping the `ctx7 library` step:\n\n| Tool              | `libraryId`                                    |\n| ----------------- | ---------------------------------------------- |\n| attw              | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome Extensions | `/websites/developer_chrome_extensions`        |\n| ESLint            | `/eslint/eslint`                               |\n| MDN Web Docs      | `/mdn/content`                                 |\n| Node.js           | `/nodejs/node`                                 |\n| npm               | `/websites/npmjs`                              |\n| Playwright        | `/microsoft/playwright`                        |\n| Prettier          | `/prettier/prettier`                           |\n| publint           | `/publint/publint`                             |\n| Shields.io        | `/websites/shields_io_badges`                  |\n| TypeScript        | `/websites/typescriptlang`                     |\n| typescript-eslint | `/typescript-eslint/typescript-eslint`         |\n| Vite              | `/vitejs/vite`                                 |\n| Vitest            | `/vitest-dev/vitest`                           |\n","isInternal":false,"tokens":898,"sizeBytes":4285},{"name":"chrome-extension.md","path":".claude/rules/chrome-extension.md","rawUrl":"https://raw.githubusercontent.com/vinta/pangu.js/HEAD/.claude/rules/chrome-extension.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths:\n  - 'browser-extensions/chrome/**'\n  - 'src/browser/**'\n---\n\n# Chrome Extension\n\n- The Chrome floor is declared three times and all must agree: `minimum_chrome_version` in `manifest.json`, `build.target` in `browser-extensions/vite.config.extension.ts`, and the `available` year of `baseline-js/use-baseline` in `eslint.config.js`. A bump also drops the fallbacks it makes dead and gets a CHANGELOG line.\n- Chrome's shipped initial value for `text-autospace` is `no-autospace` (verified in Chrome 150 on 2026-07-23), whatever chromestatus and the blink-dev intent say about default-on. The `html { text-autospace: normal }` content-script CSS is the permanent opt-in on every Chrome version, never redundant. A computed `normal` on a page proves the extension CSS injected.\n- `requestIdleCallback` never fires in a hidden document (background tab, minimized window), and the `timeout` option does not rescue it. Everything routed through `BrowserPangu.schedule()` waits until the tab is visible again. Focus does not matter, only visibility.\n  - `claude-in-chrome` opens tabs as background tabs, so they are hidden until the user selects one. A logged label with an unchanged page in such a tab means the late fix is queued, not broken. Verify scheduled work in a visible tab.\n","frontmatter":{"paths":["browser-extensions/chrome/**","src/browser/**"]},"isInternal":false,"tokens":297,"sizeBytes":1288},{"name":"SKILL.md","path":".claude/skills/verify-before-npm-publish/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vinta/pangu.js/HEAD/.claude/skills/verify-before-npm-publish/SKILL.md","title":"verify-before-npm-publish","category":"anthropic-skill","format":"markdown","content":"---\nname: verify-before-npm-publish\ndescription: (project) Before publishing pangu, check the release is ready, pack it, and run the examples app against that tarball to prove the about-to-ship build is consumable by a real install\ndisable-model-invocation: true\nallowed-tools:\n  - Bash(npm:*)\n  - Bash(node:*)\n  - Bash(mktemp:*)\n  - Bash(cp:*)\n  - Bash(ls:*)\n  - Bash(rm:*)\n  - Bash(tar:*)\n  - Bash(git diff:*)\n  - Bash(git show:*)\n  - Bash(git describe:*)\n  - Bash(sort:*)\n  - Bash(comm:*)\n---\n\n# Verify before publish\n\nThe `examples/` app is a real consumer: its own `package.json` pins a published `pangu`, its own `tsconfig.json` resolves under `NodeNext`, and its checks reach pangu through `node_modules` and the `exports` map. That is exactly the path pangu's own build never takes, because the build resolves `src/` directly, sibling file to sibling file. So a `.d.ts` with a bare relative import, or a file the `files` list forgot to ship, reaches npm green.\n\nThis skill briefly repoints that consumer at the **freshly packed tarball** instead of its published pin, runs every example check against it, and restores the pin. The packed tarball is the only honest artifact. Repointing is also what lets the check run before publish at all: pangu's next version is not on the registry yet, so the pin cannot resolve until the tarball stands in for it.\n\n## Run the verification\n\nEverything below runs as one command so the `trap` restores `examples/package.json` whether the checks pass, fail, or error midway. Never split it.\n\nThe checks capture their exit code explicitly instead of leaning on `set -e` to abort. Some runners (Claude Code's Bash tool among them) invoke this in a shell where `set -e` does not stop the script, and a bare `echo \"VERIFY OK\"` after an unguarded `npm run verify` then prints a pass over a real failure. Report the `VERIFY OK` / `VERIFY FAILED` line, never the tool's own exit status.\n\n```bash\nset -eu\nfail() { echo \"VERIFY FAILED ($1)\"; exit 1; }\n\n# Pushing the tag publishes the tagged commit without running test.yml, so it must pass the suite here and carry an unused version\nVERSION=\"$(node -p \"require('./package.json').version\")\"\n[ -z \"$(git status --porcelain)\" ] || fail \"dirty working tree\"\n[ \"$(git branch --show-current)\" = master ] || fail \"not on master\"\nif git rev-parse -q --verify \"refs/tags/v$VERSION\" >/dev/null; then fail \"tag v$VERSION exists locally\"; fi\n[ -z \"$(npm view \"pangu@$VERSION\" version)\" ] || fail \"pangu@$VERSION already on npm\"\n{ npm test && npm run lint && npm run typecheck; } || fail \"tests, lint, or typecheck\"\n\n# Build and pack the exact bytes that would go to npm\nnpm run build\nTARBALL_DIR=\"$(mktemp -d)\"\nnpm pack --pack-destination \"$TARBALL_DIR\"\nTGZ=\"$(ls \"$TARBALL_DIR\"/pangu-*.tgz)\"\n\n# Every file under dist/ lives in an entry folder. A file directly in dist/ is a code-split chunk that a build config change let back in\nif tar -tzf \"$TGZ\" | grep -E '^package/dist/[^/]+$'; then echo \"VERIFY FAILED (stray file in dist/)\"; exit 1; fi\n\n# bump-version writes every version copy, so a mismatch means a hand edit missed one\ntar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"\n[ \"$(node -p \"require('$TARBALL_DIR/package/package.json').version\")\" = \"$VERSION\" ] || fail \"tarball package.json version\"\n[ \"$(node --input-type=module -e \"import { pangu } from '$TARBALL_DIR/package/dist/shared/index.js'; console.log(pangu.version)\")\" = \"$VERSION\" ] || fail \"pangu.version\"\n# The last store release is the manifest version at the latest tag, so any change in extension build inputs since then needs a manifest bump, and no change needs a restored manifest\nMANIFEST=\"$(node -p \"require('./browser-extensions/chrome/manifest.json').version\")\"\nSTORE=\"$(git show \"$(git describe --tags --abbrev=0):browser-extensions/chrome/manifest.json\" | node -p \"JSON.parse(require('fs').readFileSync(0,'utf8')).version\")\"\nif git diff --quiet -I \"readonly version: string\" \"v$STORE\" HEAD -- src/browser src/shared browser-extensions/chrome browser-extensions/vite.config.extension.ts ':!browser-extensions/chrome/manifest.json' ':!browser-extensions/chrome/tests'; then\n  [ \"$MANIFEST\" != \"$VERSION\" ] || fail \"extension unchanged since v$STORE, restore the manifest version\"\nelse\n  [ \"$MANIFEST\" = \"$VERSION\" ] || fail \"extension changed since v$STORE, manifest version\"\nfi\n[ \"$(node -p \"require('./examples/package.json').dependencies.pangu\")\" = \"$VERSION\" ] || fail \"examples pin\"\ngrep -m1 '^## v' CHANGELOG.md | grep -q \"^## v$VERSION \" || fail \"CHANGELOG top heading\"\n\n# CDN links reach any shipped path, so a file dropped since the published version breaks them\nPUBLISHED=\"$(npm view pangu version)\"\n# A user-level min-release-age would refuse a version published days ago\nnpm pack \"pangu@$PUBLISHED\" --min-release-age=0 --pack-destination \"$TARBALL_DIR\"\ntar -tzf \"$TARBALL_DIR/pangu-$PUBLISHED.tgz\" | sort > \"$TARBALL_DIR/published.txt\"\ntar -tzf \"$TGZ\" | sort > \"$TARBALL_DIR/packed.txt\"\nREMOVED=\"$(comm -23 \"$TARBALL_DIR/published.txt\" \"$TARBALL_DIR/packed.txt\")\"\n[ -z \"$REMOVED\" ] || { echo \"$REMOVED\"; fail \"files removed since $PUBLISHED\"; }\n\n# From here the examples pin is modified, so guarantee its restoration on any exit\ncp examples/package.json \"$TARBALL_DIR/package.json.orig\"\ntrap 'cp \"$TARBALL_DIR/package.json.orig\" examples/package.json; rm -rf \"$TARBALL_DIR\" examples/node_modules examples/package-lock.json' EXIT\n\n# Repoint the consumer at the tarball, install, and run every example check against it\nnode -e \"const fs=require('fs'),f='examples/package.json',p=JSON.parse(fs.readFileSync(f));p.dependencies.pangu='file:'+process.argv[1];fs.writeFileSync(f,JSON.stringify(p,null,2)+'\\n')\" \"$TGZ\"\nnpm install --prefix examples --no-audit --no-fund\n\nrc=0\nnpm run verify --prefix examples || rc=$?\nif [ \"$rc\" -eq 0 ]; then echo \"VERIFY OK\"; else echo \"VERIFY FAILED (exit $rc)\"; fi\nexit \"$rc\"\n```\n\n## Reading the result\n\n- `VERIFY OK` on the last line → the tarball is clear to publish.\n- `VERIFY FAILED` → do not publish. The `trap` has already restored the pin and dropped the throwaway install; diagnose from the message or the table below, fix, and rerun.\n\nEither way, confirm the pin came back before moving on: `git diff --quiet examples/package.json` must exit 0. A dirty `examples/package.json` means the run was interrupted before the trap fired; restore it with `git checkout -- examples/package.json`.\n\n## What each failure means\n\n| Symptom                          | Cause                                                                                    |\n| -------------------------------- | ---------------------------------------------------------------------------------------- |\n| `TS2834` in a `dist/**/*.d.ts`   | a relative import in `src/` is missing its `.js` extension                               |\n| `TS2339` on an inherited method  | a base-class `.d.ts` import failed to resolve, collapsing the subclass to an error type  |\n| `Cannot find module` at run time | `package.json` `files` doesn't ship the referenced file, or an `exports` path is wrong   |\n| `stray file in dist/`            | the `esm` environment emitted a hashed shared chunk; keep `output.preserveModules` on it |\n| `files removed since X.Y.Z`      | a rename or build change dropped a shipped path; restore it or release as a major        |\n\nTo read the shipped artifact directly, unpack the tarball: `tar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"` exposes `package/dist/**/*.d.ts` and `package/package.json`.\n\n## What the run exercises\n\n`npm run verify` in `examples/` chains the runtime entrypoints (`verify:commonjs`, `verify:esm`, `verify:cli`), the browser page (`verify:browser`, which serves `verify-browser.html` and drives the UMD `<script>` tag and the ESM `import` in headless Chromium), and the type check (`typecheck`, whose `tsconfig.json` checks `verify-types.ts` under the `require` condition and `verify-types.mts` under the `import` condition, covering both branches of the dual-package `exports` map). Every one runs against the installed tarball, not the published pin.\n","frontmatter":{"name":"verify-before-npm-publish","description":"(project) Before publishing pangu, check the release is ready, pack it, and run the examples app against that tarball to prove the about-to-ship build is consumable by a real install","disable-model-invocation":true,"allowed-tools":["Bash(npm:*)","Bash(node:*)","Bash(mktemp:*)","Bash(cp:*)","Bash(ls:*)","Bash(rm:*)","Bash(tar:*)","Bash(git diff:*)","Bash(git show:*)","Bash(git describe:*)","Bash(sort:*)","Bash(comm:*)"],"allowedTools":["Bash(npm:*)","Bash(node:*)","Bash(mktemp:*)","Bash(cp:*)","Bash(ls:*)","Bash(rm:*)","Bash(tar:*)","Bash(git diff:*)","Bash(git show:*)","Bash(git describe:*)","Bash(sort:*)","Bash(comm:*)"],"disableModelInvocation":true},"allowedTools":["Bash(npm:*)","Bash(node:*)","Bash(mktemp:*)","Bash(cp:*)","Bash(ls:*)","Bash(rm:*)","Bash(tar:*)","Bash(git diff:*)","Bash(git show:*)","Bash(git describe:*)","Bash(sort:*)","Bash(comm:*)"],"isInternal":false,"tokens":2036,"sizeBytes":8039},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/vinta/pangu.js/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\n## Spacing Rules\n\n- `CONTEXT.md` is the glossary of terms the spacing rules, docs, and code share. Read it before touching spacing rules, and use its terms, never the synonyms it lists under _Avoid_.\n- Decisions that changed a spacing contract are ADRs in `docs/adr/`. Check them before proposing a rule change that reverses one.\n- When tweaking spacing rules, if the simpler rule is blocked only by rare test cases (typo-shaped input, degenerate shapes, nothing a real user reported), challenge the user to drop those cases in favor of the simpler rule instead of complicating the rule to preserve them. Show the candidate rule and exactly which expectations it breaks, then recommend dropping. Comment dropped cases out in place with `// Rare cases, ignore`, and record reversals of documented contracts as an ADR (precedent: ADR 0007). This licenses dropping rare-case contracts, not pruning tests in general.\n- When fixing a spacing issue, first try absorbing it into an existing rule (widen a character class, adjust a lookahead, rename the rule if its name stops matching). Add a separate rule when the tweak would overcomplicate the existing one, such as forcing one regex to serve two unrelated readings: optimize for total complexity of the rule set, not rule count.\n- Before changing a spacing rule, prototype every candidate on a patched copy of the built engine (`dist/shared/index.js`) and generate a per-case table with one block per case: input, rules output, extension output. Cover the symbol's test file plus real-world shapes, and decide on the diff between candidates, never on argument.\n- Example inputs use real-world text only. Never invent a name, term, or sentence to show an unlisted or boundary case; made-up input proves nothing about real text. A synthetic token is allowed only for a regex-boundary unit test, and says so.\n- A per-line reading (pipe, plus, hyphen) must test its contact before an earlier rule spaces that contact away. Plus reading missed every `CJK+A` line until it moved ahead of the operator rules, while the glossary promised the flip all along. When a glossary promise fails on a shape, check rule order before the regex.\n\n## Releasing\n\n```bash\nnpm run bump-version 1.2.3      # Bumps package.json, extension manifest, src/shared/index.ts, examples/package.json, then builds and packs the extension zip. Does NOT commit or tag.\n```\n\nWhen the release won't ship a new extension to the Chrome Web Store, restore the manifest version with `git restore browser-extensions/chrome/manifest.json` before committing, so the manifest doesn't claim an extension release that never happens. `/verify-before-npm-publish` decides: it fails when extension build inputs changed since the last store release but the manifest wasn't bumped, or didn't change but the manifest was.\n\n1. **Verify**: `/verify-before-npm-publish` must print `VERIFY OK` on the exact HEAD being tagged, so rerun it whenever HEAD moves after a pass.\n2. **Tag**: tag (`git tag -a vX.Y.Z -m \"\"`) and push master plus the tag only after the user says \"publish\".\n3. **npm publishing** runs in GitHub Actions (`publish.yml`) on a pushed `v*` tag, using npm Trusted Publishing (OIDC). Never run `npm publish` locally. ADR 0014 records why publishing is one job, not a pack/publish split.\n4. **Confirm**: `npm install pangu@X.Y.Z --min-release-age=0` in a scratch directory.\n\n## Gotcha\n\n- When adding or editing a glossary entry, a definition uses only bold glossary terms, platform names as the platform spells them (`Text` node, string, line, element), and ordinary English in its ordinary sense. One word carries one sense: a word needed in a technical sense gets its own entry or gets replaced. A term matches the code identifier for the same concept; when they diverge, define the code's word or rename the code.\n- Never blanket find-replace a domain term: pangu vocabulary doubles as ordinary verbs (space, settle). After a rename, grep the changed files for the old word's remaining forms, identifiers included, and read each hit for noun versus verb.\n- Write code comments in English with ANS characters only. Never paste CJK sample text from tests into a comment; describe the shape generically (`CJK | CJK`, `A+CJK`) and use `\\uXXXX` escape notation when a specific character matters.\n- In `tests/shared/`, write each `spaceText` case as its own literal `expect(...).toBe(...)` line, never a loop or `it.each`, so a failure names its input.\n- Keep every `// FIXME` line and `FIXME:` prefix in tests, even beside `it.fails` or `test.fail`: the user greps FIXME to find pending work.\n- Prefix a console call with `[pangu]` only in code that runs in an ordinary web page's console (content scripts, `src/shared/`), where the prefix is the only filter separating our logs from the site's. Options page, popup, and service worker logs stay unprefixed.\n- `fixtures/` has no `.prettierignore` on purpose. A byte-sensitive fixture carries a leading `<!-- prettier-ignore -->` pragma that `loadFixture()` strips before browser tests byte-compare `innerHTML` against the `.expected.html` files. The pragma guards only the next node, so many fixtures fail `prettier --check`. Never format fixtures in bulk (a repo-wide `prettier --write` breaks browser tests), and leave unreferenced fixtures alone.\n\n## External Tool Documentation\n\nPre-resolved Context7 IDs for the `find-docs` skill. Pass them to `ctx7 docs` and skip `ctx7 library`:\n\n| Tool               | `libraryId`                                    |\n| ------------------ | ---------------------------------------------- |\n| attw               | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome built-in AI | `/websites/developer_chrome_ai`                |\n| Chrome Extensions  | `/websites/developer_chrome_extensions`        |\n| ESLint             | `/eslint/eslint`                               |\n| GitHub Actions     | `/websites/github_en_actions`                  |\n| MDN Web Docs       | `/mdn/content`                                 |\n| Node.js            | `/nodejs/node`                                 |\n| npm                | `/websites/npmjs`                              |\n| Playwright         | `/microsoft/playwright`                        |\n| Prettier           | `/prettier/prettier`                           |\n| publint            | `/publint/publint`                             |\n| Shields.io         | `/websites/shields_io_badges`                  |\n| TypeScript         | `/websites/typescriptlang`                     |\n| typescript-eslint  | `/typescript-eslint/typescript-eslint`         |\n| Vite               | `/vitejs/vite`                                 |\n| Vitest             | `/vitest-dev/vitest`                           |\n","isInternal":false,"tokens":1483,"sizeBytes":6749}],"systemPromptSnippet":"<yakaai_skills repo=\"vinta/pangu.js\">\n<!-- File: AGENTS.md (Tokens: ~898 | Category: root-instruction) -->\n# AGENTS.md\n\n## Constraints\n\n- Generated files: `dist/` and `browser-extensions/chrome/dist/` are build or test output; avoid hand-editing them unless the task explicitly targets generated artifacts.\n- Preserve the zero-runtime-dependency package design unless the user explicitly accepts a new dependency and its maintenance cost.\n- Keep exact dependency versions in `package.json`; do not introduce `^` or `~` ranges.\n- Use `node:` prefixes for Node.js built-in modules.\n- Avoid hard-wrapping Markdown prose; keep paragraphs and list items on single logical lines unless a format requires manual line breaks.\n- Do not wrap code comments early. Fill each comment line up to Prettier's `printWidth` (200, in `.prettierrc.json`) and break only when the line would exceed it. Prettier never reflows comment text, so an early-wrapped comment stays narrow forever.\n\n## Workflow\n\n- For multi-step, ambiguous, broad, or high-risk work, state a short 3–5 item plan before editing, covering approach, files, verification, and consequential open questions. Implement narrow, well-scoped fixes directly after gathering context.\n- For model-assisted prompt experiments, use [prompt-experiments](.agents/skills/prompt-experiments/SKILL.md). Ordinary deterministic spacing fixes use the checks below.\n- Before writing code that uses dependencies, tools, browser or extension APIs, or release machinery, invoke `find-docs` and check current documentation. Use local source as needed; do not rely on training data for API or configuration details. For Context7 lookups, use the pre-resolved IDs below.\n\n## Verification\n\n- Run `npm run build` before `npm run lint` or `npm run typecheck`; these checks depend on generated package outputs. The `test` and `test:*` scripts already run the full build.\n- Run the smallest meaningful check for the files changed, then broaden only when risk or failures require it.\n- For shared spacing changes, run `npm run test:shared`; add or update tests in `tests/shared/` when behavior changes.\n- For Node API or CLI changes, run `npm run test:node`; use focused fixture coverage for file-processing behavior.\n- For browser DOM behavior, run `npm run test:browser` or a focused Playwright test when possible.\n- For extension source changes, run `npm run build:extension`; run broader tests if shared or browser library behavior is affected.\n- For build, packaging, or export changes, run `npm run build` and the relevant package or smoke check.\n- For docs/config-only changes, at minimum run `git diff --check`.\n- Do not leave new warnings behind. If a meaningful check cannot run, state exactly what was skipped, why, and the next best check.\n\n## Publishing And Releases\n\n- Do not run `npm publish` locally. npm publishing is handled by GitHub Actions through the `v*` tag workflow and Trusted Publishing.\n- Use `npm run bump-version <version>` only when the task explicitly asks for a release/version flow.\n- Before changing release or packaging scripts, verify their generated output paths.\n\n## Context7 Library IDs\n\nPre-resolved IDs for the `find-docs` skill. Pass directly to `ctx7 docs`, skipping the `ctx7 library` step:\n\n| Tool              | `libraryId`                                    |\n| ----------------- | ---------------------------------------------- |\n| attw              | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome Extensions | `/websites/developer_chrome_extensions`        |\n| ESLint            | `/eslint/eslint`                               |\n| MDN Web Docs      | `/mdn/content`                                 |\n| Node.js           | `/nodejs/node`                                 |\n| npm               | `/websites/npmjs`                              |\n| Playwright        | `/microsoft/playwright`                        |\n| Prettier          | `/prettier/prettier`                           |\n| publint           | `/publint/publint`                             |\n| Shields.io        | `/websites/shields_io_badges`                  |\n| TypeScript        | `/websites/typescriptlang`                     |\n| typescript-eslint | `/typescript-eslint/typescript-eslint`         |\n| Vite              | `/vitejs/vite`                                 |\n| Vitest            | `/vitest-dev/vitest`                           |\n\n<!-- File: .claude/rules/chrome-extension.md (Tokens: ~297 | Category: anthropic-skill) -->\n---\npaths:\n  - 'browser-extensions/chrome/**'\n  - 'src/browser/**'\n---\n\n# Chrome Extension\n\n- The Chrome floor is declared three times and all must agree: `minimum_chrome_version` in `manifest.json`, `build.target` in `browser-extensions/vite.config.extension.ts`, and the `available` year of `baseline-js/use-baseline` in `eslint.config.js`. A bump also drops the fallbacks it makes dead and gets a CHANGELOG line.\n- Chrome's shipped initial value for `text-autospace` is `no-autospace` (verified in Chrome 150 on 2026-07-23), whatever chromestatus and the blink-dev intent say about default-on. The `html { text-autospace: normal }` content-script CSS is the permanent opt-in on every Chrome version, never redundant. A computed `normal` on a page proves the extension CSS injected.\n- `requestIdleCallback` never fires in a hidden document (background tab, minimized window), and the `timeout` option does not rescue it. Everything routed through `BrowserPangu.schedule()` waits until the tab is visible again. Focus does not matter, only visibility.\n  - `claude-in-chrome` opens tabs as background tabs, so they are hidden until the user selects one. A logged label with an unchanged page in such a tab means the late fix is queued, not broken. Verify scheduled work in a visible tab.\n\n<!-- File: .claude/skills/verify-before-npm-publish/SKILL.md (Tokens: ~2036 | Category: anthropic-skill) -->\n---\nname: verify-before-npm-publish\ndescription: (project) Before publishing pangu, check the release is ready, pack it, and run the examples app against that tarball to prove the about-to-ship build is consumable by a real install\ndisable-model-invocation: true\nallowed-tools:\n  - Bash(npm:*)\n  - Bash(node:*)\n  - Bash(mktemp:*)\n  - Bash(cp:*)\n  - Bash(ls:*)\n  - Bash(rm:*)\n  - Bash(tar:*)\n  - Bash(git diff:*)\n  - Bash(git show:*)\n  - Bash(git describe:*)\n  - Bash(sort:*)\n  - Bash(comm:*)\n---\n\n# Verify before publish\n\nThe `examples/` app is a real consumer: its own `package.json` pins a published `pangu`, its own `tsconfig.json` resolves under `NodeNext`, and its checks reach pangu through `node_modules` and the `exports` map. That is exactly the path pangu's own build never takes, because the build resolves `src/` directly, sibling file to sibling file. So a `.d.ts` with a bare relative import, or a file the `files` list forgot to ship, reaches npm green.\n\nThis skill briefly repoints that consumer at the **freshly packed tarball** instead of its published pin, runs every example check against it, and restores the pin. The packed tarball is the only honest artifact. Repointing is also what lets the check run before publish at all: pangu's next version is not on the registry yet, so the pin cannot resolve until the tarball stands in for it.\n\n## Run the verification\n\nEverything below runs as one command so the `trap` restores `examples/package.json` whether the checks pass, fail, or error midway. Never split it.\n\nThe checks capture their exit code explicitly instead of leaning on `set -e` to abort. Some runners (Claude Code's Bash tool among them) invoke this in a shell where `set -e` does not stop the script, and a bare `echo \"VERIFY OK\"` after an unguarded `npm run verify` then prints a pass over a real failure. Report the `VERIFY OK` / `VERIFY FAILED` line, never the tool's own exit status.\n\n```bash\nset -eu\nfail() { echo \"VERIFY FAILED ($1)\"; exit 1; }\n\n# Pushing the tag publishes the tagged commit without running test.yml, so it must pass the suite here and carry an unused version\nVERSION=\"$(node -p \"require('./package.json').version\")\"\n[ -z \"$(git status --porcelain)\" ] || fail \"dirty working tree\"\n[ \"$(git branch --show-current)\" = master ] || fail \"not on master\"\nif git rev-parse -q --verify \"refs/tags/v$VERSION\" >/dev/null; then fail \"tag v$VERSION exists locally\"; fi\n[ -z \"$(npm view \"pangu@$VERSION\" version)\" ] || fail \"pangu@$VERSION already on npm\"\n{ npm test && npm run lint && npm run typecheck; } || fail \"tests, lint, or typecheck\"\n\n# Build and pack the exact bytes that would go to npm\nnpm run build\nTARBALL_DIR=\"$(mktemp -d)\"\nnpm pack --pack-destination \"$TARBALL_DIR\"\nTGZ=\"$(ls \"$TARBALL_DIR\"/pangu-*.tgz)\"\n\n# Every file under dist/ lives in an entry folder. A file directly in dist/ is a code-split chunk that a build config change let back in\nif tar -tzf \"$TGZ\" | grep -E '^package/dist/[^/]+$'; then echo \"VERIFY FAILED (stray file in dist/)\"; exit 1; fi\n\n# bump-version writes every version copy, so a mismatch means a hand edit missed one\ntar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"\n[ \"$(node -p \"require('$TARBALL_DIR/package/package.json').version\")\" = \"$VERSION\" ] || fail \"tarball package.json version\"\n[ \"$(node --input-type=module -e \"import { pangu } from '$TARBALL_DIR/package/dist/shared/index.js'; console.log(pangu.version)\")\" = \"$VERSION\" ] || fail \"pangu.version\"\n# The last store release is the manifest version at the latest tag, so any change in extension build inputs since then needs a manifest bump, and no change needs a restored manifest\nMANIFEST=\"$(node -p \"require('./browser-extensions/chrome/manifest.json').version\")\"\nSTORE=\"$(git show \"$(git describe --tags --abbrev=0):browser-extensions/chrome/manifest.json\" | node -p \"JSON.parse(require('fs').readFileSync(0,'utf8')).version\")\"\nif git diff --quiet -I \"readonly version: string\" \"v$STORE\" HEAD -- src/browser src/shared browser-extensions/chrome browser-extensions/vite.config.extension.ts ':!browser-extensions/chrome/manifest.json' ':!browser-extensions/chrome/tests'; then\n  [ \"$MANIFEST\" != \"$VERSION\" ] || fail \"extension unchanged since v$STORE, restore the manifest version\"\nelse\n  [ \"$MANIFEST\" = \"$VERSION\" ] || fail \"extension changed since v$STORE, manifest version\"\nfi\n[ \"$(node -p \"require('./examples/package.json').dependencies.pangu\")\" = \"$VERSION\" ] || fail \"examples pin\"\ngrep -m1 '^## v' CHANGELOG.md | grep -q \"^## v$VERSION \" || fail \"CHANGELOG top heading\"\n\n# CDN links reach any shipped path, so a file dropped since the published version breaks them\nPUBLISHED=\"$(npm view pangu version)\"\n# A user-level min-release-age would refuse a version published days ago\nnpm pack \"pangu@$PUBLISHED\" --min-release-age=0 --pack-destination \"$TARBALL_DIR\"\ntar -tzf \"$TARBALL_DIR/pangu-$PUBLISHED.tgz\" | sort > \"$TARBALL_DIR/published.txt\"\ntar -tzf \"$TGZ\" | sort > \"$TARBALL_DIR/packed.txt\"\nREMOVED=\"$(comm -23 \"$TARBALL_DIR/published.txt\" \"$TARBALL_DIR/packed.txt\")\"\n[ -z \"$REMOVED\" ] || { echo \"$REMOVED\"; fail \"files removed since $PUBLISHED\"; }\n\n# From here the examples pin is modified, so guarantee its restoration on any exit\ncp examples/package.json \"$TARBALL_DIR/package.json.orig\"\ntrap 'cp \"$TARBALL_DIR/package.json.orig\" examples/package.json; rm -rf \"$TARBALL_DIR\" examples/node_modules examples/package-lock.json' EXIT\n\n# Repoint the consumer at the tarball, install, and run every example check against it\nnode -e \"const fs=require('fs'),f='examples/package.json',p=JSON.parse(fs.readFileSync(f));p.dependencies.pangu='file:'+process.argv[1];fs.writeFileSync(f,JSON.stringify(p,null,2)+'\\n')\" \"$TGZ\"\nnpm install --prefix examples --no-audit --no-fund\n\nrc=0\nnpm run verify --prefix examples || rc=$?\nif [ \"$rc\" -eq 0 ]; then echo \"VERIFY OK\"; else echo \"VERIFY FAILED (exit $rc)\"; fi\nexit \"$rc\"\n```\n\n## Reading the result\n\n- `VERIFY OK` on the last line → the tarball is clear to publish.\n- `VERIFY FAILED` → do not publish. The `trap` has already restored the pin and dropped the throwaway install; diagnose from the message or the table below, fix, and rerun.\n\nEither way, confirm the pin came back before moving on: `git diff --quiet examples/package.json` must exit 0. A dirty `examples/package.json` means the run was interrupted before the trap fired; restore it with `git checkout -- examples/package.json`.\n\n## What each failure means\n\n| Symptom                          | Cause                                                                                    |\n| -------------------------------- | ---------------------------------------------------------------------------------------- |\n| `TS2834` in a `dist/**/*.d.ts`   | a relative import in `src/` is missing its `.js` extension                               |\n| `TS2339` on an inherited method  | a base-class `.d.ts` import failed to resolve, collapsing the subclass to an error type  |\n| `Cannot find module` at run time | `package.json` `files` doesn't ship the referenced file, or an `exports` path is wrong   |\n| `stray file in dist/`            | the `esm` environment emitted a hashed shared chunk; keep `output.preserveModules` on it |\n| `files removed since X.Y.Z`      | a rename or build change dropped a shipped path; restore it or release as a major        |\n\nTo read the shipped artifact directly, unpack the tarball: `tar -xzf \"$TGZ\" -C \"$TARBALL_DIR\"` exposes `package/dist/**/*.d.ts` and `package/package.json`.\n\n## What the run exercises\n\n`npm run verify` in `examples/` chains the runtime entrypoints (`verify:commonjs`, `verify:esm`, `verify:cli`), the browser page (`verify:browser`, which serves `verify-browser.html` and drives the UMD `<script>` tag and the ESM `import` in headless Chromium), and the type check (`typecheck`, whose `tsconfig.json` checks `verify-types.ts` under the `require` condition and `verify-types.mts` under the `import` condition, covering both branches of the dual-package `exports` map). Every one runs against the installed tarball, not the published pin.\n\n<!-- File: CLAUDE.md (Tokens: ~1483 | Category: claude-rule) -->\n# CLAUDE.md\n\n## Spacing Rules\n\n- `CONTEXT.md` is the glossary of terms the spacing rules, docs, and code share. Read it before touching spacing rules, and use its terms, never the synonyms it lists under _Avoid_.\n- Decisions that changed a spacing contract are ADRs in `docs/adr/`. Check them before proposing a rule change that reverses one.\n- When tweaking spacing rules, if the simpler rule is blocked only by rare test cases (typo-shaped input, degenerate shapes, nothing a real user reported), challenge the user to drop those cases in favor of the simpler rule instead of complicating the rule to preserve them. Show the candidate rule and exactly which expectations it breaks, then recommend dropping. Comment dropped cases out in place with `// Rare cases, ignore`, and record reversals of documented contracts as an ADR (precedent: ADR 0007). This licenses dropping rare-case contracts, not pruning tests in general.\n- When fixing a spacing issue, first try absorbing it into an existing rule (widen a character class, adjust a lookahead, rename the rule if its name stops matching). Add a separate rule when the tweak would overcomplicate the existing one, such as forcing one regex to serve two unrelated readings: optimize for total complexity of the rule set, not rule count.\n- Before changing a spacing rule, prototype every candidate on a patched copy of the built engine (`dist/shared/index.js`) and generate a per-case table with one block per case: input, rules output, extension output. Cover the symbol's test file plus real-world shapes, and decide on the diff between candidates, never on argument.\n- Example inputs use real-world text only. Never invent a name, term, or sentence to show an unlisted or boundary case; made-up input proves nothing about real text. A synthetic token is allowed only for a regex-boundary unit test, and says so.\n- A per-line reading (pipe, plus, hyphen) must test its contact before an earlier rule spaces that contact away. Plus reading missed every `CJK+A` line until it moved ahead of the operator rules, while the glossary promised the flip all along. When a glossary promise fails on a shape, check rule order before the regex.\n\n## Releasing\n\n```bash\nnpm run bump-version 1.2.3      # Bumps package.json, extension manifest, src/shared/index.ts, examples/package.json, then builds and packs the extension zip. Does NOT commit or tag.\n```\n\nWhen the release won't ship a new extension to the Chrome Web Store, restore the manifest version with `git restore browser-extensions/chrome/manifest.json` before committing, so the manifest doesn't claim an extension release that never happens. `/verify-before-npm-publish` decides: it fails when extension build inputs changed since the last store release but the manifest wasn't bumped, or didn't change but the manifest was.\n\n1. **Verify**: `/verify-before-npm-publish` must print `VERIFY OK` on the exact HEAD being tagged, so rerun it whenever HEAD moves after a pass.\n2. **Tag**: tag (`git tag -a vX.Y.Z -m \"\"`) and push master plus the tag only after the user says \"publish\".\n3. **npm publishing** runs in GitHub Actions (`publish.yml`) on a pushed `v*` tag, using npm Trusted Publishing (OIDC). Never run `npm publish` locally. ADR 0014 records why publishing is one job, not a pack/publish split.\n4. **Confirm**: `npm install pangu@X.Y.Z --min-release-age=0` in a scratch directory.\n\n## Gotcha\n\n- When adding or editing a glossary entry, a definition uses only bold glossary terms, platform names as the platform spells them (`Text` node, string, line, element), and ordinary English in its ordinary sense. One word carries one sense: a word needed in a technical sense gets its own entry or gets replaced. A term matches the code identifier for the same concept; when they diverge, define the code's word or rename the code.\n- Never blanket find-replace a domain term: pangu vocabulary doubles as ordinary verbs (space, settle). After a rename, grep the changed files for the old word's remaining forms, identifiers included, and read each hit for noun versus verb.\n- Write code comments in English with ANS characters only. Never paste CJK sample text from tests into a comment; describe the shape generically (`CJK | CJK`, `A+CJK`) and use `\\uXXXX` escape notation when a specific character matters.\n- In `tests/shared/`, write each `spaceText` case as its own literal `expect(...).toBe(...)` line, never a loop or `it.each`, so a failure names its input.\n- Keep every `// FIXME` line and `FIXME:` prefix in tests, even beside `it.fails` or `test.fail`: the user greps FIXME to find pending work.\n- Prefix a console call with `[pangu]` only in code that runs in an ordinary web page's console (content scripts, `src/shared/`), where the prefix is the only filter separating our logs from the site's. Options page, popup, and service worker logs stay unprefixed.\n- `fixtures/` has no `.prettierignore` on purpose. A byte-sensitive fixture carries a leading `<!-- prettier-ignore -->` pragma that `loadFixture()` strips before browser tests byte-compare `innerHTML` against the `.expected.html` files. The pragma guards only the next node, so many fixtures fail `prettier --check`. Never format fixtures in bulk (a repo-wide `prettier --write` breaks browser tests), and leave unreferenced fixtures alone.\n\n## External Tool Documentation\n\nPre-resolved Context7 IDs for the `find-docs` skill. Pass them to `ctx7 docs` and skip `ctx7 library`:\n\n| Tool               | `libraryId`                                    |\n| ------------------ | ---------------------------------------------- |\n| attw               | `/arethetypeswrong/arethetypeswrong.github.io` |\n| Chrome built-in AI | `/websites/developer_chrome_ai`                |\n| Chrome Extensions  | `/websites/developer_chrome_extensions`        |\n| ESLint             | `/eslint/eslint`                               |\n| GitHub Actions     | `/websites/github_en_actions`                  |\n| MDN Web Docs       | `/mdn/content`                                 |\n| Node.js            | `/nodejs/node`                                 |\n| npm                | `/websites/npmjs`                              |\n| Playwright         | `/microsoft/playwright`                        |\n| Prettier           | `/prettier/prettier`                           |\n| publint            | `/publint/publint`                             |\n| Shields.io         | `/websites/shields_io_badges`                  |\n| TypeScript         | `/websites/typescriptlang`                     |\n| typescript-eslint  | `/typescript-eslint/typescript-eslint`         |\n| Vite               | `/vitejs/vite`                                 |\n| Vitest             | `/vitest-dev/vitest`                           |\n</yakaai_skills>"}