Skip to content

docs(claude): restructure .claude/ into path-scoped rules and mirror them to Copilot - #652

Merged
jooyoungseo merged 13 commits into
mainfrom
claude/maidr-claude-config-structure-p4d4vx
Jul 29, 2026
Merged

jooyoungseo merged 13 commits into
mainfrom
claude/maidr-claude-config-structure-p4d4vx

Conversation

@jooyoungseo

@jooyoungseo jooyoungseo commented Jul 28, 2026 •

Copy link
Copy Markdown
Member

Description

Reorganises .claude/ to follow Anthropic's documented .claude/ layout, then makes the repository's GitHub Copilot configuration a generated mirror of it so the two toolchains cannot drift.


1. Restructure .claude/ into path-scoped rules

The three reference docs under .claude/ were pulled in by @-imports from CLAUDE.md. Imports are expanded at launch, so ARCHITECTURE.md, PATTERNS.md, and DEBUGGING.md loaded in full on every session — roughly 60KB (~15k tokens) regardless of the task. The docs are explicit that this doesn't help: "Splitting into @path imports helps organization but doesn't reduce context, since imported files load at launch."

.claude/rules/ is the mechanism designed for this. Rules with paths: frontmatter load only when Claude opens a matching file.

Loaded every session Worst case (all rules matched)
Before ~60 KB (~15k tokens) —
After ~4.5 KB (~1.1k tokens) ~25 KB (~6.3k tokens)

93% smaller baseline.

Do agents/ and rules/ both belong here?

Yes — this was the open question behind the change. They're orthogonal, not alternatives, and Anthropic's .claude/ reference lists both as first-class directories. Rules are instructions that shape how work is done; agents are workers with their own context window. They compose: a subagent inherits the CLAUDE.md hierarchy including project rules, so a rule written once applies in the main conversation and inside every subagent. That's what lets the agent definitions get shorter here — they now describe a role, not the codebase.

Rules added

Rule Scope
architecture.md src/** — MVVC layer contracts, canonical data flow
model.md src/model/** — traces, notifyStateUpdate(), highlight ownership
service.md src/service/**, controller.ts — Observer, Emitter, Disposable, secrets
viewmodel.md src/state/** — the Redux bridge
ui.md src/ui/** — React rendering rules, no untrusted markup
command.md src/command/**, keybinding services — commands and scopes
accessibility.md UI + the four modality services
typescript.md all .ts/.tsx — types, style, error handling
testing.md test/**, e2e_tests/** — Jest and Playwright
git-workflow.md unscoped — Conventional Commits and semantic-release

git-workflow.md is deliberately unscoped: commit conventions are needed even in sessions that never open a source file.

Also here

  • Debugging playbook → skill (.claude/skills/debug-maidr/). Per the docs, a multi-step procedure belongs in a skill rather than always-on context. Named debug-maidr so it doesn't shadow the bundled /debug.
  • CLAUDE.md trimmed to ~95 lines, no heavy imports, and corrected commands that don't exist — there is no npm run dev or npm run preview; the real ones are dev:recharts / dev:victory and build:preview.
  • Subagents consolidated. Merged react-ts-code-reviewer → code-reviewer and react-ts-engineer → implementer; overlapping descriptions make delegation ambiguous, and both were ~1500 chars of inline examples loaded on every request. The surviving eight match .github/agents/ exactly, which already had the clean set.
  • feature-builder used the legacy Task(...) alias, whose agent-type allowlist is ignored inside a subagent definition, so the restriction was a no-op → switched to Agent, with the delegation list stated in the body where it can actually be read.
  • debugger preloads the skill via skills:; test-runner gained the Skill tool. Neither could reach a skill before.

2. Drop redundant skills, mirror rules into Copilot

Skills: two of three removed

code and code-review were ~90% restatements of what the new rules now deliver — TypeScript standards, React rules, MVVC layer contracts, accessibility — with the remainder duplicating the implementer and code-reviewer subagents. Two sources for one convention is exactly the drift risk the docs warn about, and the project code-review skill shadowed Claude Code's bundled skill of the same name.

Deleting both means the bundled /code-review is reachable again. Their one piece of unique content — the XSS/secrets checklist — moved into rules/ui.md and rules/service.md, where it now loads at write time rather than only when a reviewer invoked a checklist.

Copilot: generated, not duplicated

The repo configures Copilot under .github/, and the two tools have the same four concepts:

Concept Claude Code GitHub Copilot
Repo-wide, always loaded CLAUDE.md .github/copilot-instructions.md
Path-scoped instructions .claude/rules/*.md (paths:) .github/instructions/*.instructions.md (applyTo:)
Isolated worker .claude/agents/*.md .github/agents/*.agent.md
On-demand workflow .claude/skills/*/SKILL.md .github/prompts/*.prompt.md

The path-scoped pair matches almost exactly, so rather than hand-maintain a second copy that's guaranteed to drift, scripts/sync-copilot-instructions.mjs generates that side:

CLAUDE.md          →  .github/copilot-instructions.md
.claude/rules/*.md →  .github/instructions/*.instructions.md

Transformations: paths: list → comma-separated applyTo:; unscoped rule → applyTo: "**"; brace groups expanded and deduplicated (since applyTo documents commas but not braces); cross-references repointed (rules/model.md → model.instructions.md) so a Copilot-only reader never follows a pointer to a file that doesn't exist on their side. Subdirectories are mirrored.

  • npm run sync:copilot regenerates; npm run sync:copilot:check fails if stale.
  • New copilot-sync CI job runs the check. Pure Node with no dependencies, so it skips npm ci; no job has needs: on it, so it gates nothing. Suggest adding it as a required check — drift nobody notices defeats the purpose, and it's invisible in review because generated files look plausible. That's branch-protection config, so it's a maintainer call rather than something this PR changes.
  • style-guide.instructions.md replaced by the generated typescript.instructions.md. Its one accurate claim not already covered — barrel index.ts at module boundaries only — was folded into the rule first. Its "80% coverage" and "100 character line length" rules were dropped as false: neither is configured in jest.config.ts or eslint.config.ts.
  • Fixed relative link paths in .github/agents/, which resolved to .github/agents/.github/... and were already broken before this PR.

Deliberately not generated: agents and prompts. The agent frontmatter schemas genuinely differ (handoffs and a display name on Copilot's side; permissionMode, memory, skills, maxTurns on Claude's), and .github/prompts/ holds GitHub chores — filing an issue from a template, replying to a PR review — that Claude Code does through its GitHub tools instead. agentParity.test.ts guards the drift that is cheap to catch: a role added, removed, or renamed on one side only.

Related Issues

None.

Screenshots (if applicable)

N/A — configuration and tooling only.

Checklist

  • I have read the Contributor Guidelines.
  • I have performed a self-review of my own code and ensured it follows the project's coding standards.
  • I have tested the changes locally, and all tests related to this pull request pass.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have updated the documentation, if applicable.
  • I have added appropriate unit tests, if applicable.

Additional Notes

On testing. No application source is touched, so there is no runtime behaviour to test manually — and there is no ManualTestingProcess.md in the repo to follow, despite the template referencing it. Three test files cover the tooling:

File Cases What it guards
test/scripts/syncCopilotInstructions.test.ts 26 Drives the generator as a CLI against fixture trees via SYNC_COPILOT_ROOT, asserting exit codes and generated output
test/scripts/claudeRuleGlobs.test.ts 58 Every generated applyTo pattern matches ≥1 tracked file via git ls-files and has no unexpanded brace; also asserts the committed mirror is in sync
test/scripts/agentParity.test.ts 4 Both tools define the same agent roles; names match filenames; descriptions stay short; skills: entries exist

Run locally with dependencies installed: lint clean, type-check clean, build succeeds, 1136 tests across 90 suites pass.

Seven defects were found in the sync script during review. Five were the same shape — wrong output, exit code 0:

Bug Effect
Non-recursive readdirSync A rule in a subdirectory was dropped; --check still said "in sync"
paths: [...] flow sequence Parsed to zero globs → scoped rule silently became applyTo: "**"
Glob scan not key-scoped A sibling tags: list folded into applyTo
Regex quote-stripping A trailing YAML comment survived into the glob, matching nothing
readTitle not fence-aware A leading code block donated its # comment as the description

Plus two lesser ones: --check created the output directory despite being documented read-only, and nested brace groups expanded correctly but emitted a duplicate pattern.

Each is now pinned by a regression case, and the cases were verified to fail against the commit that had the bug — a suite that passes against buggy code would be worse than none.

Reviewer note: scripts/sync-copilot-instructions.mjs and its three test files are the real code here. The files under .github/instructions/ are mechanical output — reviewing .claude/rules/*.md covers their content.

Sources: CLAUDE.md and .claude/rules/ · Subagents · Copilot custom instructions · Copilot custom agents · VS Code custom instructions


🤖 Generated with Claude Code

The three reference docs under .claude/ were pulled in by @-imports from
CLAUDE.md. Imports are expanded at launch, so ARCHITECTURE.md, PATTERNS.md,
and DEBUGGING.md loaded in full on every session — ~60KB (~15k tokens) of
context regardless of the task.

Reorganise around Anthropic's documented .claude/ layout, where each
mechanism is defined by when it loads:

- Add .claude/rules/ with nine path-scoped rules distilled from the three
  docs. Each declares `paths:` frontmatter, so it costs nothing until Claude
  opens a file it applies to. git-workflow.md is deliberately unscoped —
  commit conventions are needed even in sessions that open no source file.
- Move the debugging playbook to a .claude/skills/debug-maidr/ skill, since
  it is a multi-step procedure rather than a convention. Named debug-maidr so
  it does not shadow the bundled /debug skill.
- Trim CLAUDE.md to 92 lines with no heavy imports, and correct commands that
  did not exist: there is no `npm run dev` or `npm run preview`, only the
  per-example dev servers and `build:preview`.
- Delete the three docs; their normative content now lives in the rules.

Also consolidate the subagents, which had grown two overlapping pairs:

- Merge react-ts-code-reviewer into code-reviewer and react-ts-engineer into
  implementer. Overlapping descriptions make delegation ambiguous, and both
  descriptions were ~1500 chars of inline examples loaded on every request.
  All eight remaining descriptions are now under 250 chars.
- Since subagents inherit project rules, the agent bodies no longer restate
  the codebase conventions and describe the role instead.
- feature-builder used the legacy `Task(...)` alias, whose agent-type
  allowlist is ignored in a subagent definition; use `Agent` and name the
  delegates in the body.
- debugger preloads the debug-maidr skill via `skills:`; test-runner gains
  the Skill tool so it can invoke it on demand.
- Repoint the stale doc links in .github/agents/ at the new locations.

Add .claude/README.md documenting what goes where and why agents/ and rules/
coexist.

Every session now loads ~4.5KB instead of ~60KB, with per-layer detail
arriving only when the matching files are opened.
@github-actions

github-actions Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

🔎 MAIDR example preview is ready

Open the preview: https://pr-652.maidr-preview.pages.dev

Built from 1061d05. This preview URL is stable and updates automatically on every new commit to the PR.

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This is a config/docs-only PR (.claude/, .github/agents/, CLAUDE.md — no src/ changes), so I focused on factual accuracy of the new rules/agents and whether the described Claude Code mechanisms actually work as claimed, since none of this is covered by lint or CI.

Verification performed

I cross-checked a large sample of claims against the actual repo state and the current Claude Code docs:

  • File/path references — src/model/*.ts (confirmed traces live directly in src/model/, not src/model/trace/ as the old docs claimed — this PR fixes a real inaccuracy), src/state/viewModel/{viewModel,registry}.ts, src/state/hook/, src/service/*.ts (all files named in rules/accessibility.md paths exist), src/command/*, src/adapters/, test/{model,service,state,command,util,adapters}, jest.config.ts, playwright.config.ts. All accurate.
  • npm scripts — dev:recharts, dev:victory, build:preview, docs:serve, type-check, e2e, lint:fix all exist in package.json; confirmed there really is no npm run dev/preview (the old CLAUDE.md's claim was wrong, this PR's correction is right).
  • Svg.markOwned / data-maidr-owned ownership claims in rules/model.md match src/util/svg.ts.
  • Commitlint/semantic-release claims in rules/git-workflow.md match commitlint.config.ts and package.json.
  • Claude Code mechanism claims, against the current docs:
    • skills: frontmatter on debugger.md is a real, documented field for preloading skill content into a subagent (and debug-maidr doesn't set disable-model-invocation, so it's actually preloadable — checked, since skills with that flag can't be preloaded).
    • The feature-builder.md change from Task(planner, architect, …) to Agent + prose is correct: the docs explicitly state that in a subagent definition (as opposed to a main-thread --agent), the type list inside Agent(...)/Task(...) parentheses is ignored — so the old restriction really was a no-op, not just an unverified assertion in the PR description.
    • test-runner.md adding the Skill tool (to invoke debug-maidr on demand) vs. debugger.md using skills: (to preload it) are both used correctly per how the two mechanisms differ.
    • All other frontmatter fields used (color, memory, permissionMode, maxTurns, model) are documented and used with valid values.
  • No dangling references — grepped for ARCHITECTURE.md/PATTERNS.md/DEBUGGING.md/react-ts-* outside this diff (README/CONTRIBUTING/CI configs); found none.
  • Relative markdown links in .github/agents/*.agent.md (e.g. ../../.claude/rules, ../../.claude/skills/debug-maidr/SKILL.md) resolve correctly from .github/agents/.
  • Dropping "Repository pattern" from the merged implementer.md (present in the old react-ts-engineer.md) is actually a correctness fix — there's no repository-pattern usage anywhere in src/, so the old agent was pointing at a pattern the codebase doesn't use.

I didn't find any inaccurate path, broken glob, or misused frontmatter field in the sampling I checked.

Minor observations (non-blocking)

  • No automated guardrail against drift. eslint.config.ts explicitly ignores .claude/, so nothing will catch a future stale path or malformed paths: glob in these rule files the way lint catches issues in src/. The author did the verification manually and disclosed exactly what was checked, which is good — but a small CI script (parse all agent/skill frontmatter as YAML, confirm every paths: glob matches ≥1 tracked file, grep for the deleted filenames) would make this self-checking on future edits instead of relying on manual diligence each time.
  • The context-savings table (~60KB → ~4.5KB baseline, ~25KB worst case) looks directionally right given the old docs were ~2,265 lines total and the new baseline is just the trimmed CLAUDE.md + unscoped git-workflow.md, but I didn't independently re-derive the exact byte counts — worth a sanity check if precision matters for the PR description.
  • Worth double-checking in the GitHub UI that the relative links in .github/agents/*.agent.md render/resolve as expected there too — the path math itself is correct, but that surface (Copilot custom agents) may consume the file differently than plain GitHub markdown rendering.

Overall

Well-scoped, and the technical claims are accurately grounded in both the actual repo layout and the current Claude Code mechanism semantics — I verified these independently rather than taking the PR description at face value. The consolidation of the two overlapping subagent pairs and the fix for the stale src/model/trace/ and nonexistent npm run dev/preview references are genuine corrections, not just reorganization.


Generated by Claude Code (automated review)

…skills

Two follow-ups to the .claude/ restructure.

Skills: `code` and `code-review` were ~90% restatements of what the new
path-scoped rules now deliver — TypeScript standards, React rules, the MVVC
layer contracts, accessibility — with the remainder duplicating the
`implementer` and `code-reviewer` subagents. Two sources for one convention is
the drift risk the docs warn about, and the project `code-review` skill also
shadowed Claude Code's bundled skill of the same name. Delete both; the
bundled `/code-review` is reachable again and the agents cover the personas.
`debug-maidr` stays: it is a procedure nothing else covers.

Copilot: the repository configures Copilot under .github/, which has the same
four concepts in different file formats. The path-scoped pair matches almost
exactly — Claude scopes a rule with a `paths:` YAML list, Copilot with a
comma-separated `applyTo:` string — so generate that side rather than
hand-maintaining a second copy:

  CLAUDE.md          →  .github/copilot-instructions.md
  .claude/rules/*.md →  .github/instructions/*.instructions.md

An unscoped rule, which loads every session in Claude Code, becomes
`applyTo: "**"`. Brace groups are expanded to comma-separated patterns, since
`applyTo` documents commas but not braces.

- Add scripts/sync-copilot-instructions.mjs with a --check mode, plus
  `sync:copilot` and `sync:copilot:check` npm scripts.
- Add a CI job running the check. It is pure Node, so it skips npm ci and
  gates no other job.
- Replace style-guide.instructions.md with the generated typescript
  instructions, folding in its one accurate claim not already covered (barrel
  index.ts at module boundaries only). Its "80% coverage" and "100 character
  line length" rules were dropped: neither is configured in jest.config.ts or
  eslint.config.ts.
- Repoint the four files that referenced style-guide.instructions.md, and fix
  the relative link paths in .github/agents/, which resolved to
  .github/agents/.github/... and were already broken.

Agents and workflow prompts are deliberately not generated: the agent
frontmatter schemas genuinely differ (handoffs and a display name on Copilot's
side, permissionMode/memory/skills/maxTurns on Claude's), and .github/prompts/
holds GitHub chores that Claude Code does through its GitHub tools instead.
.claude/README.md documents the mapping and the asymmetries.
@jooyoungseo jooyoungseo changed the title docs(claude): restructure .claude/ into path-scoped rules docs(claude): restructure .claude/ into path-scoped rules and mirror them to Copilot Jul 28, 2026
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This is a docs/config-only change (.claude/, .github/, one new script) — no src/ files are touched, so there's no runtime behavior to test. I read the full diff plus the generated output to check internal consistency rather than just skimming. Overall this is a well-executed, well-reasoned restructure; my findings are minor.

What's good

  • scripts/sync-copilot-instructions.mjs is clean, dependency-free, and does what it claims. I traced readPaths/expandBraces/render by hand against .claude/rules/typescript.md and the committed .github/instructions/typescript.instructions.md — the generated output matches exactly (frontmatter, banner, body). Good use of --check failing CI on drift rather than silently rewriting.
  • Spot-checked several paths: globs against the actual src/ tree (src/model/abstract.ts, movable.ts, context.ts, factory.ts; src/service/audio.ts, audioPalette.ts, braille.ts, description.ts, highlight.ts, highContrast.ts, keybinding.ts, notification.ts, text.ts) — all real, matching the PR's claim that every glob was checked against git ls-files.
  • The relative-link fixes in .github/agents/*.agent.md are correct (e.g. ../../.claude/rules and ../copilot-instructions.md from .github/agents/) — the old links (bare .claude/ARCHITECTURE.md) were indeed broken from that directory, so this is a genuine fix, not just a rename.
  • ci.yml's new copilot-sync job is well-scoped: read-only permissions, skips npm ci since the script has no deps, and doesn't gate other jobs.
  • No dangling references to the deleted ARCHITECTURE.md/PATTERNS.md/DEBUGGING.md/code/code-review skills remain anywhere in the diff.

Findings

  1. .claude/README.md agents table is missing a row. The "Do agents/ and rules/ both belong here?" section says "Both directories carry the same eight roles," and there are indeed 8 files in both .claude/agents/ and .github/agents/ (including debugger). But the agents/ table just above it only lists 7: planner, architect, implementer, code-reviewer, test-runner, accessibility-expert, feature-builder — debugger is missing from the table despite being modified in this PR and referenced elsewhere in the same README (implicitly). Minor, but worth a one-line fix since this doc is meant to be the source of truth for "what goes where."

  2. Unverified central assumption: do path-scoped rules actually load inside subagents based on files they open? The whole redesign hinges on "a subagent inherits the CLAUDE.md hierarchy, including project rules" (.claude/README.md), and several agent bodies now say things like "the project rules in .claude/rules/ are already in your context" (planner.md, implementer.md) instead of restating conventions. If a given subagent's own tool-use during its run doesn't happen to open a file matching a rule's paths: glob before it needs that convention (e.g. planner reasoning about src/service/* conventions before reading a matching file), the agent could be flying without guidance that used to be guaranteed via the deleted docs. This is presumably true per Anthropic's docs (linked in the PR), but since it's the load-bearing assumption for deleting ~2,265 lines of reference material, it'd be worth a quick empirical check (e.g. running architect or planner on a task and confirming the rule content actually shows up in its context) rather than trusting the docs alone.

  3. Not a bug, just a note on the unchecked checklist boxes — the PR's reasoning for not running tests/lint is sound given no src/ files changed and eslint.config.ts ignores .claude//.github//CLAUDE.md. Confirmed this by checking package.json/CI as committed at HEAD, so that's an accurate claim.

Nit

  • sync-copilot-instructions.mjs's non---check path only logs orphaned .github/instructions/*.instructions.md files as a note rather than deleting them. That's a reasonable, safe default (avoid surprise deletes), but combined with --check treating orphans as failures, a contributor who renames a rule locally and runs the plain sync:copilot script won't get the stale file cleaned up automatically — they'll only find out when CI fails. Worth a one-line mention in the tool's own output or the README so it's not a surprise.

No security concerns (no secrets, no code execution changes beyond the new build-time script which only reads/writes within the repo), and no test coverage changes needed given the scope.

The script lives under scripts/, which eslint.config.ts does not ignore, so
`npm run lint` failed on it. Six errors, all mine:

- `BANNER` was an arrow constant; antfu/top-level-function wants a declaration.
  Renamed to `banner` now that it reads as a function.
- `process` needs an explicit `node:process` import under
  node/prefer-global/process.
- `/^#\s+(.+)$/m` let `\s+` and `.+` exchange spaces, which
  regexp/no-super-linear-backtracking flags as polynomial backtracking.
  Anchoring the capture to `\S` makes the boundary unambiguous.

Also filled in the four @returns descriptions that were warnings.

Verified locally with dependencies installed: lint, type-check, build, and
1048 unit tests all pass.
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This is a config/docs-only PR (no src/ changes), and it's a genuine improvement: the old @-import setup loaded ~60KB into every session regardless of task, and the new path-scoped rules/ mechanism is the right fix. I cross-checked every path glob (src/service/audio.ts, src/model/context.ts, src/state/viewModel/registry.ts, Svg.markOwned/data-maidr-owned, etc.) and every command mentioned in the new CLAUDE.md (dev:recharts, dev:victory, build:preview, docs:serve, e2e) against the actual package.json and source tree — all accurate. Nice catch also fixing the pre-existing wrong claim that traces live in src/model/trace/ (they live directly in src/model/).

Per the PR's own request, I focused most scrutiny on scripts/sync-copilot-instructions.mjs, since it's the one piece of real logic here.

Bug: .claude/README.md documents subdirectory support the sync script doesn't implement

.claude/README.md states:

Subdirectories are discovered automatically, so rules/frontend/react.md works.

But render() in scripts/sync-copilot-instructions.mjs does:

```js
for (const file of readdirSync(RULES_DIR).filter(f => f.endsWith('.md')).sort()) {
```

readdirSync without { recursive: true } only lists immediate entries. A nested file like rules/frontend/react.md shows up as the directory entry frontend, which fails f.endsWith('.md') and is silently dropped — no error, no "orphaned" warning, nothing. If a future contributor follows the README's explicit advice and adds a scoped rule in a subdirectory, it will apply in Claude Code but never make it into .github/instructions/, and sync:copilot:check will still report "in sync" — exactly the silent-drift failure mode this whole PR exists to prevent.

Fix: either pass { recursive: true } to readdirSync (and flatten the relative path into the generated filename), or correct the README to say subdirectories aren't supported yet.

Minor: mirrored body text leaks Claude-only references into Copilot's files

Because CLAUDE.md/rule bodies are copied verbatim (only frontmatter is transformed), a few Claude-specific pointers end up unresolved on the Copilot side:

  • .github/copilot-instructions.md tells the reader to "Run /debug-maidr for the workflow" and "See .claude/README.md for what goes where" — but debug-maidr is a Claude Code skill with explicitly no Copilot counterpart (per the PR description itself), and .claude/README.md is written for Claude Code's mental model.
  • .github/instructions/architecture.instructions.md says "See rules/model.md" — that path doesn't exist relative to .github/instructions/; the real file is model.instructions.md in the same directory.

None of this breaks the sync mechanism, but an agent or contributor working purely from the Copilot side following one of these pointers will hit a dead end. Worth a follow-up pass (or a small templating step) if the goal is for Copilot's copy to stand on its own.

Everything else checked out

  • CI job (copilot-sync) correctly skips npm ci and gates nothing else.
  • npm run sync:copilot / sync:copilot:check scripts match usage in CI and docs.
  • All paths: globs across the new rules resolve to real files/directories.
  • The four files that referenced the deleted style-guide.instructions.md were all repointed, and the .github/agents/*.agent.md relative links were fixed (previously resolved to .github/agents/.github/...).
  • Agent consolidation (react-ts-code-reviewer→code-reviewer, react-ts-engineer→implementer) reads cleanly; no leftover references to the deleted files found in the diff.
  • feature-builder's switch from the Task(...) allowlist alias to Agent plus an explicit "delegate only to these six" sentence in the body is a reasonable fix for a no-op restriction.

No test coverage concerns beyond what's already called out in the PR body (no application code touched, .claude//.github//CLAUDE.md are lint-ignored). No security issues — the sync script only reads/writes within the repo tree and takes no untrusted input.


🤖 Generated with Claude Code

Addresses review feedback on #652.

The sync script listed `.claude/rules/` non-recursively, so a rule in a
subdirectory was silently dropped: no output file, no orphan warning, and
`--check` still reported "in sync". Confirmed by adding
`.claude/rules/frontend/react.md` and watching the check pass while nothing was
generated. That is the exact silent-drift failure the script exists to prevent,
and `.claude/README.md` actively invites it by documenting that nested rules
work — which they do in Claude Code, and now in the mirror too.

Both tools support nesting, so the structure is mirrored rather than flattened:
`.claude/rules/frontend/react.md` generates
`.github/instructions/frontend/react.instructions.md`. Orphan detection walks
recursively as well, and output directories are created as needed.

Also repoint cross-references. Rule bodies were copied verbatim, so a rule
saying "See `rules/model.md`" left a Copilot-only reader chasing a path that
does not exist from `.github/instructions/`; those now rewrite to
`model.instructions.md`. The same problem in CLAUDE.md is fixed at the source
by naming `.claude/skills/debug-maidr/SKILL.md` before the `/debug-maidr`
shortcut, and by describing `.claude/` and `.github/` together.

Docs: add the missing `debugger` row to the agents table in
`.claude/README.md`, and document the subdirectory mirroring, the
cross-reference rewriting, and that a renamed rule leaves an orphan the plain
sync only warns about.

Verified: 8-case regression pass over the script (clean, edited rule, nested
rule, nested output path, nested orphan, flat orphan, malformed frontmatter,
restored), plus lint clean.

Copy link
Copy Markdown
Member Author

Thanks — the subdirectory finding was a real bug and I've fixed it in b58f35d. Responses to each point:

1. Nested rules silently dropped — confirmed and fixed

You were right, and it was worse than a missing feature: it was silent. I reproduced it before fixing:

$ mkdir -p .claude/rules/frontend && echo '...' > .claude/rules/frontend/react.md
$ node scripts/sync-copilot-instructions.mjs --check
Copilot instructions are in sync (11 files).      # exit 0
$ ls .github/instructions | grep react
(nothing)

A rule that Claude Code loads, that the mirror never generates, and a check that passes anyway — exactly the drift this script exists to catch, made worse by .claude/README.md explicitly telling contributors nested rules work.

I went with mirroring the structure rather than flattening, since Copilot supports subdirectories of .github/instructions/ too — so .claude/rules/frontend/react.md → .github/instructions/frontend/react.instructions.md. Flattening to frontend-react.instructions.md would have introduced a collision case (frontend/react.md vs frontend-react.md) for no benefit. Orphan detection now walks recursively as well, which the flat version would also have missed.

2. Claude-only references leaking into Copilot's copy — fixed, at two different levels

Split this by cause:

  • Cross-references between rules (See \rules/model.md`) are now rewritten by the generator to model.instructions.md`. This is mechanical and belongs in the script.
  • /debug-maidr in copilot-instructions.md I fixed at the source instead. Rather than adding a substitution rule to the script, CLAUDE.md now names the real file first — "the workflow and per-layer checklists are in .claude/skills/debug-maidr/SKILL.md; in Claude Code, run /debug-maidr" — which reads correctly on both sides and points somewhere real either way. Same treatment for the .claude/README.md pointer, which now describes .claude/ and .github/ together.

I deliberately didn't build a general templating/conditional-block mechanism. Two dead pointers didn't justify a second syntax in these files, and rewording is harder to get subtly wrong.

3. Missing debugger row in the agents table

Correct, and it was a genuine inconsistency with the "same eight roles" sentence a few lines below. Added. I also reworded feature-builder's row, which said "orchestrates the six above" — adding a seventh row above it would have quietly made that wrong.

4. Orphan handling asymmetry

Fair. Kept the behaviour (plain sync warns, --check fails) since surprise deletes are worse, but it's now documented in .claude/README.md so it isn't discovered via a CI failure.

On the earlier review's point about rules reaching subagents

Worth recording since it's load-bearing for deleting the old docs — it's explicit in the subagents documentation, under what a subagent's initial context contains:

CLAUDE.md files: every level of the CLAUDE.md hierarchy the main conversation loads, including ~/.claude/CLAUDE.md, project rules, CLAUDE.local.md, and managed policy files.

So project rules are injected at subagent startup, not conditionally on that subagent's own file reads.

Verification

I installed dependencies this round rather than reasoning about what CI would say. Full suite green locally — lint, type-check, build, 1048/1048 unit tests — plus an 8-case regression pass over the sync script: clean state, edited rule, nested rule, nested output path, nested orphan, flat orphan, malformed frontmatter, restored clean. The nested cases fail against the previous commit and pass against this one.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This is a docs/config-only PR (no src/ changes), so I focused on the one real code artifact — scripts/sync-copilot-instructions.mjs — plus spot-checking the new rule content for accuracy against the actual codebase, per the reviewer note in the PR description.

Verified correct

  • Technical accuracy of the rules is a real improvement over the old docs. E.g. .claude/rules/model.md correctly says trace classes live flat in src/model/ (bar.ts, line.ts, …) — I checked ls src/model/ and confirmed there is no src/model/trace/ subdirectory, which the old .claude/ARCHITECTURE.md incorrectly implied (BarTrace.ts under src/model/trace/). Same for the data-maidr-owned / Svg.markOwned highlight-ownership claim — confirmed present in src/util/svg.ts, src/model/heatmap.ts, src/model/box.ts, etc.
  • CLAUDE.md command corrections check out: npm run dev and npm run preview indeed don't exist in package.json; dev:recharts, dev:victory, and build:preview do. src/index.tsx (not .ts) is also correct.
  • sync-copilot-instructions.mjs cross-reference rewriting is correct: rule bodies use the bare rules/model.md form, and rewriteRuleLinks (/\brules\/([\w./-]+)\.md\b/g) turns that into model.instructions.md — verified against the generated .github/instructions/architecture.instructions.md diff.
  • readTitle's heading regex (/^#\s+(\S.*)$/m) correctly matches only level-1 # headings, not ## subheadings (the second # immediately fails \s+), so it won't misfire on files with ## -style subsections before the first content.
  • CI wiring: confirmed the new copilot-sync job has no needs: pointing at it from any other job — it's genuinely non-gating, matching the PR description's claim.

Minor / non-blocking findings

  1. readPaths silently widens scope on a malformed paths: list. If paths: is present but written as a YAML flow sequence (paths: [a, b]) instead of the block-list style (- a) every existing rule uses, readPaths won't throw — it just returns [] (no line starts with - ), and render() then falls through to applyTo: "**". That's the opposite of fail-safe: a scoped rule silently becomes unscoped in the Copilot mirror instead of the sync erroring loudly. Given the script already validates "frontmatter present but no paths: key" explicitly, it'd be worth also asserting globs.length > 0 whenever a paths: key exists, so a future rule author gets a hard error instead of over-broad applyTo: "**".

  2. Generated Copilot files can reference Claude-only mechanisms verbatim. architecture.md says "Run the /debug-maidr skill for the full workflow," and this line is mirrored as-is into architecture.instructions.md. Slash-command skills are a Claude Code concept Copilot doesn't have, so a Copilot-only reader hits a dead reference. rewriteRuleLinks handles rules/*.md cross-references but not skill invocations — probably fine to leave as a known limitation (documented as "not mirrored" for agents/skills in .claude/README.md), but worth flagging since it's inside a mirrored rule body rather than the explicitly-excluded agents/prompts.

  3. fs.readdirSync(dir, { recursive: true }) (used in listMarkdown) requires Node ≥20.1.0. The repo's workflows all pin Node 20/22/lts/*, so CI is fine, but package.json has no engines field — a contributor running npm run sync:copilot locally on an older Node (18.x is still common) will get a hard crash with a fairly opaque error. Not a blocker, just worth a one-line note in the script header or a engines.node bump if this matters to the project.

None of these are functional blockers — the author's own verification (27 paths: globs checked, 8-case regression pass on the sync script's --check mode, link resolution, lint/type-check/build/1048 tests passing per the last commit message) covers the common paths well. Nice reduction in per-session context footprint (~60KB → ~4.5KB baseline) while keeping the detail one file-open away.

Addresses the second round of review feedback on #652.

`readPaths` only recognised block-list syntax, but the guard checked for the
`paths:` key rather than the parsed result. A rule written as a YAML flow
sequence — `paths: ["src/model/**"]` — passed the key check, parsed to zero
globs, and fell through to `applyTo: "**"`. Confirmed before fixing: that input
mirrored a model-layer rule as applying to every file in the repository.

Silently widening scope is the wrong failure direction, so a `paths:` key that
yields no globs is now an error naming the supported syntax. A rule with no
frontmatter at all still maps to `applyTo: "**"`, which is intentional — that
is what an unscoped rule means in Claude Code.

Also reword the `/debug-maidr` reference in rules/architecture.md, which is
mirrored verbatim into Copilot's copy where slash-command skills do not exist.
Same treatment already applied to CLAUDE.md last commit; this was the remaining
instance inside a mirrored body.

Note the Node 18.17.0 floor for `fs.readdirSync`'s `recursive` option in the
script header. Not adding an `engines.node` field: this package is published,
so that would change install behaviour for every consumer, which is a
project-wide call rather than something to slip into a config change.

Regression suite now 11 cases, including the flow-sequence and empty-`paths:`
inputs and a check that an unscoped rule still maps to "**". Lint clean.

Copy link
Copy Markdown
Member Author

All three addressed in 8e917c2. Findings 1 and 2 were real; I reproduced both before fixing.

1. readPaths silently widening scope — fixed

Good catch, and the failure direction was the bad one. Reproduced:

$ cat .claude/rules/zz-flow.md
---
paths: ["src/model/**"]
---
$ node scripts/sync-copilot-instructions.mjs && grep applyTo .github/instructions/zz-flow.instructions.md
applyTo: "**"

A model-layer rule mirroring as "applies to every file in the repo." The guard was checking for the paths: key rather than the parsed result, so the flow sequence sailed through it and then hit the globs.length > 0 ? … : '**' fallback.

A paths: key that yields no globs is now an error naming the supported syntax:

sync-copilot-instructions: .claude/rules/zz-flow.md: has a `paths:` key but no
globs were parsed from it. Use a block list, one glob per line (`- "src/**"`);
flow sequences (`paths: [...]`) are not supported

A rule with no frontmatter at all still maps to applyTo: "**" — that's intentional, since it's what an unscoped rule means in Claude Code. There's now a regression case pinning both behaviours apart so a future "fix" can't collapse them. Also documented the block-list requirement in .claude/README.md's "adding a rule" section, so authors hit it before the script does.

2. /debug-maidr inside a mirrored rule body — fixed

Right, and it was a miss on my part rather than a deliberate limitation: I reworded this exact phrasing in CLAUDE.md last commit and didn't grep for other instances. rules/architecture.md had the same line. Now reads "the full workflow and the per-layer checklists are in .claude/skills/debug-maidr/SKILL.md; in Claude Code, run /debug-maidr" — points at a real file from either side.

I kept this as source rewording rather than adding a skill-invocation rule to rewriteRuleLinks. Cross-references between rules are a closed, mechanical set; "any Claude-only concept in prose" isn't, and a substitution table would be a quiet source of wrong rewrites later.

3. Node version floor — noted, but not via engines

Checked this one because the version looked off: the recursive option for fs.readdirSync landed in v18.17.0, not v20.1.0 (PR #41439, shipped in the 18.17.0 release). So the floor is a release lower than stated and 18.x contributors are mostly fine. There was a real bug in that option between v18.17.0 and v18.19.0, but only when combined with withFileTypes, which this script doesn't use.

I documented the requirement in the script header rather than adding engines.node to package.json. maidr is a published package, so an engines field changes install-time behaviour for every consumer — that's a project-wide decision, not something to slip into a config PR. Happy to add it if a maintainer wants it, as its own change.

Also verified from your list

Confirming the two you flagged as correct, since both were things I'd want caught if wrong: readTitle's /^#\s+(\S.*)$/m does reject ## headings (the second # fails \s+), and copilot-sync has no job depending on it, so it stays non-gating.

Regression suite is now 11 cases — clean, edited rule, nested rule, nested output path, nested orphan, flat orphan, missing paths: key, flow sequence, empty paths:, unscoped-still-maps-to-**, restored clean. Lint clean.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This PR touches no application code — it's a restructure of .claude/ into path-scoped rules plus a generator (scripts/sync-copilot-instructions.mjs) that mirrors those rules into .github/. Per the PR's own reviewer note, I focused most of the scrutiny on the sync script since it's the one piece of real logic here, and spot-checked its output against the generated files in the diff (accessibility.instructions.md, copilot-instructions.md, the repointed links in .github/agents/*.md) — the output matches what the script should produce, frontmatter conversion (paths: → applyTo:), brace expansion, and cross-reference rewriting all check out.

Findings

1. readPaths() scans the whole frontmatter block, not just the paths: key — low severity, but a real footgun (scripts/sync-copilot-instructions.mjs, readPaths)

const globs = frontmatter
  .split('\n')
  .filter(line => line.trim().startsWith('- '))
  ...

This grabs every line starting with - anywhere in the frontmatter, not just lines under paths:. Today every rule's frontmatter has paths: as its only key, so it's harmless. But if a future rule adds another block-list key before or after paths: (e.g. tags: or an allowed-tools: list), its items would silently get merged into the glob set and mirrored into applyTo, with no error — because globs.length would still be nonzero, so the "fail instead of silently widening" guard never fires. That guard is exactly the failure mode the last two commits in this PR were written to close (the flow-sequence paths: [...] bug and the non-recursive nested-rule bug), so it'd be worth scoping the scan to only the lines between paths: and the next top-level (^\S) key.

2. No automated test for the sync script (test coverage)
The PR description documents an 11-case manual regression suite (clean, edited rule, nested rule, nested orphan, flow-sequence input, empty paths:, malformed frontmatter, etc.) that was run by hand and isn't checked into the repo. This is exactly the kind of script where two genuine regressions already slipped in during review (silently dropped nested rules; silently widened scope from a flow-sequence paths:) — both fixed in later commits here, but nothing stops a third one next time someone touches render() or readPaths(). The new testing.md rule this PR adds says "new behaviour ships with a test that would have failed before it"; the script itself doesn't meet that bar yet. Worth adding e.g. test/scripts/sync-copilot-instructions.test.ts that invokes the script against fixture rule files and asserts on the generated output / thrown errors for at least the cases already found by hand.

3. Nice-to-have: no pre-commit hook wired to sync:copilot
The repo already uses Husky for commitlint. A contributor who edits CLAUDE.md or a rule and forgets npm run sync:copilot only finds out from CI or a reviewer comment. Not blocking since the CI job (copilot-sync) backstops it, just a possible follow-up for faster feedback.

Verified as correct

  • expandBraces, the recursive listMarkdown, and the orphan-detection logic all behave as documented when checked against the generated output in the diff.
  • The readPaths "fail loud on a flow sequence" and "recurse into subdirectories" guards (added in the last two commits) are real fixes for real bugs, confirmed by the commit messages' repro steps and the corresponding rule files.
  • Spot-checked several factual corrections against the actual codebase: src/model/ has no trace/ subdirectory (all traces live flat, e.g. bar.ts, line.ts) — the old CLAUDE.md/agent docs saying src/model/trace/ were wrong and are now fixed. npm run dev / npm run preview don't exist in package.json; dev:recharts, dev:victory, build:preview do. jest.config.ts has no coverageThreshold and eslint.config.ts has no line-length rule, confirming the deleted style-guide.instructions.md claims (80% coverage, 100-char lines) were inaccurate and correctly dropped.
  • The relative-link fixes in .github/agents/*.agent.md (e.g. ../../.claude/rules, ../copilot-instructions.md) resolve correctly from that directory's location — the old links were indeed broken as claimed.
  • No security concerns: the script only reads/writes local markdown under a fixed root, no user input, no injection surface. CI job correctly skips npm ci since the script only uses Node built-ins, and gates nothing else in the pipeline.

Overall

Well-scoped, well-documented tooling change with a sensible design (generate the Copilot side instead of hand-maintaining two copies). The two follow-up commits show the author caught and fixed real bugs in their own script through iteration, which is a good sign. The one gap worth addressing before/after merge is locking in that regression coverage with an actual test file rather than a manually-run suite, plus the minor readPaths scoping issue above.

…ping

Addresses the third round of review feedback on #652.

The glob scan read every `- ` line in a rule's frontmatter rather than only
the block under `paths:`, so a sibling list folded into the mirror. Confirmed
before fixing: a rule with `paths: [src/model/**]` plus a `tags:` list
generated `applyTo: "src/model/**,leaked-value"`. The empty-result guard added
last commit could not catch this, since the result was not empty. The scan now
stops at the next top-level key.

Check in the regression suite that had been run by hand. Three genuine bugs
reached review in this script — nested rules dropped silently, a flow-sequence
`paths:` widening scope to the whole repo, and now this one — and
`.claude/rules/testing.md`, added by this same PR, says new behaviour ships
with a test that would have failed before it. The script did not meet that bar.

`test/scripts/syncCopilotInstructions.test.ts` drives the CLI end to end: each
case builds a fixture tree, points the generator at it, and asserts on the exit
code and generated files. `SYNC_COPILOT_ROOT` exists for that; it defaults to
the repository root, so nothing changes for normal use.

The 18 cases were verified to fail against the code they describe: 1 fails
against the previous commit (key scoping), 7 against the commit before it
(nested rules, nested orphans, flow sequence, empty `paths:`,
cross-references).

Verified: lint clean, 1066 unit tests across 88 suites pass, mirror in sync.

Copy link
Copy Markdown
Member Author

Findings 1 and 2 are addressed in 5533222. Declining 3, with reasoning.

1. readPaths scanning the whole frontmatter — confirmed and fixed

Real, and reproduced before fixing:

$ cat .claude/rules/zz-leak.md
---
paths:
  - "src/model/**"
tags:
  - "leaked-value"
---
$ node scripts/sync-copilot-instructions.mjs && grep applyTo .github/instructions/zz-leak.instructions.md
applyTo: "src/model/**,leaked-value"

Your read of why it evades the guard is exactly right: globs.length is non-empty, so the "fail instead of widening" check added last commit never fires. Third bug in this script of the same shape — wrong output, no error. The scan now stops at the next top-level (^\S) key.

2. No automated test — you're right, and it was my own rule I was failing

This is the finding that should have blocked. .claude/rules/testing.md, added by this very PR, says "new behaviour ships with a test that would have failed before it," and I was running an 11-case suite by hand and not checking it in. Three regressions have now reached review in this one file. Manual diligence demonstrably wasn't holding.

Added test/scripts/syncCopilotInstructions.test.ts — 18 cases driving the CLI end to end. Each builds a fixture tree in a temp directory, points the generator at it via a new SYNC_COPILOT_ROOT env var (defaults to the repo root, so normal use is unchanged), and asserts on exit code and generated output.

I then checked the tests actually catch what they claim, by running them against the earlier commits:

Script version Result
5533222 (this commit) 18 passed
8e917c2 (before key scoping) 1 failed — should ignore block lists belonging to other frontmatter keys
009c958 (before recursive + flow-seq) 7 failed — nested rule, nested orphan, flow sequence, empty paths:, cross-references

A suite that passes against the buggy code would have been worse than none, so that seemed worth confirming rather than asserting.

Full run: lint clean, 1066 tests across 88 suites pass.

3. Pre-commit hook — declining, but happy to be overruled

Skipping this one deliberately rather than by oversight. Husky currently runs only commit-msg, which is a validation on text the contributor just typed. A pre-commit hook is a different thing: it adds latency to every commit for every contributor, including ones who never touch .claude/. The copilot-sync CI job already catches this, and it's non-gating and finishes in ~5 seconds, so the feedback loop is short.

Changing everyone's commit flow feels like a maintainer call rather than something to fold into a config PR. If you'd like it, a pre-commit that early-exits unless CLAUDE.md or .claude/rules/ is staged would be the cheap version — say the word and I'll add it as its own change.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This PR touches no application code — it's a restructuring of .claude/ into path-scoped rules plus a generator that mirrors them into .github/instructions/ for Copilot. I focused review on the one piece of real logic, scripts/sync-copilot-instructions.mjs, and spot-checked that the committed generated files actually match what the generator would produce.

Code quality / correctness

  • sync-copilot-instructions.mjs is solid. Frontmatter parsing correctly scopes the paths: scan to stop at the next top-level key (so a sibling tags: block list isn't folded in), rejects flow-sequence globs and empty paths: blocks instead of silently widening to applyTo: "**", and expandBraces correctly handles multiple brace groups in one glob via recursion. I traced through readPaths, expandBraces, and the frontmatter splitter by hand and didn't find an edge case that breaks them.
  • Verified the generated output actually matches the generator's logic, not just eyeballed — checked architecture.instructions.md, accessibility.instructions.md (11-glob applyTo list), typescript.instructions.md (brace expansion), and git-workflow.instructions.md (unscoped → applyTo: "**") against their source rules, plus that cross-reference rewriting (rules/model.md → model.instructions.md) round-trips correctly in architecture.instructions.md. All consistent.
  • No dangling references. Grepped the whole tree for the deleted ARCHITECTURE.md/PATTERNS.md/DEBUGGING.md, the removed react-ts-* agents, the removed code/code-review skills, and style-guide.instructions.md — nothing left pointing at them.
  • Commands in the new CLAUDE.md all exist (dev:recharts, dev:victory, build:preview, docs:serve, etc. all present in package.json), which does fix the previously-inaccurate npm run dev/npm run preview references called out in the PR description.
  • Every paths: glob I spot-checked (e.g. src/service/audioPalette.ts, commandExecutor.ts, help.ts) resolves to a real tracked file.

Test coverage

test/scripts/syncCopilotInstructions.test.ts is genuinely good: it drives the script as a CLI against fixture trees (not just unit-testing internal functions), and several cases are explicit regression tests for real bugs called out in comments — nested rules silently dropped by a non-recursive readdirSync, and a scoped rule silently widening to applyTo: "**" when the glob parse fell through empty. That's the right instinct for a script whose failure mode is "silently wrong output that still exits 0."

Minor suggestions (non-blocking)

  1. No pre-commit hook wired to sync:copilot. Drift is only caught in CI (copilot-sync job), which means a contributor finds out after pushing rather than at commit time. Given Husky is already used for commitlint in this repo, a lint-staged/husky pre-commit step for .claude/rules/** would shift that feedback earlier — worth considering if PRs touching rules are common.
  2. sync:copilot (non-check mode) doesn't delete orphaned instruction files, only logs a note ("has no matching rule — delete it if the rule is gone"). That's a reasonable safety choice (avoids surprise deletions), but it does mean npm run sync:copilot alone won't make --check pass after a rule is removed — worth a one-line callout in the README's "adding/removing a rule" section so contributors don't get stuck wondering why CI still fails after running the generator.
  3. Nit: readTitle's regex comment explains the ReDoS-avoidance reasoning, which is a nice touch — but it might be worth a similar one-line note on why splitFrontmatter uses indexOf('\n---\n', 3) rather than a regex, for consistency (minor, purely stylistic).

Overall

No security concerns (pure Node built-ins, no dependency additions, operates only on repo-local paths). No performance concerns (small file set, runs once in CI). This is a well-scoped, well-tested infra change with good self-verification already documented in the PR description — I independently confirmed the specific claims (glob coverage, link resolution, generated-output correctness) rather than taking them at face value, and they hold up.

🤖 Generated with Claude Code

Orphan handling was documented, but under the Copilot section, ~80 lines from
the "Adding a rule" heading someone reads when changing rules. It came up in
two consecutive review rounds, which is a reasonable signal the placement was
the problem rather than the wording.

Retitle that section to cover removal and state the extra step there: the
generator deliberately does not delete the instruction file a removed rule
leaves behind, so `sync:copilot:check` keeps failing in CI until you delete it
yourself.

Docs only; no generated files change.

Copy link
Copy Markdown
Member Author

Thanks. CI is green across all 13 checks on 5533222. Taking the three nits in turn — one acted on, two declined:

2. Orphan callout — acted on (1df96e4)

This was already documented, but you're right that it wasn't where you'd look: the note lived in the Copilot section, ~80 lines below the "Adding a rule" heading someone actually reads when changing rules. Orphan handling has now come up in two consecutive review rounds, which is decent evidence the placement was the problem rather than the wording. Retitled the section to "Adding or removing a rule" and put the extra step there:

Removing or renaming a rule takes one extra step. The generator will not delete the instruction file left behind, to avoid surprise deletions — it only prints a note naming it. Delete that file yourself, or sync:copilot:check keeps failing in CI after you have "already run the generator."

Which is exactly the stuck state you described.

1. Pre-commit hook — declining, already answered last round

Short version: commit-msg validates text the contributor just typed; pre-commit taxes every commit from every contributor, including those who never touch .claude/. The copilot-sync job already catches it, is non-gating, and finishes in ~8s. Changing everyone's commit flow reads as a maintainer decision rather than something to fold into a config PR — happy to add it as its own change if a maintainer wants it.

3. splitFrontmatter comment — declining

The readTitle comment earns its place because the regex looks needlessly convoluted; without the note someone would "simplify" (\S.*) back to (.+) and reintroduce the backtracking. indexOf('\n---\n', 3) has no such trap — it's the obvious way to find a delimiter, and a comment saying so would just be a line explaining that straightforward code is straightforward. I'd rather comments stay load-bearing so the ones that exist keep getting read.


Nothing outstanding on my side. Green and ready for a maintainer look.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review in progress — will follow up with full comments shortly.

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This PR touches no application source (src/** is untouched), so the review focused on the one piece of real logic — scripts/sync-copilot-instructions.mjs — plus the consistency of the generated output and the doc/link claims in the description.

What was verified

  • Diffed every generated .github/instructions/*.instructions.md against its .claude/rules/*.md source by hand (e.g. architecture.md vs architecture.instructions.md, git-workflow.md vs git-workflow.instructions.md) — frontmatter transform, banner, and body all match what the scripts render() function produces.
  • Confirmed the factual claims in the PR body against the actual repo state: npm run dev:recharts / dev:victory / build:preview exist in package.json (the old npm run dev/preview do not), Jests testMatch is **/test/**/*.test.ts (matches the new tests location), and eslint.config.ts does ignore .claude/, .github/, and CLAUDE.md — so the "lint does not cover these paths" and "80%/100-char rules are not configured" claims both check out.
  • Re-pointed links in .github/agents/*.agent.md and .github/prompts/*.prompt.md now resolve correctly (previously .github/agents/.github/..., confirmed broken before this PR).
  • test/scripts/syncCopilotInstructions.test.ts is a solid end-to-end suite (18 cases) — it drives the script as a CLI via SYNC_COPILOT_ROOT against fixture trees and specifically pins the two regressions called out in its comments (nested rules silently dropped, a scoped rule silently widening to applyTo: "**"). Encoding past bugs as fixtures rather than only describing them in prose is a good pattern to keep using.
  • The script fails loudly on ambiguous input (unterminated frontmatter, a flow-sequence paths: [...], an empty paths: key) instead of guessing — refusing to fall back to applyTo: "**" when scope parsing comes up empty is the right call, since a silent widen would be a much worse failure mode than a red CI check.

Minor / non-blocking

  1. readPaths glob parsing does not strip inline YAML comments. A line like - "src/**" X frontend only (where X marks a trailing comment) would produce a glob with the comment text still attached — the leading quote gets stripped, but the trailing-quote regex anchor no longer matches because the comment text follows it — silently corrupting that one applyTo entry instead of erroring. None of the current 10 rules do this today, but since the parser is a hand-rolled subset of YAML rather than a real parser, a future contributor copying a trailing comment into a paths: list would hit this with no error message pointing at the cause. Worth either stripping trailing comments explicitly or documenting in .claude/README.md that inline comments in paths: are not supported.

  2. No automated check that a paths: glob actually matches a tracked file. The PR description notes this was verified manually (every one of the 27 paths globs matches at least one tracked file, no unescaped bracket), which is great due diligence for this PR, but nothing in sync-copilot-instructions.mjs or its test enforces it going forward — a future rule with a typo in a glob would parse fine, generate a plausible-looking applyTo, and pass --check while silently matching nothing on either side. Since the copilot-sync CI job already exists as a natural home for this, a follow-up that checks each rules paths: globs against git ls-files (and fails if any pattern matches zero files) would close that gap cheaply.

Both are small enough to not block on — the core mechanism (single authored source, generated mirror, CI drift check) is sound and well-tested for the cases it does cover.

Everything else
The .claude/agents/*.md rewrites (shorter descriptions, role-only bodies now that conventions live in rules/) and the Task(...) to Agent fix in feature-builder.md read as genuine improvements — the claim that the old Task(planner, architect, ...) allowlist syntax was a no-op inside a subagent definition is consistent with switching to a plain Agent tool plus an explicit "delegate only to these six" sentence in the body, which is the only enforcement mechanism available either way.

No security concerns — the script only reads and writes files under a ROOT that is either the repo itself or a test-controlled temp directory (SYNC_COPILOT_ROOT), and it touches no application code.

…files

Addresses the fourth round of review feedback on #652.

Stripping quotes with `replace(/^["']|["']$/g, '')` only works when the quote
is the last character. A YAML inline comment breaks that: confirmed before
fixing, `- "src/model/**"  # only the model layer` generated
`applyTo: "src/model/**\"  # only the model layer"` — a pattern matching
nothing, written without complaint.

`readGlob` now reads a quoted scalar to its closing quote, allows a trailing
comment after it, and strips ` #` comments from unquoted values. Input it
cannot read — an unterminated quote, stray text after the closing quote — is an
error rather than a guess, consistent with how the parser already treats a
flow-sequence `paths:`.

Separately, nothing checked that a glob matches anything. A typo parses fine,
generates a plausible `applyTo`, and passes the sync check while the rule is
simply never loaded by either tool. That was verified by hand for this PR;
`test/scripts/claudeRuleGlobs.test.ts` keeps it verified, asserting every
generated `applyTo` pattern matches at least one tracked file and carries no
unexpanded brace group. It reads the patterns from the generated files rather
than re-parsing the rules, so the generator's parser is not duplicated.

`git ls-files` does the matching — it is the authority on what is tracked and
handles the glob styles in use, so no dependency is added.

Both additions were verified to fail against the code they describe: the four
comment cases fail on the previous commit, and the glob check fails when
`src/model/**` is changed to `src/modle/**`.

Verified: lint clean, type-check clean, 1127 tests across 89 suites pass.

Copy link
Copy Markdown
Member Author

Both acted on in 1ad1980. Both reproduced first.

1. Inline YAML comments corrupting a glob — fixed

Real, and it produced exactly the silent-wrong output you described:

$ cat .claude/rules/zz-c.md
---
paths:
  - "src/model/**"  # only the model layer
---
$ node scripts/sync-copilot-instructions.mjs && grep applyTo .github/instructions/zz-c.instructions.md
applyTo: "src/model/**\"  # only the model layer"

Your diagnosis of the mechanism is precise — replace(/^["']|["']$/g, '') only works when the quote is the final character, so the leading one goes and the trailing one doesn't, taking the comment along with it.

I went with supporting comments rather than documenting them as unsupported. They're ordinary YAML, a contributor writing one is doing nothing wrong, and "documented footgun" still leaves a rule silently dead. readGlob now reads a quoted scalar to its closing quote, permits a trailing comment, and strips # from unquoted values. Input it can't read — unterminated quote, stray text after the closing quote — errors rather than guesses, matching how the parser already handles a flow-sequence paths:.

2. No check that a glob matches anything — fixed

This was the better of the two findings, and it's the same shape as everything else that's gone wrong in this file: parses fine, generates plausible output, exits 0, rule silently never loads. Manual verification doesn't survive contact with future edits — that's precisely how the last three bugs got in.

test/scripts/claudeRuleGlobs.test.ts asserts every generated applyTo pattern matches at least one tracked file, and carries no unexpanded brace group.

Two design notes:

  • Reads the patterns from the generated instruction files, not by re-parsing the rules. Re-parsing would duplicate the generator's parser, and a bug in that copy could mask the very thing being checked. The copilot-sync job already guarantees the generated files agree with the rules, so they're a safe proxy.
  • git ls-files does the matching. It's the authority on what's tracked, it handles every glob style in use here (verified src/**, src/**/*.ts, src/model/**, bare file paths, and a nonexistent path returning 0), and it means no new dependency for glob matching.

I put it in the test suite rather than in the sync script, since the script has to stay runnable against the fixture trees its own tests build, which aren't git repositories.

Verification

Both additions were checked against the code they describe, since a test that passes on the buggy version is worse than none:

Result
Four comment cases vs 5533222 4 failed
Glob check with src/model/** → src/modle/** 1 failed, naming the pattern
1ad1980 79 passed in test/scripts

Full run: lint clean, type-check clean, 1127 tests across 89 suites.


Worth noting where this landed: four review rounds, four bugs in this one script, all the same shape — wrong output, exit 0. The mechanism is now guarded by tests at both levels: the generator's behaviour, and the rules' own globs.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This is a config/tooling PR — no application source under src/ changes, so the MVVC/accessibility checklist in CLAUDE.md mostly doesn't apply here. I focused on the one piece of real code, scripts/sync-copilot-instructions.mjs, plus the CI wiring and the accuracy of the rewritten docs. Overall this is a well-executed change: the generator is carefully written, defensively parses YAML instead of guessing, and is backed by a genuinely thorough test suite (including regression tests for three bugs the author says were caught in self-review). The rewritten CLAUDE.md commands were spot-checked against package.json — dev:recharts/dev:victory, build:preview, docs:serve, type-check, e2e, src/index.tsx, src/state/ layout — and everything checks out; the claim that the old npm run dev/npm run preview never existed is correct.

Code quality / best practices

  • scripts/sync-copilot-instructions.mjs is clean, well-commented (comments explain why, not what, consistent with this repo's own conventions), and fails loudly instead of silently widening scope (e.g. rejecting a paths: [...] flow sequence rather than falling through to applyTo: "**"). This is exactly the right failure mode for a drift-prevention tool — a silently-wrong mirror would be worse than no mirror.
  • .claude/README.md explicitly documents the one rough edge in the design: npm run sync:copilot (non---check) doesn't delete orphaned .github/instructions/*.md files when a rule is renamed/removed — it only prints a note, and --check is what actually fails on them. That's called out clearly enough (both in the README and in the script's own console output) that I'd call it a documented tradeoff rather than a bug, but it is a bit of friction: a contributor can run the sync script, see it exit 0-ish looking successful, and still get a red CI job for a file they didn't know to delete. Might be worth an obvious ⚠ prefix on that note, or a --prune flag, but not blocking.
  • readTitle() takes the first line matching /^#\s+(\S.*)$/m anywhere in the rule body as the description. Since it's not markdown-aware, a rule whose body happens to lead with a fenced code block containing a bash comment (# something) before the real # Heading would silently pick the wrong title. Today's rules all start with their heading so this doesn't bite, but it's a latent trap for a future rule edit — worth a one-line comment noting the assumption, similar to the care taken elsewhere in the file.

Potential bugs

  • None found in the generator logic itself — I traced readGlob, readPaths, expandBraces, and rewriteRuleLinks against the actual committed .claude/rules/*.md content and the generated .github/instructions/*.md output, and they're consistent (multi-brace expansion, nested-directory mirroring, and the rules/x.md → x.instructions.md link rewrite all check out against real examples in the diff).
  • The new .github/agents/*.agent.md relative link fixes (e.g. .claude/ARCHITECTURE.md → ../../.claude/rules, .github/copilot-instructions.md → ../copilot-instructions.md) are correct relative to .github/agents/'s location — confirmed these were indeed broken before (resolving to .github/agents/.github/...) as the description claims.

Performance

  • N/A for this change — it's a build-time/CI script over a handful of small markdown files, not a hot path.

Security

  • No concerns. The script only reads/writes files inside the repo it's run against (or a temp fixture dir in tests via SYNC_COPILOT_ROOT), takes no untrusted input, and the CI job only has contents: read. No injection surface — content is repo-authored markdown, not user-supplied.

Test coverage

  • Genuinely strong here. test/scripts/syncCopilotInstructions.test.ts drives the CLI end-to-end via execFileSync against fixture trees and covers the interesting failure modes (unterminated frontmatter/quotes, flow-sequence paths:, empty paths:, sibling-key leakage, nested-directory rules, orphan detection, --check not writing). test/scripts/claudeRuleGlobs.test.ts is a nice complementary check that guards against a correctly parsed but wrong glob (typo'd path that matches zero tracked files) — a class of bug the generator's own tests can't catch since it doesn't know what globs should match.
  • One dependency worth flagging: claudeRuleGlobs.test.ts reads from the committed .github/instructions/*.md output rather than regenerating it, so it's implicitly relying on copilot-sync CI job to keep that output current. That's reasonable (avoids duplicating the parser) and is called out in the test's own comment, so just noting it as intentional coupling rather than a gap.

Nice work — the generator + tests are the kind of thing that's easy to under-invest in for a "just docs" PR, and this doesn't.

Addresses the fifth round of review feedback on #652.

`readTitle` matched the first `# ` line anywhere in the body, with no idea what
a code fence is. Confirmed before fixing: a rule opening with a shell example
donated its comment as the description —

    ```bash
    # install deps first
    ```

    # Model layer

generated `description: "install deps first"`. Low impact, since the
description is only hover text on Copilot's side, but it is the same silent
substitution of wrong-for-right as the earlier parser bugs, and skipping fences
costs about as much as the comment explaining the hazard would have.

Also make the orphaned-file note in the non-check path a WARNING that says what
to do about it. A contributor could run the generator, see it exit 0, and still
get a red CI job for a file they were never told to delete. Not adding a
`--prune` flag: deleting files the caller did not ask about is the surprising
behaviour, and the note plus the README section now cover it.

The fence case fails against the previous commit and passes here. Verified:
lint clean, 1129 tests across 89 suites pass, mirror in sync.

Copy link
Copy Markdown
Member Author

Both addressed in 15f0331.

readTitle and fenced code blocks — fixed rather than commented

You suggested a one-line comment noting the assumption. I fixed it instead, because skipping fences costs about what the comment would have. Reproduced first:

$ cat .claude/rules/zz-t.md
---
paths:
  - "src/model/**"
---

```bash
# install deps first
npm install

Real heading

$ node scripts/sync-copilot-instructions.mjs && grep description .github/instructions/zz-t.instructions.md
description: "install deps first"


You're right that it's low impact — the description is hover text, not scope — but it's the same shape as the four parser bugs already fixed here: a plausible wrong value, substituted silently. Documenting a known-wrong behaviour when the fix is a fence-state boolean seemed like the wrong trade, especially in a file whose entire theme has been "fails loudly instead of guessing."

The ReDoS-safe `(\S.*)` capture is preserved; it just runs per line now instead of with `/m`.

### Orphan note — louder, but no `--prune`

Took the first option. The note now reads:

WARNING: .github/instructions/foo.instructions.md has no matching rule. Nothing
was deleted — remove it yourself, or sync:copilot:check will keep failing in CI.


That names the exact stuck state you described — generator looks successful, CI still red for a file you didn't know about.

Declining `--prune`. Deleting files the caller didn't ask about is the surprising behaviour, and a flag people have to know to pass doesn't help the contributor who doesn't know the file exists. The warning plus the README's "Adding **or removing** a rule" section reach them without a flag.

### On the coupling you flagged

Confirming it's intentional, since you noted it as such: `claudeRuleGlobs.test.ts` reading the committed `.github/instructions/` output rather than regenerating is the point. Re-parsing the rules would duplicate the generator's parser, and a bug in that copy could mask the thing being checked. `copilot-sync` already guarantees the output is current, so the coupling buys independence from the code under test.

### Verification

Fence case fails against `1ad1980`, passes here. Full run: lint clean, **1129 tests across 89 suites**, mirror in sync.

---

Five rounds, five findings in this script — every one the same failure mode, wrong output with a zero exit. Both layers are now test-guarded, and I don't have anything outstanding. Green and ready for a maintainer.

---
_Generated by [Claude Code](https://claude.ai/code)_

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This PR touches no src/ application code — it's a repo-tooling change that restructures .claude/ into path-scoped rules and adds a generator (scripts/sync-copilot-instructions.mjs) that mirrors those rules into .github/instructions/ for GitHub Copilot, plus a CI job (copilot-sync) that enforces the mirror stays in sync. I focused the review on the generator script, its test suite, the CI wiring, and spot-checked the generated content against the source rules and the actual codebase.

Code quality — generator script

scripts/sync-copilot-instructions.mjs is well-built for a "small internal tool" bar:

  • Fails loud instead of guessing: a flow-sequence paths: [...], an empty paths: block, unterminated frontmatter, an unterminated quote, or stray text after a quote all throw rather than silently falling through to applyTo: "**" (which would widen a scoped rule to the whole repo without anyone noticing). That's the right failure mode for a generator whose whole job is preventing silent drift.
  • Uses only Node built-ins (no new dependency), and execFileSync/readFileSync are used correctly (array-form args, no shell interpolation) — no injection surface.
  • --check never writes, main() writes only to files that actually changed, and it deliberately never deletes orphaned generated files (it just warns) — sensible, since silent deletion in a generator is its own footgun.

Tests

test/scripts/syncCopilotInstructions.test.ts and test/scripts/claudeRuleGlobs.test.ts are genuinely good tests, not filler:

  • End-to-end via a real fixture tree + SYNC_COPILOT_ROOT, asserting on actual exit codes and file contents rather than mocking internals.
  • Several cases are explicitly regression-pinned to bugs caught in review (nested rules dropped by non-recursive readdirSync, a scoped rule silently widening to "**", a trailing YAML comment surviving into the glob) — good practice, and the comments explain why each case exists.
  • claudeRuleGlobs.test.ts closes the loop by checking that every real applyTo pattern in the generated output actually matches a tracked file via git ls-files, so a typo'd glob that "parses fine but matches nothing" gets caught too.

All CI checks are green, including Copilot Instructions In Sync and Unit Tests, so the generated files in this PR do match what the script produces from the committed rules.

Consistency check

I diffed a sample of the generated .github/instructions/*.md files against their .claude/rules/*.md sources by hand (architecture, git-workflow) and against CLAUDE.md → copilot-instructions.md — content, frontmatter (paths: → applyTo:), and cross-reference rewriting (rules/model.md → model.instructions.md) all match exactly. The new rules also fix a real staleness bug from the old docs: the previous ARCHITECTURE.md/agent files pointed at src/model/trace/BarTrace.ts, but traces actually live directly in src/model/ (bar.ts, line.ts, ...) — the new model.md rule and implementer.md correctly describe the real layout.

Minor, non-blocking

  • CLAUDE.md principle 3 ("Debug before you edit") points at .claude/skills/debug-maidr/SKILL.md and says "in Claude Code, run /debug-maidr". That line is mirrored verbatim into .github/copilot-instructions.md (and into every rule's "Adding to the system" pointer), so a Copilot user reading copilot-instructions.md directly hits a pointer to a mechanism (skills/slash commands) that doesn't exist on their side. .github/agents/debugger.agent.md was correctly repointed at the skill file's raw path, but copilot-instructions.md itself wasn't given an equivalent Copilot-side pointer (e.g. to .github/agents/debugger.agent.md). Cosmetic — worth a follow-up, not blocking.
  • Since the deleted .claude/skills/code-review/ and .claude/skills/code/ skills shadowed Claude Code's bundled /code-review and similar, removing them to stop the shadowing (per the new .claude/README.md) is the right call, but it's worth double-checking in practice that the bundled skills actually cover what those two provided, since the deleted ones had some MAIDR-specific detail (e.g., explicit "no dangerouslySetInnerHTML" checks) that isn't obviously replicated anywhere else. Not blocking since code-reviewer/implementer agents and the new rules cover most of the same ground.

Security / performance

No security concerns — no new dependencies, no shell interpolation, script only reads/writes within the repo tree it's pointed at (ROOT/SYNC_COPILOT_ROOT). No performance implications since this is dev/CI tooling, not runtime code.

Test coverage

Strong for the new script; not applicable elsewhere since no application code changed.

Overall: a well-scoped, well-tested infra change that removes a real drift risk (two hand-maintained copies of the same conventions) and, as a side effect, fixes some stale documentation. Nothing here blocks merging.

Follow-up to review feedback on #652: a reviewer asked whether the deleted
`code-review` skill's security checklist survived anywhere. Checked, and it
only partly did.

Grepping `.claude/` for innerHTML/XSS/sanitize turns up exactly one hit, in
`agents/code-reviewer.md`. So the guidance reached someone *reviewing* a
change and nobody *writing* one — the rules load at write time, and none of
them said anything about it. That is the wrong way round for a preventable
class of bug.

Scoped each part to where it can actually go wrong rather than adding a broad
security rule:

- `rules/ui.md` — no `dangerouslySetInnerHTML` or `innerHTML` with data
  arriving from chart input, an LLM response, or a live feed. Nothing in
  `src/` uses it today, so this is preventive.
- `rules/service.md` — no API keys in source, and treat chart input, LLM
  responses, and live data as untrusted at the boundary. Names `chat.ts` and
  the LLM services, where it would actually slip.

Verified: lint clean, 1129 tests across 89 suites, mirror in sync with the
cross-reference correctly rewritten to `ui.instructions.md` on Copilot's side.

Copy link
Copy Markdown
Member Author

Item 2 was a real gap — fixed in bef0b77. Declining item 1, with reasoning.

2. Did the deleted skills' security content survive? Partly, in the wrong place

Good question to ask, and worth checking rather than assuming. Grepping .claude/ for XSS-related terms:

$ grep -rn -i "innerhtml\|xss\|sanitiz" .claude/rules/
(nothing)
$ grep -rn -i "innerhtml\|xss\|sanitiz" .claude/agents/
.claude/agents/code-reviewer.md:54:  ... No `innerHTML` with data that could come from a user.

So it survived only in the reviewer agent. That means the guidance reached someone reviewing a change and nobody writing one — rules load at write time, and none of them said anything about it. Backwards for a preventable bug class, and a real regression from deleting the skill even though the words existed somewhere.

Rather than add a broad security.md that loads across src/**, I scoped each part to where it can actually go wrong:

  • rules/ui.md — no dangerouslySetInnerHTML or innerHTML with data arriving from chart input, an LLM response, or a live feed; render as text and let React escape it. Confirmed nothing in src/ uses it today, so this is preventive rather than a fix.
  • rules/service.md — no API keys in source, and treat chart input, LLM responses, and live data as untrusted at the boundary. Names chat.ts and the LLM services specifically, since that's where it would slip.

This is arguably a better home than the skill had: it now loads automatically when someone opens a UI or service file, instead of only when a reviewer remembered to invoke a checklist.

1. /debug-maidr pointer in copilot-instructions.md — declining

I'd already changed this line in an earlier round, and I think it's landed where it should. It currently reads:

The full workflow and the per-layer checklists are in .claude/skills/debug-maidr/SKILL.md; in Claude Code, run /debug-maidr.

The real file path comes first and is readable from either side; the slash command is explicitly qualified as Claude Code-specific. A Copilot reader isn't sent somewhere that doesn't exist — they're told where the content lives and that the shortcut belongs to the other tool.

Adding a Copilot-side pointer to .github/agents/debugger.agent.md would mean either putting Copilot routing into CLAUDE.md (the Claude-facing source, which would then carry instructions for a tool it isn't for), or teaching the generator to inject tool-specific text — a templating layer I declined earlier and would rather not add for one qualified sentence.

Verification

Lint clean, 1129 tests across 89 suites, mirror in sync — and the new cross-reference rewrote correctly, so service.instructions.md points at ui.instructions.md rather than rules/ui.md.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

Reviewed the final merged state of this PR (.claude/ restructure + generated Copilot mirror). Overall this is a well-executed piece of tooling work — the generator script, its regression tests, and the CI gate are all solid.

Strengths

  • scripts/sync-copilot-instructions.mjs is careful and defensive: it refuses to guess on ambiguous frontmatter (unterminated quotes, flow-sequence paths: [...], empty paths: block) rather than silently widening a scoped rule to applyTo: "**". That's exactly the right failure mode for a generator whose whole job is preventing drift.
  • Test coverage is unusually good for a repo-tooling change: test/scripts/syncCopilotInstructions.test.ts pins two real regressions (dropped nested rules, silently-widened scope) as fixture-driven CLI tests, and test/scripts/claudeRuleGlobs.test.ts cross-checks the generated applyTo globs against git ls-files so a typo'd path can't pass silently. Both use execFileSync (no shell interpolation), so no injection risk there.
  • CI wiring is correct and non-blocking-by-design: copilot-sync job runs npm run sync:copilot:check independently of the other jobs, with a comment explaining why it doesn't gate them. package.json scripts (sync:copilot, sync:copilot:check) match.
  • Spot-checked several generated files (architecture.instructions.md, model.instructions.md, etc.) against their .claude/rules/ sources — frontmatter conversion (paths: → applyTo:), banner insertion, and cross-reference rewriting (rules/model.md → model.instructions.md) all line up correctly.
  • No dangling @-imports left in CLAUDE.md pointing at the deleted ARCHITECTURE.md/PATTERNS.md/DEBUGGING.md, and all 8 agent roles listed in the new .claude/README.md exist on both the .claude/agents/ and .github/agents/ sides.

Minor / non-blocking

  1. debugger.md still duplicates the debug-maidr skill it now preloads. .claude/agents/debugger.md gained skills: [debug-maidr] and an updated intro line, but it still carries its own ~40-line "Layer-by-layer debugging guide" and "Common issue patterns" table that substantially overlap the symptom table and layer checklists already in .claude/skills/debug-maidr/SKILL.md. Given the PR's own stated goal is eliminating exactly this class of drift risk ("two hand-written copies... guarantees they drift"), and the README explicitly says rules/skills are what let "agent definitions stay short — they describe a role, not the codebase," this looks like a leftover that could be trimmed now that the skill covers the same ground. Not a bug, just an inconsistency with the PR's own premise.
  2. Worth double-checking in the actual GitHub UI that copilot-sync isn't (or is, depending on intent) added to required status checks for the branch protection rule — the PR description frames it as advisory ("reports drift... blocking nothing"), which is a reasonable choice, but it's worth confirming that's what's configured, since a generated-file drift that nobody notices defeats the purpose.

Not reviewed here

This PR touches no src/ application code, so there's nothing to assess against the audio/text/braille/keyboard accessibility surface, and no runtime behavior to test manually.

Nice work on the test-first approach to a generator script — the fixture-driven CLI tests are a good pattern to reuse for future repo-tooling changes.

…r agent

Follow-up to review feedback on #652, which is fair against this PR's own
premise: `agents/debugger.md` preloads the `debug-maidr` skill via `skills:`
and then restated it, carrying its own layer-by-layer guide and symptom table
alongside the skill's. Since preloading injects the skill in full, that agent
started every run with two copies of the same guidance — the drift risk this
PR exists to remove, and the opposite of what `.claude/README.md` says agent
bodies are for.

Two details in the agent were genuinely absent from the skill, so they moved
into it rather than being deleted with the rest:

- a chat row for the symptom table (`chat.ts`, `llmValidation.ts` — unset or
  rejected API key, or a promise that never settles)
- the `Movable` strategies in the model checklist (`MovableGrid`,
  `MovableGraph`, `MovablePlane`), since a trace moving in the wrong direction
  is often wired to the wrong one

Both verified against `src/` before being propagated.

The agent is now role-only: how to work, what to report, how to finish. 79
lines to 59, with the reference material in one place.

Verified: lint clean, 1129 tests across 89 suites, mirror in sync.

Copy link
Copy Markdown
Member Author

Item 1 fixed in ef13c6a. Item 2 is a maintainer call — details below.

1. debugger.md duplicating the skill it preloads

You're right, and it's the sharpest version of the point: skills: injects the skill in full, so that agent was starting every run with two copies of the same layer guide and symptom table. Exactly the drift risk this PR exists to remove, and directly against what .claude/README.md says agent bodies are for. A leftover from trimming the other agents and not re-reading this one after adding skills: to it.

Before deleting, I diffed the agent's content against the skill's and found two things that weren't in the skill, so they moved rather than went away:

  • A chat row for the symptom table — chat.ts / llmValidation.ts, unset or rejected API key, or a promise that never settles. The skill's table had no chat entry at all.
  • The Movable strategies in the model checklist — MovableGrid, MovableGraph, MovablePlane. A trace moving in the wrong direction is often wired to the wrong one.

Both verified against src/ first (src/model/movable.ts exports all three; heatmap.ts uses MovableGrid) rather than propagated on the old file's say-so — the old docs in this repo had a track record of stale claims.

Agent is now role-only: how to work, what to report, how to finish. 79 → 59 lines, reference material in one place.

2. copilot-sync and branch protection

Splitting this one, because half is mine and half isn't:

In ci.yml, which this PR controls: no job has needs: [copilot-sync], so it can't block the pipeline. That's deliberate and matches the description — the check is cheap and dependency-free, and gating Build/Unit Tests behind a docs-mirror check would be the wrong trade.

Whether it's a required status check is branch-protection configuration on main, which lives in repo settings, not in this diff — I can't read or change it from here, and wouldn't want to alter branch protection as a side effect of a config PR.

My recommendation: add it as required. Your reasoning is right that drift nobody notices defeats the purpose. The failure mode it catches — Copilot silently working from stale conventions — is invisible in review precisely because the generated files look plausible. But that's a maintainer decision, so flagging rather than acting.

Verification

Lint clean, 1129 tests across 89 suites, mirror in sync.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This PR restructures .claude/ (and its .github/instructions/ mirror) rather than touching application code, so I focused the review on documentation accuracy, internal consistency, and the tooling that keeps the two config trees in sync.

What's solid

  • The token-savings claim checks out. The deleted ARCHITECTURE.md/PATTERNS.md/DEBUGGING.md totaled ~58KB that loaded on every session via @-imports; the new baseline (trimmed CLAUDE.md + the unscoped git-workflow.md rule) is close to the claimed ~4.5KB, with the rest loading only when a matching path is opened.
  • Content fidelity is strong, and some things actually got fixed in the move. Spot-checking rules/model.md and rules/service.md against the deleted docs, the new rules correctly describe trace files living flat in src/model/ and the entry point as src/index.tsx — both of which the old docs got wrong (src/model/trace/, src/index.ts). Good catch, and worth calling out in the PR description since it's a real (if small) correctness fix riding along with the reorg.
  • paths: frontmatter is accurate — every glob I checked matches real files/directories, and the count in the PR body matches what's actually in the diff.
  • No dangling references to the deleted docs or old @-import paths remain, and the previously-broken relative links in .github/agents/*.agent.md (which resolved to a nonexistent path) are now correctly pointing at ../../.claude/....
  • scripts/sync-copilot-instructions.mjs + the copilot-sync CI job is a nice piece of engineering — it fails loudly on flow-sequence paths: YAML instead of silently widening a rule's scope to **, and gives the rules↔instructions mirror actual drift protection instead of relying on developer discipline.

Minor items worth a look

  1. implementer.md may have lost a capability in the merge. react-ts-engineer.md described "researches best practices when uncertain" (implying broader tool access / willingness to look things up); the merged implementer.md keeps the narrower Read, Edit, Write, Bash, Grep, Glob toolset with no obvious research path. Worth double-checking this wasn't silently dropped rather than intentionally cut.
  2. feature-builder.md's switch from Task(planner, architect, ...) to bare Agent + prose relies on the claim that the allowlist syntax was inert inside subagent definitions. If that claim turns out to be wrong (i.e., the allowlist was being enforced), this change removes a real guardrail rather than just cleaning up dead syntax. Might be worth a one-line note on how this was verified.
  3. The .claude/agents/ ↔ .github/agents/ mirror is still hand-maintained, per the new README — same class of drift risk this PR just fixed for the rules↔instructions mirror. Not a blocker, but a natural follow-up now that the pattern (generated + CI-checked) exists.
  4. claudeRuleGlobs.test.ts validates globs via git ls-files pathspec matching, which isn't necessarily identical to the glob semantics Claude Code / Copilot use at runtime. A passing test is a good signal but not a strict guarantee of matching runtime behavior in both tools.

Other review categories

  • Bugs/security: none found — this is docs/config only, no executable app logic changed, no secrets in the diff.
  • Performance: the whole point of the PR is a context-loading performance win, and the math behind it holds up.
  • Test coverage: the new sync-copilot-instructions.mjs script and its CI gate are effectively the test coverage for this change; item 4 above is the only gap I'd flag.

Nice, well-verified cleanup overall — nothing here blocks merging as far as I can tell.

…ecks

Addresses review feedback on #652.

The `react-ts-engineer` merge quietly narrowed the implementer's tools. That
agent had no `tools:` key, so it inherited everything including WebFetch and
WebSearch, and its body told it to research when unsure. The merge kept
`implementer`'s existing allowlist and dropped both. That was not a decision, it
was an oversight — for a library built on Web Audio, ARIA, and SVG, looking up
the specification beats guessing at it.

Restored, with a guardrail: the codebase is the authority on convention, the
specification on the platform, and a pattern found online that contradicts the
project rules does not get imported.

Add `test/scripts/agentParity.test.ts`. Agents cannot be generated the way
rules are — the two tools genuinely disagree on frontmatter, and a mapping
table would be its own bug source — but the realistic drift is cheap to catch:
an agent added, removed, or renamed on one side only. It also checks that
`name` matches the filename, that descriptions stay short since they load on
every request, and that a `skills:` entry names a skill that exists.

Both drift cases were simulated to confirm the test fails on them.

That test caught a claim of my own: descriptions were asserted to be "under 250
characters" while `feature-builder` sat at exactly 250. Reworded to 201.

Also note in `claudeRuleGlobs.test.ts` that `git ls-files` pathspec matching is
close to, but not identical with, the runtime matchers — it catches a typo'd
path, it does not prove parity with either tool.

Verified: lint clean, 1133 tests across 90 suites, mirror in sync.

Copy link
Copy Markdown
Member Author

Items 1 and 3 acted on in be8ac4b. Item 2 was verified against the docs; item 4 is a fair limitation, now written down.

1. implementer lost research access — you were right, and it wasn't intentional

Checked, and it was silently dropped rather than cut:

$ git show origin/main:.claude/agents/react-ts-engineer.md | grep -E "^tools:|^model:"
model: inherit

No tools: key, so it inherited everything — including WebFetch and WebSearch — and its body had a "Research When Uncertain" step. The merge kept implementer's existing narrow allowlist and dropped both. I merged the bodies and didn't audit the frontmatter difference. Thanks for catching it.

Restored deliberately, with a guardrail rather than just re-adding the tools. For a library built on Web Audio, ARIA, and SVG, looking up the spec beats guessing — but the risk is importing a web pattern that fights the project's conventions, so the new step reads:

Look it up rather than guess when the uncertainty is external — a Web Audio behaviour, an ARIA pattern, an SVG detail, a chart library's API. The codebase is the authority on convention; the specification is the authority on the platform. Never import a pattern found online that contradicts the project rules.

2. How the Task(...) allowlist claim was verified

Reasonable thing to want evidence for, since if the allowlist had been live I'd have deleted a guardrail. It's stated directly in the subagents documentation:

The Agent(agent_type) allowlist syntax applies only to an agent running as the main thread with claude --agent. In a subagent definition, listing Agent in tools lets that subagent spawn subagents of its own while the depth limit allows it, but any type list inside the parentheses is ignored.

feature-builder is a subagent definition, so the parenthesised list was inert. The same page notes Task(...) still resolves as an alias for Agent, so the old form wasn't broken — it just wasn't restricting anything. The "delegate only to these six" sentence in the body is the only mechanism available either way, which is why it's now stated explicitly instead of implied by syntax that does nothing.

3. Hand-maintained agent mirror — parity now checked

Holding the position that agents shouldn't be generated: the schemas genuinely disagree (permissionMode/memory/skills/maxTurns vs handoffs/display name/a different tool vocabulary), and a mapping table would be its own bug source — this PR has already produced five bugs in one generator, which is evidence enough about mapping layers.

But you're right that the drift risk is the same class, and the realistic drift is cheap to catch: someone adds, removes, or renames an agent on one side only. test/scripts/agentParity.test.ts asserts the two directories describe the same roles, plus three things worth pinning while I was there — name matches the filename, descriptions stay short (they load on every request), and a skills: entry names a skill that exists. Both drift cases were simulated to confirm it fails on them.

It immediately caught a false claim of mine. I'd written that all descriptions are "under 250 characters"; feature-builder was exactly 250. Reworded to 201 — so the statement is now true rather than the test being loosened to match it.

4. git ls-files vs runtime glob semantics

Agreed, and you characterised it correctly. Exact parity would mean reimplementing both tools' matchers and then trusting the reimplementation — more surface than it buys. It catches the realistic failure (a typo'd path matching nothing anywhere) and doesn't prove byte-for-byte agreement. That caveat is now in the test file's header comment rather than only in this thread.

Verification

Lint clean, 1133 tests across 90 suites, mirror in sync.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This PR restructures .claude/ into path-scoped rules and adds a generator that mirrors them into .github/instructions/ for Copilot. As the PR description notes, the actual code surface is scripts/sync-copilot-instructions.mjs plus its three test files (syncCopilotInstructions.test.ts, claudeRuleGlobs.test.ts, agentParity.test.ts) — everything else is markdown. I focused my review there.

Code quality

The sync script is genuinely careful work. Highlights:

  • readPaths/readGlob fail loudly on anything ambiguous (flow-sequence paths:, unterminated quotes, stray text after a quote) rather than silently widening a scoped rule to applyTo: "**". That's the right failure direction for a script whose whole job is preventing silent drift.
  • The frontmatter glob scan correctly stops at the next top-level key so a sibling tags: block can't leak into applyTo — this was a real bug caught during the PR's own review cycle and is now regression-tested.
  • readTitle skips fenced code blocks so a rule opening with a shell example doesn't donate a # comment as its description.
  • Good instinct diagnosing the "gates nothing" problem in the new copilot-sync CI job yourselves — worth acting on before merge, since a check nobody requires is exactly the kind of drift this PR is trying to eliminate.

Potential issues (minor, non-blocking)

  1. mkdirSync(INSTRUCTIONS_DIR, { recursive: true }) runs unconditionally in main(), even under --check (scripts/sync-copilot-instructions.mjs, in main()). A --check run is documented as read-only, but it still creates the output directory as a side effect if it doesn't exist. Harmless in this repo (the directory is already committed), but if you ever move/rename INSTRUCTIONS_DIR, a stale --check run in CI could silently create an empty directory. Consider only creating the directory in the non-check branch, right before the first write.
  2. expandBraces doesn't handle nested brace groups (e.g. a/*.{ts,{jsx,tsx}}) — it works correctly for the flat groups actually used in this PR's rules, and claudeRuleGlobs.test.ts would already catch a resulting unmatched pattern, so this is very low risk. Just noting it in case a future rule tries a nested group and gets a confusing recursive expansion instead of a clear error.
  3. The debugger.md agent's "Finishing" section states no-console is enforced by lint — I wasn't able to verify a no-console rule is actually configured (project's eslint.config.ts only overrides style/brace-style and unused-imports/no-unused-vars on top of @antfu/eslint-config, and node_modules wasn't available in my environment to check the base preset's defaults). Worth a quick double-check that this claim is accurate before it becomes agent-context gospel.

Bugs

None found in the current state. The PR history shows five real bugs in the sync script (non-recursive directory listing, flow-sequence paths: widening scope, un-scoped glob leaking from a sibling key, quote-stripping mangling a trailing comment, and fence-blind title extraction) — all fixed with regression tests that were verified to fail against the buggy commit. That's a solid bar and I didn't find anything in that class surviving.

Test coverage

Strong. syncCopilotInstructions.test.ts drives the script end-to-end as a CLI against fixture trees (good choice — avoids testing implementation details), covers nested rules, malformed frontmatter, --check idempotency, and confirms --check doesn't write files. claudeRuleGlobs.test.ts cleverly validates the generated globs against git ls-files rather than re-parsing the rules, so it can't duplicate the generator's own bugs. agentParity.test.ts guards the one thing that can't be auto-generated (agent frontmatter differs between tools) by checking for drift instead.

One gap: I didn't see a test asserting the content mirroring end-to-end against the real .claude/rules/ + .github/instructions/ — i.e., that npm run sync:copilot:check passes against the actual repo state as committed. The new copilot-sync CI job covers this at merge/push time, but a local unit test would catch a rule edited without regenerating before it even reaches CI. Not blocking, since CI does catch it, just a thought for defense in depth.

Performance / security

No concerns. The script only touches Node built-ins, runs against small markdown files, and doesn't execute or eval any file content. Frontmatter parsing is hand-rolled but scoped (fails closed on ambiguity rather than guessing), which is the right tradeoff for a security-adjacent parsing task like this.

Overall

This is a well-executed, thoroughly self-reviewed change — the commit history shows a real debug-first process (confirm the bug before fixing it, add a regression test that fails on the old code) that matches the very principles this PR is trying to encode into the rules. The two non-blocking nits above (--check mkdir side effect, nested-brace edge case) are worth a look but don't need to hold up merging.

Addresses review feedback on #652.

`--check` is documented as read-only but called `mkdirSync` on the output
directory before branching, so a check run created a directory. The call was
also redundant: `listMarkdown` already tolerates a missing directory, and each
write creates its own parent. Removed, with a test asserting a check run leaves
no directory behind.

`expandBraces` handles nested groups correctly — `{ts,{jsx,tsx}}` does expand —
but repeated a pattern, since both branches of the outer group produce `ts`.
Harmless in `applyTo`, and no rule uses a nested group today, but the output is
tidier deduplicated and the behaviour is now pinned by a test.

Both cases fail against the previous commit.

Also make the `no-console` claim precise. A reviewer could not verify it, so I
checked by linting a file with a `console.log`: the rule is real, and it allows
`console.warn` and `console.error`. The skill and the debugger agent said only
that it "is enforced", which risks reading as a ban on deliberate error
reporting — they now name the exemptions.

Finally, assert the committed mirror is in sync from the test suite as well as
the CI job, so a rule edited without regenerating fails on `npm test` rather
than after a push.

Verified: lint clean, type-check clean, 1136 tests across 90 suites.

Copy link
Copy Markdown
Member Author

All four addressed in 1061d05. Two needed correcting, one needed measuring, one you diagnosed slightly differently than it turned out.

1. mkdirSync under --check — real, fixed

You're right that a documented read-only mode shouldn't write. It was also redundant: listMarkdown already tolerates a missing directory, and each write creates its own parent. So the call was pure side effect and is simply gone, with a test asserting a check run leaves no directory behind.

2. Nested braces — measured, and it's a duplicate rather than a failure

Checked before changing anything, and the behaviour is slightly better than described:

a/*.{ts,tsx}        -> ["a/*.ts","a/*.tsx"]
a/*.{ts,{jsx,tsx}}  -> ["a/*.ts","a/*.jsx","a/*.ts","a/*.tsx"]
{a,b}/{c,d}/*.ts    -> ["a/c/*.ts","a/d/*.ts","b/c/*.ts","b/d/*.ts"]

Nesting does expand — the regex \{([^{}]*)\} matches the innermost group, so recursion unwinds correctly. What it produces is a duplicate (a/*.ts twice, once from each branch of the outer group), not a confusing expansion. Harmless in applyTo, but noise, so it's deduplicated now and pinned by a test.

Worth noting because it changes the risk: there was never a case here that would silently mis-scope a rule.

3. no-console — verified rather than assumed, and the claim was imprecise

Good instinct to flag an unverifiable claim in agent context; that's exactly the stale-docs problem this PR exists to fix. I could check, since I have dependencies installed. Dropped a probe file into src/:

$ npx eslint src/zz-probe.ts
  2:3  error  Unexpected console statement. Only these console methods are allowed: warn, error  no-console

So the rule is real — but the wording was imprecise in a way that could mislead. "no-console is enforced by lint" reads as a blanket ban, while console.warn and console.error are allowed, and rules/typescript.md elsewhere tells people to log errors with context. An agent could reasonably have concluded it should strip a legitimate console.error from a catch block. Both the skill and the debugger agent now name the exemptions.

4. No local test that the real repo is in sync

Fair, and cheap to close. claudeRuleGlobs.test.ts now runs the generator with --check against the actual repo, so a rule edited without regenerating fails on npm test instead of after a push. The CI job still backstops it; this just shortens the loop.

On copilot-sync gating nothing

Flagging again since you raised it: within ci.yml no job has needs: on it, which is deliberate. Making it a required status check is branch-protection config on main — outside this diff, and not something I'll change as a side effect. I'd recommend adding it, for the reason you give.

Verification

Both new cases fail against be8ac4b and pass here. Lint clean, type-check clean, 1136 tests across 90 suites.


Generated by Claude Code

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review

This is a well-executed restructuring of .claude/ and the generated GitHub Copilot mirror. No src/ application code is touched, so I focused on the sync tooling, its test coverage, and consistency across the .claude/ and .github/ trees. Overall this is high quality — the generator is defensive by design (fails loudly instead of silently widening scope), and the test suite specifically pins two real regressions the author hit during development. A few notes below, nothing blocking.

Verified consistent (no action needed)

  • .claude/agents/ and .github/agents/ both end up with exactly 8 matching roles (architect, code-reviewer, debugger, feature-builder, implementer, planner, test-runner, accessibility-expert) — agentParity.test.ts will pass.
  • The one true orphan (style-guide.instructions.md, superseded by typescript.instructions.md) is already deleted in this PR rather than left for CI to complain about.
  • Cross-references in .github/agents/*.agent.md and .github/prompts/*.prompt.md were correctly updated to relative paths (../copilot-instructions.md instead of .github/copilot-instructions.md, which would have resolved to a nonexistent nested path from inside .github/agents/) — nice catch.
  • scripts/sync-copilot-instructions.mjs is careful in the right places: it errors on unparsable paths: (flow sequences, empty lists, unterminated quotes) rather than silently falling back to applyTo: "**", which is exactly the kind of silent-widening bug that's easy to ship in a generator like this.

Minor observations

  1. feature-builder.md's tools: frontmatter lost its enforced allowlist.
    tools: Task(planner, architect, implementer, code-reviewer, test-runner, accessibility-expert) became tools: Agent, Read, Grep, Glob, Bash, with the restriction to "these six" moved into prose in the body. If the old parenthetical syntax was actually enforced by the platform (vs. just documentation), this trades a hard constraint for a soft one that the agent could drift from under context pressure. Worth double-checking whether Agent supports the same restriction syntax Task did — if not, this is an unavoidable consequence of the tool rename rather than a regression.

  2. Orphaned generated files are never auto-deleted, only warned about (sync-copilot-instructions.mjs main()). This is a deliberate, documented safety choice (avoid a script silently deleting the wrong file), and the README calls it out — just flagging that sync:copilot:check will keep failing in CI indefinitely if someone renames/removes a rule and forgets the manual delete step, until a human notices the CI failure and reads the message.

  3. claudeRuleGlobs.test.ts matches globs via git ls-files as a proxy for how Claude Code / Copilot actually match paths:/applyTo at runtime. The test comment is upfront that this is an approximation, not exact parity — reasonable trade-off given reimplementing both matchers would be worse, just noting it as a known gap rather than a bug.

  4. CI's new copilot-sync job reports drift but never blocks (per its own comment: "gates nothing"). That seems intentional so a docs-only sync failure doesn't block unrelated PRs, but it does mean a stale mirror can sit unnoticed unless someone is watching the Actions tab — might be worth a follow-up to make it required once the team is confident it's stable.

Test coverage

Strong — syncCopilotInstructions.test.ts and claudeRuleGlobs.test.ts cover the generator end-to-end (brace expansion incl. nested/deduped braces, nested rule directories, malformed frontmatter, --check read-only guarantees, orphan detection), and agentParity.test.ts guards the one thing that can't be generated (agent role parity) with cheap structural checks. The regression comments embedded in the tests (nested rules silently dropped, scoped rule silently widening to **) are a nice touch for future maintainers.

No security concerns — the generator only reads/writes within the repo tree it's pointed at (SYNC_COPILOT_ROOT is test-only), and no secrets or untrusted input are involved.

@jooyoungseo
jooyoungseo merged commit 207574b into main Jul 29, 2026
13 checks passed
@jooyoungseo
jooyoungseo deleted the claude/maidr-claude-config-structure-p4d4vx branch July 29, 2026 00:46
@xabilitylab

Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 3.75.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@xabilitylab xabilitylab added the released For issues/features released label Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released For issues/features released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants