Skip to content

feat(cli): mirror skills to .agents/skills/ when .agents/ exists#2488

Open
ArgonarioD wants to merge 6 commits into
abhigyanpatwari:mainfrom
ArgonarioD:main
Open

feat(cli): mirror skills to .agents/skills/ when .agents/ exists#2488
ArgonarioD wants to merge 6 commits into
abhigyanpatwari:mainfrom
ArgonarioD:main

Conversation

@ArgonarioD

Copy link
Copy Markdown

Some agents prefer repo-local .agents/skills/ over the global install. When .agents/ is present, mirror the standard and generated skills written to .claude/skills/ so those agents serve up-to-date copies. Opt-in via .agents/; absent directory leaves the layout untouched.

Summary

analyze writes skills to .claude/skills/. Some agents instead read skills from a repo-local .agents/skills/ directory and prefer it over the global ~/.agents/skills/ install. This PR mirrors the same SKILL.md files into .agents/skills/ when the repo already contains an .agents/ directory, so those agents serve the up-to-date repo-specific skills instead of stale global copies.

Motivation / context

setup already installs the bundled GitNexus skills globally to ~/.agents/skills/ for Codex (editor-targets.ts). But analyze generates repo-specific skills (community skills from --skills, plus the standard skill set) only under .claude/skills/. Agents that resolve skills from a repo-local .agents/skills/ never see those repo-specific skills and keep serving the stale global set. Mirroring — gated on the presence of .agents/ — closes that gap without changing behavior for repos that haven't opted in.

No linked issue; surfaced while investigating where analyze writes repo-level skills.

Areas touched

  • gitnexus/ (CLI / core / MCP server)
  • gitnexus-web/ (Vite / React UI)
  • .github/ (workflows, actions)
  • eval/ or other tooling
  • Docs / agent config only (AGENTS.md, CLAUDE.md, .cursor/, llms.txt, etc.)

Scope & constraints

In scope

  • gitnexus/src/cli/ai-context.ts — installSkills mirrors the 6 standard skills to .agents/skills/gitnexus/ when .agents/ exists; new exported shouldMirrorSkillsToAgents(repoPath) gate.
  • gitnexus/src/cli/skill-gen.ts — generateSkillFiles mirrors generated community skills to .agents/skills/generated/, clearing the mirror dir first to avoid stale entries.
  • generateAIContextFiles reports the mirror in its files list.

Explicitly out of scope / not done here

  • No change to setup / editor-targets.ts — the global ~/.agents/skills/ install path is untouched.
  • No change to AGENTS.md / CLAUDE.md generation content; the skill-reference table still points at .claude/skills/... paths.
  • Mirroring is not configurable via flag — it is purely opt-in via the presence of .agents/.

Implementation notes

  • Opt-in gate: shouldMirrorSkillsToAgents does a single fs.stat on /.agents; absent or not-a-directory returns false and the layout is left exactly as before (only .claude/skills/ is written).
  • Mirror writes are best-effort and per-skill: a failed mirror copy logs a warning and continues, matching the existing per-skip tolerance of installSkills — it never fails the whole analyze run.
  • skill-gen.ts clears .agents/skills/generated/ before writing the fresh set, keeping it in lockstep with .claude/skills/generated/ so renamed/removed communities don't accumulate stale skill dirs.
  • Comments and user-facing strings deliberately say "agents that read repo-local skills" rather than naming any specific agent.

Testing & verification

  • cd gitnexus && npx tsc --noEmit — clean, no errors
  • cd gitnexus && npm test — 480 test files, 9870 passed / 30 skipped
  • cd gitnexus && npm run test:integration — 204 test files, 4230 passed / 19 skipped / 1 failed
    • The single failure is test/integration/swift-scope-capture-tripwire.test.ts (a Swift tree-sitter parser performance timing tripwire: asserts per-entity cost ratio 250→1000 entities stays < 8×; it measured 8.09×, ~1% over). It does not import or exercise any code in this PR (it tests core parser scaling). Re-running it standalone passed (2/2), confirming it is flaky timing noise on a loaded machine, not a regression.
  • Manual / local verification — built locally and ran gitnexus analyze (with and without --skills) against a separate local repo containing an .agents/ directory; confirmed the standard skills mirror to .agents/skills/gitnexus/ and community skills mirror to .agents/skills/generated/, with mirrored content byte-identical to the .claude/skills/ copies. Repos without .agents/ are unchanged (only .claude/skills/ written).
  • cd gitnexus-web && npm test — N/A (no web changes)
  • cd gitnexus-web && npx tsc -b --noEmit — N/A (no web changes)

Risk & rollout

  • Low risk. Behavior is strictly additive and gated on .agents/ existing; repos without .agents/ are byte-for-byte unchanged.
  • No breaking changes, no migration, no index refresh required. Existing indexes pick up the mirror on the next gitnexus analyze.
  • Release note: "Skills generated by analyze are now mirrored to .agents/skills/ when the repo contains an .agents/ directory, for agents that read repo-local skills."

Checklist

  • PR body meets repo minimum length (workflow may label short descriptions)
  • If AGENTS.md / overlays changed: headers, scope block, and changelog updated per project conventions — N/A, no AGENTS.md/overlay changes
  • No secrets, tokens, or machine-specific paths committed

Some agents prefer repo-local .agents/skills/ over the global install. When
.agents/ is present, mirror the standard and generated skills written to
.claude/skills/ so those agents serve up-to-date copies. Opt-in via .agents/;
absent directory leaves the layout untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

@ArgonarioD is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@magyargergo

Copy link
Copy Markdown
Collaborator

Thank you for your contribution! We appreciate the time and effort you put into this but there's one ready PR in the queue already for this #2434

@ArgonarioD

Copy link
Copy Markdown
Author

Thank you for your contribution! We appreciate the time and effort you put into this but there's one ready PR in the queue already for this #2434

@magyargergo Thanks for pointing me to #2434. I've read through it — it flattens .claude/skills/ (standard skills as direct children, community skills under a gitnexus-area-* namespace), which fixes discoverability for Claude Code. That covers the Claude side.

My PR targets a different gap #2434 doesn't touch: .agents/. Concretely, the current Codex app asks users to migrate repository-level skills from .claude/ into .agents/, then reads only .agents/ afterward. So when gitnexus analyze updates skills under .claude/skills/ (including #2434's new community skills), Codex never picks up those updates — it keeps serving the migrated stale copies. Mirroring the writes to .agents/skills/ on every analyze is what keeps Codex in sync.

#2434 has zero references to .agents/, so this isn't covered. Is the .agents/ mirroring intentionally out of scope, or should it land as a follow-up?

@magyargergo magyargergo reopened this Jul 16, 2026
# Conflicts:
#	gitnexus/src/cli/ai-context.ts
#	gitnexus/src/cli/skill-gen.ts
@ArgonarioD
ArgonarioD requested a review from azizur100389 as a code owner July 17, 2026 03:35

@magyargergo magyargergo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: mirror skills to .agents/skills/ when .agents/ exists

Reviewed at head 7ba5483f against merge-base 91955e65, in a dedicated worktree with a fresh GitNexus index. Evidence: full diff read; detect_changes (exactly the 5 changed symbols, zero affected execution flows); tsc --noEmit clean; all 67 tests in the two touched files pass locally; CI green (fork-level Vercel auth noise aside); a combined domain + cross-cutting expert pass; and the headline finding reproduced empirically through this repo's own test harness.

The feature itself is sound and well-scoped: the opt-in gate is a single stat, canonical writes always precede mirror writes, the gitnexus-area-* mirror cleanup is correctly namespace-scoped (standard and user-authored skills survive it, symmetric with the canonical cleanup), the mirrored content is byte-identical and contains no .claude-specific references, and the skill-gen→ai-context import is safe. Flag interplay (--skills / --skip-skills / --index-only) produces no state where the mirror is written but canonical is skipped.

Two defects need fixing before merge, both cheap:

Empirical repro for the headline finding

With .agents/ present but .agents/skills occupied by a file (harness-driven, this repo's own test helpers):

threw:            Error: EEXIST: file already exists, mkdir '…/.agents/skills'
canonicalExists:  false   ← zero .claude/skills/gitnexus-area-* written

And because the canonical gitnexus-area-* cleanup at skill-gen.ts:89-98 runs before the throwing mirror mkdir, a rerun in that state deletes previously generated canonical skills and writes nothing back — all swallowed silently by analyze.ts's pre-existing catch { /* best-effort */ }. This contradicts the PR body's own contract ("Mirror writes are best-effort and per-skill: a failed mirror copy logs a warning and continues"), which the ai-context.ts half honors and the skill-gen.ts half does not. Details inline.

Findings

  • MEDIUM (inline, skill-gen.ts:163) — unwrapped mirror mkdir/writeFile silently abort canonical community-skill generation and destroy prior output
  • MEDIUM (inline, ai-context.ts:392) — .agents missing from the up-to-date fast-path dirty-check exclusion list; repos with a tracked .agents/ lose the fast path permanently
  • LOW (inline, ai-context.ts:372) — the gate fires on .agents/ directories that exist for non-skill purposes (e.g. Codex plugin marketplaces — this repo itself carries .agents/plugins/marketplace.json)
  • LOW — docs/help staleness and PR-body drift: README.md:184 ("6 agent skills installed to .claude/skills/"), README.md:392/:448 and the --skip-skills help text (src/cli/index.ts:93-94) all omit the mirror (--skip-skills now also suppresses the standard-skill mirror; --skills community skills also land in .agents/skills/). The PR body also describes a nested .agents/skills/gitnexus/ + generated/ layout and manual verification of those paths — the shipped code writes the flat .agents/skills/<name>/ layout and clears the gitnexus-area-* namespace, so the description and the recorded manual verification predate the final revision; please re-verify against head and update the body. DoD.md line 62 asks for the doc updates in the same change.

Coverage and residual risk

The 4 new tests cover the mirror-present/mirror-absent happy paths for both write sites, with no conditionals around assertions. Missing: any failure-path test (.agents/skills as a file — the headline trigger; the ai-context.ts warn path), and a fast-path regression test with .agents/ present (the existing #1233 test never creates it). CHANGELOG correctly untouched. Verified clean: architectural fit (repo-local analyze-time injection already lives in these two files; editor-targets.ts remains the global setup/uninstall source of truth, untouched), TypeScript conformance (no any, unknown-typed catches, house test idioms), and YAGNI (the per-site stat and the {skills, agentsMirror} return-shape change are both fine).

Verdict

REQUEST CHANGES — both MEDIUMs have small, mechanical fixes: a shared per-skill try/catch mirror helper used by both sites (which also restores the PR body's stated contract), and two :(exclude) pathspecs in run-analyze.ts. Evidence is valid for head 7ba5483f; re-verify if the head moves.

// Step 4: Ensure the shared project-skill root exists. Never clear it: it
// also contains user-authored and standard GitNexus skills.
await fs.mkdir(outputDir, { recursive: true });
if (mirrorToAgents) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — unwrapped mirror writes silently abort canonical community-skill generation and destroy prior output (empirically reproduced).

This mkdir — and the per-skill mirror mkdir/writeFile at lines 218-222 — are the only unwrapped filesystem ops in the mirror feature. Reproduced through this repo's own test harness with .agents/ present but .agents/skills occupied by a file:

  • generateSkillFiles throws EEXIST here, after the canonical gitnexus-area-* cleanup (lines 89-98) already ran → previously generated canonical skills are deleted and nothing is written back (canonicalExists: false for every community skill);
  • a mid-loop failure at line 219-221 aborts canonical writes for all remaining skills, leaving a partial canonical set;
  • the caller (analyze.ts ~1508) swallows the rejection with a silent catch { /* best-effort */ }, so the user gets no community skills and no message.

The PR body's stated contract — "Mirror writes are best-effort and per-skill: a failed mirror copy logs a warning and continues … it never fails the whole analyze run" — is implemented in ai-context.ts (per-skill try/catch + logger.warn) but not here. No test covers any mirror failure path.

Remediation: wrap the mirror ops (this mkdir and the loop write) in try/catch + warn, mirroring ai-context.ts's pattern — simplest shape is a shared mirrorSkill(dir, name, content) helper used by both sites, which also prevents the two implementations drifting again. A regression test with .agents/skills as a file would pin the contract.

const skillsDir = path.join(repoPath, '.claude', 'skills');
const legacySkillsDir = path.join(skillsDir, 'gitnexus');
const installedSkills: string[] = [];
const agentsMirror = await shouldMirrorSkillsToAgents(repoPath);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — .agents is missing from the up-to-date fast-path dirty-check exclusion list, permanently defeating "Already up to date" for exactly the repos this feature targets.

run-analyze.ts:960-977 runs git status --porcelain with :(exclude) pathspecs for every path GitNexus itself writes during analyze — .gitnexus, .claude/**, .cursor/**, AGENTS.md, CLAUDE.md — with the contract spelled out in the comment above it: counting self-written paths as dirty "would perpetually defeat the up-to-date fast path" (#1233 regression guard). This PR adds a new analyze-time write surface (.agents/skills/**, from this call plus generateSkillFiles on --skills) without adding it to that list.

Failing scenario: a repo with a tracked .agents/ (e.g. a committed plugin marketplace) and no .agents/* ignore rule. Run 1 writes untracked .agents/skills/gitnexus-*/SKILL.md mirrors; every subsequent same-commit analyze sees ?? .agents/skills/…, reports dirty, and falls through to the full incremental pipeline (lbug write lock, parse-cache diff, context rewrite — which rewrites the mirror, keeping the loop alive) instead of the instant early return. Permanent until the user commits or ignores the files, and amplified by hook-triggered analyzes contending for the lock. The PR body's "strictly additive… no index refresh required" doesn't hold for this population. The existing fast-path test (#1233) never creates .agents/, so nothing catches it.

Remediation: add ':(exclude).agents' + ':(exclude).agents/**' to the pathspec array at run-analyze.ts:969-976, and a fast-path test with .agents/ present.

* an `.agents/` directory, skills written to `.claude/skills/` are mirrored
* there too so those agents serve the up-to-date copies.
*/
export async function shouldMirrorSkillsToAgents(repoPath: string): Promise<boolean> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — the opt-in gate fires on .agents/ directories that exist for non-skill purposes.

.agents/ is also the Codex plugin-marketplace convention — this repository itself carries a checked-in .agents/plugins/marketplace.json (see CONTRIBUTING.md:179, with the rest of .agents/ gitignored). A repo carrying .agents/plugins/ only — no agent reading repo-local skills — gets 6 (up to ~26 with --skills) unrequested skill directories injected, and absent an ignore rule, the fast-path defeat from the other comment. Harmless in this repo only because its .gitignore happens to swallow the rest of .agents/.

Cheap option: gate on .agents/skills/ existing rather than .agents/ (a repo that wants the mirror creates the directory once), or document the trigger in the README so plugin-only repos know how to avoid it. Flagged as a design cost with a concrete carrying scenario, not a blocker — author's call.

@azizur100389

Copy link
Copy Markdown
Collaborator

Thanks for the update. I rechecked the current head against current main.

I agree with the concern that this overlaps with the already-ready skill-layout work in #2434, so I would not merge this as-is unless the maintainers explicitly want this narrower .agents/skills mirror as a separate follow-up.

There is also still a correctness gap in the generated-skill mirror path. Standard skill mirroring in ai-context.ts is best-effort: if writing .agents/skills/... fails, it logs a warning and the canonical .claude/skills/... install still succeeds. Generated community skills in skill-gen.ts do not have the same error boundary: after writing the canonical .claude/skills/gitnexus-area-*/SKILL.md, the mirror write to .agents/skills/... can throw and fail the whole generation, leaving a partial state.

If this PR continues, generated skill mirroring should match the standard-skill behavior: canonical .claude output should remain the source of truth, .agents mirroring should be best-effort with a warning, and there should be a regression test where the .agents mirror write fails but generateSkillFiles still succeeds with the canonical skills present.

@azizur100389 azizur100389 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes.

gitnexus/src/cli/skill-gen.ts should treat generated-skill mirroring to .agents/skills/generated/ as best-effort, the same way standard skill mirroring is handled. Right now a mirror mkdir/write failure can escape after canonical cleanup and leave .claude/skills/generated/ missing or partial. Please wrap the mirror path with a warning-only error boundary and add a regression test where .agents/skills fails but canonical .claude output still succeeds.

Please also exclude .agents / .agents/** from the analyze dirty-check pathspecs in gitnexus/src/core/run-analyze.ts, otherwise repos that track .agents/ can lose the up-to-date fast path after mirror writes.

Since this changes user-visible analyze output, the README/help text should mention the .agents mirror behavior and how --skip-skills affects it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants