Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

Project context, architecture, and conventions live in [CLAUDE.md](../CLAUDE.md). Both this file and CLAUDE.md are read by Copilot code review (cap ~4,000 chars each). This file lists the rules worth named attention on every PR.

## Global rules to flag

These are Joe's user-level conventions from `~/.claude/CLAUDE.md`, restated here because Copilot's PR review bot does not read user-level files.

- **Sentence case** in UI text, headings, comments, prose, and commit messages. Code identifiers follow language conventions: camelCase (JS), kebab-case (CSS class names), snake_case (Python). The rule is about not writing English prose in Title Case, not about overriding language naming conventions.
- **No emojis** in source code, log messages, comments, commits, PR bodies, or any output. Plain text only.
- **No AI-authorship attribution.** Never include "Generated with Claude Code", `Co-Authored-By: Claude` trailers, or any "AI-assisted"/"Generated by"-style notes in PR bodies, commit trailers, code comments, or committed docs. Mentioning model/company names in technical documentation (including this file's anti-slop guidance) is fine — the ban is on AI authorship credits.
- **Banned words / anti-slop.** Replace or delete (this list is a review reference and is exempt from its own rule): *comprehensive, sophisticated, robust, transformative, leveraging, seamlessly, innovative, cutting-edge, state-of-the-art, holistic, synergy, ecosystem, paradigm, empower*.
- **Every public-facing HTML page must include the favicon link tag** `<link rel="icon" type="image/svg+xml" href="...">` (use a relative path from each subdirectory) and full OG/Twitter meta tags: `og:title`, `og:description`, `og:type`, `og:url`, `og:image` (1200x630), `og:image:width=1200`, `og:image:height=630`, `twitter:card=summary_large_image`, `twitter:title`, `twitter:description`, `twitter:image`. Missing favicons are a security signal — automated scanners flag pages without them.
User-level globals (sentence case, no emojis, banned words, no AI-authorship attribution, code-quality basics) live in [`.github/instructions/globals.instructions.md`](instructions/globals.instructions.md), generated from `~/.claude/copilot-globals.md` by `scripts/sync-copilot-globals.py`. Don't restate them here.

## Project-specific bug classes to flag

- **Every deployed HTML page** under `resource-kit/docs/**/*.html` (the GitHub Pages source) must include the favicon link tag `<link rel="icon" type="image/svg+xml" href="...">` (relative path from each subdirectory) and full OG/Twitter meta tags: `og:title`, `og:description`, `og:type`, `og:url`, `og:image` (1200x630), `og:image:width=1200`, `og:image:height=630`, `twitter:card=summary_large_image`, `twitter:title`, `twitter:description`, `twitter:image`. Files outside `resource-kit/docs/` (including `_archive/**`) are not deployed and exempt.

- **No hardcoded API keys or secrets.** Static-site repo, no server. Any inline key in HTML/JS is a leak.
- **Event listeners attached to the wrong scope.** The sidebar and modal in the LLM Advisor live OUTSIDE `#llm-tool-advisor-container`. Listeners attached only to the main container miss those elements.
- **Generic class names inside SVG `<style>` tags.** SVG styles are not scoped and leak into the whole document. A `.block` rule inside an inline SVG applies to every Tailwind `block` element on the page. Require prefixed names (`svg-block`, `svg-shift`).
Expand Down
29 changes: 29 additions & 0 deletions .github/instructions/globals.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
applyTo: "**"
---

<!-- Generated by tools/scripts/sync-copilot-globals.py from ~/.claude/copilot-globals.md. Edit the source, then re-run the script. -->

# Global review rules for Joe's repos

These rules apply to ALL pull requests across Joe's repositories. They live here once and are synced into each subscribed repo's `.github/instructions/globals.instructions.md` by `tools/scripts/sync-copilot-globals.py`. Each project's `.github/copilot-instructions.md` then carries only project-specific bug classes, leaving room under the ~4k-char Copilot read cap for that project's real architecture rules.

The Copilot PR review bot reads this file in addition to `.github/copilot-instructions.md` and the project's `CLAUDE.md` / `AGENTS.md` / `GEMINI.md`. None of those need to restate the rules below.

## Writing style

- **Sentence case** in UI text, headings, comments, prose, and commit messages. Code identifiers follow language conventions: camelCase (JS), kebab-case (CSS class names), snake_case (Python), UPPER_SNAKE_CASE (constants), PascalCase (classes). The rule is about not writing English prose in Title Case, not about overriding language naming conventions.
- **No emojis** in source code, log messages, comments, commits, PR bodies, or any committed output. Plain text only.
- **Banned words / anti-slop.** Replace or delete (this list is itself a review reference and is exempt from its own rule): *comprehensive, sophisticated, robust, transformative, leveraging, seamlessly, innovative, cutting-edge, state-of-the-art, holistic, synergy, ecosystem, paradigm, empower*. Substitutes: "full" / "complete" for comprehensive, "using" for leveraging, "stable" / "reliable" for robust, "advanced" or describe specifics for sophisticated.
- **AP style** for prose: "more than" for quantities (never "over"), Oxford comma, sentence-case headings.

## AI authorship

- **No AI-authorship attribution.** Never include "Generated with Claude Code", `Co-Authored-By: Claude` trailers, "AI-assisted" notes, or any "Generated by [model]"-style credit in PR bodies, commit trailers, code comments, or committed docs. Mentioning model or company names in technical documentation (e.g., "uses Claude Sonnet 4.6 for fast chat") is fine — the ban is specifically on AI authorship credit lines.

## Code quality

- **Test suite first.** Tests must accompany feature code and bug fixes. Lint + tests must pass before requesting review.
- **No backwards-compatibility shims for unused code.** Don't keep dead `_var` renames, `// removed` comment markers, or feature flags for code being deleted.
- **No defensive code for impossible states.** Don't add error handling, fallbacks, or validation for scenarios the type system or framework already guarantees. Validate at system boundaries (user input, external APIs) only.
- **Comments only when WHY is non-obvious.** No multi-paragraph code comments. One short line maximum. Don't restate WHAT the code does — names should already do that. Don't reference current task / fix / callers — that belongs in the PR description.
24 changes: 24 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@ python -m http.server 8000
**Deploy:** GitHub Actions (`static.yml`) uploads `resource-kit/docs/` as a Pages artifact on push to master
**Fallback:** `bash deploy.sh` deploys to Cloudflare Pages (`tools-pages.pages.dev`) — only use if GitHub Pages is down

## Copilot review globals (cross-repo sync)

This repo hosts the script that distributes Joe's user-level review rules to every subscribed repo as a path-scoped Copilot instructions file.

**Source of truth:** `~/.claude/copilot-globals.md` (writing style, banned words, no AI-attribution, code-quality basics). Hand-edit there when a rule changes.

**Sync target:** each subscribed repo's `.github/instructions/globals.instructions.md`. Copilot's PR review bot reads these files in addition to `.github/copilot-instructions.md` (with `applyTo` glob and `excludeAgent` opt-out support, per the GitHub Copilot path-specific instructions docs).

**Why:** Each Copilot-readable file has roughly a 4,000-char silent-truncation cap. Restating ~1,700 chars of globals in every project's `copilot-instructions.md` was pushing several past the cap (rosen-frontend, class, houseofjawn-bot at 3995 / 3993 / 3992 chars at the time of #59). Hoisting globals into a separate path-scoped file frees that space for project-specific bug classes.

**Subscribed repos:** listed in `scripts/sync-targets.txt` (one absolute path per line, `#` for comments, `~` expansion supported).

**After editing the source:**

```bash
~/projects/tools/scripts/sync-copilot-globals.py # write to all subscribed repos
~/projects/tools/scripts/sync-copilot-globals.py --dry-run # preview without writing
~/projects/tools/scripts/sync-copilot-globals.py --only tools # filter to one repo by name
```

Each subscribed repo should commit its `.github/instructions/globals.instructions.md` (the file is generated but committed so Copilot can read it without runtime sync). When a repo first subscribes, also slim its `.github/copilot-instructions.md` by removing the now-redundant "Global rules to flag" section.

This setup does **not** modify any project's `CLAUDE.md`. CLAUDE.md remains the single source of truth for primary coding assistants and is not bound by the 4k Copilot-read cap.

## LLM Advisor architecture

The LLM Advisor (`resource-kit/docs/llm-advisor/`) uses:
Expand Down
128 changes: 128 additions & 0 deletions scripts/sync-copilot-globals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Sync ~/.claude/copilot-globals.md into each subscribed repo's
.github/instructions/globals.instructions.md.

Background: GitHub Copilot's PR review bot reads `.github/copilot-instructions.md`
plus any `.github/instructions/*.instructions.md` files (with applyTo glob support
and excludeAgent opt-out). Each file has roughly a 4,000-char silent-truncation
cap. Joe's user-level globals previously had to be restated in every project's
copilot-instructions.md, pushing several past the cap. This script hoists the
globals into a path-scoped instructions file maintained in one place.

Usage:
sync-copilot-globals.py [--source PATH] [--targets PATH] [--dry-run]
[--only NAME[,NAME...]] [REPO ...]

If REPO arguments are given, they override the targets file.
Default source: ~/.claude/copilot-globals.md
Default targets file: tools/scripts/sync-targets.txt (sibling to this script).

Exit codes:
0 all targets updated (or unchanged)
1 one or more targets failed
"""

import argparse
import sys
from pathlib import Path

DEFAULT_SOURCE = Path.home() / ".claude" / "copilot-globals.md"
DEFAULT_TARGETS_FILE = Path(__file__).resolve().parent / "sync-targets.txt"
GENERATED_HEADER = (
"<!-- Generated by tools/scripts/sync-copilot-globals.py "
"from ~/.claude/copilot-globals.md. Edit the source, then re-run the script. -->"
)


def load_targets(targets_file: Path) -> list[Path]:
if not targets_file.exists():
return []
out = []
for raw in targets_file.read_text().splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
out.append(Path(line).expanduser())
return out


def render_output(source_text: str) -> str:
"""Insert the generated-header marker after the YAML frontmatter."""
if source_text.startswith("---\n"):
end = source_text.find("\n---\n", 4)
if end != -1:
frontmatter = source_text[: end + 5]
body = source_text[end + 5 :].lstrip("\n")
return f"{frontmatter}\n{GENERATED_HEADER}\n\n{body}"
return f"{GENERATED_HEADER}\n\n{source_text}"


def sync_to(repo: Path, content: str, dry_run: bool) -> str:
target = repo / ".github" / "instructions" / "globals.instructions.md"
existing = target.read_text() if target.exists() else None
if existing == content:
return f"unchanged: {target}"
verb_prefix = "would " if dry_run else ""
verb = "create" if existing is None else "update"
if dry_run:
return f"{verb_prefix}{verb}: {target}"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
return f"{verb}d: {target}"


def main() -> int:
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--source", type=Path, default=DEFAULT_SOURCE,
help=f"Source file (default: {DEFAULT_SOURCE})")
parser.add_argument("--targets", type=Path, default=DEFAULT_TARGETS_FILE,
help=f"Targets list file (default: {DEFAULT_TARGETS_FILE})")
parser.add_argument("--dry-run", action="store_true",
help="Print what would change without writing")
parser.add_argument("--only", default="",
help="Comma-separated repo names to filter the targets list")
parser.add_argument("repos", nargs="*", type=Path,
help="Repo paths (overrides the targets file)")
args = parser.parse_args()

if not args.source.exists():
print(f"error: source not found: {args.source}", file=sys.stderr)
return 1

content = render_output(args.source.read_text())

repos = args.repos or load_targets(args.targets)
if not repos:
print(
f"error: no target repos given and {args.targets} is empty or missing",
file=sys.stderr,
)
return 1

if args.only:
only = {n.strip() for n in args.only.split(",") if n.strip()}
repos = [r for r in repos if r.name in only]
if not repos:
print(f"error: --only {args.only} matched no targets", file=sys.stderr)
return 1

failed = 0
for repo in repos:
if not (repo / ".git").is_dir():
print(f"skip: not a git repo: {repo}", file=sys.stderr)
failed += 1
continue
try:
print(sync_to(repo, content, dry_run=args.dry_run))
except OSError as exc:
print(f"error: {repo}: {exc}", file=sys.stderr)
failed += 1

return 1 if failed else 0


if __name__ == "__main__":
sys.exit(main())
16 changes: 16 additions & 0 deletions scripts/sync-targets.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Repos subscribed to ~/.claude/copilot-globals.md.
# One absolute path per line. Lines starting with # are ignored.
# Tilde expansion is supported.
#
# To subscribe a new repo:
# 1. Add its path here
# 2. Run scripts/sync-copilot-globals.py
# 3. In that repo, slim .github/copilot-instructions.md by removing the
# "Global rules to flag" section (now lives in
# .github/instructions/globals.instructions.md)
# 4. Open a PR with both files in the same commit
#
# After ~/.claude/copilot-globals.md changes, run from anywhere:
# ~/projects/tools/scripts/sync-copilot-globals.py

~/projects/tools