Files
orca/src/cli/skill-guide-cli-parity.test.ts
T
Jinwoo Hong fb322046e8 skills: rewrite and trim the seven non-orchestration guides (#19128)
* skills: rewrite the seven non-orchestration guides to one outcome-first standard

Every guide leads with Result / Done / Safe failure, states conditions instead of case lists, keeps one done bar and one autonomy envelope, and loads references at the point of use via `skills get <topic> --full`. orca-cli drops from 424 to 260 always-loaded lines with three references; orca-per-workspace-env from 794 to 397 with five.

Defects fixed in shipped guides: `emulator camera` (no such command), iOS `permissions` (backend refuses it), Android pane described as in development, `relayGracePeriodSeconds: 0` documented as immediate teardown (it is unbounded), doctor `ok: true` hiding `warn`, an SSH exemplar setting both `jumpHost` and `proxyCommand`, a provisioned-root fetch from `origin`, and the Linear unconfirmed-write rule keyed on four verbs when ten emit it.

The resolver ladder, placeholder rule, and older-binary fallback shared by every installable SKILL.md now come from one skill-stubs/_shared/cli-resolution.md fragment composed by the generator, which also bundles per-guide references into --full. New guards: every ORCA invocation and flag resolves against COMMAND_SPECS, descriptions carry no angle-bracket tokens, reference routing is checked both ways, and an always-loaded size ratchet (300 lines) that guides may leave but never join.

* skills: address review on the SSH recipe and the parity guard

- ssh-host create script: route the bootstrap ssh through the chosen jump host or proxy command, refuse both at once, use StrictHostKeyChecking=accept-new instead of a blind ssh-keyscan append, and pass gh_token/project_root/repo_url/repo_ref to the remote bash via printf %q so a quote in a value cannot break out of the command.
- per-workspace-env envelope: the step-10 workspace test the user asked for is no longer forbidden by the same paragraph.
- linear guides: name the full verb, ORCA linear list-issues.
- parity guard: a prefix reference such as ORCA linear --help or ORCA emulator --webcam now has its flags checked against every command under that prefix; only an exact path or an explicit ... was checked before.

* skills: tighten prose in the seven rewritten guides

Shorter outcome spines, one idea per sentence, no restated rationale after a rule. No rule, command, or pinned phrase changes; 47 net lines fewer across the guides and references.

* skills: route orca-cli and per-workspace-env gates through --reference

Both guides told agents to load --full at a gate because the per-reference
selector did not exist when they were written. Now that main serves
`skills get <topic> --reference references/<file>.md`, load only the
named file and keep --full as the fallback for an older CLI, matching the
orchestration kernel.

* skills: drop outcome-spine boilerplate from the CLI-wrapper guides

The Result/Done/Safe-failure preambles and Next Action closers restated
rules the body already carries. Agents stop fine without them, and for
a CLI wrapper the command surface is the guide. Keeps the one substantive
rule computer-use's Done block added (never report unverified as success)
inside Action Rules. orchestration and per-workspace-env keep theirs:
those are multi-step workflows where the done bar is load-bearing.

(cherry picked from commit 44a74baf73)

* skills: trim the guides and stubs to what agents actually need

- Drop the Result/Done/Safe-failure preambles and Next Action closers from
  the six CLI-wrapper guides; the one substantive rule (never report an
  unverified computer-use action as success) moves into Action Rules.
- Drop the 'guide may be stale, trust --help' lines: the guide is served by
  the binary that runs the commands, so it cannot be stale relative to it.
- Drop the status --json / open --json preflight from every guide; the stub
  no-guessing paragraph now says to start Orca only when a command reports
  it is not running.
- Cut the ORCA placeholder paragraph in each guide to one line that points
  back at the stub's resolution.
- Trim the orchestration, orca-cli, and computer-use descriptions to trigger
  phrases plus one line of scope.
- Remove the older-binary fallback section from every stub (and its two
  shared blocks); a binary without skills get gets one sentence.
- Remove the guide size ratchet test.

* skills: apply independent review cleanup

* skills: clarify guide loading and Linear command discovery

* skills: harden environment recipe examples

* test: complete branch rename journal doubles

* skills: clarify custom Codex launch and refresh model example

* test: deduplicate journal fix now present on main
2026-09-07 00:03:48 -04:00

190 lines
6.4 KiB
TypeScript

import { readdirSync, readFileSync } from 'node:fs'
import { join, relative, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { CLI_GLOBAL_FLAGS } from '../shared/cli-argument-boundary'
import { specPaths } from './command-spec'
import { COMMAND_SPECS } from './specs'
// Why: a guide is the version-matched surface for the binary that shipped it, so a command
// path or flag it names must exist in COMMAND_SPECS. `orca emulator camera --webcam` was
// documented for months without ever existing (#16904 review C1).
// Why __dirname: it works under both Vitest and the CommonJS tsc emit that build:cli type-checks
// this file against; import.meta.dirname does not (TS1470).
const projectDir = resolve(__dirname, '..', '..')
const guideRoot = join(projectDir, 'skill-guides')
const MAX_COMMAND_DEPTH = 3
type Invocation = { file: string; line: number; text: string }
function guideFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const full = join(directory, entry.name)
if (entry.isDirectory()) {
return guideFiles(full)
}
return entry.isFile() && entry.name.endsWith('.md') ? [full] : []
})
}
/**
* The invocation span is the command text only — never the surrounding prose or table cell.
* `skill-guides/orca-emulator.md` describes serve-sim's own `--detach` in a Notes column beside
* an `ORCA ...` cell, and that is correct prose a line-scoped check would flag.
*/
function invocationSpans(contents: string, file: string): Invocation[] {
const found: Invocation[] = []
let inFence = false
contents.split(/\r?\n/u).forEach((line, index) => {
if (/^\s*(?:```|~~~)/u.test(line)) {
inFence = !inFence
return
}
const spans = inFence ? [line] : [...line.matchAll(/`([^`]+)`/gu)].map((match) => match[1])
for (const span of spans) {
const starts = [...span.matchAll(/\bORCA\b/gu)].map((match) => match.index)
starts.forEach((start, position) => {
found.push({
file,
line: index + 1,
text: span.slice(start, starts[position + 1] ?? span.length).trim()
})
})
}
})
return found
}
/** Blank out quoted values so a nested `--model` inside `--command "codex --model ..."` is not read as a flag. */
function maskQuotedValues(text: string): string {
let masked = ''
let quote: string | null = null
for (const character of text) {
if (quote) {
masked += character === quote ? character : ' '
if (character === quote) {
quote = null
}
} else if (character === '"' || character === "'") {
quote = character
masked += character
} else {
masked += character
}
}
return masked
}
const specByPath = new Map<string, (typeof COMMAND_SPECS)[number]>()
const pathPrefixes = new Set<string>()
for (const spec of COMMAND_SPECS) {
for (const path of specPaths(spec)) {
specByPath.set(path.join(' '), spec)
for (let length = 1; length < path.length; length += 1) {
pathPrefixes.add(path.slice(0, length).join(' '))
}
}
}
function longestKnownPrefix(tokens: string[]): string | null {
for (let length = tokens.length; length >= 1; length -= 1) {
const candidate = tokens.slice(0, length).join(' ')
if (specByPath.has(candidate) || pathPrefixes.has(candidate)) {
return candidate
}
}
return null
}
function allowedFlagsFor(prefix: string): Set<string> {
const exact = specByPath.get(prefix)
const flags = new Set<string>(CLI_GLOBAL_FLAGS)
const specs = exact
? [exact]
: COMMAND_SPECS.filter((spec) =>
specPaths(spec).some((path) => path.join(' ').startsWith(`${prefix} `))
)
for (const spec of specs) {
for (const flag of spec.allowedFlags) {
flags.add(flag)
}
}
return flags
}
function describeFailure(invocation: Invocation, detail: string): string {
const location = `${relative(projectDir, invocation.file)}:${invocation.line}`
return `${location}: ${detail}\n ${invocation.text}`
}
function parityFailures(invocation: Invocation): string[] {
const masked = maskQuotedValues(invocation.text).replace(/\s#.*$/u, '')
const tokens: string[] = []
for (const token of masked.slice('ORCA'.length).trim().split(/\s+/u)) {
if (!/^[a-z][a-z0-9-]*$/u.test(token) || tokens.length === MAX_COMMAND_DEPTH) {
break
}
tokens.push(token)
}
if (tokens.length === 0) {
return []
}
const failures: string[] = []
let command: string | null = null
for (let length = tokens.length; length >= 1 && command === null; length -= 1) {
const candidate = tokens.slice(0, length).join(' ')
if (specByPath.has(candidate)) {
command = candidate
}
}
if (command === null) {
// A prefix reference such as `ORCA emulator ...` or `ORCA linear --help` names no exact
// path, but its flags still have to belong to some command under that prefix.
if (pathPrefixes.has(tokens.join(' '))) {
command = tokens.join(' ')
}
}
if (command === null) {
failures.push(
describeFailure(invocation, `no COMMAND_SPECS path or alias for "${tokens.join(' ')}"`)
)
command = longestKnownPrefix(tokens)
if (command === null) {
return failures
}
}
const allowed = allowedFlagsFor(command)
for (const match of masked.matchAll(/--([a-z][a-z0-9-]*)/gu)) {
if (!allowed.has(match[1])) {
failures.push(describeFailure(invocation, `--${match[1]} is not a flag of "${command}"`))
}
}
return failures
}
describe('skill guides only name commands and flags the CLI defines', () => {
const invocations = guideFiles(guideRoot).flatMap((file) =>
invocationSpans(readFileSync(file, 'utf8'), file)
)
it('extracts a nonempty invocation corpus across guides and references', () => {
expect(invocations.length).toBeGreaterThan(150)
expect(new Set(invocations.map((invocation) => invocation.file)).size).toBeGreaterThan(8)
})
it('checks extracted ORCA command paths and flags against COMMAND_SPECS', () => {
expect(invocations.flatMap(parityFailures)).toEqual([])
})
it('checks flags on a prefix reference against every command under it', () => {
const at = (text: string) => parityFailures({ file: 'x.md', line: 1, text })
expect(at('ORCA emulator ...')).toEqual([])
expect(at('ORCA linear --help')).toEqual([])
expect(at('ORCA emulator --webcam')).toEqual([
expect.stringContaining('--webcam is not a flag of "emulator"')
])
})
})