mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-11 13:28:57 +00:00
1e3991c865
* feat(schema): publish cmdhelp.schema.json v0.1 (TASK-932)
Adds the formal JSON Schema (draft 2020-12) describing cmdhelp v0.1's
`--format json` wire format, plus a schema/README.md documenting intended
use for CLI authors and consumer wrappers.
Per IDEA-927 the schema enforces the v0.1 contracts:
- Required top-level: cmdhelp_version, binary, commands.
- cmdhelp_version pattern is MAJOR.MINOR (no PATCH); '0.1.0' invalid.
- Argument types: closed set {string,int,float,bool,enum,path,url,
duration,date,datetime,json,ref} plus x-* extension namespace.
- Boolean flag arity (§5.3): negate_flag only valid when type=bool.
- exit_codes union (§5.2): each entry is string OR object{when,recovery,
message_template}; object form requires `when`. Codes must be numeric.
- Dynamic enums (§7): enum_source pattern is `^dynamic:.+$`.
- additionalProperties: true at extension points for forward-compat (§9).
Validated locally with python jsonschema: 8 tests (1 valid sample,
5 negative cases, 2 x-*/dynamic positive cases) all pass.
Unblocks TASK-934 (JSON emitter), TASK-938 (test suite validates against
this schema), and TASK-940 (publish on getpad.dev).
Parent: PLAN-930.
* fix(schema): enforce flag-name pattern + clarify test-suite wording per Codex review (round 1)
Two findings from Codex round 1 on PR #325:
1. flagMap accepted invalid keys ('--verbose', empty string, digit-leading)
despite README documenting "without leading --". Added propertyNames
pattern `^[a-zA-Z][a-zA-Z0-9_-]*$` so producers can't ship malformed
flag names that consumers would parse incorrectly.
2. schema/README.md described `internal/cmdhelp/` as if it existed and
already validated in `go test ./...`. The package ships in TASK-934
(and the validation test in TASK-938). Reworded as future/planned.
Verification:
- Schema syntax: still valid Draft 2020-12.
- Original 8 tests still pass.
- 3 new negative tests for the propertyNames pattern: '--verbose',
empty string, and digit-leading keys all correctly rejected.
- Single-letter short-flag keys like `h` still accepted.
- `make check` clean.
Note for next review round: Codex's first run reported a `httptest`
panic in `cmd/pad` tests; that's a sandbox networking constraint
(read-only mode can't bind sockets), not a regression — verified
locally with `go test ./cmd/pad/ -run TestEnsureWorkspaceSlugAttachExisting -count=1` (passes).
272 lines
11 KiB
JSON
272 lines
11 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://getpad.dev/cmdhelp.schema.json",
|
|
"title": "cmdhelp v0.1",
|
|
"description": "JSON Schema for output of `<cmd> help --format json` per the cmdhelp v0.1 spec. Tools advertising cmdhelp/0.1 via their capability bit emit documents matching this schema. Consumers (LLM harnesses, MCP servers, IDE plugins, wrappers) validate against this schema before parsing. See IDEA-927 for the full spec.",
|
|
"type": "object",
|
|
"required": ["cmdhelp_version", "binary", "commands"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"cmdhelp_version": {
|
|
"description": "Wire format version. MAJOR.MINOR only — never includes a PATCH component. See spec §9.",
|
|
"type": "string",
|
|
"pattern": "^[0-9]+\\.[0-9]+$"
|
|
},
|
|
"binary": {
|
|
"description": "The CLI's binary name as invoked on the command line (e.g. \"pad\", \"git\", \"kubectl\").",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"version": {
|
|
"description": "The implementation's own software version (independent from cmdhelp_version). Free-form; semver recommended.",
|
|
"type": "string"
|
|
},
|
|
"summary": {
|
|
"description": "One-line description of the binary, suitable for an LLM context blurb.",
|
|
"type": "string"
|
|
},
|
|
"homepage": {
|
|
"description": "Optional canonical URL for the project.",
|
|
"type": "string",
|
|
"format": "uri"
|
|
},
|
|
"global_flags": {
|
|
"description": "Flags that apply to every command in the tree (e.g. --workspace, --format). Same shape as a per-command `flags` object.",
|
|
"$ref": "#/$defs/flagMap"
|
|
},
|
|
"commands": {
|
|
"description": "Map of command paths (space-joined, e.g. \"item create\") to their definitions.",
|
|
"type": "object",
|
|
"additionalProperties": { "$ref": "#/$defs/command" },
|
|
"minProperties": 0
|
|
},
|
|
"schemas": {
|
|
"description": "Reusable JSON Schema fragments referenced by per-command stdout definitions via `json_schema_ref` (e.g. \"#/schemas/Item\").",
|
|
"type": "object",
|
|
"additionalProperties": true
|
|
},
|
|
"context": {
|
|
"description": "Optional dynamic context spliced into help output by CLIs with session state. See spec §7.",
|
|
"type": "object",
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"workspace": { "type": "string" },
|
|
"profile": { "type": "string" },
|
|
"auth": { "type": "string" }
|
|
}
|
|
}
|
|
},
|
|
|
|
"$defs": {
|
|
|
|
"command": {
|
|
"description": "Definition of a single command in the CLI.",
|
|
"type": "object",
|
|
"required": ["summary"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"summary": {
|
|
"description": "One-line description of the command.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"description": {
|
|
"description": "Optional longer prose explanation, may include markdown.",
|
|
"type": "string"
|
|
},
|
|
"args": {
|
|
"description": "Positional arguments in order.",
|
|
"type": "array",
|
|
"items": { "$ref": "#/$defs/arg" }
|
|
},
|
|
"flags": {
|
|
"description": "Map of flag names (without leading dashes) to flag definitions.",
|
|
"$ref": "#/$defs/flagMap"
|
|
},
|
|
"stdin": {
|
|
"description": "Whether the command accepts stdin and the expected format.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"accepted": { "type": "boolean" },
|
|
"format": { "type": "string", "description": "MIME-style hint such as text/markdown or application/json." }
|
|
},
|
|
"required": ["accepted"]
|
|
},
|
|
"stdout": {
|
|
"description": "What the command writes to stdout on success.",
|
|
"type": "object",
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"text_template": { "type": "string", "description": "Template string for the human-readable line; may use {placeholder} substitutions." },
|
|
"json_schema_ref": { "type": "string", "description": "JSON Pointer (typically into the top-level `schemas` map, e.g. \"#/schemas/Item\") describing the JSON shape printed under --format json." }
|
|
}
|
|
},
|
|
"exit_codes": {
|
|
"description": "Map of exit codes (as decimal strings) to their meaning. Each entry MAY be a terse string OR a rich object — consumers MUST handle both forms (spec §5.2).",
|
|
"type": "object",
|
|
"patternProperties": {
|
|
"^[0-9]+$": {
|
|
"oneOf": [
|
|
{ "type": "string", "minLength": 1 },
|
|
{ "$ref": "#/$defs/exitCodeRich" }
|
|
]
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
"examples": {
|
|
"description": "Canonical examples — single source of truth, rendered to both --format json and --format md (spec §6).",
|
|
"type": "array",
|
|
"items": { "$ref": "#/$defs/example" }
|
|
},
|
|
"see_also": {
|
|
"description": "Other command paths a reader might find relevant (e.g. [\"item update\", \"item delete\"]).",
|
|
"type": "array",
|
|
"items": { "type": "string" }
|
|
},
|
|
"since": {
|
|
"description": "Implementation version in which the command first appeared (independent of cmdhelp_version).",
|
|
"type": "string"
|
|
},
|
|
"stability": {
|
|
"description": "Stability promise. Free-form; common values: experimental, beta, stable, deprecated.",
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
|
|
"arg": {
|
|
"description": "A positional argument.",
|
|
"type": "object",
|
|
"required": ["name", "type"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"name": { "type": "string", "minLength": 1 },
|
|
"type": { "$ref": "#/$defs/typeName" },
|
|
"required": { "type": "boolean" },
|
|
"description": { "type": "string" },
|
|
"default": { "description": "Default value if argument is omitted (when not required)." },
|
|
"format": { "type": "string", "description": "Free-form sub-type hint (e.g. \"key=value\")." },
|
|
"repeatable": { "type": "boolean" },
|
|
"enum": { "$ref": "#/$defs/enumValues" },
|
|
"enum_source": { "$ref": "#/$defs/enumSource" }
|
|
},
|
|
"allOf": [
|
|
{ "$ref": "#/$defs/enumConsistency" }
|
|
]
|
|
},
|
|
|
|
"flagMap": {
|
|
"description": "Map of flag names (without leading --) to flag definitions. Keys MUST match the flag-name pattern: start with a letter, followed by letters, digits, hyphens, or underscores. Long-form (e.g. `format`) and short-form (e.g. `h`) names are both valid; consumers add the leading `-` or `--` themselves at invocation time.",
|
|
"type": "object",
|
|
"propertyNames": {
|
|
"pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$"
|
|
},
|
|
"additionalProperties": { "$ref": "#/$defs/flag" }
|
|
},
|
|
|
|
"flag": {
|
|
"description": "A flag (option) on a command.",
|
|
"type": "object",
|
|
"required": ["type"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"type": { "$ref": "#/$defs/typeName" },
|
|
"required": { "type": "boolean" },
|
|
"description": { "type": "string" },
|
|
"default": { "description": "Default value if flag is omitted." },
|
|
"format": { "type": "string", "description": "Free-form sub-type hint (e.g. \"key=value\")." },
|
|
"repeatable": { "type": "boolean", "description": "True if the flag may appear more than once on a single invocation." },
|
|
"enum": { "$ref": "#/$defs/enumValues" },
|
|
"enum_source": { "$ref": "#/$defs/enumSource" },
|
|
"negate_flag": {
|
|
"description": "Optional negation form for a bool flag (e.g. \"--no-cache\"). Only meaningful when type=bool. See spec §5.3.",
|
|
"type": "string",
|
|
"pattern": "^--[a-zA-Z0-9][a-zA-Z0-9-]*$"
|
|
}
|
|
},
|
|
"allOf": [
|
|
{ "$ref": "#/$defs/enumConsistency" },
|
|
{
|
|
"description": "negate_flag only applies to bool-typed flags.",
|
|
"if": { "required": ["negate_flag"] },
|
|
"then": { "properties": { "type": { "const": "bool" } } }
|
|
}
|
|
]
|
|
},
|
|
|
|
"typeName": {
|
|
"description": "Argument/flag type. Closed set per spec §5.1, plus x-* extension namespace for tool-specific types.",
|
|
"anyOf": [
|
|
{
|
|
"type": "string",
|
|
"enum": [
|
|
"string", "int", "float", "bool", "enum",
|
|
"path", "url", "duration", "date", "datetime", "json", "ref"
|
|
]
|
|
},
|
|
{
|
|
"type": "string",
|
|
"pattern": "^x-[a-zA-Z0-9][a-zA-Z0-9_-]*$",
|
|
"description": "Tool-specific extension type. Wrappers MAY treat unknown x-* types as `string`."
|
|
}
|
|
]
|
|
},
|
|
|
|
"enumValues": {
|
|
"description": "Concrete list of allowed values when type=enum (or as a snapshot for dynamic enums).",
|
|
"type": "array",
|
|
"items": {
|
|
"type": ["string", "number", "boolean"]
|
|
},
|
|
"minItems": 0
|
|
},
|
|
|
|
"enumSource": {
|
|
"description": "Reference to a command that resolves the enum dynamically at help-emission time (e.g. \"dynamic:pad collection list\"). The literal `enum` array, when present alongside, is a snapshot.",
|
|
"type": "string",
|
|
"pattern": "^dynamic:.+$"
|
|
},
|
|
|
|
"enumConsistency": {
|
|
"description": "If `enum` or `enum_source` is set, the type SHOULD be `enum`. Encoded as a soft constraint via if/then so this remains permissive for tools that prefer to type the slot more specifically.",
|
|
"if": {
|
|
"anyOf": [
|
|
{ "required": ["enum"] },
|
|
{ "required": ["enum_source"] }
|
|
]
|
|
},
|
|
"then": {
|
|
"properties": {
|
|
"type": { "type": "string" }
|
|
}
|
|
}
|
|
},
|
|
|
|
"exitCodeRich": {
|
|
"description": "Rich object form of an exit-code entry. The string form is also valid (see exit_codes patternProperties).",
|
|
"type": "object",
|
|
"required": ["when"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"when": { "type": "string", "minLength": 1, "description": "Concise description of the condition that produces this exit code." },
|
|
"recovery": { "type": "string", "description": "Suggested user/agent action to recover from this exit code." },
|
|
"message_template": { "type": "string", "description": "Template for the stderr message printed alongside the exit; may use {placeholder} substitutions." }
|
|
}
|
|
},
|
|
|
|
"example": {
|
|
"description": "A canonical example invocation. Same source for --format json and --format md (spec §6).",
|
|
"type": "object",
|
|
"required": ["cmd"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"cmd": { "type": "string", "minLength": 1, "description": "The runnable invocation. MUST resolve against the live command tree (validated in test suite per spec §6)." },
|
|
"note": { "type": "string", "description": "Short prose accompanying the invocation." }
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|