Skip to content

feat(cli): add --skip-skills and --index-only flags to analyze#742

Open
achianuri wants to merge 1 commit intoabhigyanpatwari:mainfrom
achianuri:feat/skip-skills-flag
Open

feat(cli): add --skip-skills and --index-only flags to analyze#742
achianuri wants to merge 1 commit intoabhigyanpatwari:mainfrom
achianuri:feat/skip-skills-flag

Conversation

@achianuri
Copy link
Copy Markdown

Summary

  • gitnexus analyze --skip-agents-md correctly suppresses AGENTS.md/CLAUDE.md, but unconditionally injects 6 skill files into .claude/skills/gitnexus/ in every analyzed repo
  • This PR adds two new flags:
    • --skip-skills — suppress standard GitNexus skill file injection
    • --index-only — pure index mode: skip all file injection (AGENTS.md, CLAUDE.md, skills), writing only to .gitnexus/

Motivation

While bulk-indexing 176 repos with --skip-agents-md, all 144 indexed repos were contaminated with .claude/skills/gitnexus/ files that had to be cleaned up manually. The --skip-agents-md flag name implies it controls all AI context injection, but skills bypass it entirely.

Changes

File Change
src/cli/index.ts Add --skip-skills and --index-only CLI options
src/cli/analyze.ts Resolve --index-only into both skip flags, pass through call chain
src/core/run-analyze.ts Add skipSkills to AnalyzeOptions, pass to generateAIContextFiles
src/cli/ai-context.ts Add skipSkills to AIContextOptions, guard installSkills() call

Three levels of control

gitnexus analyze --skip-agents-md .  # suppress only AGENTS.md/CLAUDE.md
gitnexus analyze --skip-skills .     # suppress only skill injection
gitnexus analyze --index-only .      # suppress everything, pure indexing

Test plan

  • Default: gitnexus analyze . creates AGENTS.md, CLAUDE.md, and 6 skill dirs
  • --skip-skills: AGENTS.md/CLAUDE.md created, no .claude/skills/gitnexus/
  • --skip-agents-md: skills created, no AGENTS.md/CLAUDE.md
  • --index-only: only .gitnexus/ created, nothing else
  • --skills --skip-skills: community-derived skills also suppressed

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 8, 2026

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

A member of the Team first needs to authorize it.

@soimy
Copy link
Copy Markdown

soimy commented Apr 10, 2026

Good work! Hope this gets merged soon

@tuanhv-miichisoft
Copy link
Copy Markdown

nice!

@magyargergo
Copy link
Copy Markdown
Collaborator

Please rebase your changes on the main branch and I'll get your changes merged in after.

The `installSkills()` call in `generateAIContextFiles()` runs
unconditionally, injecting 6 skill files into `.claude/skills/gitnexus/`
even when `--skip-agents-md` is passed. This is problematic for bulk
indexing operations on read-only mirrors or third-party repos.

Add two new flags:
- `--skip-skills`: suppress standard GitNexus skill file injection
- `--index-only`: pure index mode that suppresses all file injection
  (AGENTS.md, CLAUDE.md, and skills), writing only to `.gitnexus/`

This gives users three levels of control:
- `--skip-agents-md` — suppress only root context files
- `--skip-skills` — suppress only skill injection
- `--index-only` — suppress everything (pure indexing)

Discovery context: while bulk-indexing 176 repos with
`--skip-agents-md`, all 144 indexed repos were contaminated with
`.claude/skills/gitnexus/` files requiring manual cleanup.
@achianuri achianuri force-pushed the feat/skip-skills-flag branch from 2ba5d44 to 4ce3f19 Compare April 20, 2026 14:51
@achianuri
Copy link
Copy Markdown
Author

Rebased on latest main (d858746) and force-pushed. Resolved conflicts in all four files against upstream's additions (--no-stats/noStats, registryName, allowDuplicateName) - all additive merges, no logic lost on either side. Ready for merge.

@duclvz
Copy link
Copy Markdown

duclvz commented Apr 27, 2026

@magyargergo please review new change and merge soon, thanks!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

Claude finished @magyargergo's task in 3m 15s —— View job


PR #742 Review: feat(cli): add --skip-skills and --index-only flags to analyze

Tasks:

  • Initialize review
  • Read DoD.md, AGENTS.md, GUARDRAILS.md, CONTRIBUTING.md, TESTING.md
  • Read changed files (index.ts, analyze.ts, run-analyze.ts, ai-context.ts)
  • Read existing tests for analyze/AI context
  • Inspect option propagation end-to-end
  • Inspect flag semantics and combinations
  • Produce findings and verdict

Review bar for this PR

Tailored from CONTRIBUTING.md (canonical PR checklist; DoD.md does not exist in this repo):

  • --skip-skills suppresses installSkills() through the full call chain: CLI → analyzeCommand()runFullAnalysis()generateAIContextFiles().
  • --index-only resolves into both skipAgentsMd=true and skipSkills=true, preventing all AI-context file writes regardless of other flags.
  • Existing --skip-agents-md behavior is unchanged: it still skips only AGENTS.md/CLAUDE.md and still installs skills.
  • Option propagation uses the resolved skip values (not raw options.skipAgentsMd) everywhere in the call chain.
  • Tests verify filesystem side effects — that .claude/skills/gitnexus/ is not created when skills are suppressed.
  • Typecheck passes (npx tsc --noEmit in gitnexus/); full test suite passes.
  • AGENTS.md/CLAUDE.md are consistent with the actual files that will exist in the analyzed repo.

Understanding of the change

User problem: gitnexus analyze --skip-agents-md skips AGENTS.md/CLAUDE.md but unconditionally calls installSkills(), writing 6 standard GitNexus skill dirs into .claude/skills/gitnexus/. During bulk indexing of 176 repos, operators expecting clean indexing got contaminated repos.

Affected file-writing paths:

  • installSkills(repoPath) → writes .claude/skills/gitnexus/{6 skill dirs}/
  • upsertGitNexusSection() → writes/updates AGENTS.md and CLAUDE.md

Flags added:

  • --skip-skills / skipSkills: guards installSkills() inside generateAIContextFiles()
  • --index-only / indexOnly: resolved in analyzeCommand() into skipAgentsMd=true + skipSkills=true before calling runFullAnalysis()

Behavior that must stay unchanged: default analyze (no flags), --skip-agents-md, --skills.


Findings

[high] No tests for skipSkills or indexOnly filesystem effects

  • Category: Testing
  • Files: gitnexus/test/unit/ai-context.test.ts (gap)
  • Issue: Zero tests added for the new flags. ai-context.test.ts already tests skipAgentsMd with real filesystem assertions (lines 140–166), but there is no equivalent asserting that .claude/skills/gitnexus/ is not created when skipSkills: true. There is also no unit or integration test for the indexOnly → skipAgentsMd + skipSkills resolution in analyzeCommand().
  • Why it matters here: The entire motivation for this PR is preventing unwanted repo contamination. Without a test asserting the skills directory is absent when skipSkills is true, a future regression (e.g. someone adding a second installSkills call without the guard) will silently reintroduce the contamination bug. Given that skipAgentsMd already has a filesystem test, the absence of the equivalent for skipSkills is conspicuous and inconsistent.
  • Recommended fix: Add to ai-context.test.ts:
    it('does not create .claude/skills/gitnexus/ when skipSkills is true', async () => {
      await generateAIContextFiles(tmpDir, storagePath, 'TestProject', stats, undefined, { skipSkills: true });
      await expect(fs.access(path.join(tmpDir, '.claude', 'skills', 'gitnexus'))).rejects.toThrow();
    });
    
    it('skips both AGENTS.md and skills when indexOnly resolves both flags', async () => {
      // Test the resolution logic in analyzeCommand or via direct skipAgentsMd+skipSkills=true
      const result = await generateAIContextFiles(tmpDir, storagePath, 'P', stats, undefined, {
        skipAgentsMd: true, skipSkills: true
      });
      expect(result.files).toContain('AGENTS.md (skipped via --skip-agents-md)');
      expect(result.files).toContain('.claude/skills/gitnexus/ (skipped via --skip-skills)');
      await expect(fs.access(path.join(tmpDir, '.claude', 'skills', 'gitnexus'))).rejects.toThrow();
    });

[medium] --index-only --skills still writes community-derived skill files

  • Category: Flag Semantics / File Side Effects
  • Files: gitnexus/src/cli/analyze.ts (lines 253–298)
  • Issue: When both --index-only and --skills are passed, skipAll=true correctly resolves skipAgentsMd=true and skipSkills=true. However, the options?.skills branch at line 253 is not gated by skipAll. generateSkillFiles() runs unconditionally and writes community-derived skill files to .claude/skills/generated/<name>/SKILL.md. Only the second generateAIContextFiles() call inside that branch gets skipSkills=true, preventing installSkills(). Community skill files are still written.
    // Current — runs even when --index-only
    if (options?.skills && result.pipelineResult) {
      const skillResult = await generateSkillFiles(repoPath, ...);
      // ↑ writes .claude/skills/generated/ regardless of --index-only
  • Why it matters here: --index-only is advertised as "skip ALL file injection." If a user runs gitnexus analyze --index-only --skills . (e.g. in a pipeline that conditionally adds --skills), repos are still contaminated. The promise of the flag is violated.
  • Recommended fix: Gate the --skills block with !skipAll:
    if (options?.skills && result.pipelineResult && !skipAll) {
    This is a one-word change that aligns --index-only semantics with its description.

[medium] AGENTS.md/CLAUDE.md skill-routing table references files that may not exist

  • Category: LLM Context Correctness
  • Files: gitnexus/src/cli/ai-context.ts (generateGitNexusContent, lines 81–88)
  • Issue: generateGitNexusContent() always emits the skills routing table pointing to .claude/skills/gitnexus/*.SKILL.md. When skipSkills=true and skipAgentsMd=false (i.e. --skip-skills without --skip-agents-md), AGENTS.md/CLAUDE.md are written with instructions directing the AI agent to read skill files that do not exist. A fresh repo run with gitnexus analyze --skip-skills . will get AGENTS.md/CLAUDE.md that reference 6 non-existent files.
  • Why it matters here: This is an LLM context engineering issue. Every time an agent loads AGENTS.md it will attempt to read the skill files from the table. On a repo where --skip-skills was used, all 6 reads will 404 or fail silently. This degrades agent behavior and undermines the point of a clean AGENTS.md injection without skills.
  • Recommended fix: Pass skipSkills to generateGitNexusContent() and conditionally omit the skills table section, or replace it with a note that skills are not installed. Smallest correct change:
    function generateGitNexusContent(
      projectName: string,
      stats: RepoStats,
      generatedSkills?: GeneratedSkillInfo[],
      groupNames?: string[],
      noStats?: boolean,
      skipSkills?: boolean,   // ← add
    ): string {
      // ...
      const skillsSection = skipSkills ? '' : `\n${skillsTable}\n`;

[low] PR test checklist item 5 contradicts the implementation

  • Category: Documentation / Test Plan Accuracy
  • Files: PR description (test plan)
  • Issue: The PR checklist says: --skills --skip-skills: community-derived skills also suppressed. But the implementation only guards installSkills() (standard skills) via skipSkills. generateSkillFiles() (community skills) is not gated by skipSkills. The PR description text consistently states --skip-skills = "suppress standard GitNexus skill injection", so the test-plan item appears to be incorrect, not the implementation. However, this creates ambiguity about intended semantics.
  • Why it matters here: If the test plan is the authoritative statement of intent, the implementation is incomplete. If the PR description text is authoritative, the test plan must be corrected. Either way this needs resolution before merge.
  • Recommended fix: Correct the test plan item to: --skills --skip-skills: standard skills suppressed; community-derived skills still generated. Or, if the intent really is to suppress all skill generation, add && !skipSkills to the generateSkillFiles() call in analyze.ts.

Definition of Done check

DoD item Status
--skip-skills suppresses installSkills() through the full call chain satisfied
--index-only resolves into both skipAgentsMd=true and skipSkills=true satisfied (for the non---skills path)
--index-only prevents ALL non-.gitnexus/ writes partially satisfied — fails when combined with --skills
Existing --skip-agents-md behavior unchanged satisfied
Option propagation uses resolved values (not raw) everywhere satisfied
Tests verify no filesystem side effects when flags are set not satisfied
Typecheck passes (npx tsc --noEmit) not verifiable from diff — CI status unclear
AGENTS.md/CLAUDE.md content is consistent with installed files not satisfied (--skip-skills leaves stale skill references)

Validation assessment

  • Tests added/updated: None. The PR description's test checklist is prose description, not automated tests.
  • Validation claimed by PR: Prose checklist only; all items unchecked at submission.
  • Validation verified from diff/CI evidence: ci-quality.yml (typecheck + lint) is not clearly passing from the available CI run data. ci-tests.yml (vitest) — no evidence of a passing run against this branch.
  • Missing/not-verifiable:
    • npx tsc --noEmit in gitnexus/ — not verifiable
    • Unit tests for generateAIContextFiles({ skipSkills: true }) — absent
    • Integration test for --skip-skills / --index-only filesystem side effects — absent
    • Test for --index-only --skills not contaminating repos — absent
  • Whether missing validation blocks production readiness: Yes. Per CONTRIBUTING.md: "Tests pass for the packages you touched." The ai-context.ts test suite (test/unit/ai-context.test.ts) tests skipAgentsMd with filesystem assertions but has no equivalent for skipSkills. This is the minimum regression test needed for the fix.

Final verdict

not production-ready

The core option plumbing is correct and the primary bug (standard skills injected during --skip-agents-md bulk indexing) is fixed. However, three issues block merge: (1) no tests asserting the filesystem state when skipSkills=true — the repo's own CONTRIBUTING.md requires tests to pass for touched packages, and this fix is specifically about filesystem contamination; (2) --index-only --skills still writes community skill files, violating the "skip ALL file injection" contract; (3) --skip-skills without --skip-agents-md produces AGENTS.md/CLAUDE.md that references non-existent skill files. Issues (2) and (3) are each a one-line fix; issue (1) requires adding 2–3 test cases to the existing ai-context.test.ts. The PR is close — these are all small, targeted changes.

Suggested fixes before merge:

  1. Fix this → — Add skipSkills filesystem tests to ai-context.test.ts
  2. Fix this → — Gate generateSkillFiles() with !skipAll in analyze.ts
  3. Fix this → — Omit skills table in generateGitNexusContent() when skipSkills=true

@github-actions
Copy link
Copy Markdown
Contributor

CI Report

Some checks failed

Pipeline Status

Stage Status Details
❌ Typecheck failure tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
6981 6884 0 97 267s

✅ All 6884 tests passed

97 test(s) skipped — expand for details
  • Swift MethodExtractor > isTypeDeclaration > recognizes class_declaration
  • Swift MethodExtractor > isTypeDeclaration > recognizes protocol_declaration
  • Swift MethodExtractor > isTypeDeclaration > rejects import_declaration
  • Swift MethodExtractor > visibility > extracts public method
  • Swift MethodExtractor > visibility > extracts private method
  • Swift MethodExtractor > visibility > defaults to internal when no modifier
  • Swift MethodExtractor > protocol methods > marks protocol method as abstract
  • Swift MethodExtractor > static and class methods > detects static func as isStatic
  • Swift MethodExtractor > static and class methods > detects class func as isStatic
  • Swift MethodExtractor > parameters > extracts parameters with types and default values
  • Swift MethodExtractor > return type > extracts return type from -> annotation
  • Swift MethodExtractor > annotations > extracts @objc attribute
  • Swift MethodExtractor > isFinal > detects final func
  • Swift MethodExtractor > isFinal > is false when not final
  • Swift MethodExtractor > isAsync > detects async func
  • Swift MethodExtractor > isOverride > detects override method
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift cross-file constructor inference uses lookupClassByName
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift explicit init inference uses lookupClassByName
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift cross-file constructor inference does not bind plain functions
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature
  • Swift constructor-inferred type resolution > detects User and Repo classes, both with save methods
  • Swift constructor-inferred type resolution > resolves user.save() to Models/User.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > resolves repo.save() to Models/Repo.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > emits exactly 2 save() CALLS edges (one per receiver type)
  • Swift self resolution > detects User and Repo classes, each with a save function
  • Swift self resolution > resolves self.save() inside User.process to User.save, not Repo.save
  • Swift parent resolution > detects BaseModel and User classes plus Serializable protocol
  • Swift parent resolution > emits EXTENDS edge: User → BaseModel
  • Swift parent resolution > emits IMPLEMENTS edge: User → Serializable (protocol conformance)
  • Swift cross-file User.init() inference > resolves user.save() via User.init(name:) inference
  • Swift cross-file User.init() inference > resolves user.greet() via User.init(name:) inference
  • Swift return type inference > detects User class and getUser function
  • Swift return type inference > detects save function on User (Swift class methods are Function nodes)
  • Swift return type inference > resolves user.save() to User#save via return type of getUser() -> User
  • Swift return-type inference via function return type > resolves user.save() to User#save via return type of getUser()
  • Swift return-type inference via function return type > user.save() does NOT resolve to Repo#save
  • Swift return-type inference via function return type > resolves repo.save() to Repo#save via return type of getRepo()
  • Swift implicit imports (cross-file visibility) > detects UserService class in Models.swift
  • Swift implicit imports (cross-file visibility) > resolves UserService() constructor call across files (no explicit import)
  • Swift implicit imports (cross-file visibility) > resolves service.fetchUser() member call across files
  • Swift implicit imports (cross-file visibility) > creates IMPORTS edges between files in the same module
  • Swift extension deduplication > detects Product class
  • Swift extension deduplication > resolves Product() constructor despite extension creating duplicate class node
  • Swift extension deduplication > resolves product.save() to Product.swift (primary definition)
  • Swift constructor call fallback (no new keyword) > resolves OCRService() as constructor call across files
  • Swift constructor call fallback (no new keyword) > resolves ocr.recognize() member call via constructor-inferred type
  • Swift export visibility (internal vs private) > resolves PublicService() constructor across files
  • Swift export visibility (internal vs private) > resolves internalHelper() across files (internal = module-scoped)
  • Swift if let / guard let binding resolution > detects User and Repo classes
  • Swift if let / guard let binding resolution > resolves user.save() inside if-let to User#save
  • Swift if let / guard let binding resolution > resolves repo.save() inside guard-let to Repo#save
  • Swift if let / guard let binding resolution > user.save() in if-let does NOT resolve to Repo#save
  • Swift await / try expression unwrapping > resolves user.save() via await fetchUser() return type
  • Swift await / try expression unwrapping > resolves repo.save() via try parseRepo() return type
  • Swift await / try expression unwrapping > detects fetchUser and parseRepo as functions
  • Swift for-in loop element type inference > detects User and Repo classes
  • Swift for-in loop element type inference > creates implicit import edges between files
  • Swift field-type resolution > detects classes and their properties
  • Swift field-type resolution > emits HAS_PROPERTY edges from class to field
  • Swift field-type resolution > resolves field-chain call user.address.save() → Address#save
  • Swift field-type resolution > emits ACCESSES edges for field reads in chains
  • Swift field-type resolution > populates field metadata (visibility, declaredType) on Property nodes
  • Swift call-result binding > resolves call-result-bound method call user.save() → User#save
  • Swift call-result binding > getUser() is present as a defined function
  • Swift call-result binding > emits processUser -> getUser CALLS edge for let-assigned free function call
  • Swift method enrichment > detects Animal protocol and Dog class
  • Swift method enrichment > emits IMPLEMENTS edge Dog -> Animal
  • Swift method enrichment > emits HAS_METHOD edges for Dog methods
  • Swift method enrichment > marks protocol Animal.speak as isAbstract
  • Swift method enrichment > marks Dog.speak as NOT isAbstract
  • Swift method enrichment > marks breathe as isFinal
  • Swift method enrichment > marks classify as isStatic
  • Swift method enrichment > captures @objc annotation on breathe
  • Swift method enrichment > populates parameterTypes for classify(_ name: String)
  • Swift method enrichment > records parameterCount for classify
  • Swift method enrichment > records returnType for speak
  • Swift method enrichment > resolves dog.speak() CALLS edge
  • Swift method enrichment > resolves Dog.classify("dog") CALLS edge
  • Swift abstract dispatch > detects Repository protocol and SqlRepository class
  • Swift abstract dispatch > emits IMPLEMENTS edge SqlRepository -> Repository
  • Swift abstract dispatch > emits HAS_METHOD edges for Repository.find and Repository.save
  • Swift abstract dispatch > emits HAS_METHOD edges for SqlRepository.find and SqlRepository.save
  • Swift abstract dispatch > marks base Repository.find as isAbstract
  • Swift abstract dispatch > marks base Repository.save as isAbstract
  • Swift abstract dispatch > marks concrete SqlRepository.find as NOT isAbstract
  • Swift abstract dispatch > resolves repo.find(id: 42) CALLS edge
  • Swift abstract dispatch > resolves repo.save(entity: user) CALLS edge
  • Swift abstract dispatch > populates parameterTypes for Repository.find
  • Swift abstract dispatch > populates parameterTypes for Repository.save
  • Swift abstract dispatch > records returnType for SqlRepository.find
  • Swift abstract dispatch > emits METHOD_IMPLEMENTS edges from SqlRepository methods → Repository protocol methods
  • Swift overloaded method disambiguation > detects 2 distinct find Method nodes on SqlRepository
  • Swift overloaded method disambiguation > emits METHOD_IMPLEMENTS edges for both find overloads
  • Swift overloaded method disambiguation > emits METHOD_IMPLEMENTS edge for save
  • Swift overloaded method disambiguation > emits exactly 3 METHOD_IMPLEMENTS edges total
  • Swift Child extends Parent — inherited method resolution (SM-9) > detects Parent and Child classes
  • Swift Child extends Parent — inherited method resolution (SM-9) > resolves c.parentMethod() to Parent.parentMethod via first-wins MRO walk

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 73.53% 18786/25548 75.59% 📉 -2.1 🔴 ██████████████░░░░░░
Branches 62.28% 11962/19206 64.5% 📉 -2.2 🔴 ████████████░░░░░░░░
Functions 78.72% 1809/2298 80.8% 📉 -2.1 🔴 ███████████████░░░░░
Lines 76.16% 17041/22373 78.39% 📉 -2.2 🔴 ███████████████░░░░░

📋 View full run · Generated by CI

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.

5 participants