agents-shipgate detect, doctor, and structured errors emit a
diagnostics[] and next_actions[] block alongside the existing
next_action: str field. A coding-agent caller can read the rank-1
action and route to the next command without consulting human-facing
docs.
Diagnostics describe conditions; the catalog itself does not pick exit
codes. A diagnostic with severity: "block" flags a blocking condition
and the caller (a CLI command) decides what to do. The current rules:
agents-shipgate scanalways exits non-zero (ConfigError(2),InputParseError(3), or the scan-policy20) on any condition that used to fail it. Diagnostics are extra context, not a replacement.agents-shipgate doctor --jsonis the agent contract: it exits 0 onSHIP-DIAG-MISSING-SOURCE-FILEso the agent can readunresolved_sources[]and route to a fix.agents-shipgate doctor(no--json) is the human contract: it exits 3 when any payload hasunresolved_sources, so an interactive user still sees a loud failure. The diagnostic block prints in the human output regardless.
This is the only place a diagnostic affects an exit code, and the
divergence is bounded to MISSING-SOURCE-FILE on doctor. Other
diagnostics (ZERO-TOOLS, CHANGE-ME-PLACEHOLDERS, etc.) print but
do not change the exit code.
Diagnostic (one per detected condition):
{
"id": "SHIP-DIAG-...",
"title": "Human-readable one-liner",
"severity": "block | warn | info",
"next_actions": [ NextAction, ... ]
}NextAction (ranked recovery step; ordered list — array position is
the rank, no separate rank field):
| Field | Type | Notes |
|---|---|---|
| kind | command | edit | review | stop |
Action category. |
| command | string | null |
Required when kind="command". Always null when "stop". |
| path | string | null |
Required when kind="edit". May be shipgate.yaml:<line>. |
| why | string |
One-sentence rationale. |
| expects | string | null |
Optional: what the next run should output if the action worked. |
The legacy next_action: str field on detect, doctor, and
agent-mode error JSON is the rank-1 action projected to a single string:
| Rank-1 kind | Legacy projection |
|---|---|
| command | the command value verbatim |
| edit | Edit <path> |
| review | Review: <why> |
| stop | Stop: <why> |
This keeps next_action string-typed even for negative-control
diagnostics where no command should run.
| ID | Severity | Fires when |
|---|---|---|
SHIP-DIAG-MISSING-MANIFEST |
block | The manifest file does not exist on disk. Rank-1 action: agents-shipgate detect --workspace <dir> --json. |
SHIP-DIAG-INVALID-MANIFEST |
block | The manifest file exists but the loader rejected it (invalid YAML, schema validation failure, unsupported version). Rank-1 action: edit <path>. |
SHIP-DIAG-NO-AGENT-SURFACE |
info | is_agent_project=false AND suggested_sources=[] AND codex_plugin_candidates=[] AND no manifest. Catch-all negative control. |
SHIP-DIAG-NON-AGENT-LIBRARY |
info | Python project (≥1 .py file + pyproject/requirements) with no agent framework, prompts, or tool surface. |
SHIP-DIAG-PURE-PROMPT-EXPERIMENT |
info | Only prompts/ is present; no Python framework, no tool sources. |
SHIP-DIAG-MCP-OPENAPI-ARTIFACT-ONLY |
info | is_agent_project=false BUT suggested_sources has MCP/OpenAPI entries. Artifact-only repos are valid Shipgate targets. |
SHIP-DIAG-CODEX-PLUGIN-PACKAGE-DETECTED |
info | is_agent_project=false BUT Codex plugin package or marketplace artifacts are present. Codex plugin repos are valid Shipgate targets. |
SHIP-DIAG-ZERO-TOOLS |
block | Manifest exists but doctor reports total_tools=0. |
SHIP-DIAG-DYNAMIC-TOOLSETS-ONLY |
warn | total_tools < 3 AND any of dynamic_toolset_count / dynamic_tool_surface_count ≥ 1 across ADK / LangChain / CrewAI surfaces. |
SHIP-DIAG-MISSING-SOURCE-FILE |
block | A required tool_sources[].path does not resolve under the manifest directory. (doctor no longer raises InputParseError(3) for this — see below.) |
SHIP-DIAG-CHANGE-ME-PLACEHOLDERS |
warn | Manifest text still contains CHANGE_ME markers. |
SHIP-DIAG-NO-PRODUCTION-PERMISSIONS |
warn | environment.target: production AND no permissions / scopes / policies declared. |
SHIP-DIAG-UNKNOWN-ADAPTER-SOURCE-TYPE |
block | Manifest references a tool_sources[].type that no registered adapter handles. Rank-1 action depends on plugin state: enable plugin discovery (AGENTS_SHIPGATE_ENABLE_PLUGINS=1) and install the third-party adapter package, or fix a typo. v0.20+. |
When more than one negative-control predicate matches, only the most specific diagnostic fires:
SHIP-DIAG-PURE-PROMPT-EXPERIMENT
> SHIP-DIAG-NON-AGENT-LIBRARY
> SHIP-DIAG-NO-AGENT-SURFACE
A workspace with both a prompts/ directory and a pyproject.toml
emits only SHIP-DIAG-PURE-PROMPT-EXPERIMENT, not the broader
SHIP-DIAG-NON-AGENT-LIBRARY.
Before this feature, agents-shipgate doctor raised InputParseError(3)
when a required tool_sources[].path failed to load. That gave a coding
agent no routable next step.
Now doctor --json exits 0 with:
unresolved_sources: [{id, declared_path, line, reason}]listing each unresolved entry.reasonis"missing"(file does not exist) or"outside_manifest_dir"(file exists but resolves outside the manifest directory; loaders refuse it on containment grounds).- a
SHIP-DIAG-MISSING-SOURCE-FILEdiagnostic whose rank-1 action is aneditpointing at<manifest_path>:<line>(the full path the user invokeddoctorwith, so workspace and nested-manifest runs stay unambiguous).
The non-JSON form (agents-shipgate doctor without --json) prints
the same unresolved_sources and diagnostic block in human-readable
form and exits 3 to preserve the pre-feature loud failure for
interactive users.
scan is unchanged — it still raises InputParseError(3) on missing
or escaped required sources regardless of --json, because once an
agent moves past doctor, those are real scan failures.
Diagnostics are emitted in three places:
detect --json— workspace classification + recovery hints.- Each
doctor --jsonpayload — per-manifest diagnostics. AGENTS_SHIPGATE_AGENT_MODE=1stderr error JSON — alongside the existingerrorandnext_actionfields, errors now also carrynext_actions: list[NextAction].
Diagnostics are not added to report.json (the v0.9 schema is
unchanged). Per-finding remediation already has its own v0.7 fields
(autofix_safe, requires_human_review, suggested_patch_kind,
docs_url); diagnostics are pre-scan recovery hints, not post-scan
remediation.