The agent contract#
How a machine drives this CLI. The rules are fixed by ADR-0017; this document is generated from the source so it cannot drift from it.
The rules#
One JSON document per invocation. In
--jsonmode stdout carries exactly one JSON document. Prose, progress and warnings go to stderr, socmd --json 2>/dev/null | jq .always parses.One diagnostic envelope, everywhere:
{ "severity": "error" | "warning" | "info", "code": "snake_case_stable_string", "message": "human sentence", "target": "the thing at fault (optional)", "fix": "one actionable sentence or command (optional)", "file": "relative/path.md (optional)", "line": 42 }Branch on
code, never onmessage.messageis prose and may be reworded;codeis the published surface.Failure carries the command's null-shape. A failure prints
{ ...nullShape, "status": [diagnostic] }and exits 1 — never bare stderr, never a partial document. A consumer never has to tell "it failed" apart from "it printed nothing".camelCase everywhere in command output. (The on-disk formats are not command output:
pack.yamlkeepsschema_versionand.specops.lockkeepspack_id.)--jsonand--format jsonare the same flag. The second spelling predates the contract and keeps working.
Exit codes#
| Situation | Exit |
|---|---|
| Success, including advisory warnings | 0 |
Command failure — one JSON document with status | 1 |
Gate failure (validate, doctor, change validate) | 1 |
| Usage error — unknown flag, missing or malformed argument | 2 |
| Required script missing (a broken installation) | 3 |
| Cancelled by the user | 130 |
A gate command exits 1 when it finds something, which is not the
same as failing to run. The status array tells the two apart: a gate that could
not run reports an error about itself.
Commands#
| Command | Document key | Gate |
|---|---|---|
csda doctor --json | doctor | yes |
csda status --json | status | no |
csda alm pull --json | pulled | no |
csda validate <dir> --json | validation | yes |
csda plan --json | plan | no |
csda report --json | report | no |
csda done <REQ> --json | requirement | no |
csda change list --json | changes | no |
csda change show <id> --json | change | no |
csda change status --json | artifacts | no |
csda change validate <id> --json | change | yes |
csda change archive <id> --json | archive | yes |
csda change instructions <artifact> --json | instructions | no |
csda change author <id> --json | change | yes |
csda specops diff --json | changes | no |
csda harness run --format json | results | no |
csda harness report --json | report | no |
The document key is what the payload carries on success and what is set to
null on failure. Every document also carries status: an array of
diagnostics, empty when there is nothing to report.
Diagnostic codes#
Harvested from the source. A trailing * marks a family whose suffix is
computed — strict_tdd_* covers strict_tdd_1 through strict_tdd_3.
| Code | Emitted by |
|---|---|
events_header_missing | scripts/validate_specs.ts |
feature_not_in_matrix | scripts/validate_specs.ts |
missing_required_file | scripts/validate_specs.ts |
no_features | scripts/validate_specs.ts |
requirement_cycle | scripts/validate_specs.ts |
self_dependency | scripts/validate_specs.ts |
strict_tdd_1 | scripts/validate_specs.ts |
strict_tdd_2 | scripts/validate_specs.ts |
strict_tdd_3 | scripts/validate_specs.ts |
traceability_unrecognized | scripts/validate_specs.ts |
unknown_dependency | scripts/validate_specs.ts |
use_cases_header_missing | scripts/validate_specs.ts |
archive_change_name_required | scripts/cli/commands/change/ChangeCommand.ts |
archive_spec_update_failed | scripts/cli/commands/change/ChangeCommand.ts |
author_agent_failed | scripts/cli/commands/change/ChangeCommand.ts |
author_agent_unset | scripts/cli/commands/change/ChangeCommand.ts |
author_needs_git | scripts/cli/commands/change/ChangeCommand.ts |
author_out_of_scope | scripts/cli/commands/change/ChangeCommand.ts |
author_tree_dirty | scripts/cli/commands/change/ChangeCommand.ts |
change_error | scripts/cli/commands/change/ChangeCommand.ts |
change_exists | scripts/cli/commands/change/ChangeCommand.ts |
change_id_required | scripts/cli/commands/change/ChangeCommand.ts |
change_not_found | scripts/cli/commands/change/ChangeCommand.ts |
change_required | scripts/cli/commands/change/ChangeCommand.ts |
invalid_change_id | scripts/cli/commands/change/ChangeCommand.ts |
missing_proposal | packages/core/src/application/ValidateChangeUseCase.ts |
no_deltas | packages/core/src/application/ValidateChangeUseCase.ts |
delta_empty | packages/core/src/domain/DeltaSpec.ts |
delta_unknown_section | packages/core/src/domain/DeltaSpec.ts |
duplicate_requirement | packages/core/src/domain/DeltaSpec.ts |
invalid_requirement_id | packages/core/src/domain/DeltaSpec.ts |
no_rfc2119_keyword | packages/core/src/domain/DeltaSpec.ts |
requirement_already_exists | packages/core/src/domain/DeltaSpec.ts |
requirement_without_scenario | packages/core/src/domain/DeltaSpec.ts |
scenario_not_gherkin | packages/core/src/domain/DeltaSpec.ts |
scenario_steps_unwritten | packages/core/src/domain/DeltaSpec.ts |
scenario_without_steps | packages/core/src/domain/DeltaSpec.ts |
unknown_requirement | packages/core/src/domain/DeltaSpec.ts |
archive_capability_retired | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_change_not_found | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_change_symlink | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_feature_exists | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_no_deltas | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_retire_not_declared | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_target_exists | packages/core/src/application/ArchiveChangeUseCase.ts |
archive_tasks_incomplete | packages/core/src/application/ArchiveChangeUseCase.ts |
traceability_upgraded | packages/core/src/application/ArchiveChangeUseCase.ts |
artifact_required | scripts/change/instructions.ts |
artifact_unknown | scripts/change/instructions.ts |
change_not_found | scripts/change/instructions.ts |
project_not_found | scripts/change/instructions.ts |
ambiguous_pack | scripts/cli/commands/specops/ContributeCommand.ts |
change_has_no_deltas | scripts/cli/commands/specops/ContributeCommand.ts |
change_not_found | scripts/cli/commands/specops/ContributeCommand.ts |
change_required | scripts/cli/commands/specops/ContributeCommand.ts |
contribute_not_pushed | scripts/cli/commands/specops/ContributeCommand.ts |
contribute_stage_failed | scripts/cli/commands/specops/ContributeCommand.ts |
no_lockfile | scripts/cli/commands/specops/ContributeCommand.ts |
nothing_to_contribute | scripts/cli/commands/specops/ContributeCommand.ts |
pack_has_no_repo | scripts/cli/commands/specops/ContributeCommand.ts |
pack_not_found | scripts/cli/commands/specops/ContributeCommand.ts |
Worked example#
csda change instructions specs --json 2>/dev/null | jq -r '.instructions.rules[]'An agent that reads fix self-corrects; one that reads only message retries
the same mistake. That is why a check without a fix is treated as unfinished.