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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.4.0 — 2026-10-11

### Added

- Claude Code support. Skills are now installed for the `claude-code` agent too, so the Skills CLI links each one from `.agents/skills/` into `.claude/skills/`, where Claude Code finds it as `/name`. aginit writes a one-line `CLAUDE.md` (`@AGENTS.md`) when none exists, and wires Graft for Claude Code (`graft init --agents ... claude`). See `docs/adr/0002-claude-code-skills-and-instructions.md`.
- New `agents.claudeCode` config flag, `true` by default. Configs without the flag keep loading and count as enabled. Set it to `false` to opt out.
- `aginit update` links already-installed skills for Claude Code when they're missing from `.claude/skills/`, and adds the `CLAUDE.md` import.
- `aginit doctor` reports skills missing from `.claude/skills/` and a `CLAUDE.md` that doesn't import `AGENTS.md`.

## 0.3.0 — 2026-10-10

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ aginit sets all of this up the same way every time, and you can re-run it whenev

| | |
|---|---|
| **Instructions** | `AGENTS.md` with the project's real commands, invariants and a first-session playbook, shared by Antigravity, Codex and any agent that reads `AGENTS.md` |
| **Skills** | All 27 stable [Matt Pocock skills](https://github.com/mattpocock/skills) (`tdd`, `code-review`, `diagnosing-bugs`, `to-spec`, `to-tickets`, `implement-spec`, `pr`...) plus [humanizer](https://github.com/blader/humanizer) and [ponytail](https://github.com/DietrichGebert/ponytail), installed in `.agents/skills/` via the [Skills CLI](https://github.com/vercel-labs/skills) |
| **Instructions** | `AGENTS.md` with the project's real commands, invariants and a first-session playbook, shared by Antigravity, Codex and any agent that reads `AGENTS.md`, plus a one-line `CLAUDE.md` (`@AGENTS.md`) so Claude Code loads it too |
| **Skills** | All 27 stable [Matt Pocock skills](https://github.com/mattpocock/skills) (`tdd`, `code-review`, `diagnosing-bugs`, `to-spec`, `to-tickets`, `implement-spec`, `pr`...) plus [humanizer](https://github.com/blader/humanizer) and [ponytail](https://github.com/DietrichGebert/ponytail), installed in `.agents/skills/` via the [Skills CLI](https://github.com/vercel-labs/skills) and linked into `.claude/skills/` for Claude Code |
| **Codebase graph** | [Graft](https://www.npmjs.com/package/@nanonets/graft): `graft map` and `graft ask` give agents the relevant code spans before they grep |
| **Test tooling** | Vitest for TypeScript projects, plus Playwright when a web framework is selected |
| **Web extras** | [Impeccable](https://github.com/pbakaus/impeccable) for UI design and [agent-browser](https://github.com/vercel-labs/agent-browser) for visual QA, with its CLI and browser binaries installed so it works out of the box |
Expand Down Expand Up @@ -67,7 +67,7 @@ npm install -g @nanonets/graft
```
aginit new [name] Create a new project
aginit init Add or update agent setup in the current directory
aginit doctor Check Node.js, package manager, Git, Graft, Codex, Skills CLI, agent-browser and config
aginit doctor Check Node.js, package manager, Git, Graft, Codex, Skills CLI, agent-browser, Claude Code wiring and config
aginit update Update installed skills and rebuild the Graft index
```

Expand Down
51 changes: 51 additions & 0 deletions docs/adr/0002-claude-code-skills-and-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 2. Make installed skills and AGENTS.md visible to Claude Code

Date: 2026-10-11

## Status
Accepted

## Context
aginit installed skills only for Antigravity and Codex, which both read
`.agents/skills/`. Claude Code discovers project skills only in
`.claude/skills/<name>/SKILL.md`, so a project set up by aginit showed none of its
skills in Claude Code. Claude Code also loads instructions from `CLAUDE.md`. Since
v2.1.277 it reads `AGENTS.md` too, but only when no `CLAUDE.md` or
`CLAUDE.local.md` exists, and not in every session (older versions, the first
session after an upgrade, or with the built-in AGENTS.md plugin disabled).

Options considered:
1. Add `claude-code` to the Skills CLI `--agent` list.
2. Symlink `.claude/skills` to `../.agents/skills` from aginit.
3. Replace `agents.primary`/`secondary` with an agent array.

## Decision
- **Option 1.** The Skills CLI (`vercel-labs/skills`) already knows the
`claude-code` agent and links each skill from the canonical
`.agents/skills/<name>` into `.claude/skills/<name>`. There is one copy on
disk, `skills update` flows through, and the CLI handles platforms where
symlinks don't work (`--copy`). aginit keeps orchestrating instead of
managing links itself.
- **Config:** add an optional `agents.claudeCode: boolean`, written as `true`
by new configs. If the field is missing (configs written before 0.4.0), it
counts as enabled, so existing projects get Claude Code support on the next
`aginit init` or `aginit update` without editing their config. Set it to
`false` to opt out. `primary`/`secondary` keep their shape.
- **Instructions:** when Claude Code is enabled and no `CLAUDE.md` exists,
write a one-line `CLAUDE.md` containing `@AGENTS.md`. Claude Code documents
this import as the way to share AGENTS.md. It works in every session, and
Claude never reads AGENTS.md twice because of it. An existing `CLAUDE.md` is
never modified; `aginit doctor` reports when it lacks the import.
- **Graft:** pass `claude` to `graft init --agents` so Graft wires its
in-repo Claude Code integration (`.claude/skills/graft`, hooks, statusline,
`.mcp.json`). `--no-global` still keeps it from writing outside the repo.
- **Update path:** `aginit update` relinks the configured sources with the full
agent list when an installed skill is missing from `.claude/skills/`.

## Consequences
- Fresh and re-initialized projects expose every skill to Claude Code as
`/name`, including skills with `disable-model-invocation: true`.
- Projects get `.claude/` and `CLAUDE.md` files to commit.
- `aginit doctor` gains two checks while Claude Code is enabled: skills
missing from `.claude/skills/`, and a `CLAUDE.md` that doesn't import
AGENTS.md.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@antomanc/aginit",
"version": "0.3.0",
"version": "0.4.0",
"description": "Set up a new or existing project for AI coding agents in one command: AGENTS.md, Matt Pocock skills, Graft codebase graph, Vitest/Playwright. Orchestrates upstream tools, idempotent.",
"type": "module",
"main": "dist/index.js",
Expand Down
31 changes: 30 additions & 1 deletion src/adapters/agents-md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ export function generateAgentsMarkdown(
'',
'## AI Capabilities & Skills',
hasSkills
? 'Configured skills install into `.agents/skills/` (shared by Antigravity and Codex):'
? config.agents.claudeCode === false
? 'Configured skills install into `.agents/skills/` (shared by Antigravity and Codex):'
: 'Configured skills install into `.agents/skills/` (shared by Antigravity and Codex, linked into `.claude/skills/` for Claude Code):'
: 'Skills installation is disabled in this configuration.',
engineeringCapability || undefined,
proseCapability || undefined,
Expand Down Expand Up @@ -175,3 +177,30 @@ export function setupAgentsMarkdown(
const content = generateAgentsMarkdown(config, readPackageManifest(targetDir)?.scripts);
return safeWriteFile(filePath, content, { overwrite: true, dryRun, silent });
}

export const CLAUDE_AGENTS_IMPORT = '@AGENTS.md';

/**
* Point Claude Code at AGENTS.md through a one-line `@AGENTS.md` import. Recent
* Claude Code reads AGENTS.md on its own, but only without a CLAUDE.md and not
* in every session; the import works everywhere and never loads it twice.
* An existing CLAUDE.md is left alone (doctor reports a missing import).
*/
export function setupClaudeMarkdown(
targetDir: string,
options: { dryRun?: boolean; silent?: boolean } = {}
): boolean {
const { dryRun = false, silent = false } = options;
const filePath = path.join(targetDir, 'CLAUDE.md');
if (fileExists(filePath)) {
if (!silent) logger.dim('CLAUDE.md already exists, preserving content.');
return true;
}
return safeWriteFile(filePath, CLAUDE_AGENTS_IMPORT + '\n', { overwrite: false, dryRun, silent });
}

export function claudeMarkdownImportsAgents(content: string): boolean {
return content
.split(/\r?\n/)
.some((line) => ['@AGENTS.md', '@./AGENTS.md'].includes(line.trim()));
}
10 changes: 6 additions & 4 deletions src/adapters/graft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export async function isGraftInstalled(): Promise<boolean> {

export async function initGraft(
targetDir: string,
options: { dryRun?: boolean; silent?: boolean } = {}
options: { dryRun?: boolean; silent?: boolean; claude?: boolean } = {}
): Promise<boolean> {
const { dryRun = false, silent = false } = options;
const { dryRun = false, silent = false, claude = true } = options;

const installed = dryRun || (await isGraftInstalled());
if (!installed) {
Expand All @@ -25,10 +25,12 @@ export async function initGraft(

if (!silent) logger.dim('Initializing Graft context graph...');

// Run graft init with antigravity and agents, without touching global system files (--no-global)
// Wire Antigravity, AGENTS.md readers and (optionally) Claude Code's in-repo
// .claude/ + .mcp.json, without touching global system files (--no-global)
const agents = ['antigravity', 'agents', ...(claude ? ['claude'] : [])];
const res = await runExecutable(
'graft',
['init', '--no-global', '--agents', 'antigravity', 'agents', '-y'],
['init', '--no-global', '--agents', ...agents, '-y'],
{
cwd: targetDir,
dryRun,
Expand Down
48 changes: 46 additions & 2 deletions src/adapters/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@ import { fileExists, readJsonFile } from '../utils/fs.js';
import { runExecutable } from '../utils/shell.js';
import { logger } from '../utils/logger.js';
import { validateUpstreamArgument } from '../config/validation.js';
import { SkillSourceConfig } from '../config/schema.js';
import { AginitConfig, SkillSourceConfig } from '../config/schema.js';

/** The skills CLI id for Claude Code; it links skills into `.claude/skills/`. */
export const CLAUDE_CODE_AGENT = 'claude-code';

export function claudeCodeEnabled(config: Pick<AginitConfig, 'agents'>): boolean {
return config.agents.claudeCode !== false;
}

/** Agents the skills CLI installs into, deduplicated. */
export function skillAgents(config: Pick<AginitConfig, 'agents'>): string[] {
const agents = [config.agents.primary, config.agents.secondary];
if (claudeCodeEnabled(config)) agents.push(CLAUDE_CODE_AGENT);
return [...new Set(agents)];
}

export interface SkillsLockFile {
version: number;
Expand All @@ -30,7 +44,7 @@ export async function installSkillSource(
source: SkillSourceConfig,
options: SkillInstallOptions = {}
): Promise<{ ok: boolean; message?: string }> {
const { agents = ['antigravity', 'codex'], dryRun = false, silent = false } = options;
const { agents = ['antigravity', 'codex', CLAUDE_CODE_AGENT], dryRun = false, silent = false } = options;

validateUpstreamArgument(source.package, 'skill package');
for (const skill of source.skills) validateUpstreamArgument(skill, 'skill name');
Expand Down Expand Up @@ -105,6 +119,36 @@ export function getInstalledSkills(targetDir: string): string[] {
return [...installed];
}

/**
* Installed skills Claude Code cannot see because `.claude/skills/<name>/SKILL.md`
* is missing (the skills CLI links them there when `claude-code` is an agent).
*/
export function getSkillsMissingForClaude(targetDir: string): string[] {
return getInstalledSkills(targetDir).filter(
(name) => !fileExists(path.join(targetDir, '.claude', 'skills', name, 'SKILL.md'))
);
}

/**
* Bring projects set up before Claude Code support up to date: when Claude Code
* is enabled and some installed skill is not linked into `.claude/skills/`,
* reinstall the configured sources with the full agent list (idempotent).
*/
export async function ensureClaudeSkillLinks(
targetDir: string,
config: AginitConfig,
options: { dryRun?: boolean; silent?: boolean } = {}
): Promise<boolean> {
if (!claudeCodeEnabled(config) || config.skills.sources.length === 0) return true;
if (getSkillsMissingForClaude(targetDir).length === 0) return true;
if (!options.silent) logger.dim('Linking skills into .claude/skills/ for Claude Code...');
const { failed } = await installAllSkills(targetDir, config.skills.sources, {
...options,
agents: skillAgents(config)
});
return failed.length === 0;
}

export async function updateSkills(
targetDir: string,
options: { dryRun?: boolean; silent?: boolean } = {}
Expand Down
57 changes: 55 additions & 2 deletions src/cli/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import {
PackageManager
} from '../config/schema.js';
import { detectPackageManager } from '../adapters/package-manager.js';
import { getInstalledSkills } from '../adapters/skills.js';
import fs from 'node:fs';
import {
claudeCodeEnabled,
getInstalledSkills,
getSkillsMissingForClaude
} from '../adapters/skills.js';
import { claudeMarkdownImportsAgents } from '../adapters/agents-md.js';
import {
agentBrowserConfigured,
agentBrowserStubInstalled,
Expand Down Expand Up @@ -169,7 +175,7 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
category: 'Project Config',
ok: !!projectConfig,
detail: projectConfig
? `Preset: "${projectConfig.preset}" (v${projectConfig.schemaVersion || '1.0.0'}), pm: ${activePm}, agents: ${projectConfig.agents.primary}/${projectConfig.agents.secondary}`
? `Preset: "${projectConfig.preset}" (v${projectConfig.schemaVersion || '1.0.0'}), pm: ${activePm}, agents: ${projectConfig.agents.primary}/${projectConfig.agents.secondary}${claudeCodeEnabled(projectConfig) ? '/claude-code' : ''}`
: configError
? `Invalid configuration: ${configError}`
: 'No aginit.config.json found in current directory',
Expand Down Expand Up @@ -220,6 +226,53 @@ export async function runDoctor(targetDir: string = process.cwd()): Promise<void
: 'Run `aginit init` or `npx skills add ...` to install skills'
});

// 9b. Claude Code: skills linked into .claude/skills/ and AGENTS.md reachable
if (projectConfig !== null && claudeCodeEnabled(projectConfig)) {
if (!skillsDisabled && installedSkills.length > 0) {
const missing = getSkillsMissingForClaude(targetDir);
checks.push({
name: 'Claude Code Skills (.claude/skills/)',
category: 'Project Config',
ok: missing.length === 0,
detail:
missing.length === 0
? `All ${installedSkills.length} skills visible to Claude Code`
: `${missing.length} skills not linked for Claude Code: ${missing.join(', ')}`,
suggestion:
missing.length === 0
? undefined
: 'Run `aginit update` or `aginit init` to link skills into .claude/skills/ (or set agents.claudeCode: false)'
});
}

const claudePath = path.join(targetDir, 'CLAUDE.md');
const claudeExists = fileExists(claudePath);
// A CLAUDE.md symlinked to AGENTS.md is as good as the import.
const claudeIsAgentsLink =
claudeExists &&
agentsExists &&
fs.realpathSync(claudePath) === fs.realpathSync(agentsPath);
const claudeOk =
!claudeExists ||
claudeIsAgentsLink ||
claudeMarkdownImportsAgents(readTextFile(claudePath) || '');
checks.push({
name: 'CLAUDE.md → AGENTS.md',
category: 'Project Config',
ok: claudeExists && claudeOk,
detail: !claudeExists
? 'No CLAUDE.md; only Claude Code v2.1.277+ reads AGENTS.md on its own'
: claudeOk
? 'CLAUDE.md imports AGENTS.md'
: 'CLAUDE.md exists without `@AGENTS.md`, so Claude Code ignores AGENTS.md',
suggestion: !claudeExists
? 'Run `aginit update` or `aginit init` to add a CLAUDE.md that imports AGENTS.md'
: claudeOk
? undefined
: 'Add a line `@AGENTS.md` to CLAUDE.md'
});
}

// 10. agent-browser runtime (only when the project ships the skill)
const agentBrowserWanted =
projectConfig !== null &&
Expand Down
10 changes: 7 additions & 3 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { readFileSync } from 'node:fs';
import { createProject } from './new.js';
import { initCurrentDirectory } from './init.js';
import { runDoctor } from './doctor.js';
import { updateSkills } from '../adapters/skills.js';
import { claudeCodeEnabled, ensureClaudeSkillLinks, updateSkills } from '../adapters/skills.js';
import { setupClaudeMarkdown } from '../adapters/agents-md.js';
import { buildGraft } from '../adapters/graft.js';
import { readProjectConfig } from '../config/validation.js';
import { logger } from '../utils/logger.js';
Expand Down Expand Up @@ -161,9 +162,12 @@ export function createCliProgram(): Command {
const targetDir = process.cwd();
const config = readProjectConfig(targetDir);
const skillsOk =
config?.skills.sources.length === 0
(config?.skills.sources.length === 0
? true
: await updateSkills(targetDir, { dryRun: options.dryRun });
: await updateSkills(targetDir, { dryRun: options.dryRun })) &&
(config ? await ensureClaudeSkillLinks(targetDir, config, { dryRun: options.dryRun }) : true);
if (config && claudeCodeEnabled(config))
setupClaudeMarkdown(targetDir, { dryRun: options.dryRun });
const graftOk =
config?.codebase.graft === false
? true
Expand Down
13 changes: 9 additions & 4 deletions src/cli/new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
} from '../config/schema.js';
import { getPreset } from '../presets/registry.js';
import { setupGit } from '../adapters/git.js';
import { setupAgentsMarkdown } from '../adapters/agents-md.js';
import { installAllSkills } from '../adapters/skills.js';
import { setupAgentsMarkdown, setupClaudeMarkdown } from '../adapters/agents-md.js';
import { claudeCodeEnabled, installAllSkills, skillAgents } from '../adapters/skills.js';
import { initGraft } from '../adapters/graft.js';
import {
agentBrowserConfigured,
Expand Down Expand Up @@ -137,13 +137,14 @@ export async function createProject(
// Step 4: Write or update AGENTS.md intake contract
if (!silent) logger.step(currentStep++, totalSteps, 'Generating AGENTS.md intake guidance');
setupAgentsMarkdown(targetDir, config, { dryRun, silent });
if (claudeCodeEnabled(config)) setupClaudeMarkdown(targetDir, { dryRun, silent });
if (!silent) logger.success('Configured AGENTS.md');

// Step 5: Install skills via skills.sh engine
if (!silent) logger.step(currentStep++, totalSteps, 'Installing upstream skills via skills.sh');
if (options.skills !== false && config.skills.sources.length > 0) {
const { installed, failed } = await installAllSkills(targetDir, config.skills.sources, {
agents: [config.agents.primary, config.agents.secondary],
agents: skillAgents(config),
dryRun,
silent
});
Expand Down Expand Up @@ -171,7 +172,11 @@ export async function createProject(
// Codebase intelligence (Graft)
if (!silent) logger.step(currentStep++, totalSteps, 'Configuring codebase intelligence');
if (config.codebase.graft && options.graft !== false) {
const graftOk = await initGraft(targetDir, { dryRun, silent });
const graftOk = await initGraft(targetDir, {
dryRun,
silent,
claude: claudeCodeEnabled(config)
});
complete &&= graftOk;
if (graftOk && !silent) {
logger.success('Graft context graph initialized');
Expand Down
Loading
Loading