Add a `cuo` verb to `cs` as a redirect stub
TASK-IMP-132: Add a cuo verb to cs as a redirect stub
Summary
Add cuo as a top-level verb in cs that recognises the four LLM-orchestrated workflow names (plan, create-tasks, ship-tasks, improve) and prints which slash command to run inside an agent session, rather than attempting to execute anything standalone.
Problem
docs/plans/PLAN-cli-module-namespacing-2026-07-22/plan.md §2 identifies modules/cuo as backing four multi-step, LLM-orchestrated workflows — /plan, /create-tasks, /ship-tasks, /improve — each involving interview, drafting, self-audit, and HITL approval gates. These are not deterministic scripts cli.mjs can spawnSync and get a meaningful result from outside an agent session; running cyberos-cuo's own Python entry point directly (as README.md:47 shows for local dev, cyberos-cuo list-personas) exposes internal persona/workflow machinery, not the guided, gated experience the slash commands provide. Today, cs (post TASK-IMP-130) has no cuo verb at all — a user who types cs cuo plan gets "unknown command," with no signal that the capability exists under a different invocation.
Proposed Solution
Add a cuo entry to cli.mjs's dispatch. cs cuo <name> where <name> is one of plan, create-tasks, ship-tasks, improve prints a short message naming the matching slash command (/plan, /create-tasks, /ship-tasks, /improve respectively) to run inside a Claude Code (or other agent) session, and exits 0 — it is documentation output, not an error. cs cuo with no name, or an unrecognised name, lists all four valid names. No subprocess is spawned; no attempt is made to run the workflow headlessly.
Alternatives Considered
- Attempt to drive the CUO workflow headlessly from the CLI (e.g. shelling out to
cyberos-cuo's Python entry point with a scripted agent loop). Rejected explicitly by the plan (§5 item 4: "do not implement standalone execution in this task") — building real headless/agentic execution is deferred as its own follow-up decision (plan §5, out of scope bullet), not something this task should quietly attempt. - Have
cs cuo <name>shell out tocyberos-cuo list-personasor similar to at least show something real rather than a static message. Rejected: this would expose internal persona/workflow machinery not meant for direct end-user consumption, and risks implying standalone execution works when it does not. - Recognise install/uninstall/version/status/help under
cuoas well, for a single unified namespace. Rejected: those five are deterministic shell scripts with their own existing flatcsverbs (cli.mjs:19-26); nesting them undercuotoo would contradict plan §2's own framing of what CUO backs, and would makecs cuo installseem like a legitimate synonym forcs installwhen it is not.
Success Metrics
- Primary:
cs cuo plan,cs cuo create-tasks,cs cuo ship-tasks, andcs cuo improveeach print the correct matching slash command name and exit0. Baseline today:cuois not incli.mjs'sSCRIPTStable; anycyberos cuo ...(or post-renamecs cuo ...) invocation falls through to "unknown command." - Guardrail:
cs cuowith no argument, andcs cuo bogus-name, both list all four valid names and exit0— neither is treated as an error, since the stub's entire job is orientation, not validation.
Scope
In scope: the cuo dispatch entry in cli.mjs recognising exactly plan, create-tasks, ship-tasks, improve; its no-argument/unrecognised-argument listing behaviour; and the help.sh/tools/install/docs/index.md mentions of the new verb.
Out of scope / Non-Goals
- Any standalone or headless execution of a CUO workflow — explicitly deferred by the plan.
- Recognising
install/uninstall/version/status/helpunder thecuonamespace — they already have their own flatcsverbs and are not CUO-backed work. - Changes to
modules/cuo's own Python implementation or persona system. - The
memoryverb — that is TASK-IMP-131.
Dependencies
Depends on TASK-IMP-130 (adds a verb to the same cli.mjs dispatch table under the renamed bin). Blocks TASK-IMP-134's end-to-end regression.
Relationship to TASK-IMP-076. Same dispatch-table precedent as TASK-IMP-131 (mcp/gates established the spawnSync-based pattern this family of verbs follows); this task, notably, does NOT spawn any subprocess at all — it is the one verb in this batch that is pure static text, which is itself a deliberate scope decision (see Alternatives Considered), not an oversight.
Sibling-task coordination with TASK-IMP-131. Both tasks add a new entry to the same cli.mjs dispatch table and depend only on TASK-IMP-130; see TASK-IMP-131's Dependencies section for the shared merge-conflict/sequencing note — it applies symmetrically to this task and is not repeated in full here to avoid two specs disagreeing if one is edited later.
AI Authorship Disclosure
- Tools used: Claude (Fable 5) running the CyberOS
task-authorskill inside Cowork. - Scope: the four-name CUO-backed subset was cross-checked directly against
tools/install/plugin/commands/'s actual file listing andREADME.md:28's broader slash-command list, not assumed from the plan's prose alone — the plan's own §2 citation was the primary source, confirmed rather than merely repeated. - Human review: task decomposition approved at the 2026-07-22 PLAN gate.
1. Description (normative)
- 1.1
cli.mjs's dispatch table MUST recognisecuoas a known top-level command. - 1.2
cs cuo planMUST print a message naming/planas the slash command to run, and MUST exit0. - 1.3
cs cuo create-tasks,cs cuo ship-tasks, andcs cuo improveMUST each print a message naming their respective matching slash command (/create-tasks,/ship-tasks,/improve) and MUST each exit0. - 1.4
cs cuowith no argument MUST print a listing of all four valid names and MUST exit0(orientation, not an error — matching the bare top-levelcsinvocation's own exit-0convention atcli.mjs:59). - 1.4a
cs cuo <unrecognised-name>MUST print the same listing but MUST exit with code2(matchingcli.mjs's established convention for a usage mistake,cli.mjs:87) — distinct from the bare-invocation case, since a mistyped name is a usage error a caller may want to detect, while a bare invocation asking for orientation is not. - 1.5
cs cuo <any argument>MUST NOT spawn any subprocess, MUST NOT invoke Python, and MUST NOT attempt to execute a CUO workflow, and MUST NOT probe whethercyberos-cuois locally installed — unlike TASK-IMP-131'smemoryverb, which does detect and report local tool availability, the plan constrainscuomore tightly ("do not implement standalone execution," no mechanism left open for later): this task's stub prints fixed text only, with no local-environment awareness at all. The two sibling verbs are deliberately asymmetric because the plan treats them differently, not by oversight. - 1.6
help.shandtools/install/docs/index.mdMUST document thecuoverb and MUST describe it, on the same line or the immediately adjacent line as the mention, as a redirect/orientation aid rather than standalone execution.tools/install/docs/index.md:27's "the same eight commands" sentence MUST be updated to the correct count oncecuo(and, per TASK-IMP-131,memory) are added — whichever of TASK-IMP-131/132 lands second is responsible for the numeral, not just appending its own verb's name.
2. Acceptance criteria
- [x] AC 1 (traces_to: #1.2, #1.1) -
cs cuo planoutput contains the literal substring/planand the process exits0- this alone provescuowas recognised as a known command (clause 1.1), since an unrecognised top-level command falls through to the "unknown command" branch and could never reach this output - test:tools/install/tests/test_cli_cuo_verb.sh::t01_plan_redirect_and_recognition - [x] AC 2 (traces_to: #1.3) - each of
cs cuo create-tasks,cs cuo ship-tasks,cs cuo improveprints its matching slash-command substring and exits0- test:tools/install/tests/test_cli_cuo_verb.sh::t02_other_three_redirects - [x] AC 3 (traces_to: #1.4) -
cs cuo(no args) prints all four valid names and exits0- test:tools/install/tests/test_cli_cuo_verb.sh::t03_bare_invocation_lists_and_exits_0 - [x] AC 4 (traces_to: #1.4a) -
cs cuo nonexistent-workflowprints all four valid names and exits with code exactly2- test:tools/install/tests/test_cli_cuo_verb.sh::t04_unrecognised_name_lists_and_exits_2 - [x] AC 5 (traces_to: #1.5) - with tripwire
python3andbashstand-ins on$PATHthat each write a marker file if invoked, runningcs cuo plan,cs cuo create-tasks,cs cuo ship-tasks, andcs cuo improvein sequence leaves both marker files absent afterward - test:tools/install/tests/test_cli_cuo_verb.sh::t05_no_subprocess_spawned(mirrors the tripwire-binary style already used in TASK-IMP-131's test suite, matching this repo's bash-test-harness convention rather than a JS-level module spy) - [x] AC 6 (traces_to: #1.6) -
help.shoutput andtools/install/docs/index.mdeach mentioncuowith a redirect-describing word on the same or adjacent line, andtools/install/docs/index.md's command-count sentence reads a number matching the actual verb count incli.mjs'sSCRIPTStable at the time of the check (not hardcoded to eight) - test:tools/install/tests/test_cli_cuo_verb.sh::t06_docs_describe_as_redirect_and_count_correct
3. Edge cases
- Argument casing or a leading slash typed by habit (
cs cuo /planinstead ofcs cuo plan) - out of scope for this task to normalise; the stub matches the four bare names exactly and falls into the "unrecognised" listing branch (AC 4) for anything else, which itself still orients the user correctly. cs cuoinvoked from a plain terminal with no agent session active at all: the printed message still names the slash command correctly - the stub does not attempt to detect whether an agent session exists, since doing so is unnecessary complexity for a text-printing command with no side effects either way.- A future fifth CUO-backed workflow is added to
modules/cuowithout a corresponding update to this stub's four-name list: the stub silently under-lists it. Not a defect this task introduces, but named here as a maintenance risk - whichever task adds a fifth CUO workflow should update this list, and TASK-IMP-134's regression does not itself guard against this drift (it only proves the current four resolve correctly). - Security-class: this verb's entire behaviour is printing a fixed string keyed on
argv[1]- it reads no file, spawns no process, and accepts no input that reaches a shell or filesystem call, so it carries no new attack surface beyond argument parsing itself.
Audit
§1 — Verdict summary
Seven §1 clauses (including a split 1.4/1.4a), six ACs, four edge cases including one security-class row. This task's findings cluster around test-design precision and an under-explained asymmetry with its sibling task, rather than a wrong technical premise — the underlying design (a pure-text redirect stub) was sound from the first draft.
§2 — Findings (all resolved)
ISS-001 — AC 1 was redundant with, and weaker than, AC 4
The first draft's AC 1 asserted only that bare cs cuo did not print "unknown command." AC 4 (the listing behaviour) already proves something strictly stronger for the same invocation — that it prints all four valid names and exits 0 — which trivially implies the weaker claim. Citing clause 1.1 to its own separate, weaker AC added a test that could pass without adding evidence beyond what AC 4 already provides. Resolved: retraced clause 1.1 to the cs cuo plan AC instead, which positively proves recognition for a different invocation shape (a valid name, not the bare case), making the two ACs complementary rather than one subsuming the other.
ISS-002 — clause 1.4 conflated two different exit-code semantics
The original single clause covered both "bare cs cuo" and "cs cuo <unrecognised-name>" with the same "exit 0" requirement. cli.mjs's own established convention (exit 2 for "unknown command," cli.mjs:87) treats a genuine usage mistake differently from a bare, information-seeking invocation (exit 0, cli.mjs:59). Treating a mistyped workflow name the same as asking for orientation loses a signal a calling script might want (was this a mistake, or a deliberate ask for help). Material: the spec would have shipped correct-looking behaviour with the wrong exit code. Resolved: split into 1.4 (bare, exit 0) and 1.4a (unrecognised name, exit 2), with matching separate ACs.
ISS-003 — AC 5's test mechanism does not match this repo's actual test conventions
The original AC 5 called for "a process-spy wrapper around child_process.spawnSync" — a JS-level module-mocking approach with no precedent anywhere in tools/install/tests/, which is exclusively bash scripts asserting on stdout/exit codes against stub binaries on $PATH (see TASK-IMP-131's own tests). An implementer following this AC literally would need to invent a mocking mechanism the rest of the suite doesn't use. Resolved: revised to the tripwire-binary style already established — stand-in python3/bash scripts that write a marker file if invoked, asserted absent afterward.
ISS-004 — AC 6's documentation check was satisfiable by an unrelated match
The original check ("contains a word from {redirect, prints, run inside}") tested for the word's presence anywhere in the whole file, which could pass on a coincidental, unrelated use of one of those common words elsewhere in help.sh or tools/install/docs/index.md (e.g. "prints this text" describing an unrelated flag) without the cuo verb itself being described as a redirect at all. Resolved: tightened to require the descriptive word on the same or adjacent line as the actual cuo mention.
ISS-005 — tools/install/docs/index.md:27's "same eight commands" count was never flagged as needing an update
This task (and its sibling TASK-IMP-131) each add a new verb to the same dispatch table tools/install/docs/index.md:27 describes as having exactly eight commands. Neither task's first draft had any clause or AC checking that this hardcoded count gets updated — a real, concrete way for the docs to go silently stale the moment either verb ships. Resolved: added to clause 1.6 and AC 6, with an explicit note that whichever of TASK-IMP-131/132 lands second owns the numeral update.
ISS-006 — the asymmetry with TASK-IMP-131's local-availability detection was unexplained
Clause 1.5 forbids cuo from probing whether cyberos-cuo is installed locally, while TASK-IMP-131's memory verb does exactly that (detects and reports local availability) for its own underlying tool. Left unexplained, a reader comparing the two sibling specs could reasonably conclude one of them made an arbitrary or inconsistent design call. It is not arbitrary — the plan itself constrains cuo more tightly than memory ("do not implement standalone execution," with no mechanism left open, versus memory's "mechanism ... is an implementation decision"). Resolved: added the explanation directly to clause 1.5.
ISS-007 — FM-101: title exceeded the 72-character limit by one (caught by the machine floor)
task-lint.mjs, run after the six findings above were resolved, flagged the title at 73 code points — one over the cap. Resolved: shortened to "Add a cuo verb to cs as a redirect stub" (title metadata only).
§3 — TRACE-006 semantic sufficiency (per clause)
| Clause | Verb demand | Cited test asserts | Verdict |
|---|---|---|---|
| 1.1 MUST recognise as known command | positive routing occurs for a valid name | AC 1: cs cuo plan reaches its redirect output, which only happens if cuo was recognised | sufficient after retracing (was redundant/weak - ISS-001) |
1.2 MUST redirect to /plan | positive text + exit code | AC 1: both asserted | sufficient |
| 1.3 MUST redirect the other three names | positive text + exit code per name | AC 2: all three asserted | sufficient |
| 1.4 bare invocation MUST list + exit 0 | positive listing + specific code | AC 3: both asserted | sufficient |
| 1.4a unrecognised name MUST list + exit 2 | positive listing + a DIFFERENT specific code than 1.4 | AC 4: both asserted, distinct from AC 3 | sufficient after the split (was conflated - ISS-002) |
| 1.5 MUST NOT spawn/probe | absence of a side effect across all four valid invocations | AC 5 (revised): tripwire markers absent after all four run | sufficient after revision (mechanism was unrealistic - ISS-003) |
| 1.6 MUST document as redirect + correct count | positive proximate text + a computed-not-hardcoded number | AC 6 (revised): both asserted with proximity + dynamic count | sufficient after revision (was loose - ISS-004, ISS-005) |
§4 — Resolution
Six findings, all material, all resolved in the audited revision. Score = 10/10.
Status transition draft -> ready_to_implement is authorised by this verdict per STATUS-REFERENCE.md §1.1. The two human-acceptance gates in /ship-tasks remain unchanged and are recorded human verdicts.
End of TASK-IMP-132 audit.