Skip to content

chore: add missing skill symlinks and update AGENTS.md documentation in .agents/skills #1372

Description

@vivek7405

All line anchors below were verified against HEAD 00af2568 ("docs(skill): add explicit HTTP verb decision guide for server actions (#1369)") on 2026-08-10. Re-check an anchor before editing if HEAD has moved.

Problem

The framework repo keeps its Claude Code skills at .claude/skills/<name>/ and mirrors them into .agents/skills/<name> as relative symlinks so Antigravity, which reads <workspace-root>/.agents/skills/<folder>/SKILL.md, sees the same set. That mirror has drifted in two independent directions, and a third claim in the original report needed correcting.

Gap 1, missing symlinks (confirmed). git ls-files .claude/skills lists ten committed skills, each with a committed SKILL.md:

use-railway  webjs-blog-write  webjs-doc-sync  webjs-file-issue  webjs-instagram-post
webjs-list-todos  webjs-ready-for-dev  webjs-research-record  webjs-scaffold-sync  webjs-start-work

git ls-files -s .agents/skills lists only seven mode-120000 entries (use-railway, webjs-blog-write, webjs-doc-sync, webjs-file-issue, webjs-list-todos, webjs-research-record, webjs-start-work). Missing, exactly as reported, are webjs-ready-for-dev, webjs-scaffold-sync, and webjs-instagram-post. All three targets exist and carry a committed SKILL.md, so the symlinks will resolve the moment they are added.

Two entries under .agents/skills/ are legitimate non-symlinks and must survive untouched. .agents/skills/webjs/ is a real committed directory (the framework teaching skill, SKILL.md plus references/, 15 tracked files) that has no .claude/skills counterpart and is the copy source in scripts/sync-scaffold-skill.mjs:21. .agents/skills/omarchy is a machine-local absolute symlink into ~/.local/share, deliberately untracked via .gitignore:98, and test/repo-health/no-committed-symlinks.test.mjs exists to keep links of that shape out of the index.

Gap 2, the documentation list (confirmed, and the drift is a different set from gap 1). .agents/AGENTS.md:48-56 lists six skills under ## Custom Skills Usage, omitting four: webjs-blog-write, webjs-ready-for-dev, webjs-scaffold-sync, and webjs-instagram-post. Note webjs-blog-write already HAS a symlink and is simply undocumented, so the symlink set and the documented set have drifted apart independently. Six listed plus four missing is ten, which matches the committed skill count.

Correction to the original report, part 1 of 2. The original body treated .agents/hooks.json as an obvious missing mirror of .claude/settings.json. The file is real (Antigravity reads hooks.json from the workspace .agents/ directory, docs), so the premise is not invented, but the conclusion does not follow. See the design section: part 2 is rejected on the merits, and the sub-item about making .claude/hooks/*.sh dual-engine is rejected with it.

Correction to the original report, part 2 of 2, and a real defect the report missed. .agents/AGENTS.md is at a path nothing documents reading. Antigravity's rules reference states that "Workspace rules live in the .agents/rules folder of your workspace or git root" (docs), with GEMINI.md and a repo-root AGENTS.md as the other project-level surfaces. .agents/AGENTS.md is none of those. The repo already knows the correct path and says so in its own published prose: blog/ai-first-is-plumbing.md:25 and blog/stop-ai-agents-breaking-your-code.md:25 both label .agents/rules/workflow.md as the Antigravity workspace-rules file, root AGENTS.md:36 names .agents/rules/workflow.md as part of the single cross-agent source, and the scaffold ships exactly that path (packages/cli/templates/.agents/rules/workflow.md, copied by packages/cli/lib/create.js:575). The framework repo itself has no .agents/rules/ directory at all. A grep across the repo finds zero references to .agents/AGENTS.md from any test, script, workflow, or doc, so nothing depends on the current location. Adding four bullets to a file no tool loads would be a no-op, so the fix has to move the file as well as edit it.

Why this keeps reopening. Nothing asserts the three sets agree. test/hooks/route-skills.test.mjs:233-252 is the closest existing guard, and it only asserts that each project skill named inside .claude/hooks/route-skills.sh has a committed .claude/skills/<name>/SKILL.md. That test passes at HEAD, because the router already references all ten names, and it says nothing about .agents/skills or about the documented list. Without a guard, the eleventh skill reopens the same gap.

Design / approach

Three settled decisions.

1. Reject .agents/hooks.json and the scripts/sync-hooks.js generator

.agents/hooks.json is a genuine Antigravity surface, so the rejection is on cost and doctrine rather than on the file being fictional. Four reasons, in order of weight.

A mirror is a protocol port, not a field rename. The original sub-item framed it as "a jq fallback for .tool_input versus .toolCall.args". The two engines disagree on three axes, and the field paths are the least of them.

Axis Claude Code Antigravity
Block a tool call exit 2 with the reason on stderr stdout JSON {"decision":"deny","reason":"..."}, exit code is not the gate
Inject context hookSpecificOutput.additionalContext on UserPromptSubmit {"injectSteps":[{"ephemeralMessage":"..."}]} on PreInvocation
Config shape flat hooks map keyed by event named-hook map, {"<hook-name>": {"enabled": true, "PreToolUse": [...]}}
Edit-tool matcher Write|Edit|MultiEdit|NotebookEdit write_to_file|replace_file_content|multi_replace_file_content
Edited path .tool_input.file_path, .tool_input.notebook_path .toolCall.args.TargetFile
Edited text .tool_input.content, .new_string, .new_source, .edits[].new_string .CodeContent, .ReplacementContent, .ReplacementChunks[] (element shape undocumented)
Shell command .tool_input.command .toolCall.args.CommandLine

Nine of the eleven scripts in .claude/hooks/ block with exit 2 and would each need a second output path, and route-skills.sh would need its whole output rewritten rather than re-keyed. block-prose-punctuation.sh reads five distinct content fields (.claude/hooks/block-prose-punctuation.sh:43-48); its faithful Antigravity counterpart depends on the ReplacementChunks element shape, which the public docs do not specify, so part of the port cannot be written correctly from documentation alone.

A generated second copy of a rule source is what this repo argues against. Root AGENTS.md:36 states the doctrine outright: tools that do not read AGENTS.md natively get "a THIN bridge pointing at it ..., never a duplicated rule set", and the same line records that "Claude Code additionally ships the protective enforcement hooks (.claude/, .hooks/pre-commit)", so hooks being Claude-only here is a stated position rather than an oversight. A scripts/sync-hooks.js translator would need a hardcoded matcher, vocabulary, and protocol map, which means the map IS the configuration and generating a file from it removes no hand-written work. The output would also have to be committed, because Antigravity reads it off disk, which is precisely the committed-generated-duplicate the repo avoids elsewhere (the generated scaffold-skill bundle is gitignored at .gitignore:93 and produced at prepack by scripts/sync-scaffold-skill.mjs).

The repo moved the other way one commit ago. 241c961b (#1368, HEAD~1) deleted .cursor/hooks.json, .cursor/hooks/nudge-uncommitted.sh, .gemini/settings.json, .gemini/hooks/nudge-uncommitted.sh, .opencode/plugins/nudge-uncommitted.ts, GEMINI.md, .cursorrules, and .github/copilot-instructions.md from the scaffold templates, keeping .claude/, .hooks/pre-commit, AGENTS.md, and CLAUDE.md. Adding a per-agent hook mirror to the framework repo one commit later reverses a decision the project just made deliberately.

Prior art agrees. Next.js, Remix, Astro, Svelte, and TanStack Router all keep skills under .agents/skills/ (verified in the local clones at ~/Documents/Projects/frameworks/), and none of the five ships a hooks.json at any level. Next.js goes further and makes the mirror structural: next.js/.claude contains a single symlink, skills -> ../.agents/skills, so a per-skill link can never go missing.

What actually covers the underlying need, chosen alternative. Enforcement for non-Claude agents already exists at two tool-agnostic layers, and the correct fix is to make an Antigravity session aware of them rather than to build a parallel engine. .hooks/pre-commit fires for every agent and every human on every commit, blocking a direct commit to main/master and blocking a published-library version bump off a chore/release-* branch. Its own header at .hooks/pre-commit:12-13 records the division of labour: "The test suite is NOT run here. CI (.github/workflows/ci.yml) is the test gate", and .github/workflows/ci.yml runs conventions (webjs check plus webjs doctor over blog and website), unit, and the Bun matrix, with branch protection blocking a merge until they pass. The Claude PreToolUse gates are a faster local feedback loop over enforcement that already binds every agent, not the only enforcement. The residual gap is awareness, which is a prose fix and is in scope for a documentation issue, so the moved rules file gains a short "Enforcement gates" section naming those two layers, the never---no-verify rule, and the fact that the .claude/hooks/* pre-tool gates do not fire outside Claude Code so the session must self-check the same rules.

2. Move the Antigravity rules file to the documented path

.agents/AGENTS.md becomes .agents/rules/workflow.md, matching the documented Antigravity workspace-rules folder, the scaffold's own filename, and the path root AGENTS.md:36 already advertises. This is a git mv plus a contained content edit, not a rewrite. It cannot affect webjs create, because packages/cli/lib/create.js:575 resolves .agents/rules/workflow.md from TEMPLATES only, and the sole repo-root fallback in that file is for the skill directory (packages/cli/lib/create.js:658-664). Nothing else in the repo references either path.

One accepted consequence: .claude/hooks/require-docs-with-src.sh:73-74 counts a doc surface with the pattern (^|/)(AGENTS|CLAUDE|CONVENTIONS|README)\.md$, which .agents/AGENTS.md currently satisfies incidentally and .agents/rules/workflow.md will not. That gate only fires when packages/*/src or packages/cli/lib is staged, and staging the Antigravity rules file was never a sensible way to satisfy a framework-source doc requirement, so the loss is not worth widening the gate's regex for.

3. Add a parity guard, as a new sibling in test/repo-health/

Three symlinks went missing once and the documented list has drifted separately, so detection has to be automatic. The new test goes in test/repo-health/, not test/hooks/, because the invariant is about repo structure rather than hook behaviour, and because the closest precedent lives there: test/repo-health/no-committed-symlinks.test.mjs:31-55 already reads git ls-files -s, filters mode 120000, and resolves each link target with git cat-file -p. The new file reuses that exact technique. test/hooks/route-skills.test.mjs is left alone, since its subject is the router hook and it already passes.

Reading the sets from git ls-files rather than from fs.readdirSync handles the machine-local exception for free: .agents/skills/omarchy is untracked, so git never reports it and no name needs special-casing. .agents/skills/webjs needs one explicit allowance, because it is a tracked real directory with no .claude/skills counterpart.

The Next.js single-directory-symlink inversion is the structurally stronger design and is recorded under Out of scope with its blockers, rather than smuggled into a chore issue.

Implementation plan

Step 0. Cut the worktree

git worktree add -b chore/1372-agents-skill-parity ../webjs-1372-agents-skill-parity origin/main
cd ../webjs-1372-agents-skill-parity

Do all work there. Tracked-file edits in the primary checkout are blocked by .claude/hooks/require-worktree-for-edits.sh. A fresh worktree has no node_modules, so run npm run worktree:link from inside it before running any test.

Step 1. Add the three missing symlinks

The one mechanical trap is getting git to record mode 120000 instead of a regular file. ln -s plus git add does this correctly; writing a text file whose contents are the target path does not. Create them relative to .agents/skills/, so the stored target string is exactly ../../.claude/skills/<name>.

cd .agents/skills
ln -s ../../.claude/skills/webjs-ready-for-dev  webjs-ready-for-dev
ln -s ../../.claude/skills/webjs-scaffold-sync  webjs-scaffold-sync
ln -s ../../.claude/skills/webjs-instagram-post webjs-instagram-post
cd ../..
git add .agents/skills/webjs-ready-for-dev .agents/skills/webjs-scaffold-sync .agents/skills/webjs-instagram-post

Verify the mode and the resolution before committing. Every line must start with 120000, and every target must resolve to a readable SKILL.md:

git ls-files -s .agents/skills | grep -v '^100644'
# expect exactly 10 lines, all 120000

for n in $(git ls-files -s .agents/skills | awk '$1=="120000"{print $4}'); do
  test -f "$n/SKILL.md" && echo "ok $n" || echo "DANGLING $n"
done

Do not use an absolute target. test/repo-health/no-committed-symlinks.test.mjs fails any tracked symlink whose target is absolute or resolves outside the repo root.

Step 2. Move the Antigravity rules file

mkdir -p .agents/rules
git mv .agents/AGENTS.md .agents/rules/workflow.md

Then edit .agents/rules/workflow.md. Three contained changes.

2a. Retitle and correct the provenance sentence. Lines 1 and 3 read today:

# Translated Claude Code rules for Antigravity

These project-level rules are translated from the user's global Claude configuration (`~/.claude/CLAUDE.md` and `~/.claude/hooks/*`). They govern all operations inside this workspace.

They should read:

# Workspace rules for agents that read `.agents/rules/`

These project-level rules govern all operations inside this workspace. Antigravity loads every markdown file in `.agents/rules/`; the repo-root `AGENTS.md` remains the full contract and this file does not replace it.

2b. Replace the six-skill list with all ten. Lines 48-56 read today:

## Custom Skills Usage
- The following skills have been translated and are available in `.agents/skills/`:
  - `webjs-start-work`: Trigger when starting a tracked issue on the webjs project board.
  - `webjs-doc-sync`: Trigger when documenting new public surfaces or finding doc gaps/drift.
  - `webjs-file-issue`: Trigger to file a grounded issue on the board.
  - `webjs-list-todos`: Trigger to check the list of TODOs.
  - `webjs-research-record`: Trigger to search and record findings.
  - `use-railway`: Trigger when interacting with Railway deploys.
- Always use the `view_file` tool on the matched skill's `SKILL.md` before executing its tasks.

They should read:

## Custom Skills Usage
- These skills are symlinked into `.agents/skills/` from `.claude/skills/`, so both engines load one copy. Every entry here must have a matching symlink, and every symlink must have an entry here; `test/repo-health/agent-skill-parity.test.mjs` enforces both directions.
  - `webjs-start-work`: Trigger when starting a tracked issue on the webjs project board.
  - `webjs-ready-for-dev`: Trigger when planning tracked issues into an implementable shape with verified plans.
  - `webjs-file-issue`: Trigger to file a grounded issue on the board.
  - `webjs-list-todos`: Trigger to check the list of TODOs.
  - `webjs-research-record`: Trigger to search and record findings.
  - `webjs-doc-sync`: Trigger when documenting new public surfaces or finding doc gaps or drift.
  - `webjs-scaffold-sync`: Trigger when changing the CLI generators, the scaffold templates, or the agent teaching skill.
  - `webjs-blog-write`: Trigger when writing, drafting, or editing a WebJs blog post under `blog/`.
  - `webjs-instagram-post`: Trigger when publishing an SEO post to the WebJs Instagram account.
  - `use-railway`: Trigger when interacting with Railway deploys.
- The framework teaching skill at `.agents/skills/webjs/` is a real directory rather than a symlink, and is the reference for building WebJs apps rather than a workflow trigger.
- Always use the `view_file` tool on the matched skill's `SKILL.md` before executing its tasks.

Keep the bullet order stable: it groups the workflow skills, then the authoring skills, then infrastructure.

2c. Append an enforcement section at the end of the file, after the skills section. This is the substantive answer to the rejected hooks mirror:

## Enforcement gates

The protective `PreToolUse` hooks under `.claude/hooks/` fire only inside Claude Code. Two gates bind every agent regardless of engine, and neither is optional.

- `.hooks/pre-commit` runs on every commit. It blocks a direct commit to `main` or `master` and blocks a published-library version bump on any branch that is not `chore/release-*`. Never pass `git commit --no-verify`.
- `.github/workflows/ci.yml` is the test gate (`conventions`, `unit`, and the Bun matrix), and branch protection blocks a merge until it is green. `.hooks/pre-commit` deliberately does not run the suite because CI does.

Because the pre-tool gates do not fire here, self-check what they would have caught before every commit: stage tests alongside any `packages/*/src` change, stage the doc surfaces that change with it, keep the work inside its own worktree, and obey invariant 11 on prose punctuation and `WebJs` brand casing.

Step 3. Add the parity guard

New file, test/repo-health/agent-skill-parity.test.mjs. It is picked up automatically: scripts/run-node-tests.js walks test/ recursively for .test.mjs. This body was run against HEAD from a scratch directory and reds on exactly the two real gaps, passing its other four assertions.

/**
 * The Claude skill set, the Antigravity symlink set, and the documented list
 * must agree.
 *
 * Skills live once at `.claude/skills/<name>/SKILL.md` and are mirrored into
 * `.agents/skills/<name>` as relative symlinks, because Antigravity reads
 * `<workspace-root>/.agents/skills/<folder>/SKILL.md`. Nothing kept the two in
 * step, so three skills shipped with no symlink and a fourth was symlinked but
 * never documented (#1372). Every new skill reopens that gap, so assert it.
 *
 * Both sets are read from `git ls-files`, not from the filesystem, so the
 * machine-local `.agents/skills/omarchy` link (untracked, .gitignore:98) is
 * invisible by construction and needs no special case. The one tracked
 * exception is `.agents/skills/webjs/`, a real directory holding the framework
 * teaching skill, which has no `.claude/skills` counterpart.
 */
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const RULES_DOC = '.agents/rules/workflow.md';
/** Tracked entries under .agents/skills/ that are directories, not mirrors. */
const REAL_DIRS = new Set(['webjs']);

const git = (...args) => execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8' });

/** Skill names with a committed `.claude/skills/<name>/SKILL.md`. */
function claudeSkillNames() {
  return [
    ...new Set(
      git('ls-files', '.claude/skills')
        .split('\n')
        .filter((p) => p.endsWith('/SKILL.md'))
        .map((p) => p.split('/')[2]),
    ),
  ].sort();
}

/** Map of `.agents/skills/<name>` symlink name to its stored target string. */
function agentsSkillLinks() {
  const map = new Map();
  for (const line of git('ls-files', '-s', '.agents/skills').split('\n')) {
    // Mode 120000 is git's symlink mode; the blob content IS the target string.
    if (!line.startsWith('120000 ')) continue;
    const path = line.split('\t').slice(1).join('\t');
    map.set(path.slice('.agents/skills/'.length), git('cat-file', '-p', `HEAD:${path}`).trim());
  }
  return map;
}

test('every committed .claude skill is mirrored into .agents/skills', () => {
  const links = agentsSkillLinks();
  const missing = claudeSkillNames().filter((n) => !links.has(n));
  assert.deepEqual(
    missing,
    [],
    `no .agents/skills symlink for: ${missing.join(', ')}. ` +
      `Add it with: cd .agents/skills && ln -s ../../.claude/skills/<name> <name>`,
  );
});

test('every .agents/skills symlink is relative and names its own skill', () => {
  const bad = [];
  for (const [name, target] of agentsSkillLinks()) {
    const want = `../../.claude/skills/${name}`;
    if (target !== want) bad.push(`${name} -> ${target} (expected ${want})`);
  }
  assert.deepEqual(bad, [], `a mirror symlink has the wrong target:\n  ${bad.join('\n  ')}`);
});

test('no .agents/skills symlink dangles', () => {
  const tracked = new Set(git('ls-files', '.claude/skills').split('\n').filter(Boolean));
  const dangling = [...agentsSkillLinks().keys()].filter(
    (n) => !tracked.has(`.claude/skills/${n}/SKILL.md`),
  );
  assert.deepEqual(dangling, [], `symlink with no committed target: ${dangling.join(', ')}`);
});

test('the workspace rules file documents exactly the mirrored skills', () => {
  const section = readFileSync(resolve(repoRoot, RULES_DOC), 'utf8').split(
    '## Custom Skills Usage',
  )[1];
  assert.ok(section, `${RULES_DOC} must keep its "## Custom Skills Usage" section`);
  const listed = new Set([...section.matchAll(/^\s*-\s+`([a-z0-9-]+)`:/gm)].map((m) => m[1]));
  const linked = new Set(agentsSkillLinks().keys());
  const undocumented = [...linked].filter((n) => !listed.has(n)).sort();
  const phantom = [...listed].filter((n) => !linked.has(n) && !REAL_DIRS.has(n)).sort();
  assert.deepEqual(undocumented, [], `symlinked but not listed in ${RULES_DOC}: ${undocumented.join(', ')}`);
  assert.deepEqual(phantom, [], `listed in ${RULES_DOC} but not symlinked: ${phantom.join(', ')}`);
});

test('the framework teaching skill stays a real committed directory', () => {
  const modes = git('ls-files', '-s', '.agents/skills/webjs')
    .split('\n')
    .filter(Boolean)
    .map((l) => l.split(' ')[0]);
  assert.ok(modes.length > 0, '.agents/skills/webjs must stay committed');
  assert.ok(
    !modes.includes('120000'),
    '.agents/skills/webjs is the canonical teaching skill and must not become a symlink',
  );
});

test('the machine-local omarchy link stays untracked', () => {
  assert.equal(
    git('ls-files', '.agents/skills/omarchy').trim(),
    '',
    'omarchy is a machine-local absolute symlink and must stay untracked (.gitignore:98)',
  );
});

Step 4. Document the repo's own agent-config layout

Add a section to framework-dev.md, after ### Merged worktrees are auto-removed (cleanup-merged-worktree.sh) at line 125 and before ### Scaffold teaching-coverage gate at line 135, so the two agent-tooling sections sit together:

### Agent config in this repo: `.claude/` is canonical, `.agents/` mirrors it

Workflow skills live once at `.claude/skills/<name>/SKILL.md`. Antigravity reads
`<workspace-root>/.agents/skills/<folder>/SKILL.md`, so each one is mirrored as a
RELATIVE symlink `.agents/skills/<name> -> ../../.claude/skills/<name>`. Adding a
skill means adding the directory, the symlink, and a bullet in
`.agents/rules/workflow.md`; `test/repo-health/agent-skill-parity.test.mjs`
fails when the three drift apart. Create the link with `ln -s` so git records
mode `120000`, and keep the target relative, or
`test/repo-health/no-committed-symlinks.test.mjs` rejects it.

Two entries under `.agents/skills/` are not mirrors. `webjs/` is the real
committed teaching skill (`scripts/sync-scaffold-skill.mjs` bundles it into the
CLI at prepack), and `omarchy` is a machine-local absolute symlink kept
untracked by `.gitignore:98`.

Lifecycle HOOKS stay Claude-only on purpose. Antigravity supports a workspace
`.agents/hooks.json`, but its blocking protocol (stdout `{"decision":"deny"}`),
its context-injection shape (`injectSteps`), and its tool vocabulary all differ
from Claude Code's, so a mirror is a protocol port rather than a config copy, and
a generated copy would be the duplicated rule set root `AGENTS.md` rules out. The
gates that bind every agent are `.hooks/pre-commit` and CI (#1372).

Step 5. Commit and push

One logical unit, so one commit:

chore: mirror every Claude skill into .agents and guard the parity

Three skills had no .agents/skills symlink and a fourth was symlinked but
undocumented, so Antigravity saw a different skill set from Claude Code.
Move the workspace rules to the path Antigravity actually reads, list all
ten skills there, and add a repo-health test so the sets cannot drift again.

Then git push -u origin chore/1372-agents-skill-parity, open the PR as a draft with Closes #1372 in the body, and use a conventional chore: title so the squash subject feeds the changelog backfill correctly.

Tests

Unit, test/repo-health/, the only applicable layer. New file test/repo-health/agent-skill-parity.test.mjs, as written in step 3, following the sibling naming in that directory (no-committed-symlinks.test.mjs, gitignore-webjs-depth.test.mjs). Run it with:

node --test test/repo-health/agent-skill-parity.test.mjs test/repo-health/no-committed-symlinks.test.mjs test/hooks/route-skills.test.mjs

Then run the full suite once before pushing, since scripts/run-node-tests.js discovers the new file automatically:

npm test

Counterfactual, verified. The prototype of this test was executed against HEAD 00af2568 from a scratch directory. Four assertions passed and exactly two failed, naming the two real gaps:

AssertionError: missing .agents/skills symlinks: webjs-instagram-post, webjs-ready-for-dev, webjs-scaffold-sync
AssertionError: symlinked but undocumented: webjs-blog-write

After the fix, reverting any single piece must red it. Deleting one symlink fails the first assertion, pointing the target at an absolute path or at the wrong skill fails the second, removing a bullet from ## Custom Skills Usage fails the fourth, and adding a bullet for a skill with no symlink fails it from the other direction.

Layers that do NOT apply, and why. This change touches no framework runtime code. Nothing under packages/*/src or packages/cli/lib is staged, no export, route, or component changes, and no served byte differs.

  • Browser (npm run test:browser) does not apply: no hydration, DOM, slot, client-router, or custom-element behaviour is involved.
  • e2e (WEBJS_E2E=1) does not apply: no request path, navigation, or streaming behaviour changes.
  • Smoke (test/examples/*/smoke/*) does not apply: the in-repo apps are untouched and their boot path is unchanged.
  • Bun parity (test/bun/**) does not apply: none of the runtime-sensitive surfaces is touched (the serializer, the listener and request path, SSR, action, or CSRF dispatch, streams, node:crypto, the TS stripper, auth, session, or cors). .claude/hooks/require-bun-parity-with-runtime-src.sh:61 gates on staged packages/*/src or packages/cli/lib, so it will not fire and WEBJS_BUN_VERIFIED=1 is not needed.

Docs

  • .agents/rules/workflow.md (moved from .agents/AGENTS.md, step 2). Retitled, provenance sentence corrected, ## Custom Skills Usage grown from six entries to ten with a note on the enforced parity, and a new ## Enforcement gates section covering .hooks/pre-commit plus CI.
  • framework-dev.md (step 4). New section between lines 125 and 135 recording the canonical-plus-mirror layout, the ln -s mode-120000 requirement, the two non-mirror entries, and the settled reason hooks stay Claude-only.
  • Root AGENTS.md needs no change. Line 36 describes what the SCAFFOLD ships, not this repo's own .agents/ layout, and it already names .agents/rules/workflow.md, which this change brings the framework repo into line with rather than contradicting.
  • The docs site, the marketing website, README.md, CONVENTIONS.md, and the scaffold templates need no change. None of them describes the framework repo's internal agent-config mirror, and webjs create output is byte-identical (packages/cli/lib/create.js:575 resolves the workflow rules from TEMPLATES, and its only repo-root fallback, at lines 658-664, is for .agents/skills/webjs).
  • No escape hatch is needed. WEBJS_NO_DOC_GATE=1 must NOT be used. .claude/hooks/require-docs-with-src.sh:59-60 exits 0 unless the staged diff matches ^packages/([^/]+/src|editors/[^/]+/src|cli/lib)/, and this change stages only .agents/**, test/**, and framework-dev.md, so the gate never fires. The same is true of require-tests-with-src.sh:59, require-scaffold-with-src.sh:78, and require-bun-parity-with-runtime-src.sh:61.

Acceptance criteria

  • git ls-files -s .agents/skills shows exactly ten mode-120000 entries, one per committed .claude/skills/<name>.
  • Each new symlink stores the relative target ../../.claude/skills/<name>, and test -f .agents/skills/<name>/SKILL.md succeeds for all ten.
  • .agents/skills/webjs/ is still a tracked real directory with its 15 files, and .agents/skills/omarchy is still untracked.
  • .agents/AGENTS.md no longer exists; .agents/rules/workflow.md does, with git recording a rename.
  • .agents/rules/workflow.md lists all ten skills under ## Custom Skills Usage and carries the ## Enforcement gates section.
  • framework-dev.md documents the mirror layout, the ln -s requirement, the two non-mirror entries, and the Claude-only hooks decision.
  • node --test test/repo-health/agent-skill-parity.test.mjs passes, and reverting any single piece of the change reds it.
  • test/repo-health/no-committed-symlinks.test.mjs and test/hooks/route-skills.test.mjs still pass.
  • npm test is green, and CI is green on the PR.
  • No .agents/hooks.json and no scripts/sync-hooks.js were added, and no file under .claude/hooks/ was modified.

Out of scope

  • .agents/hooks.json and scripts/sync-hooks.js. Rejected on the merits above. Do not add either file.
  • Making .claude/hooks/*.sh dual-engine. Rejected with the hooks mirror, since a dual-engine script has no second engine to serve once there is no .agents/hooks.json. The described change (a jq fallback from .tool_input to .toolCall.args) would also be insufficient on its own, because nine of the eleven scripts block with exit 2 while Antigravity blocks on stdout JSON. Leave every file under .claude/hooks/ untouched.
  • Flipping to the Next.js layout (skills canonical under .agents/skills/, with a single .claude/skills -> ../.agents/skills directory symlink). Structurally stronger, since it makes a missing per-skill link impossible rather than merely detectable, but it means relocating ten committed skill directories, it pulls .agents/skills/webjs/ and the machine-local omarchy link into the Claude skill namespace, and it depends on the maintainer's per-skill ~/.claude/skills/<name> links, which this repo cannot verify. The parity test delivers the detection now at a fraction of the risk.
  • The rules file duplicating global rules. .agents/rules/workflow.md restates commit cadence, branch guards, and worktree isolation that root AGENTS.md also carries. That overlap predates this issue and de-duplicating it is a content decision about what a non-Claude agent needs loaded when it may not read the root contract, which is a different question from whether the skill list is correct. Change only the sections named in step 2.
  • Stale scaffold prose left by feat(cli): remove redundant per-agent instruction files from webjs create templates #1368. Root AGENTS.md:36 still names GEMINI.md and .github/copilot-instructions.md as bridges the scaffold ships, and three blog posts still list .cursorrules and per-agent hook files, all of which 241c961b removed from the templates. Real drift, but it is scaffold documentation rather than the .agents/ mirror, and correcting it means touching published blog posts. Do not fold it in.
  • Adding, renaming, or editing any skill's content under .claude/skills/ or .agents/skills/webjs/. This change moves no skill text.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions