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
10 changes: 10 additions & 0 deletions ix-cli/src/cli/__tests__/unresolved-machine-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ describe("unresolved targets in machine formats", () => {
["context", async () => (await import("../commands/context.js")).registerContextCommand],
["explain", async () => (await import("../commands/explain.js")).registerExplainCommand],
["read", async () => (await import("../commands/read.js")).registerReadCommand],
["overview", async () => (await import("../commands/overview.js")).registerOverviewCommand],
["impact", async () => (await import("../commands/impact.js")).registerImpactCommand],
["contains", async () => (await import("../commands/contains.js")).registerContainsCommand],
["callers", async () => (await import("../commands/callers.js")).registerCallersCommand],
["callees", async () => (await import("../commands/callers.js")).registerCallersCommand],
["imports", async () => (await import("../commands/imports.js")).registerImportsCommand],
["imported-by", async () => (await import("../commands/imports.js")).registerImportsCommand],
["depends", async () => (await import("../commands/depends.js")).registerDependsCommand],
["trace", async () => (await import("../commands/trace.js")).registerTraceCommand],
["history", async () => (await import("../commands/history.js")).registerHistoryCommand],
] as const)("returns JSON and a non-zero status from ix %s", async (command, loadRegister) => {
const result = await run(await loadRegister(), [command, "DefinitelyMissing", "--format", "json"]);

Expand Down
12 changes: 4 additions & 8 deletions ix-cli/src/cli/commands/callers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ import { formatEdgeResults, relativePath } from "../format.js";
import { parsePickOption } from "../options.js";
import { resolveFileOrEntity, printResolved } from "../resolve.js";
import { stderr } from "../stderr.js";
import { llmLine, llmError } from "../llm.js";

/** Emit a structured llm error for a failed resolution, or nothing for other formats. */
function llmUnresolved(format: string, symbol: string): void {
if (format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
}
import { llmLine } from "../llm.js";
import { reportUnresolvedTarget } from "../ui.js";

const execFileAsync = promisify(execFile);

Expand All @@ -31,7 +27,7 @@ export function registerCallersCommand(program: Command): void {
const limit = parseInt(opts.limit, 10);
const resolveOpts = { kind: opts.kind, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) { llmUnresolved(opts.format, symbol); return; }
if (!target) { reportUnresolvedTarget(symbol, opts.format); return; }
if (opts.format === "text") printResolved(target);
// Use expand by entity ID to avoid aggregating results across all same-named entities
const result = await client.expand(target.id, {
Expand Down Expand Up @@ -134,7 +130,7 @@ export function registerCallersCommand(program: Command): void {
const calleeLimit = parseInt(opts.limit, 10);
const resolveOpts = { kind: opts.kind, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) { llmUnresolved(opts.format, symbol); return; }
if (!target) { reportUnresolvedTarget(symbol, opts.format); return; }
if (opts.format === "text") printResolved(target);
// Use expand by entity ID to avoid aggregating results across all same-named entities
const result = await client.expand(target.id, {
Expand Down
4 changes: 2 additions & 2 deletions ix-cli/src/cli/commands/contains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getEndpoint } from "../config.js";
import { formatEdgeResults } from "../format.js";
import { parsePickOption } from "../options.js";
import { resolveFileOrEntity, printResolved } from "../resolve.js";
import { llmError } from "../llm.js";
import { reportUnresolvedTarget } from "../ui.js";

export function registerContainsCommand(program: Command): void {
program
Expand All @@ -22,7 +22,7 @@ export function registerContainsCommand(program: Command): void {
const resolveOpts = { kind: opts.kind, path: opts.path, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) {
if (opts.format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
reportUnresolvedTarget(symbol, opts.format);
return;
}
if (opts.format === "text") printResolved(target);
Expand Down
5 changes: 3 additions & 2 deletions ix-cli/src/cli/commands/depends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { IxClient } from "../../client/api.js";
import { getEndpoint } from "../config.js";
import { resolveFileOrEntity, printResolved, isRawId } from "../resolve.js";
import { compactTreeNode, relativePath } from "../format.js";
import { llmLine, llmError } from "../llm.js";
import { llmLine } from "../llm.js";
import { parsePickOption } from "../options.js";
import { reportUnresolvedTarget } from "../ui.js";

// ── Tree types ──────────────────────────────────────────────────────

Expand Down Expand Up @@ -216,7 +217,7 @@ export function registerDependsCommand(program: Command): void {
};
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) {
if (opts.format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
reportUnresolvedTarget(symbol, opts.format);
return;
}

Expand Down
5 changes: 3 additions & 2 deletions ix-cli/src/cli/commands/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { IxClient } from "../../client/api.js";
import { getEndpoint } from "../config.js";
import { resolveFileOrEntity, printResolved } from "../resolve.js";
import { relativePath } from "../format.js";
import { llmLine, llmError } from "../llm.js";
import { llmLine } from "../llm.js";
import { parsePickOption } from "../options.js";
import { reportUnresolvedTarget } from "../ui.js";

/** Render an entity's provenance chain as llm records: a header then one `patch` row per revision. */
export function renderHistoryLlm(
Expand Down Expand Up @@ -41,7 +42,7 @@ export function registerHistoryCommand(program: Command): void {
const resolveOpts = { kind: opts.kind, path: opts.path, pick: opts.pick };
const resolved = await resolveFileOrEntity(client, target, resolveOpts);
if (!resolved) {
if (opts.format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${target}".`));
reportUnresolvedTarget(target, opts.format);
return;
}

Expand Down
8 changes: 3 additions & 5 deletions ix-cli/src/cli/commands/impact.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Command } from "commander";
import chalk from "chalk";
import { renderSection, renderKeyValue, renderNote, renderResolvedHeader, colorizeKind } from "../ui.js";
import { renderSection, renderKeyValue, renderNote, renderResolvedHeader, colorizeKind, reportUnresolvedTarget } from "../ui.js";
import { IxClient } from "../../client/api.js";
import { getEndpoint } from "../config.js";
import { resolveFileOrEntity, printResolved } from "../resolve.js";
import { bucketByHierarchy, getSystemPath, formatSystemPath, hasMapData, type SystemPath } from "../hierarchy.js";
import { inferRiskSemantics, humanizeLabel, type ImpactFacts, type RiskSemantics } from "../impact/risk-semantics.js";
import { stripNulls } from "../format.js";
import { llmLine, llmError } from "../llm.js";
import { llmLine } from "../llm.js";
import { parsePickOption } from "../options.js";

const CONTAINER_KINDS = new Set(["class", "module", "file", "object", "trait", "interface"]);
Expand Down Expand Up @@ -37,9 +37,7 @@
const resolveOpts = { kind: opts.kind, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) {
// The resolver already printed human guidance to stderr; for llm
// consumers emit a structured error record on stdout as well.
if (opts.format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
reportUnresolvedTarget(symbol, opts.format);
return;
}

Expand Down Expand Up @@ -115,7 +113,7 @@
risk: RiskSemantics,
topMembers: Array<{ name: string; kind: string; callerCount: number }>,
callerNames?: string[],
calleeNames?: string[],

Check warning on line 116 in ix-cli/src/cli/commands/impact.ts

View workflow job for this annotation

GitHub Actions / Lint & Typecheck

'calleeNames' is defined but never used. Allowed unused args must match /^_/u
): void {
const items: string[] = [];

Expand Down
10 changes: 3 additions & 7 deletions ix-cli/src/cli/commands/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ import { IxClient } from "../../client/api.js";
import { getEndpoint } from "../config.js";
import { formatEdgeResults } from "../format.js";
import { resolveFileOrEntity, printResolved } from "../resolve.js";
import { llmError } from "../llm.js";
import { parsePickOption } from "../options.js";

function llmUnresolved(format: string, symbol: string): void {
if (format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
}
import { reportUnresolvedTarget } from "../ui.js";

export function registerImportsCommand(program: Command): void {
program
Expand All @@ -24,7 +20,7 @@ export function registerImportsCommand(program: Command): void {
const limit = parseInt(opts.limit, 10);
const resolveOpts = { kind: opts.kind, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) { llmUnresolved(opts.format, symbol); return; }
if (!target) { reportUnresolvedTarget(symbol, opts.format); return; }
if (opts.format === "text") printResolved(target);
const result = await client.expand(target.id, { direction: "out", predicates: ["IMPORTS"] });
formatEdgeResults(result.nodes.slice(0, limit), "imports", target.name, opts.format, target, "graph");
Expand All @@ -43,7 +39,7 @@ export function registerImportsCommand(program: Command): void {
const limit = parseInt(opts.limit, 10);
const resolveOpts = { kind: opts.kind, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) { llmUnresolved(opts.format, symbol); return; }
if (!target) { reportUnresolvedTarget(symbol, opts.format); return; }
if (opts.format === "text") printResolved(target);
const result = await client.expand(target.id, { direction: "in", predicates: ["IMPORTS"] });
formatEdgeResults(result.nodes.slice(0, limit), "imported-by", target.name, opts.format, target, "graph");
Expand Down
7 changes: 3 additions & 4 deletions ix-cli/src/cli/commands/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { resolveFileOrEntity, printResolved } from "../resolve.js";
import { getEffectiveSystemPath, getSystemPath, hasMapData } from "../hierarchy.js";
import { humanizeLabel } from "../impact/risk-semantics.js";
import { relativePath } from "../format.js";
import { llmLine, llmError, type LlmValue } from "../llm.js";
import { llmLine, type LlmValue } from "../llm.js";
import { parsePickOption } from "../options.js";
import { renderSection, renderKeyValue, renderNote, renderBreadcrumb } from "../ui.js";
import { renderSection, renderKeyValue, renderNote, renderBreadcrumb, reportUnresolvedTarget } from "../ui.js";

const CONTAINER_KINDS = new Set(["class", "module", "file", "trait", "object", "interface"]);
const STRUCTURAL_CONTAINER_KINDS = new Set(["class", "object", "trait", "interface"]);
Expand Down Expand Up @@ -58,8 +58,7 @@ Examples:
const resolveOpts = { kind: opts.kind, path: opts.path, pick: opts.pick };
const target = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!target) {
// Resolver printed human guidance to stderr; add a structured record for llm.
if (opts.format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
reportUnresolvedTarget(symbol, opts.format);
return;
}

Expand Down
8 changes: 4 additions & 4 deletions ix-cli/src/cli/commands/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { IxClient } from "../../client/api.js";
import { getEndpoint } from "../config.js";
import { resolveFileOrEntity, isRawId, activeReadScope, ensureReadScope } from "../resolve.js";
import type { ResolvedEntity } from "../resolve.js";
import { renderSection, renderKeyValue, renderResolvedHeader, colorizeKind } from "../ui.js";
import { renderSection, renderKeyValue, renderResolvedHeader, colorizeKind, reportUnresolvedTarget } from "../ui.js";
import { compactTreeNode, relativePath } from "../format.js";
import { llmLine, llmError, type LlmValue } from "../llm.js";
import { llmLine, type LlmValue } from "../llm.js";
import { parsePickOption } from "../options.js";

// ── Types ────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -463,7 +463,7 @@ export function registerTraceCommand(program: Command): void {
resolveFileOrEntity(client, opts.to, toResolveOpts),
]);
if (!fromTarget || !toTarget) {
if (opts.format === "llm") console.log(llmError("unresolved_target", `Could not resolve ${!fromTarget ? symbol : opts.to}.`));
reportUnresolvedTarget(!fromTarget ? symbol : opts.to, opts.format);
return;
}

Expand Down Expand Up @@ -541,7 +541,7 @@ export function registerTraceCommand(program: Command): void {
// ── Directional mode ────────────────────────────────────────
const resolvedTarget = await resolveFileOrEntity(client, symbol, resolveOpts);
if (!resolvedTarget) {
if (opts.format === "llm") console.log(llmError("unresolved_target", `No entity resolved for "${symbol}".`));
reportUnresolvedTarget(symbol, opts.format);
return;
}
let target: ResolvedEntity = resolvedTarget;
Expand Down
Loading