Files
orca/src/cli/vocabulary-policy.test.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

54 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { CommandSpec } from './args'
import { COMMAND_SPECS } from './specs'
import { findVocabularyViolations } from './vocabulary-policy'
const spec = (path: string[], aliases?: string[][]): CommandSpec => ({
path,
aliases,
summary: 's',
usage: 'u',
allowedFlags: []
})
describe('vocabulary policy (live tree)', () => {
it('has no off-policy verbs that are not grandfathered or alias-bridged', () => {
expect(findVocabularyViolations(COMMAND_SPECS)).toEqual([])
})
})
describe('findVocabularyViolations (fixtures)', () => {
it('flags a new deletion command using an off-policy verb', () => {
const violations = findVocabularyViolations([spec(['gadget', 'delete'])])
expect(violations).toHaveLength(1)
expect(violations[0]).toMatchObject({ command: 'gadget delete', canonical: 'rm' })
})
it('passes a deletion command that bridges to the canonical verb via an alias', () => {
const violations = findVocabularyViolations([spec(['gadget', 'delete'], [['gadget', 'rm']])])
expect(violations).toEqual([])
})
it('rejects a canonical verb alias under an unrelated command prefix', () => {
const violations = findVocabularyViolations([spec(['gadget', 'delete'], [['other', 'rm']])])
expect(violations).toHaveLength(1)
})
it('rejects a canonical verb alias at a different command depth', () => {
const violations = findVocabularyViolations([
spec(['gadget', 'delete'], [['gadget', 'nested', 'rm']])
])
expect(violations).toHaveLength(1)
})
it('passes the canonical deletion verb outright', () => {
expect(findVocabularyViolations([spec(['gadget', 'rm'])])).toEqual([])
})
it('flags a new single-item read using get instead of show', () => {
const violations = findVocabularyViolations([spec(['gadget', 'get'])])
expect(violations[0]).toMatchObject({ verb: 'get', canonical: 'show' })
})
})