Files
orca/src/cli/agent-context.ts
T
e2b4bc2c2c feat(cli): make the CLI self-correcting and self-describing for agents (#6303)
* feat(cli): make the CLI self-correcting and self-describing for agents

Agents build a generalized model of how CLIs work and apply it to every
tool. When orca diverged — `rm` where git uses `remove` — a reasonable
first guess (`orca worktree remove`) dead-ended on a bare "Unknown
command" with no path forward. This makes the CLI degrade gracefully when
the orca-cli skill isn't loaded in context.

- First-class CommandSpec.aliases, resolved to the canonical path before
  dispatch (no new handler registrations). `worktree remove`/`delete` now
  resolve to `rm`; the ad-hoc `terminal focus` duplicate spec/handler is
  migrated onto the mechanism.
- Did-you-mean suggestions on unknown commands and unknown flags, ranked
  by edit distance over the live registry, surfaced in both stderr and
  --json error.data (reusing the existing nextSteps channel).
- `orca agent-context [--json]`: a versioned, machine-readable dump of the
  command schema. Pure local read (no RPC), so it works over SSH and when
  the app isn't running.
- CI guards: specs<->handlers parity, and a vocabulary policy that fails
  on new off-policy deletion/read verbs (existing ones grandfathered).

* Address PR review feedback (#6303)

- agent-context now emits each command's effective flag set (globals +
  conditional --page), not just allowedFlags, so the schema no longer
  under-reports --json/--help. Shared as effectiveAllowedFlags() between
  validation and the schema.
- Collision check now covers alias paths too, so a duplicate alias that
  would silently shadow a real command fails the build.

* fix(cli): harden agent recovery and introspection

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-10 19:17:01 -07:00

61 lines
1.9 KiB
TypeScript

import type { CommandSpec } from './args'
import { effectiveAllowedFlags } from './args'
// Why: serialize the live spec table so agent discovery cannot drift from the
// command surface it describes.
const SCHEMA_VERSION = 1
export type AgentContextCommand = {
command: string
path: string[]
aliases: string[][]
argumentMode: 'parsed' | 'passthrough'
summary: string
usage: string
flags: string[]
positionalArgs: string[]
examples: string[]
notes: string[]
}
export type AgentContextSchema = {
schemaVersion: number
commandCount: number
commands: AgentContextCommand[]
}
export function buildAgentContext(specs: CommandSpec[]): AgentContextSchema {
const commands = specs
.map((spec) => ({
command: spec.path.join(' '),
path: spec.path,
aliases: spec.aliases ?? [],
argumentMode: spec.argumentMode ?? 'parsed',
summary: spec.summary,
usage: spec.usage,
// Why: the effective accepted set (globals + conditional --page), not just
// allowedFlags — otherwise agents treat --json/--help as unsupported.
flags: effectiveAllowedFlags(spec),
positionalArgs: spec.positionalArgs ?? [],
examples: spec.examples ?? [],
notes: spec.notes ?? []
}))
// Why: deterministic ordering so the JSON diffs cleanly across runs.
.sort((a, b) => a.command.localeCompare(b.command))
return {
schemaVersion: SCHEMA_VERSION,
commandCount: commands.length,
commands
}
}
export function formatAgentContextSummary(schema: AgentContextSchema): string {
// Why: keep the default (human) output bounded — the full surface is large, so
// point the reader at --json rather than dumping every command.
return [
`${schema.commandCount} commands (schema v${schema.schemaVersion}).`,
'Run `orca agent-context --json` for the full machine-readable command schema.'
].join('\n')
}