Skip to content
Open
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
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **`init` command** — agent adoption layer. `codebase-intelligence init [path]` writes
an idempotent, marked instruction block ("query CI before grep/read") into each
agent's repo file (`AGENTS.md`, `CLAUDE.md`,
selected agent's repo file (`AGENTS.md`, `CLAUDE.md`,
`.cursor/rules/codebase-intelligence.mdc`, `.github/copilot-instructions.md`,
`GEMINI.md`, `CONVENTIONS.md`) and installs a portable skill to
`GEMINI.md`, `CONVENTIONS.md`) and optionally installs a portable skill to
`~/.claude/skills/codebase-intelligence/SKILL.md`.
- `--agents <list>` to target a subset of agents (default: all).
- `--no-skill` to skip the global skill install.
- `--json` for machine-readable output.
- **Opt-in by design** — nothing is written unless chosen. On a TTY, an interactive
picker (`AGENTS.md` + `CLAUDE.md` preselected); non-interactively, those two by
default. The global skill installs only with `--skill`.
- `--agents <list>` to select explicitly, `--all` for every agent, `--yes` for
non-interactive defaults, `--json` for machine-readable output.
- Writes are idempotent — only content between the
`codebase-intelligence:start`/`:end` markers is ever touched; existing user content
is preserved.
Expand Down
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,22 @@ codebase-intelligence has the data — but AI agents only benefit if they actual
*query* it instead of defaulting to grep/read. `init` closes that gap.

```bash
codebase-intelligence init # current repo, all agents + skill
codebase-intelligence init ./repo --agents claude,agents
codebase-intelligence init --no-skill
codebase-intelligence init # interactive picker (TTY)
codebase-intelligence init --agents claude,cursor
codebase-intelligence init --all --skill # every agent + global skill
codebase-intelligence init --yes # non-interactive defaults
```

It writes an idempotent, marked instruction block ("query CI before grep/read") into
each agent's native file, and installs a portable skill:
Nothing is written unless you choose it. On a terminal, `init` shows an interactive
picker (`AGENTS.md` + `CLAUDE.md` preselected); non-interactively it defaults to those
two. The global skill is **opt-in** (`--skill`). It writes an idempotent, marked
instruction block ("query CI before grep/read") into each selected agent's native file:

| Layer | Target |
|---|---|
| Repo instructions | `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/codebase-intelligence.mdc`, `.github/copilot-instructions.md`, `GEMINI.md`, `CONVENTIONS.md` (Aider) |
| Portable skill | `~/.claude/skills/codebase-intelligence/SKILL.md` |
| Layer | Target | Default |
|---|---|---|
| Repo instructions | `AGENTS.md`, `CLAUDE.md` | selected |
| Repo instructions | `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`, `GEMINI.md`, `CONVENTIONS.md` (Aider) | opt-in |
| Portable skill | `~/.claude/skills/codebase-intelligence/SKILL.md` | opt-in (`--skill`) |

Writes are idempotent — only content between the
`<!-- codebase-intelligence:start -->` / `:end` markers is ever touched, so re-running
Expand Down
21 changes: 14 additions & 7 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,19 @@ codebase-intelligence clusters <path> [--min-files <n>] [--json] [--force]

### init

Make AI agents use codebase-intelligence: write a managed instruction block into each
agent's repo file (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/codebase-intelligence.mdc`,
`.github/copilot-instructions.md`, `GEMINI.md`, `CONVENTIONS.md`) and install the
portable skill to `~/.claude/skills/`. Idempotent — only content between the
Set up AI agents to use codebase-intelligence by writing a managed instruction block
into each selected agent's repo file (`AGENTS.md`, `CLAUDE.md`,
`.cursor/rules/codebase-intelligence.mdc`, `.github/copilot-instructions.md`,
`GEMINI.md`, `CONVENTIONS.md`) and optionally installing the portable skill to
`~/.claude/skills/`. Idempotent — only content between the
`codebase-intelligence:start`/`:end` markers is touched.

Opt-in by design: on a TTY it shows an interactive picker (`AGENTS.md` + `CLAUDE.md`
preselected). Non-interactively (or with `--yes`/`--json`) it defaults to those two.
The global skill is never installed unless `--skill` is passed.

```bash
codebase-intelligence init [path] [--agents <list>] [--no-skill] [--json]
codebase-intelligence init [path] [--agents <list>] [--all] [--skill] [--yes] [--json]
```

**Output:** per-file actions (created / updated / unchanged) and skill install status.
Expand All @@ -187,8 +192,10 @@ codebase-intelligence init [path] [--agents <list>] [--no-skill] [--json]
| `--entry <name>` | processes | Filter by entry point name |
| `--min-files <n>` | clusters | Min files per cluster |
| `--no-dry-run` | rename | Actually perform the rename (default: dry run) |
| `--agents <list>` | init | Comma-separated agents (default: all) |
| `--no-skill` | init | Skip installing the global Claude skill |
| `--agents <list>` | init | Comma-separated agents, non-interactive (default: agents,claude) |
| `--all` | init | Target every agent (non-interactive) |
| `--skill` | init | Also install the global Claude skill (opt-in) |
| `-y, --yes` | init | Accept defaults without prompting |

## Behavior

Expand Down
16 changes: 9 additions & 7 deletions llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,17 @@ Community-detected file clusters (Louvain algorithm).

### init
```bash
codebase-intelligence init [path] [--agents <list>] [--no-skill] [--json]
codebase-intelligence init [path] [--agents <list>] [--all] [--skill] [--yes] [--json]
```
Make AI agents actually use codebase-intelligence. Writes an idempotent, marked
instruction block ("query CI before grep/read") into each agent's repo file —
Set up AI agents to use codebase-intelligence. Writes an idempotent, marked
instruction block ("query CI before grep/read") into each selected agent's repo file —
`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/codebase-intelligence.mdc`,
`.github/copilot-instructions.md`, `GEMINI.md`, `CONVENTIONS.md` — and installs the
portable skill to `~/.claude/skills/codebase-intelligence/SKILL.md`. Only content
between the `codebase-intelligence:start`/`:end` markers is ever touched, so re-running
is safe. `--agents` limits targets (default: all); `--no-skill` skips the skill.
`.github/copilot-instructions.md`, `GEMINI.md`, `CONVENTIONS.md`. Opt-in: on a TTY it
shows an interactive picker (`AGENTS.md` + `CLAUDE.md` preselected); non-interactively
(or `--yes`/`--json`) it defaults to those two. `--agents` selects explicitly, `--all`
targets every agent, `--skill` also installs the portable skill to
`~/.claude/skills/codebase-intelligence/SKILL.md` (never installed otherwise). Only
content between the `codebase-intelligence:start`/`:end` markers is ever touched.

## Global Behavior

Expand Down
68 changes: 43 additions & 25 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ import {
import {
installRepoFiles,
installGlobalSkill,
isAgentId,
resolveInitPlan,
ALL_AGENT_IDS,
} from "./install/index.js";
import { promptSelection } from "./install/prompt.js";
import type { CodebaseGraph } from "./types/index.js";
import type { AgentId } from "./install/index.js";

const INDEX_DIR_NAME = ".code-visualizer";

Expand Down Expand Up @@ -188,7 +188,9 @@ interface McpOptions {

interface InitOptions {
agents?: string;
all?: boolean;
skill?: boolean;
yes?: boolean;
json?: boolean;
}

Expand Down Expand Up @@ -953,36 +955,50 @@ program

program
.command("init")
.description("Make AI agents use codebase-intelligence: write per-agent instruction files + install the skill")
.description("Set up AI agents to use codebase-intelligence: write per-agent instruction files (+ optional skill)")
.argument("[path]", "Repo root (default: current directory)", ".")
.option("--agents <list>", `Comma-separated agents to target (default: all). Available: ${ALL_AGENT_IDS.join(", ")}`)
.option("--no-skill", "Skip installing the global Claude skill")
.option("--json", "Output as JSON")
.action((targetPath: string, options: InitOptions) => {
.option("--agents <list>", `Comma-separated agents, non-interactive. Available: ${ALL_AGENT_IDS.join(", ")}`)
.option("--all", "Target every agent (non-interactive)")
.option("--skill", "Also install the global Claude skill (opt-in)")
.option("-y, --yes", "Accept defaults without prompting")
.option("--json", "Output as JSON (implies non-interactive)")
.action(async (targetPath: string, options: InitOptions) => {
const resolved = path.resolve(targetPath);
if (!fs.existsSync(resolved)) {
process.stderr.write(`Error: Path does not exist: ${targetPath}\n`);
process.exit(1);
}

let agents: AgentId[] | undefined;
if (options.agents) {
const requested = options.agents
.split(",")
.map((a) => a.trim())
.filter(Boolean);
const invalid = requested.filter((a) => !isAgentId(a));
if (invalid.length > 0) {
process.stderr.write(
`Error: Unknown agents: ${invalid.join(", ")}. Available: ${ALL_AGENT_IDS.join(", ")}\n`,
);
process.exit(2);
const isTty = process.stdin.isTTY && process.stdout.isTTY;
const plan = resolveInitPlan(options, isTty);

if (plan.invalidAgents.length > 0) {
process.stderr.write(
`Error: Unknown agents: ${plan.invalidAgents.join(", ")}. Available: ${ALL_AGENT_IDS.join(", ")}\n`,
);
process.exit(2);
}

let agents = plan.agents;
let installSkill = plan.installSkill;

if (plan.mode === "interactive") {
const selection = await promptSelection(agents, installSkill);
if (!selection) {
output("Cancelled — nothing written.");
return;
}
agents = requested.filter(isAgentId);
agents = selection.agents;
installSkill = selection.skill;
}

if (agents.length === 0 && !installSkill) {
output("Nothing selected — nothing to do.");
return;
}

const repoResults = installRepoFiles(resolved, { agents });
const skillResult = options.skill === false ? undefined : installGlobalSkill();
const skillResult = installSkill ? installGlobalSkill() : undefined;

if (options.json) {
outputJson({ repoFiles: repoResults, skill: skillResult ?? null });
Expand All @@ -991,17 +1007,19 @@ program

output(`Codebase Intelligence — agent adoption`);
output(`──────────────────────────────────────`);
output(`Repo instruction files (${resolved}):`);
for (const r of repoResults) {
output(` ${r.action.padEnd(9)} ${r.path}`);
if (repoResults.length > 0) {
output(`Repo instruction files (${resolved}):`);
for (const r of repoResults) {
output(` ${r.action.padEnd(9)} ${r.path}`);
}
}
if (skillResult) {
output(``);
output(`Global skill:`);
output(` ${skillResult.action.padEnd(9)} ${skillResult.path}`);
}
output(``);
output(`Done. Agents in this repo will now be told to query codebase-intelligence first.`);
output(`Done. Selected agents will be told to query codebase-intelligence first.`);
output(`Re-run anytime — writes are idempotent (managed blocks only).`);
});

Expand Down
53 changes: 53 additions & 0 deletions src/install/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import {
installRepoFiles,
installGlobalSkill,
isAgentId,
resolveInitPlan,
AGENT_TARGETS,
ALL_AGENT_IDS,
DEFAULT_AGENTS,
DEFAULT_MARKERS,
} from "./index.js";

Expand Down Expand Up @@ -194,3 +196,54 @@ describe("installGlobalSkill", () => {
expect(second.action).toBe("unchanged");
});
});

describe("resolveInitPlan", () => {
it("defaults to AGENTS.md + CLAUDE.md only", () => {
expect([...DEFAULT_AGENTS]).toEqual(["agents", "claude"]);
});

it("--all selects every agent (explicit, non-interactive)", () => {
const plan = resolveInitPlan({ all: true }, true);
expect(plan.mode).toBe("explicit");
expect(plan.agents).toEqual([...ALL_AGENT_IDS]);
expect(plan.installSkill).toBe(false);
});

it("--agents picks the listed agents", () => {
const plan = resolveInitPlan({ agents: "claude,gemini" }, true);
expect(plan.mode).toBe("explicit");
expect(plan.agents).toEqual(["claude", "gemini"]);
expect(plan.invalidAgents).toEqual([]);
});

it("--agents reports unknown ids and keeps valid ones", () => {
const plan = resolveInitPlan({ agents: "claude,bogus" }, true);
expect(plan.agents).toEqual(["claude"]);
expect(plan.invalidAgents).toEqual(["bogus"]);
});

it("no flags on a TTY → interactive, seeded with the default set", () => {
const plan = resolveInitPlan({}, true);
expect(plan.mode).toBe("interactive");
expect(plan.agents).toEqual([...DEFAULT_AGENTS]);
});

it("no flags without a TTY → non-interactive default", () => {
const plan = resolveInitPlan({}, false);
expect(plan.mode).toBe("default");
expect(plan.agents).toEqual([...DEFAULT_AGENTS]);
});

it("--json forces non-interactive even on a TTY", () => {
expect(resolveInitPlan({ json: true }, true).mode).toBe("default");
});

it("--yes forces non-interactive even on a TTY", () => {
expect(resolveInitPlan({ yes: true }, true).mode).toBe("default");
});

it("--skill is opt-in across modes", () => {
expect(resolveInitPlan({ skill: true }, false).installSkill).toBe(true);
expect(resolveInitPlan({}, false).installSkill).toBe(false);
});
});
69 changes: 69 additions & 0 deletions src/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,75 @@ export function isAgentId(value: string): value is AgentId {
return (ALL_AGENT_IDS as readonly string[]).includes(value);
}

/**
* Default selection when the user doesn't choose explicitly: the universal
* `AGENTS.md` standard plus `CLAUDE.md`. Everything else is opt-in.
*/
export const DEFAULT_AGENTS: readonly AgentId[] = ["agents", "claude"];

// ── Init planning (pure) ────────────────────────────────────

export interface InitFlags {
/** Comma-separated agent ids from `--agents`. */
agents?: string;
/** `--all`: every agent. */
all?: boolean;
/** `--skill`: install the global skill (opt-in). */
skill?: boolean;
/** `--json`: machine output, implies non-interactive. */
json?: boolean;
/** `--yes`: accept defaults without prompting. */
yes?: boolean;
}

export type InitMode = "explicit" | "interactive" | "default";

export interface InitPlan {
/** Agents to write (preselection when mode is "interactive"). */
agents: AgentId[];
/** Whether to install the global skill (default when mode is "interactive"). */
installSkill: boolean;
/** How the selection was decided. "interactive" → caller should prompt. */
mode: InitMode;
/** Unknown ids passed to `--agents`. */
invalidAgents: string[];
}

/**
* Decide what `init` should do from its flags and whether stdout is a TTY.
* Pure — no prompting or filesystem. When `mode` is "interactive" the caller
* presents a picker seeded with `agents`/`installSkill`; otherwise the plan is
* final.
*/
export function resolveInitPlan(flags: InitFlags, isTty: boolean): InitPlan {
const installSkill = flags.skill === true;

if (flags.all === true) {
return { agents: [...ALL_AGENT_IDS], installSkill, mode: "explicit", invalidAgents: [] };
}

if (flags.agents !== undefined) {
const requested = flags.agents
.split(",")
.map((a) => a.trim())
.filter(Boolean);
return {
agents: requested.filter(isAgentId),
installSkill,
mode: "explicit",
invalidAgents: requested.filter((a) => !isAgentId(a)),
};
}

const interactive = isTty && flags.json !== true && flags.yes !== true;
return {
agents: [...DEFAULT_AGENTS],
installSkill,
mode: interactive ? "interactive" : "default",
invalidAgents: [],
};
}

// ── Content (single source of truth) ────────────────────────

/**
Expand Down
Loading
Loading