Skip to content
Draft
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Detailed command contracts and JSON shapes live in [docs/cli.md](./docs/cli.md).
- Per-file symbol indexes with locals, exports, docstrings, line spans, and lightweight complexity metadata.
- Cross-file go-to-definition and find-references support across the shared source-language pipeline.
- Deterministic agent exploration, orientation, packet retrieval, search, bounded explanations, portable artifact bundles, and MCP tools across files, symbols, chunks, SQL objects, graph neighborhoods, and review ranges with stable follow-up targets.
- Project lifecycle commands initialize, inspect, refresh, and remove `.codegraph/manifest.json` metadata while reusing the existing disk cache.
- Semantic chunking for code and text files, including Vue and Svelte single-file component block splitting.
- Duplicate and near-duplicate detection over indexed symbols, semantic chunks, text chunks, token fingerprints, and AST shape hashes when parser context is available.
- AST grep, public API summaries, unresolved import reports, hotspot analysis, cycle detection, and shortest dependency paths.
Expand Down Expand Up @@ -96,6 +97,10 @@ node ./dist/cli.js orient --root . --budget small --pretty
node ./dist/cli.js search "build review report" --json
node ./dist/cli.js explain src/cli.ts

# optional lifecycle manifest and cache warmup
node ./dist/cli.js init --root .
node ./dist/cli.js status --root . --json

# optional runtime and artifact health check
node ./dist/cli.js doctor

Expand Down Expand Up @@ -140,6 +145,11 @@ codegraph orient --root . --budget small --pretty
codegraph search "build review report" --json
codegraph explain src/review.ts

# project lifecycle marker and cache warmup
codegraph init --root .
codegraph status --root . --json
codegraph sync --root .

# semantic navigation
codegraph goto <file> <line> <column>
codegraph refs --file src/index.ts --line 12 --col 17 --pretty
Expand Down
3 changes: 3 additions & 0 deletions codegraph-skill/codegraph/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ Then choose the smallest useful follow-up:
- review: `codegraph review --base HEAD --head WORKTREE --summary`
- drift: `codegraph drift ./src --base origin/main --head HEAD --pretty --graph-edges summary --public-api removals`
- installer: `codegraph install --target codex,claude --dry-run`
- lifecycle: `codegraph init --root .`, `codegraph status --root . --json`, `codegraph sync --root .`

Use `--root` to define the project boundary for config lookup, cache scope, path confinement, and output normalization.
For `orient`, `drift`, and positional graph commands, positional paths are include roots inside that project.
Use `codegraph install --target <ids> --yes` to configure supported local agent clients. Use `--dry-run` or `--print-config <target>` first; uninstall removes only Codegraph-owned marker blocks, marker files, or MCP entries whose command is `codegraph`.
Lifecycle commands own only `.codegraph/manifest.json` metadata. `init` and `sync` may warm or update `.codegraph-cache/index-v1/`; other commands do not depend on the manifest. Use `uninit` to remove recognized lifecycle state.
Lifecycle commands accept either a positional project path or `--root <path>`; never combine both.

## Output Choice

Expand Down
14 changes: 14 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ codegraph graph --root . --sql-artifacts --json
# SQLite export
codegraph graph --sqlite ./codegraph.sqlite

# Project lifecycle marker and cache warmup
codegraph init --root .
codegraph status --root . --json
codegraph sync --root .
codegraph uninit --root . --force

# Build and report diagnostics
codegraph graph --report
codegraph index --report
Expand All @@ -111,6 +117,14 @@ codegraph review --report --report-file review.report.json

Graph, index, and review reports include `backend.native.byLanguage` so native usage and fallback remain visible per language. Build reports also include `backend.parser` when syntax-tree backend degradation leaves files without parser context. Reports also include `graph.fallbackImportExtraction.byLanguage` and `byReason` when regex import extraction is used. Review JSON reports `diagnostics.symbolMappingParseFailures`, `diagnostics.missingFiles`, `changedFiles[].status` as `updated`, `deleted`, or `missing`, and `sqlContext` when changed SQL files or changed SQL literals make SQL artifact facts relevant.

### Project lifecycle

- `init` creates `.codegraph/manifest.json`, warms the existing disk cache through the index build path, and is idempotent when the manifest is current. Use `--force` to rebuild and overwrite the manifest metadata.
- `status` reports whether lifecycle metadata exists, last sync time, then/current file counts, config/build-option drift, analysis label, and the suggested next command. Use `--json` for `schemaVersion: 1`.
- `sync` refreshes the manifest after edits and requires an initialized project unless `--init` is passed.
- `uninit` removes only recognized lifecycle state by default. It refuses unknown `.codegraph/` entries unless `--force` is passed.
- Lifecycle commands accept either a positional project path or `--root <path>`. They reject using both together because lifecycle manifests always describe one project boundary, not include-root subsets.

### Symbols, navigation, grep, and chunking

```bash
Expand Down
48 changes: 47 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { handleGrepCommand } from "./cli/grep.js";
import { CLI_HELP_TEXT, helpTextForCommand, isKnownCliCommand } from "./cli/help.js";
import { handleImpactCommand } from "./cli/impact.js";
import { handleInstallerCommand } from "./cli/install.js";
import { handleLifecycleCommand } from "./cli/lifecycle.js";
import { CodegraphLifecycleUserError } from "./lifecycle/manifest.js";
import { handleIndexCommand } from "./cli/index.js";
import { handleHotspotsCommand, handleInspectCommand } from "./cli/inspect.js";
import { handleOrientCommand } from "./cli/orient.js";
Expand Down Expand Up @@ -80,6 +82,10 @@ function isExistingDirectory(filePath: string): boolean {
}
}

function isLifecycleCommand(command: string): command is "init" | "status" | "sync" | "uninit" {
return command === "init" || command === "status" || command === "sync" || command === "uninit";
}

function assertValidIncludeRoots(command: string, baseRoot: string, includeRoots: readonly string[]): void {
const globLikeRoot = includeRoots.find((includeRoot) => looksLikeGlobPattern(baseRoot, includeRoot));
if (!globLikeRoot) return;
Expand Down Expand Up @@ -213,6 +219,25 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
}

const firstPositionalRoot = parsed.positionals.length === 1 ? resolveAbs(parsed.positionals[0]!) : undefined;
if (isLifecycleCommand(cmd) && rootOpt && parsed.positionals.length) {
writeStderrLine(
`Invalid ${cmd} path "${parsed.positionals[0]!}". Positional paths cannot be combined with --root for lifecycle commands.`,
);
exitCli(2);
return;
}
if (
isLifecycleCommand(cmd) &&
!rootOpt &&
firstPositionalRoot !== undefined &&
!isExistingDirectory(firstPositionalRoot)
) {
writeStderrLine(
`Invalid ${cmd} path "${parsed.positionals[0]!}". Expected an existing directory or use --root <path>.`,
);
exitCli(2);
return;
}
const defaultProjectRoot =
(cmd === "graph" ||
cmd === "graph-delta" ||
Expand All @@ -221,7 +246,8 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
cmd === "hotspots" ||
cmd === "inspect" ||
cmd === "duplicates" ||
cmd === "impact") &&
cmd === "impact" ||
isLifecycleCommand(cmd)) &&
!rootOpt &&
firstPositionalRoot !== undefined &&
isExistingDirectory(firstPositionalRoot)
Expand Down Expand Up @@ -538,6 +564,26 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
return await resolveFilesFromRoots();
};

if (isLifecycleCommand(cmd)) {
try {
await handleLifecycleCommand({
command: cmd,
root: projectRootFs,
buildOptions: buildAgentOptions(),
hasFlag,
writeJSONLine,
writeStdoutLine,
});
} catch (error) {
if (error instanceof CodegraphLifecycleUserError) {
writeStderrLine(error.message);
exitCli(1);
}
throw error;
}
return;
}

if (cmd === "explore") {
await handleExploreCommand({
positionals: parsed.positionals,
Expand Down
31 changes: 31 additions & 0 deletions src/cli/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Commands:
drift Compare architecture health between refs or artifacts
mcp Serve MCP tools for agent graph navigation
index Build the project symbol index
init Initialize project-local Codegraph lifecycle metadata
status Inspect project-local Codegraph lifecycle metadata
sync Refresh project-local Codegraph lifecycle metadata
uninit Remove project-local Codegraph lifecycle metadata
goto Go to definition
refs Find references
deps List dependencies
Expand Down Expand Up @@ -84,6 +88,10 @@ Examples:
codegraph explore "how does auth reach db?" --pretty
codegraph explain src/auth.ts --json
codegraph impact --provider git --base HEAD --head WORKTREE
codegraph init --root .
codegraph status --root . --json
codegraph sync --root .
codegraph uninit --root . --force
codegraph packet get file:src%2Fcli.ts --json
codegraph artifact build --root . --out codegraph-out --json
codegraph mcp serve --root . --stdio
Expand Down Expand Up @@ -129,6 +137,7 @@ const knownCliCommands = new Set([
"hotspots",
"impact",
"index",
"init",
"install",
"inspect",
"mcp",
Expand All @@ -140,8 +149,11 @@ const knownCliCommands = new Set([
"review",
"search",
"skill",
"status",
"sync",
"sql",
"unresolved",
"uninit",
"version",
"uninstall",
]);
Expand All @@ -150,6 +162,23 @@ export function isKnownCliCommand(command: string): boolean {
return knownCliCommands.has(command);
}

export const LIFECYCLE_HELP_TEXT = `codegraph init/status/sync/uninit - Initialize, inspect, refresh, or remove project-local Codegraph state

Usage:
codegraph init [path] [--force] [--json]
codegraph init --root <path> [--force] [--json]
codegraph status [path] [--json]
codegraph status --root <path> [--json]
codegraph sync [path] [--init] [--json]
codegraph sync --root <path> [--init] [--json]
codegraph uninit [path] [--force] [--json]
codegraph uninit --root <path> [--force] [--json]

State:
Lifecycle commands own only .codegraph/manifest.json metadata. Init and sync may warm or update the disk cache under .codegraph-cache/index-v1/. Other commands do not depend on the manifest.
Positional paths and --root are alternatives for lifecycle commands; do not combine them.
`;

export const INSTALL_HELP_TEXT = `codegraph install - Configure Codegraph for supported agent clients

Usage: codegraph install [target] [--target <codex,claude,cursor,gemini,opencode,agents>] [--yes | --dry-run] [--print-config <target>] [--detect] [--json]
Expand Down Expand Up @@ -388,6 +417,8 @@ export function helpTextForCommand(command: string, positionals: readonly string
if (command === "explain") return EXPLAIN_HELP_TEXT;
if (command === "install") return INSTALL_HELP_TEXT;
if (command === "uninstall") return UNINSTALL_HELP_TEXT;
if (command === "init" || command === "status" || command === "sync" || command === "uninit")
return LIFECYCLE_HELP_TEXT;
if (command === "drift") return DRIFT_HELP_TEXT;
if (command === "duplicates") return DUPLICATES_HELP_TEXT;
if (command === "artifact") return ARTIFACT_HELP_TEXT;
Expand Down
92 changes: 92 additions & 0 deletions src/cli/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { BuildOptions } from "../indexer/types.js";
import {
getCodegraphLifecycleStatus,
initCodegraphLifecycle,
syncCodegraphLifecycle,
uninitCodegraphLifecycle,
type CodegraphLifecycleStatus,
type CodegraphLifecycleSyncResult,
type CodegraphLifecycleUninitResult,
} from "../lifecycle/manifest.js";

export type LifecycleCommandContext = {
command: "init" | "status" | "sync" | "uninit";
root: string;
buildOptions?: BuildOptions;
hasFlag: (name: string) => boolean;
writeJSONLine: (value: unknown) => void;
writeStdoutLine: (message: string) => void;
};

export async function handleLifecycleCommand(context: LifecycleCommandContext): Promise<void> {
if (context.command === "init") {
const result = await initCodegraphLifecycle(context.root, {
...(context.buildOptions ? { buildOptions: context.buildOptions } : {}),
force: context.hasFlag("--force"),
});
writeLifecycleResult(context, result, formatSyncResult("Initialized", result));
return;
}

if (context.command === "sync") {
const result = await syncCodegraphLifecycle(context.root, {
...(context.buildOptions ? { buildOptions: context.buildOptions } : {}),
init: context.hasFlag("--init"),
});
writeLifecycleResult(context, result, formatSyncResult("Synced", result));
return;
}

if (context.command === "uninit") {
const result = await uninitCodegraphLifecycle(context.root, { force: context.hasFlag("--force") });
writeLifecycleResult(context, result, formatUninitResult(result));
return;
}

const result = await getCodegraphLifecycleStatus(context.root, {
...(context.buildOptions ? { buildOptions: context.buildOptions } : {}),
});
writeLifecycleResult(context, result, formatStatus(result));
}

function writeLifecycleResult(
context: LifecycleCommandContext,
value: CodegraphLifecycleStatus | CodegraphLifecycleSyncResult | CodegraphLifecycleUninitResult,
pretty: string,
): void {
if (context.hasFlag("--json")) {
context.writeJSONLine(value);
} else {
context.writeStdoutLine(pretty);
}
}

function formatSyncResult(label: string, result: CodegraphLifecycleSyncResult): string {
const delta = result.changedFiles.totalDelta;
const deltaLabel = delta ? `, delta ${delta}` : "";
return `${label} Codegraph at ${result.root}: ${result.manifest.fileCount} files${deltaLabel}. Manifest: ${result.manifestPath}`;
}

function formatUninitResult(result: CodegraphLifecycleUninitResult): string {
if (!result.removed) return `Codegraph is not initialized at ${result.root}.`;
return `Removed Codegraph lifecycle state at ${result.root}.`;
}

function formatStatus(status: CodegraphLifecycleStatus): string {
if (!status.initialized) {
return `Codegraph is not initialized at ${status.root}. Next: ${status.suggestedNextCommand}`;
}
const fileCount = status.fileCount ? `${status.fileCount.then} then, ${status.fileCount.current} current` : "unknown";
const configChanged = status.configChanged ? "yes" : "no";
const buildOptionsChanged = status.buildOptionsChanged ? "yes" : "no";
const analysis = status.analysis?.label ?? "unknown";
return [
`Codegraph initialized at ${status.root}.`,
`Last sync: ${status.lastSyncAt ?? "unknown"}`,
`Files: ${fileCount}`,
`Config changed: ${configChanged}`,
`Build options changed: ${buildOptionsChanged}`,
`Analysis: ${analysis}`,
`Next: ${status.suggestedNextCommand}`,
].join("\n");
}
32 changes: 32 additions & 0 deletions src/cli/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,14 @@ const CLI_COMMAND_SCHEMAS = new Map<string, CliCommandSchema>([
),
],
["index", graphCommandSchema({ kind: "any" })],
[
"init",
commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS, "--force"], SHARED_BUILD_OPTIONS, {
kind: "max",
max: 1,
usage: "Usage: codegraph init [path] [--force] [--json] OR codegraph init --root <path> [--force] [--json]",
}),
],
[
"install",
commandSchema(["--detect", "--dry-run", "--json", "--yes"], ["--print-config", "--target"], {
Expand Down Expand Up @@ -480,13 +488,37 @@ const CLI_COMMAND_SCHEMAS = new Map<string, CliCommandSchema>([
usage: "Usage: codegraph skill <install|print-path|doctor> [--agent <name> | --target <dir>] [--force]",
}),
],
[
"status",
commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS], SHARED_BUILD_OPTIONS, {
kind: "max",
max: 1,
usage: "Usage: codegraph status [path] [--json] OR codegraph status --root <path> [--json]",
}),
],
[
"sync",
commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS, "--init"], SHARED_BUILD_OPTIONS, {
kind: "max",
max: 1,
usage: "Usage: codegraph sync [path] [--init] [--json] OR codegraph sync --root <path> [--init] [--json]",
}),
],
[
"sql",
commandSchema(["--json"], ["--db", "--query", "--sqlite"], {
kind: "none",
usage: 'Usage: codegraph sql --db <sqlite path> --query "SELECT ..."',
}),
],
[
"uninit",
commandSchema(["--force", "--json"], ["--root"], {
kind: "max",
max: 1,
usage: "Usage: codegraph uninit [path] [--force] [--json] OR codegraph uninit --root <path> [--force] [--json]",
}),
],
[
"unresolved",
commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS, "--verbose"], SHARED_BUILD_OPTIONS, {
Expand Down
Loading