Common startup rules live in AGENTS.md. This document holds procedures and conventions to read when they apply, not extra startup imports.
After meaningful changes, assess documentation before declaring completion. Meaningful changes include user-visible behavior, architecture/data flow, maintenance/setup/debugging workflows, and subsystem contracts. Formatting, behavior-preserving refactors and isolated test changes need no doc update. Prefer the existing authoritative architecture doc or nearest module README; README.md owns top-level user/developer entry points. Update stale routes, paths, commands and contracts you encounter, or explicitly flag unresolved claims in the final summary. Repo docs remain canonical regardless of authorship.
Keep AGENTS.md limited to repository-wide constraints and task routing. Do not add feature histories, method lists, schema inventories or troubleshooting procedures to it. All coding agents, including Claude Code, read AGENTS.md; do not create a second copy of the common instructions. Use the context map for the owning document and relevant skill. Read multiple contracts for cross-domain work. Add a new canonical document only when no existing owner fits; link it from the map.
When relocating knowledge, compare both sources and the destination, retain unique exceptions and rationale, and record corrections with code evidence. The 2026-09 migration ledger records the initial move; it is an audit artifact, not required reading for feature work. Later normal edits maintain the canonical docs, not duplicate historical prose.
Use Claude Code 2.1.281 or newer with its built-in AGENTS.md plugin enabled. Native support arrived in 2.1.277; 2.1.281 fixed availability for sessions with telemetry disabled and some cloud providers. See the official instruction-loading documentation.
The repository uses AGENTS.md directly and does not need a CLAUDE.md wrapper.
By default, CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in the working
directory or an ancestor prevents AGENTS.md loading. If you keep one locally,
set Project instructions to claude-md-and-agents-md in /config.
Personal ~/.claude/CLAUDE.md and .claude/rules/ can remain alongside AGENTS.md.
Start a new session after upgrading and check the AGENTS.md loading notice.
Repository skills are shared with Codex from .agents/skills/. Each skill has
a relative directory symlink in .claude/skills/, so both agents load the same
source. See Repository skills for maintenance and checkout setup.
Run pnpm run agents:validate after guidance changes. It checks line/byte budgets,
root imports and local navigation links/anchors, including migration destinations.
CLAUDE.md is optional; if present, it still has a 30-line / 2 KiB budget and
must contain exactly one standalone AGENTS.md import.
Line budgets count LF, CRLF and standalone CR endings consistently.
Markdown navigation is parsed with the already-declared marked dependency;
undefined explicit references (including shortcut images) are errors, and code examples are excluded.
Backticked concrete paths in root guidance and the context map are checked from
the repository root, including unknown top-level directories and filenames.
Write generic filenames as prose; commands, templates, globs, URLs, package
aliases and dotted code symbols are excluded. Bare dotted names with conventional
file suffixes (such as .md, .json or .ts) are treated as filenames. Document formats
share the suffix set used by package-import guards, including PDF and AsciiDoc. Use a ./
prefix or Markdown link for other ambiguous filenames that resemble code symbols.
Explicit relative literals denote paths, including spaces, filesystem punctuation and hyphenated words.
Put executable command examples in fenced code when their syntax also looks like a path.
Multi-part dotfiles are path candidates too.
Conventional extensionless filenames such as Dockerfile, Makefile and LICENSE
are also path candidates; use an explicit ./ prefix for other extensionless files.
Link paths and fragments are decoded separately so encoded filename delimiters
stay in the filename. Fenced and indented examples
do not count as root guidance imports or satisfy the required Claude import.
The required Claude import must be an unformatted standalone line in a top-level
paragraph; headings, quotes and list items do not satisfy it.
The parsed HTML tree also verifies that this paragraph is outside HTML containers,
including templates split across Markdown tokens. Generated HTML is inspected
in memory only; it is never executed or emitted.
Heading anchors decode HTML character references in text and use github-slugger
for GitHub-compatible character filtering and duplicate suffixes.
Only headings present outside inert HTML containers contribute slugs or duplicate counters.
Explicit HTML anchors use parse5, excluding comments, scripts, styles and template contents.
Rendered HTML anchor and image-map area hrefs and image sources use the same local-reference checks
as Markdown links, including decoded attributes and fragment validation.
URL attributes remove ASCII tabs/newlines throughout and discard surrounding
ASCII control/space characters before resolution.
Iframe/embed sources and object data attributes are document references and retain Markdown-target anchor checks.
Inline iframe srcdoc documents are traversed too, with their own fragment anchors.
The first active HTML base href sets reference resolution, including nested srcdoc bases.
Resolved file URLs use native filesystem conversion, including Windows drive paths.
Explicit srcset attributes must contain at least one parsed candidate.
Image and media references require a nonempty path that resolves to a file, not a directory.
Direct file URLs, Windows drive paths and HTML bases using either form are rejected; use portable repository-relative paths.
Image references check file existence without interpreting image fragments as
Markdown headings; document links keep anchor checks even when sharing a target.
SVG image/use hrefs (including xlink), HTML image-input, video, audio, source and track src assets and video posters use the same
existence checks as images. Entity decoding uses full HTML text/attribute rules,
including references whose semicolon may be omitted.
Inline guidance imports are rejected after punctuation as well as whitespace.
At-signs inside external URIs (including www autolinks and explicit opaque autolinks such as mailto) are excluded
per HTML text node, preserving adjacent imports. A colon directly before an import
does not make that import a URI. Opaque schemes are excluded only in parsed links
whose visible text equals their URI, so colon-labeled prose remains checked.
A closing bracket or matching enclosing quote followed by punctuation and an at-sign terminates a bare URL exclusion.
Extensionless inline candidates are also imports when they resolve to repository files,
checking the full filename before prefixes at ASCII/Unicode prose separators,
including opening parentheses, brackets and braces. Each at-sign candidate is
checked independently, including imports nested next to a package mention.
An at-sign inside a word (for example, foo@INSTRUCTIONS or an email address)
is not an import boundary, including within parenthetical prose.
Declared scoped dependencies, scope wildcards and matching TypeScript path aliases
are recognized as package/alias mentions. Traversal and document-file imports are
rejected before those exemptions, including document paths with fragments or queries.
All recognized Markdown extensions share the document-import guard; reStructuredText
and AsciiDoc, PDF, Word, OpenDocument, RTF, Org and TeX documents are also excluded
from package exemptions. Recognized extensionless guidance names (including AGENTS,
CLAUDE, INSTRUCTIONS, README, CONTRIBUTING and SECURITY, case-insensitively) are excluded in package subpaths too. URL-encoded
paths do not receive package exemptions. TypeScript configuration is parsed as JSONC.
Declared packages also permit safe subpaths; exact aliases stay exact.
Federated handles in the @user@host form are prose, not imports; trailing closing
ASCII/Unicode punctuation and possessive apostrophe-s suffixes are ignored. Opening
delimiters separate adjacent prose; nested imports remain checked. Extra at-signs do not qualify for that exemption.
Exact declared packages remain exempt after version normalization.
Declared package mentions may include a version (including semver comparators and wildcard ranges) or dist-tag qualifier.
Qualifier handling includes unscoped names; terminal sentence punctuation is
removed before matching a declared package, as are straight/curly apostrophe possessives.
Unicode punctuation and ASCII opening delimiters, commas, semicolons, colons, question/exclamation marks
separate package mentions from adjacent prose.
Markdown destinations decode HTML entities before URI parsing, matching rendered links.
Heading-anchor lookup is limited to Markdown targets. Source-file line fragments,
PDF page fragments and other non-Markdown fragments retain file-existence checks.
The import scan separates HTML block/table elements and includes visible text and literal backticks; it excludes parsed code nodes and non-rendered containers.
Navigation uses the parsed rendered tree too. Temporary in-memory markers retain
definition, unresolved-reference and literal-path metadata, so Markdown inside
inert templates is excluded consistently with raw HTML navigation.
Image source sets use parse-srcset to check each candidate URL. Root-relative
literals never suppress source-relative definition checks.
The Nx test hash includes marked, parse5, github-slugger, parse-srcset and typescript
so dependency changes invalidate parser coverage.
It cannot prove semantic equivalence; review changed contracts as well.
Never run whole-file prettier --write on AGENTS.md or docs/**.
Upstream formatting is not uniformly Prettier-clean: whole-file writes can
corrupt nested list indentation or change a literal continuation into a bullet.
Format only intended new lines. If accidental formatting occurred, reconstruct
from the pre-edit version and reapply only intended changes; preserve unrelated
user edits. Use the merge-base version only if it actually represents that
pre-edit state. Review the diff rather than blindly restoring an older branch.
Save only finalized plans in .plans/YYYY-MM-DD-short-topic.md; use numeric
suffixes for collisions. Do not save drafts or questions there. If an active
mode forbids writes, save the approved plan on entering execution.
Completion reports list changed docs, tests added/updated, commands and results,
skipped validation with reasons, and release-note status. A docs-only task needs
Markdown/link validation, not app unit/E2E tests. Tooling changes need their own tests.
A push to a pull-request branch can restart the CI matrix, and runs from several open pull requests can queue behind one another. Fix rounds happen locally: push only a commit that both reviewers have already accepted.
Greptile reviews committed work only. Commit first, then fetch the destination
branch and resolve it to a commit SHA. For a PR into master on origin:
review_base=$(
git fetch --quiet --no-tags origin \
+refs/heads/master:refs/remotes/origin/master &&
git rev-parse --verify 'refs/remotes/origin/master^{commit}'
)For a fork, use the remote for the repository receiving the PR in both lines inside the assignment. If the fetch and resolution succeed, pass the SHA to both reviewers so they use the same commit even if they select different Git remotes. Run them from the branch's worktree and keep their output outside it.
- Codex:
codex review --base "$review_base" -c model_reasoning_effort=high. The verdict is the final message on stdout; stderr carries the session log. The flag pins the review effort whatever the caller's default is. Codex otherwise runs with the caller's own configuration and may install dependencies or run tests in the worktree, so do not start other installs or builds there while it reviews. - Greptile:
greptile review -b "$review_base" --json. Clean meansconfidenceis 5 andcommentsis empty. A non-zero exit means the review did not run, not that it found problems.
- Finish the change and its targeted checks, then commit.
- Run both reviewers on the same commit; they are independent and can run in parallel.
- Check every finding against the code. Fix the real ones and note a one-line reason for each one declined. Never clear a finding by suppressing a rule or weakening a test.
- Commit the fixes and review again. Stop when both reviewers are clean on the same commit. Also stop after five rounds, or when a round repeats the previous findings, and report what remains instead of pushing.
- Push, open the pull request, and name the reviewed commit and any declined findings in the completion report.
Check which commit each GitHub bot reviewed. After a push, request a fresh
review if a bot still reports an older head. Comment @codex review for Codex
or @greptileai for Greptile. Collect the whole round of bot findings and CI
failures, fix it locally, pass both local reviewers again and push once.
When reporting whether a PR is ready, distinguish the reviewed head from the
base actually tested by CI. Query the live destination branch in the receiving
repository with gh api repos/OWNER/REPO/git/ref/heads/BRANCH --jq .object.sha.
The PR's stored base SHA can lag the branch. For a check that used
GitHub's PR merge checkout,
the checkout log's commit identifies the tested merge. Compare its first
parent with the intended base and its second parent with the reviewed head.
For a head-only checkout, compare the checkout SHA with the reviewed head.
If newer base changes overlap the PR, verify the affected behavior in the
combined tree before relying on the earlier CI results.
A reviewer whose CLI is missing, signed out, outside a Greptile organization or left without reviewable files (Greptile ignores Markdown-only changes) does not block the other one. Say which reviewer did not run; do not install a CLI, sign in or onboard an account on the maintainer's behalf.
Repository skills live under .agents/skills/. Descriptions are trigger-only,
begin with Use when, and each skill is at most 500 words. Frontmatter owns
trigger descriptions; avoid copying them into navigation prose. Skills provide
workflow and links to authoritative contracts, not a second contract copy.
Codex discovers this directory directly. Claude Code discovers the relative
symlinks in .claude/skills/; do not keep separate skill copies or restore the
old Codex skills directory. Edit only the canonical sources. When adding a skill,
add its directory symlink and opt that link into .gitignore alongside the
existing shared skills. Personal Claude skills remain ignored.
For example, from the repository root:
ln -s ../../.agents/skills/my-skill .claude/skills/my-skillGit must check out directory symlinks as symlinks. On Windows, enable Developer
Mode or run with symlink privileges and clone with git -c core.symlinks=true clone;
for an existing checkout, enable core.symlinks and re-check out the tracked links
after preserving local changes. Plain text link placeholders do not load as skills.
The validator checks every shared skill's relative Claude link and retains the
required release skills. Historical plans and migration ledgers keep their original
paths as an audit record; use the context map for current locations.
Run pnpm run skills:validate after changing a committed skill or a literal
path it documents. New guidance tooling belongs to the existing
repository-skills Nx project; no new project is needed for another validator.
Use signal-based queries (viewChild, viewChildren, contentChild,
contentChildren) and inputs/outputs (input, output). For required queries,
use viewChild.required. Unwrap signals when passing values in templates:
readonly menu = viewChild.required<MatMenu>('menuRef');
readonly title = input.required<string>();
readonly size = input<number>(10);
readonly clicked = output<void>();
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);<button [matMenuTriggerFor]="menu()">Open Menu</button>Use signal, computed, effect and linkedSignal for reactive state. Existing
host bindings/listeners use @HostBinding and @HostListener; this relocation
does not change that convention. Prefer @if, @for (with a stable track),
and @switch over the legacy structural directives. A signal is a function;
passing menu instead of menu() to Material supplies the wrong value.
For IPC, define the handler in the appropriate Electron events module,
register it in the event bootstrap, expose a typed preload method, and consume
it through the renderer service. Keep channel contracts typed in
ElectronBridgeApi; use the database worker contract for heavy database work.
See Electron security and
DB worker ownership.
For a playlist source, extend the shared playlist type, add the backend event
handler, add the import UI under libs/playlist/import/feature, and update
state actions/effects. Preserve runtime capabilities and migration behavior.
NgRx owns M3U global state; portal/feature state uses composed NgRx Signal Store;
component-local state uses Angular signals. Avoid expanding large classes:
extract components/services or with* store features before exceeding limits;
shared types belong in their own contract modules. The hard production limit
is 400 (not a variable 350–400); target under 300. See
Nx file-size policy.
Use local pnpm nx for underlying project targets. Package scripts are the
supported entry points for composed tasks:
| Task | Command |
|---|---|
| Web development | pnpm run serve:frontend |
| PWA development | pnpm nx serve web --configuration=pwa |
| Electron development | pnpm run serve:backend |
| Electron frontend | pnpm run build:frontend |
| PWA frontend | pnpm run build:frontend:pwa |
| Electron backend | pnpm run build:backend |
| Package without installers | pnpm run package:app |
| Create installers | pnpm run make:app |
Electron backend build depends on the web build; outputs live under
dist/apps/electron-backend and dist/apps/web and packaging combines them.
Use the validation map for test/lint tasks
and the release pipeline for packaging.
Use foldSearchText from libs/shared/interfaces/src/lib/search-text-fold.util.ts
on both sides of every in-memory search comparison. Channel lists, catalog and
category filters, command palette, sources and downloads must share this fold;
row filtering and count/queue sources must not diverge. It lowercases without a
locale, normalizes to NFC, then strips remaining combining marks. Composing first
keeps canonical spellings equivalent while preserving accents; the leftover dot
in Turkish dotted İ is stripped so it matches plain i. Do not substitute
toLocaleLowerCase. Electron content search uses the same fold and adds explicit
Turkish-locale İ variants to LIKE/GLOB queries because SQLite LIKE folds only ASCII.