Skip to main content

CLEO CLI Design Standard

Developer implementation guide for LLM-agent-first CLI design Version: 3.4 | Updated: 2026-02-10 Scope: CLI command development standards for agent automation

RFC 2119 Conformance

This specification uses RFC 2119 keywords to indicate requirement levels:
KeywordMeaning
MUSTAbsolute requirement. Non-compliance is a specification violation.
MUST NOTAbsolute prohibition.
SHALLEquivalent to MUST.
SHOULDRecommended but not mandatory. Valid reasons may exist to ignore.
SHOULD NOTDiscouraged but not prohibited.
MAYOptional. Implementations can choose to include or omit.

DocumentRelationship
SPEC-BIBLE-GUIDELINES.mdAUTHORITATIVE for specification standards
LLM-TASK-ID-SYSTEM-DESIGN-SPEC.mdAUTHORITATIVE for task ID format, validation, error codes 10-22
TASK-HIERARCHY-SPEC.mdHierarchy features (type, parentId, size) affecting JSON output
RCSD-PIPELINE-SPEC.mdUses: RCSD commands (consensus, spec, decompose) MUST follow this spec
LLM-TASK-ID-SYSTEM-DESIGN-IMPLEMENTATION-REPORT.mdTracks implementation status against all LLM specs

Executive Summary

Mission Statement

Design CLI tools with LLM-agent-first principles: JSON output by default, human output opt-in via --human flag, structured errors, and consistent behavior across all commands.

Core Principles

PrincipleRequirement
JSON by DefaultAll commands MUST default to JSON output
Human Opt-InHuman-readable output via explicit --human flag
Structured ErrorsAll errors MUST return JSON with error codes
Consistent FlagsAll commands MUST support --format and --quiet
Documented Exit CodesEvery exit code MUST be a defined constant
Schema ValidationAll JSON MUST include $schema field

Reference Implementation

analyze.sh exemplifies the gold standard for LLM-agent-first design:
  • JSON output is DEFAULT (human requires explicit --human flag)
  • Comprehensive _meta envelope with version, timestamp, algorithm
  • Structured recommendations with action_order, recommendation.command
  • Exit codes documented (0=success, 1=error, 2=no tasks)

Part 1: Command Inventory

All Commands (34 total)

#CommandScriptCategoryRequirements
1addadd.shWriteJSON output, --format, --quiet
2analyzeanalyze.shReadJSON default, --human opt-in
3archivearchive.shWriteJSON output, --format, --quiet, --dry-run
4backupbackup.shMaintenanceJSON output, --format, --quiet
5blockersblockers.shReadJSON output, --format, --quiet
6completecomplete.shWriteJSON output, --format, --quiet, --dry-run
7commandscommands.shReadJSON default, --human opt-in, filters
8configconfig.shMaintenanceJSON output, --format, --quiet
9dashdash.shReadJSON output, --format, --quiet
10depsdeps.shReadJSON output, --format, --quiet
11existsexists.shReadJSON output, --format, --quiet
12exportexport.shReadMulti-format, --quiet
13extractextract.shSyncJSON output, --format, --quiet, --dry-run
14findfind.shReadJSON output, --format, --quiet, fuzzy search
15focusfocus.shWriteJSON output, --format, --quiet
16historyhistory.shReadJSON output, --format, --quiet
17initinit.shSetupJSON output, --format, --quiet
18injectinject.shSyncJSON output, --format, --quiet, --dry-run
19labelslabels.shReadJSON output, --format, --quiet
20listlist.shReadJSON output, --format, --quiet
21loglog.shReadJSON output, --format, --quiet
22migratemigrate.shMaintenanceJSON output, --format, --quiet, --dry-run
23migrate-backupsmigrate-backups.shMaintenanceJSON output, --format, --quiet, --dry-run
24nextnext.shReadJSON output, --format, --quiet
25phasephase.shWriteJSON output, --format, --quiet
26phasesphases.shReadJSON output, --format, --quiet
27researchresearch.shReadJSON output, --format, --quiet, Context7 integration
28restorerestore.shMaintenanceJSON output, --format, --quiet, --dry-run
29sessionsession.shWriteJSON output, --format, --quiet
30showshow.shReadJSON output, --format, --quiet
31statsstats.shReadJSON output, --format, --quiet
32syncsync.shSyncJSON output, --format, --quiet, --dry-run
33updateupdate.shWriteJSON output, --format, --quiet, --dry-run
34validatevalidate.shMaintenanceJSON output, --format, --quiet

Command Categories

CategoryCommandsSpecial Requirements
Writeadd, archive, complete, focus, phase, session, updateMUST return created/updated object, MUST support --dry-run
Readanalyze, blockers, commands, dash, deps, exists, export, find, history, labels, list, log, next, phases, research, show, statsMUST support filtering, MUST return structured data
Syncextract, inject, syncMUST support --dry-run, MUST report conflicts
Maintenancebackup, config, init, migrate, migrate-backups, restore, validateMUST report status, SHOULD support --dry-run
SetupinitMUST be idempotent

Part 2: Gap Analysis

Gap 1: JSON Output Inconsistencies

Impact: Agents need consistent JSON envelope across all commands
CommandCurrent OutputRequired Fix
addHas JSON but missing $schemaAdd schema, standardize envelope
updateHas JSON but inconsistentStandardize envelope
completeHas JSON outputStandardize envelope
archiveHas JSON outputStandardize envelope
phase subcommandsPartial JSONComplete JSON for all subcommands
Required JSON Output (v0.17.0 hierarchy fields):
// ct add "Task" --parent T001 --format json
{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"command": "add", "timestamp": "...", "version": "..."},
  "success": true,
  "task": {
    "id": "T042",
    "type": "task",
    "parentId": "T001",
    "size": null,
    "title": "...",
    "status": "pending",
    "createdAt": "..."
  }
}

// ct update T042 --priority high --format json
{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"command": "update", "timestamp": "..."},
  "success": true,
  "taskId": "T042",
  "changes": {"priority": {"before": "medium", "after": "high"}},
  "task": {
    "id": "T042",
    "type": "task",
    "parentId": "T001",
    "priority": "high",
    /* ... full updated task */
  }
}

// ct complete T042 --format json
{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"command": "complete", "timestamp": "..."},
  "success": true,
  "taskId": "T042",
  "completedAt": "2025-12-17T10:00:00Z",
  "cycleTimeDays": 3.5,
  "parentAutoComplete": false
}

Gap 2: JSON Default Implementation

Location: lib/output-format.sh resolve_format() Current Issue: Not all scripts call resolve_format() or respect its result. Required Behavior (LLM-Agent-First):
# Default fallback: JSON by default (LLM-Agent-First)
if [[ -z "$resolved_format" ]]; then
  resolved_format="json"  # JSON is always the default
fi
Rationale: Per LLM-Agent-First philosophy, agents are the primary consumer. JSON output by default enables seamless agent integration without requiring explicit flags. Developers use --human when they need human-readable output. MUST be implemented in ALL commands via resolve_format() call.

Gap 3: Standardized Error JSON Format

Current Status: Error JSON implemented in lib/error-json.sh Error JSON Envelope (IMPLEMENTED):
// Task not found
{
  "$schema": "https://cleo.dev/schemas/v1/error.schema.json",
  "_meta": {"command": "show", "timestamp": "...", "version": "..."},
  "success": false,
  "error": {
    "code": "E_TASK_NOT_FOUND",
    "message": "Task T999 does not exist",
    "exitCode": 4,
    "recoverable": false,
    "suggestion": "Use 'ct exists' to verify task ID"
  }
}

// Hierarchy error
{
  "$schema": "https://cleo.dev/schemas/v1/error.schema.json",
  "_meta": {"command": "add", "timestamp": "...", "version": "..."},
  "success": false,
  "error": {
    "code": "E_PARENT_NOT_FOUND",
    "message": "Parent task T999 does not exist",
    "exitCode": 10,
    "recoverable": true,
    "suggestion": "Use 'ct list --type epic,task' to find valid parents",
    "context": {"requestedParent": "T999"}
  }
}

Gap 4: Phase Commands Need Full JSON

phase.sh subcommands MUST output JSON when --format json is specified. Required for each subcommand:
// phase show --format json
{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"command": "phase show", "timestamp": "..."},
  "success": true,
  "currentPhase": {
    "slug": "core",
    "name": "Core Development",
    "status": "active",
    "startedAt": "2025-12-10T14:30:00Z",
    "durationDays": 7.2
  }
}

Gap 5: Flag Inconsistency Across Commands

Conflict Matrix:
Short FlagConflicting UsesResolution
-f--format (7 commands) vs --files (update)Keep -f for --format, --files long-form only
-n--notes (3 commands) vs --count (next)Keep -n for --notes, use -c for --count
Missing Universal Flags:
FlagCurrent CoverageTarget
--format17/32 (53%)100%
--quiet21/32 (66%)100%
--verbose2/32 (6%)All display commands
--dry-run3/32 (9%)All write operations

Part 3: Standardized Systems

3.1 Exit Code Standard (AUTHORITATIVE)

File: lib/exit-codes.sh Exit Code Ranges:
  • 0: Success
  • 1-9: General errors
  • 10-19: Hierarchy errors (see LLM-TASK-ID-SYSTEM-DESIGN-SPEC)
  • 20-29: Concurrency errors
  • 30-39: Session errors (multi-session, scope, focus)
  • 100+: Special conditions (not errors)

Complete Exit Code Table

CodeConstantMeaningRecoverableExample
General (0-9)
0EXIT_SUCCESSOperation completed successfullyN/ATask created
1EXIT_GENERAL_ERRORUnspecified errorYesUnknown failure
2EXIT_INVALID_INPUTInvalid user input/argumentsYesMissing required arg
3EXIT_FILE_ERRORFile system operation failedNoPermission denied
4EXIT_NOT_FOUNDRequested resource not foundYesTask ID not found
5EXIT_DEPENDENCY_ERRORMissing dependencyNojq not installed
6EXIT_VALIDATION_ERRORData validation failedYesSchema violation
7EXIT_LOCK_TIMEOUTFailed to acquire lockYesConcurrent write
8EXIT_CONFIG_ERRORConfiguration errorYesInvalid config
Hierarchy (10-19)
10EXIT_PARENT_NOT_FOUNDparentId references non-existent taskYes—parent T999 invalid
11EXIT_DEPTH_EXCEEDEDMax hierarchy depth (3) exceededYesToo deeply nested
12EXIT_SIBLING_LIMITMax siblings exceeded (if configured)YesParent at configured limit
13EXIT_INVALID_PARENT_TYPEsubtask cannot have childrenYessubtask as parent
14EXIT_CIRCULAR_REFERENCETask would be ancestor of itselfNoCycle detected
15EXIT_ORPHAN_DETECTEDTask has invalid parentIdYesParent was deleted
Concurrency (20-29)
20EXIT_CHECKSUM_MISMATCHFile modified externallyYesRetry operation
21EXIT_CONCURRENT_MODIFICATIONMulti-agent conflictYesRetry with backoff
22EXIT_ID_COLLISIONID generation conflictYesRegenerate ID
Session (30-39)
30EXIT_SESSION_EXISTSSession already active for scopeYesUse existing session or end it first
31EXIT_SESSION_NOT_FOUNDSession ID not foundYesList sessions, start new
32EXIT_SCOPE_CONFLICTScope overlaps with existing sessionYesUse different scope
33EXIT_SCOPE_INVALIDInvalid scope format or emptyYesCheck scope syntax
34EXIT_TASK_NOT_IN_SCOPETask outside session scopeYesFocus task within scope
35EXIT_TASK_CLAIMEDTask focused by another sessionYesChoose different task
36EXIT_SESSION_REQUIREDOperation requires active sessionYesStart session first
37EXIT_SESSION_CLOSE_BLOCKEDCannot close - tasks incompleteYesComplete or remove tasks
38EXIT_FOCUS_REQUIREDOperation requires focused taskYesSet focus first
39EXIT_NOTES_REQUIREDSession notes requiredYesProvide notes
Special (100+)
100EXIT_NO_DATANo data to process (not error)N/AEmpty query result
101EXIT_ALREADY_EXISTSResource already existsN/ATask ID exists
102EXIT_NO_CHANGENo changes neededN/AUpdate was no-op

Exit Code Semantics (AUTHORITATIVE)

Commands MUST use the following exit codes:
ScenarioExit CodeError Code
Task not found4E_TASK_NOT_FOUND
Invalid task ID format2E_TASK_INVALID_ID
File not readable3E_FILE_READ_ERROR
Missing required argument2E_INPUT_MISSING
JSON schema validation failed6E_VALIDATION_SCHEMA
Parent task not found (hierarchy)10E_PARENT_NOT_FOUND
Would create circular reference14E_CIRCULAR_REFERENCE
Lock acquisition timeout7N/A (no E_ code)
Empty query result100N/A (not an error)
Session not found31E_SESSION_NOT_FOUND
Scope conflicts with existing session32E_SCOPE_CONFLICT
Task outside session scope34E_TASK_NOT_IN_SCOPE
Task claimed by another session35E_TASK_CLAIMED
Session required for operation36E_SESSION_REQUIRED
Focus required for operation38E_FOCUS_REQUIRED

3.2 Error Code Standard (AUTHORITATIVE)

File: lib/error-json.sh Convention: All error codes use E_ prefix.

Complete Error Code Table (29 codes)

CategoryCodeExit CodeDescription
Task Errors
E_TASK_NOT_FOUND4Task ID does not exist
E_TASK_ALREADY_EXISTS101Task ID already exists
E_TASK_INVALID_ID2Task ID format is invalid
E_TASK_INVALID_STATUS2Status value not in enum
File Errors
E_FILE_NOT_FOUND4File does not exist
E_FILE_READ_ERROR3Cannot read file
E_FILE_WRITE_ERROR3Cannot write file
E_FILE_PERMISSION3Permission denied
Validation Errors
E_VALIDATION_SCHEMA6JSON schema validation failed
E_VALIDATION_CHECKSUM6Checksum mismatch
E_VALIDATION_REQUIRED6Required field missing
Input Errors
E_INPUT_MISSING2Required argument missing
E_INPUT_INVALID2Argument value invalid
E_INPUT_FORMAT2Argument format incorrect
Dependency Errors
E_DEPENDENCY_MISSING5Required tool not installed
E_DEPENDENCY_VERSION5Tool version incompatible
Phase Errors
E_PHASE_NOT_FOUND4Phase slug does not exist
E_PHASE_INVALID2Phase definition invalid
Session Errors
E_SESSION_ACTIVE101Session already active (legacy)
E_SESSION_NOT_ACTIVE4No active session (legacy)
E_SESSION_EXISTS30Session already active for scope
E_SESSION_NOT_FOUND31Session ID not found
E_SCOPE_CONFLICT32Scope overlaps with existing session
E_SCOPE_INVALID33Invalid scope format or empty
E_TASK_NOT_IN_SCOPE34Task outside session scope
E_TASK_CLAIMED35Task focused by another session
E_SESSION_REQUIRED36Operation requires active session
E_SESSION_CLOSE_BLOCKED37Cannot close session - tasks incomplete
E_FOCUS_REQUIRED38Operation requires focused task
E_NOTES_REQUIRED39Session notes required
General Errors
E_UNKNOWN1Unknown/unspecified error
E_NOT_INITIALIZED4Project not initialized
Hierarchy Errors
E_PARENT_NOT_FOUND10Parent task does not exist
E_DEPTH_EXCEEDED11Hierarchy depth limit exceeded
E_SIBLING_LIMIT12Sibling limit exceeded
E_INVALID_PARENT_TYPE13Parent type cannot have children
E_CIRCULAR_REFERENCE14Would create cycle
E_ORPHAN_DETECTED15Task references invalid parent
Concurrency Errors
E_CHECKSUM_MISMATCH20File modified during operation
E_CONCURRENT_MODIFICATION21Multi-agent conflict detected
E_ID_COLLISION22Generated ID already exists

3.3 JSON Schema Standard

Schema Files

SchemaFileStatusPurpose
Task Dataschemas/todo.schema.jsonEXISTSTask/project data validation
Archiveschemas/archive.schema.jsonEXISTSArchived tasks validation
Logschemas/log.schema.jsonEXISTSAudit log validation
Configschemas/config.schema.jsonEXISTSConfiguration validation
Responseschemas/output.schema.jsonEXISTSSuccess response envelope
Errorschemas/error.schema.jsonEXISTSError response envelope
Critical Pathschemas/critical-path.schema.jsonEXISTSCritical path analysis response

Response Schema (schemas/output.schema.json)

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://cleo.dev/schemas/v1/output.schema.json",
  "title": "CLEO Response Envelope",
  "type": "object",
  "required": ["_meta", "success"],
  "properties": {
    "$schema": {"type": "string"},
    "_meta": {
      "type": "object",
      "required": ["command", "timestamp", "version"],
      "properties": {
        "format": {"type": "string", "const": "json"},
        "version": {"type": "string"},
        "command": {"type": "string"},
        "timestamp": {"type": "string", "format": "date-time"},
        "checksum": {"type": "string"},
        "execution_ms": {"type": "integer", "minimum": 0}
      }
    },
    "success": {"type": "boolean"},
    "summary": {"type": "object"},
    "data": {},
    "task": {"type": "object"},
    "tasks": {"type": "array"},
    "warnings": {"type": "array"}
  }
}

Error Schema (schemas/error.schema.json)

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://cleo.dev/schemas/v1/error.schema.json",
  "title": "CLEO Error Envelope",
  "type": "object",
  "required": ["_meta", "success", "error"],
  "properties": {
    "$schema": {"type": "string"},
    "_meta": {
      "type": "object",
      "required": ["command", "timestamp", "version"],
      "properties": {
        "format": {"type": "string", "const": "json"},
        "version": {"type": "string"},
        "command": {"type": "string"},
        "timestamp": {"type": "string", "format": "date-time"}
      }
    },
    "success": {"const": false},
    "error": {
      "type": "object",
      "required": ["code", "message", "exitCode"],
      "properties": {
        "code": {"type": "string", "pattern": "^E_[A-Z_]+$"},
        "message": {"type": "string"},
        "exitCode": {"type": "integer", "minimum": 1},
        "recoverable": {"type": "boolean"},
        "suggestion": {"type": ["string", "null"]},
        "context": {"type": "object"}
      }
    }
  }
}

3.4 JSON Envelope Standard (AUTHORITATIVE)

All JSON outputs MUST follow this envelope:
{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {
    "format": "json",
    "version": "<version>",
    "command": "<command-name>",
    "timestamp": "<ISO-8601>",
    "checksum": "<sha256>",      // OPTIONAL: For data integrity
    "execution_ms": <ms>         // OPTIONAL: For performance monitoring
  },
  "success": true,
  "summary": {},                  // OPTIONAL: Aggregated stats
  "data": []                      // OR task/tasks/etc depending on command
}

Required _meta Fields

FieldTypeRequiredDescription
formatstringMUSTAlways "json" for JSON output
versionstringMUSTcleo version (e.g., "0.17.0")
commandstringMUSTCommand name (e.g., "add", "list")
timestampstringMUSTISO-8601 UTC timestamp
checksumstringMAYSHA256 of file for integrity
execution_msintegerMAYExecution time in milliseconds

3.5 Universal Flag Standard (AUTHORITATIVE)

FlagLong FormPurposeDefaultCommands
-f--formatOutput formatjsonALL
-q--quietSuppress non-essential outputfalseALL
-v--verboseDetailed outputfalseALL read commands
--humanForce human-readable textfalseALL
--jsonForce JSON (shortcut for --format json)N/A (already default)ALL
--dry-runPreview changesfalseALL write commands
--forceSkip confirmationsfalseDestructive commands
LLM-Agent-First Principle: JSON is the default output format. Use --human to get human-readable text output. The --json flag exists for explicit clarity but is redundant since JSON is already the default.

Format Values

FormatDescriptionUse Case
textHuman-readable colored outputInteractive terminal
jsonMachine-readable JSON envelopeAgent automation
jsonlJSON Lines (one object per line)Streaming/logging
markdownMarkdown formattedDocumentation
tableASCII tableTerminal display

Part 4: Required Libraries

Foundation Libraries

All commands MUST source these libraries:
LibraryPurposeRequired By
lib/exit-codes.shStandardized exit code constantsALL commands
lib/error-json.shFormat-aware error outputALL commands
lib/output-format.shTTY-aware format resolutionALL commands

Library Integration Pattern

#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$(dirname "$SCRIPT_DIR")/lib"

# MUST source these libraries
source "${LIB_DIR}/exit-codes.sh"
source "${LIB_DIR}/error-json.sh"
source "${LIB_DIR}/output-format.sh"

# MUST set command name for error reporting
COMMAND_NAME="<command>"

Part 5: Write Command Requirements

All Write Commands MUST:

  1. Return the created/updated object in JSON output
  2. Include $schema field pointing to output.schema.json
  3. Include complete _meta envelope
  4. Support --dry-run to preview changes without executing
  5. Use output_error() for all error conditions

JSON Output Examples

add Command Output

{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"format": "json", "version": "0.17.0", "command": "add", "timestamp": "..."},
  "success": true,
  "task": {"id": "T042", "type": "task", "parentId": null, "title": "...", "status": "pending"}
}

update Command Output

{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"format": "json", "version": "0.17.0", "command": "update", "timestamp": "..."},
  "success": true,
  "taskId": "T042",
  "changes": {"priority": {"before": "medium", "after": "high"}},
  "task": {"id": "T042", "priority": "high", "...": "..."}
}

complete Command Output

{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"format": "json", "version": "0.17.0", "command": "complete", "timestamp": "..."},
  "success": true,
  "taskId": "T042",
  "completedAt": "2025-12-17T10:00:00Z",
  "cycleTimeDays": 3.5
}

Subcommand Requirements (e.g., phase.sh)

Commands with subcommands MUST:
  • Accept --format flag before the subcommand
  • Each subcommand MUST respect the FORMAT variable
  • Each subcommand MUST output proper JSON envelope

Part 5.3: Input Validation Requirements

Write commands MUST validate all inputs before modifying state:

Field Length Limits

FieldMax LengthError Code
title120 charsE_INPUT_INVALID
description2000 charsE_INPUT_INVALID
notes (each)5000 charsE_INPUT_INVALID
blockedBy reason300 charsE_INPUT_INVALID
sessionNote2500 charsE_INPUT_INVALID
label name50 charsE_INPUT_INVALID
phase slug30 charsE_INPUT_INVALID

Validation Order

Commands MUST validate in this order:
  1. Required arguments present (E_INPUT_MISSING)
  2. Format/type validation (E_INPUT_FORMAT)
  3. Length validation (E_INPUT_INVALID)
  4. Semantic validation (E_VALIDATION_*)

Validation Response

Failed validation MUST return immediately (fail-fast) with:
  • Specific error code
  • Field name in error message
  • Actual vs. allowed value info

Part 5.4: Dry-Run Semantics

Commands with --dry-run MUST follow these semantics:
BehaviorWith —dry-runWithout —dry-run
ValidationFullFull
File lockingNoneFull
State modificationNoneFull
JSON outputFull (with dryRun: true)Full
Exit codeSame as realSame

Dry-Run Output

{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"format": "json", "version": "0.17.0", "command": "add"},
  "success": true,
  "dryRun": true,
  "wouldCreate": {
    "id": "T999",
    "title": "Example task"
  }
}
Dry-run mode allows agents to validate inputs without side effects.

Part 5.6: Idempotency Requirements

Write commands MUST be idempotent where feasible to support agent retries without side effects:
CommandIdempotencyMechanism
addSHOULDDetect duplicate title+phase within 60s window, return existing task
updateMUSTUpdating with identical values returns EXIT_NO_CHANGE (102)
completeMUSTCompleting already-done task returns EXIT_NO_CHANGE (102)
archiveMUSTRe-archiving already-archived tasks is a no-op
restoreMUSTRestoring already-active task returns EXIT_NO_CHANGE (102)

Duplicate Detection for add

When creating a new task, the CLI SHOULD check if an identical task (same title, same phase) was created within the last 60 seconds. If found:
  1. Return the existing task with success: true
  2. Include "duplicate": true in response
  3. Use exit code 0 (success, not error)
This prevents agents from creating duplicates during retry loops.

EXIT_NO_CHANGE Semantics

Exit code 102 (EXIT_NO_CHANGE) indicates:
  • The command was valid
  • No changes were made (state unchanged)
  • Agents SHOULD treat this as success and not retry
Example response:
{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"format": "json", "version": "0.17.0", "command": "complete"},
  "success": true,
  "noChange": true,
  "message": "Task T042 is already complete"
}

Non-Idempotent Operations

The following operations are inherently non-idempotent and MUST be documented in help text:
  • backup create - Creates new backup each time
  • session start - Only valid if no session active
  • log entries - Each invocation appends

Part 5.7: Retry Protocol for Recoverable Errors

Agents SHOULD implement retry logic for recoverable errors with exponential backoff:
Exit CodeNameMax RetriesInitial DelayBackoff Factor
7EXIT_LOCK_TIMEOUT3100ms2x
20EXIT_CHECKSUM_MISMATCH550ms1.5x
21EXIT_CONCURRENT_MODIFICATION5100ms2x
22EXIT_ID_COLLISION30msimmediate regenerate

Retry Algorithm

def execute_with_retry(command, max_retries, initial_delay_ms, backoff_factor):
    delay = initial_delay_ms
    for attempt in range(max_retries + 1):
        exit_code, output = execute(command)

        if exit_code == 0 or not is_recoverable(exit_code):
            return exit_code, output

        if attempt < max_retries:
            sleep(delay / 1000)  # Convert to seconds
            delay *= backoff_factor

    return exit_code, output  # Final failure

def is_recoverable(code):
    return code in [7, 20, 21, 22]

ID Collision Handling (Exit Code 22)

For EXIT_ID_COLLISION, agents SHOULD:
  1. Extract the colliding ID from error JSON
  2. Regenerate a new ID (or let CLI auto-generate)
  3. Retry immediately without delay

Maximum Total Wait

Agents SHOULD cap total retry wait time at 5 seconds to avoid blocking workflows.

Part 6: Testing Requirements (AUTHORITATIVE)

6.1 Exit Code Testing

All commands MUST have tests verifying:
#!/usr/bin/env bash
# Test success
ct add "Test task" -q
[[ $? -eq 0 ]] || echo "FAIL: add should exit 0"

# Test not found
ct show T999 2>/dev/null
[[ $? -eq 4 ]] || echo "FAIL: show non-existent should exit 4"

# Test invalid input
ct add 2>/dev/null
[[ $? -eq 2 ]] || echo "FAIL: add without title should exit 2"

# Test hierarchy errors
ct add "Task" --parent T999 2>/dev/null
[[ $? -eq 10 ]] || echo "FAIL: invalid parent should exit 10"

6.2 JSON Output Testing

All commands with JSON output MUST have tests verifying:
#!/usr/bin/env bash
# Test add returns valid JSON with task
result=$(ct add "JSON Test" --format json)
echo "$result" | jq -e '.success == true' || echo "FAIL: success should be true"
echo "$result" | jq -e '.task.id' || echo "FAIL: should have task.id"
echo "$result" | jq -e '._meta.command == "add"' || echo "FAIL: should have _meta.command"
echo "$result" | jq -e '."$schema"' || echo "FAIL: should have $schema"

# Test error JSON
result=$(ct show T999 --format json 2>&1)
echo "$result" | jq -e '.success == false' || echo "FAIL: should be unsuccessful"
echo "$result" | jq -e '.error.code' || echo "FAIL: should have error.code"

6.3 TTY Detection Testing

#!/usr/bin/env bash
# Piped output should be JSON
format=$(ct list | jq -r '._meta.format' 2>/dev/null)
[[ "$format" == "json" ]] || echo "FAIL: piped output should default to JSON"

# Explicit --human should override
output=$(ct list --human | head -1)
[[ "$output" != "{" ]] || echo "FAIL: --human should output text"

# Environment variable should work
CLEO_FORMAT=json ct list | jq -e '._meta' || echo "FAIL: env var should work"

6.4 Test Coverage Requirements

CategoryMinimum CoverageCommands
Exit codes100% of defined codesAll
JSON envelopeAll required fieldsAll JSON-enabled
Error JSONAll error codes usedAll
Flag parsingAll flagsAll
TTY detectionAuto-detect + overridesAll format-enabled

Part 7: Agent Integration Guide

Environment Setup

# Agent-optimized environment
export CLEO_FORMAT=json
export NO_COLOR=1
export CLEO_AGENT_MODE=1

Query Patterns (Work Today)

# Task listing (auto-JSON when piped)
ct list | jq '.tasks[]'

# Analysis (already JSON default!)
ct analyze | jq '.recommendations'

# Single task
ct show T001 --format json

# Validation
ct validate --json --quiet && echo "Valid"

Write Patterns (v0.17.0)

# Single command returns complete result
task_json=$(ct add "Task")
task_id=$(echo "$task_json" | jq -r '.task.id')

# Update returns changes + updated task
ct update T001 --priority high | jq '.changes'

# Complete returns confirmation
ct complete T001 | jq '.cycleTimeDays'

Part 8: Compliance Metrics

Required Metrics for Full Compliance

MetricRequired Value
Agent workflow steps per mutation1x (command returns result)
Commands requiring explicit --format json0/32 (auto-detect via TTY)
Error handling methodJSON with E_ error codes
Write confirmation in responseYES (full object returned)
Exit code coverage100% (all codes are constants)
$schema field presence100% of JSON outputs
_meta envelope presence100% of JSON outputs

Compliance Definition

A command is fully compliant when it:
  1. Sources all required libraries (exit-codes.sh, error-json.sh, output-format.sh)
  2. Calls resolve_format() after argument parsing
  3. Supports --format, --quiet, --json, --human flags
  4. Returns JSON with $schema and _meta envelope
  5. Uses output_error() for all errors
  6. Uses exit code constants (no magic numbers)
  7. (Write commands) Supports --dry-run
  8. (Write commands) Returns created/updated object

Part 9: Command Compliance Requirements

All commands MUST meet these requirements:
CommandJSONQuietFormatDry-Runexit-codeserror-jsonresolve_format
add
analyzeN/AN/A
archive
backupN/A
blockersN/A
complete
commandsN/A
configN/A
dashN/A
depsN/A
existsN/A
exportN/A
extract
findN/A
focusN/A
historyN/A
initN/A
inject
labelsN/A
listN/A
logN/A
migrate
migrate-backups
nextN/A
phaseN/A
phasesN/A
researchN/A
restore
sessionN/A
showN/A
statsN/A
sync
update
validateN/A
Legend: ✅ = REQUIRED | N/A = Not Applicable for this command type All 34 commands MUST achieve 100% compliance with applicable requirements.

Part 10: Development Workflow (AUTHORITATIVE)

Adding a New Command

When adding a new command to cleo, MUST follow this checklist:

1. Foundation

  • Source lib/exit-codes.sh at script start
  • Source lib/error-json.sh at script start
  • Source lib/output-format.sh at script start
  • Set COMMAND_NAME variable for error reporting

2. Flag Parsing

  • Implement --format flag (text|json|jsonl|markdown|table)
  • Implement --quiet flag (suppress non-essential output)
  • Implement --human shortcut (sets format=text)
  • Implement --json shortcut (sets format=json)
  • For write commands: implement --dry-run
  • For destructive commands: implement --force
  • Call resolve_format() after parsing all arguments

3. JSON Output

  • Include $schema field in all JSON outputs
  • Include complete _meta envelope (format, version, command, timestamp)
  • Include success boolean field
  • For task operations: include full task object with hierarchy fields
  • For errors: use output_error() from error-json.sh

4. Exit Codes

  • Use constants from lib/exit-codes.sh (never magic numbers)
  • Document all possible exit codes in command help
  • Return EXIT_SUCCESS (0) on success
  • Return appropriate error code on failure
  • Return special codes (100+) for non-error conditions

5. Testing

  • Add unit tests for flag parsing
  • Add unit tests for JSON output structure
  • Add unit tests for all exit codes
  • Add integration tests for TTY detection
  • Verify JSON validates against schema

6. Documentation

  • Update this spec’s command matrix
  • Add command to docs/commands/ if user-facing
  • Document exit codes in help text
  • Document JSON output format in help text

Code Template

#!/usr/bin/env bash
# <command>.sh - <description>
set -euo pipefail

# ============================================================================
# SETUP
# ============================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$(dirname "$SCRIPT_DIR")/lib"

# Source required libraries
source "${LIB_DIR}/exit-codes.sh"
source "${LIB_DIR}/error-json.sh"
source "${LIB_DIR}/output-format.sh"

# Load VERSION from central location
if [[ -n "${CLEO_HOME:-}" ]] && [[ -f "$CLEO_HOME/VERSION" ]]; then
    VERSION=$(cat "$CLEO_HOME/VERSION" | tr -d '[:space:]')
elif [[ -f "${SCRIPT_DIR}/../VERSION" ]]; then
    VERSION=$(cat "${SCRIPT_DIR}/../VERSION" | tr -d '[:space:]')
else
    VERSION="0.0.0"
fi

# Command identification (for error reporting)
COMMAND_NAME="<command>"

# ============================================================================
# FLAG DEFAULTS
# ============================================================================

FORMAT=""        # Resolved after parsing
QUIET=false
VERBOSE=false
DRY_RUN=false

# ============================================================================
# HELP
# ============================================================================

# Show help message
show_help() {
    cat << 'EOF'
Usage: <command>.sh [OPTIONS] [ARGS]

Options:
  -f, --format FORMAT   Output format (text|json|jsonl|markdown|table)
  --json                Shortcut for --format json
  --human               Shortcut for --format text
  -q, --quiet           Suppress non-essential output
  -v, --verbose         Enable verbose output
  --dry-run             Preview changes without applying
  -h, --help            Show this help message
EOF
}

# ============================================================================
# ARGUMENT PARSING
# ============================================================================

while [[ $# -gt 0 ]]; do
  case "$1" in
    -f|--format)  FORMAT="$2"; shift 2 ;;
    --json)       FORMAT="json"; shift ;;
    --human)      FORMAT="text"; shift ;;
    -q|--quiet)   QUIET=true; shift ;;
    -v|--verbose) VERBOSE=true; shift ;;
    --dry-run)    DRY_RUN=true; shift ;;
    -h|--help)    show_help; exit 0 ;;
    *)            # Handle positional args
                  shift ;;
  esac
done

# Resolve format (TTY-aware auto-detection)
FORMAT=$(resolve_format "$FORMAT")

# ============================================================================
# MAIN LOGIC
# ============================================================================

main() {
  # Your implementation here

  # Success output
  if [[ "$FORMAT" == "json" ]]; then
    jq -n \
      --arg version "$VERSION" \
      --arg cmd "$COMMAND_NAME" \
      --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
      '{
        "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
        "_meta": {
          "format": "json",
          "version": $version,
          "command": $cmd,
          "timestamp": $ts
        },
        "success": true,
        "data": {}
      }'
  else
    [[ "$QUIET" != true ]] && echo "Success message"
  fi

  exit $EXIT_SUCCESS
}

# Error handling example
handle_error() {
  output_error "E_TASK_NOT_FOUND" "Task $1 not found" $EXIT_NOT_FOUND true \
    "Use 'ct list' to see available tasks"
  exit $EXIT_NOT_FOUND
}

main "$@"

Part 11: Backward Compatibility Policy

Breaking Change Policy

Change TypePolicyDeprecation Period
Exit code value changesMUST NOT change within major version2 major versions
Error code string changesMUST NOT change within major version2 major versions
JSON field removalMUST NOT remove within major version2 major versions
Flag removalMUST NOT remove within major version1 major version
JSON field renameAdd new, deprecate old2 major versions
Default behavior changeDocument clearly, consider --legacy flag1 major version

Deprecation Process

  1. Announce: Mark deprecated in next minor release
  2. Warn: Emit deprecation warning to stderr (unless --quiet)
  3. Document: Add to CHANGELOG.md deprecation section
  4. Sunset: Remove after deprecation period

Deprecation Registry

Maintain a deprecation registry in docs/DEPRECATIONS.md tracking:
  • Deprecated item (exit code, error code, flag, field)
  • Version deprecated
  • Replacement (if any)
  • Sunset version
This ensures API stability for LLM agents while allowing long-term evolution.

JSON Stability Guarantees

FieldStability
$schemaStable (version in URL)
_meta.versionStable
_meta.commandStable
_meta.timestampStable
successStable
error.codeStable within major version
error.exitCodeStable within major version
Command-specific fieldsSee individual command docs

Part 12: Compliance Validation Checklist

Per-Command Checklist

Use this checklist to validate each command’s compliance:
## Command: <name>

### Foundation
- [ ] Sources `lib/exit-codes.sh`
- [ ] Sources `lib/error-json.sh`
- [ ] Sources `lib/output-format.sh`
- [ ] Sets `COMMAND_NAME` variable

### Flags
- [ ] Has `--format` flag
- [ ] Has `--quiet` flag
- [ ] Has `--json` shortcut
- [ ] Has `--human` shortcut
- [ ] Calls `resolve_format()` after arg parsing
- [ ] (Write commands) Has `--dry-run`

### JSON Output
- [ ] Includes `$schema` field
- [ ] Includes `_meta.format` field
- [ ] Includes `_meta.version` field
- [ ] Includes `_meta.command` field
- [ ] Includes `_meta.timestamp` field
- [ ] Includes `success` boolean
- [ ] (Task operations) Includes hierarchy fields

### Exit Codes
- [ ] Uses constants (no magic numbers)
- [ ] Returns 0 on success
- [ ] Returns correct code on each error type
- [ ] Documents exit codes in help

### Errors
- [ ] Uses `output_error()` for errors
- [ ] Uses correct `E_` error codes
- [ ] Includes suggestions where helpful

### Testing
- [ ] Has exit code tests
- [ ] Has JSON structure tests
- [ ] Has TTY detection tests

Automated Compliance Check

A compliance check script SHOULD be created at dev/check-compliance.sh:
#!/usr/bin/env bash
# Check all commands for LLM-Agent-First compliance

PASS=0
FAIL=0

for script in scripts/*.sh; do
  cmd=$(basename "$script" .sh)

  # Check required sources
  grep -q "exit-codes.sh" "$script" || { echo "FAIL: $cmd missing exit-codes.sh"; ((FAIL++)); continue; }
  grep -q "error-json.sh" "$script" || { echo "FAIL: $cmd missing error-json.sh"; ((FAIL++)); continue; }
  grep -q "resolve_format" "$script" || { echo "FAIL: $cmd missing resolve_format"; ((FAIL++)); continue; }

  # Check flags
  grep -q "\-\-format" "$script" || { echo "WARN: $cmd missing --format flag"; }
  grep -q "\-\-quiet\|\-q" "$script" || { echo "WARN: $cmd missing --quiet flag"; }

  echo "PASS: $cmd"
  ((PASS++))
done

echo "Results: $PASS passed, $FAIL failed"

Part 13: Files Reference

Required Library Files

FilePurpose
lib/exit-codes.shExit code constants (17 codes)
lib/error-json.shError JSON output (29 error codes)
lib/output-format.shFormat resolution with TTY detection
lib/hierarchy.shHierarchy validation functions

Required Schema Files

FilePurpose
schemas/todo.schema.jsonTask data schema
schemas/archive.schema.jsonArchive data schema
schemas/log.schema.jsonAudit log schema
schemas/config.schema.jsonConfiguration schema
schemas/output.schema.jsonSuccess response envelope
schemas/error.schema.jsonError response envelope
schemas/critical-path.schema.jsonCritical path analysis response

Reference Implementations

These commands exemplify best practices:
FileWhy Study It
scripts/analyze.shGold standard - JSON default, --human flag
scripts/exists.shPerfect exit codes pattern
scripts/list.shComprehensive JSON envelope
scripts/validate.sh--fix and JSON patterns

Appendix A: Quick Reference Card

Exit Code Quick Reference

0   SUCCESS              100 NO_DATA (not error)
1   GENERAL_ERROR        101 ALREADY_EXISTS (not error)
2   INVALID_INPUT        102 NO_CHANGE (not error)
3   FILE_ERROR
4   NOT_FOUND            10  PARENT_NOT_FOUND
5   DEPENDENCY_ERROR     11  DEPTH_EXCEEDED
6   VALIDATION_ERROR     12  SIBLING_LIMIT
7   LOCK_TIMEOUT         13  INVALID_PARENT_TYPE
8   CONFIG_ERROR         14  CIRCULAR_REFERENCE
                         15  ORPHAN_DETECTED
                         20  CHECKSUM_MISMATCH
                         21  CONCURRENT_MODIFICATION
                         22  ID_COLLISION

SESSION (30-39):
30  SESSION_EXISTS       35  TASK_CLAIMED
31  SESSION_NOT_FOUND    36  SESSION_REQUIRED
32  SCOPE_CONFLICT       37  SESSION_CLOSE_BLOCKED
33  SCOPE_INVALID        38  FOCUS_REQUIRED
34  TASK_NOT_IN_SCOPE    39  NOTES_REQUIRED

Error Code Quick Reference

Task:       E_TASK_NOT_FOUND, E_TASK_ALREADY_EXISTS, E_TASK_INVALID_ID, E_TASK_INVALID_STATUS
File:       E_FILE_NOT_FOUND, E_FILE_READ_ERROR, E_FILE_WRITE_ERROR, E_FILE_PERMISSION
Validation: E_VALIDATION_SCHEMA, E_VALIDATION_CHECKSUM, E_VALIDATION_REQUIRED
Input:      E_INPUT_MISSING, E_INPUT_INVALID, E_INPUT_FORMAT
Hierarchy:  E_PARENT_NOT_FOUND, E_DEPTH_EXCEEDED, E_SIBLING_LIMIT, E_INVALID_PARENT_TYPE,
            E_CIRCULAR_REFERENCE, E_ORPHAN_DETECTED
Concurrency: E_CHECKSUM_MISMATCH, E_CONCURRENT_MODIFICATION, E_ID_COLLISION
Phase:      E_PHASE_NOT_FOUND, E_PHASE_INVALID
Session:    E_SESSION_EXISTS, E_SESSION_NOT_FOUND, E_SCOPE_CONFLICT, E_SCOPE_INVALID,
            E_TASK_NOT_IN_SCOPE, E_TASK_CLAIMED, E_SESSION_REQUIRED, E_SESSION_CLOSE_BLOCKED,
            E_FOCUS_REQUIRED, E_NOTES_REQUIRED (legacy: E_SESSION_ACTIVE, E_SESSION_NOT_ACTIVE)
General:    E_UNKNOWN, E_NOT_INITIALIZED, E_DEPENDENCY_MISSING, E_DEPENDENCY_VERSION

JSON Envelope Quick Reference

{
  "$schema": "https://cleo.dev/schemas/v1/output.schema.json",
  "_meta": {"format": "json", "version": "0.17.0", "command": "add", "timestamp": "2025-12-17T12:00:00Z"},
  "success": true,
  "task": {"id": "T001", "type": "task", "parentId": null, "size": null, ...}
}

Part 14: Pagination Standards (v0.88.0+)

14.1 Pagination Envelope

Commands returning arrays SHOULD include a pagination object in the JSON envelope when results are paginated:
{
  "$schema": "https://cleo-dev.com/schemas/v1/output.schema.json",
  "_meta": { "format": "json", "command": "list", "timestamp": "...", "version": "..." },
  "success": true,
  "pagination": {
    "total": 150,
    "limit": 50,
    "offset": 0,
    "hasMore": true
  },
  "tasks": [ ... ]
}
The pagination object conforms to mcp-server/schemas/common/pagination.schema.json. All four fields (total, limit, offset, hasMore) are REQUIRED when the pagination key is present.

14.2 Smart Default Limits

Each command type has a context-optimized default page size via get_default_limit() in lib/json-output.sh:
CommandDefaultOverride
list / tasks50--limit N
session list10--limit N
find / search10--limit N
log20--limit N
archive25--limit N
Others50--limit N
Use --limit 0 to disable pagination and retrieve all items.

14.3 Pagination Flags

FlagTypeDefaultDescription
--limit NintegerSmart defaultMaximum items to return (0 = unlimited)
--offset Ninteger0Number of items to skip
Commands that support pagination MUST document these flags in their ###CLEO header block.

14.4 Compact Output Mode

List commands SHOULD use compact representations to minimize token consumption:
  • compact_task() strips notes, description, acceptance, files, verification, _archive
  • compact_session() strips focusHistory, stats, taskSnapshots, notes, events
Agents use ct show <id> when full detail is needed for a specific item.

14.5 Agent Pagination Pattern

Agents SHOULD follow this pattern for paginated data:
# Page 1 (uses smart default limit)
result=$(ct list)
has_more=$(echo "$result" | jq -r '.pagination.hasMore')

# Page 2 (only if needed)
if [[ "$has_more" == "true" ]]; then
  offset=$(echo "$result" | jq '.pagination.offset + .pagination.limit')
  result=$(ct list --offset "$offset")
fi

14.6 Implementation Library

All pagination functionality is centralized in lib/json-output.sh:
FunctionPurpose
output_successStandard non-paginated success envelope
output_error_envelopeLightweight error envelope
output_paginatedPaginated success envelope with metadata
apply_paginationSlice a JSON array by limit/offset
get_pagination_metaGenerate pagination metadata object
get_default_limitSmart default limit by command type
compact_taskStrip verbose fields from tasks
compact_sessionStrip verbose fields from sessions
Full specification: DYNAMIC-OUTPUT-LIMITS-SPEC.md
Specification v3.4 - Authoritative Standard for LLM-Agent-First CLI Design Applicable to: cleo and any LLM-agent-first CLI project Last updated: 2026-02-10