@knowttl/gitea-axi 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) knowttl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,20 @@
1
1
  # gitea-axi
2
2
 
3
- An agent CLI for Gitea repository, issue and pull request reads with private credential profiles.
4
- It includes a bounded repository dashboard, list and detail views, reviews, checks, diffs and classified raw GET requests.
5
- Verification uses offline fakes only; live Gitea access remains unverified.
3
+ Agent-facing Gitea CLI with private credential profiles, read-only by default.
6
4
 
7
- Use Node 24.14.0 (minimum 24.11.0) and npm 11.9.0.
8
- Dependencies are exact in `package.json` and transitive versions are locked in `package-lock.json`.
5
+ [Why](#why-this-exists) · [Quickstart](#quickstart) · [Commands](#commands) · [Safety](#safety) · [Configuration](#configuration) · [Limitations](#limitations) · [Development](#development) · [License](#license)
6
+
7
+ ## Why this exists
8
+
9
+ Agents need bounded, predictable output and explicit write controls instead of guessed API calls and unbounded responses.
10
+ gitea-axi offers a bounded repository dashboard, detailed reads, Actions task summaries, bounded search, repository, release, issue, label and pull request mutations, and classified raw API requests.
11
+ Output is compact TOON with structured errors.
12
+ Reads are bounded, and writes require an exact operation grant, a preview and a matching confirmation.
13
+ See [Limitations](#limitations) for verification scope.
14
+
15
+ ## Quickstart
16
+
17
+ Requires Node.js 24.11.0 or later (below 25).
9
18
  Install the public npm package:
10
19
 
11
20
  ```sh
@@ -13,24 +22,64 @@ npm install -g @knowttl/gitea-axi
13
22
  gitea-axi --version
14
23
  ```
15
24
 
16
- The executable is `gitea-axi`.
17
- To build from source:
25
+ Without a configured profile, the dashboard exits 1 with `AUTH_REQUIRED` and asks for a configured profile or an explicit anonymous `--origin`:
26
+
27
+ ```text
28
+ error:
29
+ code: AUTH_REQUIRED
30
+ operation: dashboard
31
+ ```
32
+
33
+ Configure a profile as described in [Configuration](#configuration), then check local credentials, read issues and verify the identity:
18
34
 
19
35
  ```sh
20
- npm ci --ignore-scripts
21
- npm run build
22
- node dist/cli.js repo view --help
23
- node scripts/verify.mjs --task S1.1
36
+ gitea-axi config path
37
+ gitea-axi auth status
38
+ gitea-axi issue list --repo OWNER/NAME --state open --limit 2
39
+ gitea-axi doctor
24
40
  ```
25
41
 
26
- The verification driver builds and packs the CLI, installs that tarball into an empty temporary prefix and invokes its installed bin from outside the source tree.
27
- Dependency installation can access npm; Gitea requests use only local fakes.
28
- Only the local HTTP fake replaces the vendor boundary.
29
- The S1.1 driver checks AC-001, AC-002 and AC-004, package contents, registry freshness, type checking, linting and refusal cases.
30
- Its report contains exact process invocations, assertions, runtime pins and the package SHA-256.
31
- Each driver prints its report path.
32
- Set `GITEA_AXI_REPORT` to retain the report at a chosen path.
33
- The default report goes to a unique file in the OS temporary directory; temporary installation files are removed.
42
+ Anonymous reads need an explicit `--origin`, for example `gitea-axi issue list --repo OWNER/NAME --origin https://gitea.example.test`.
43
+
44
+ ## Use with your agent
45
+
46
+ The generated [skill](generated/SKILL.md) is the full command reference for agents.
47
+ Session hook installation (`gitea-axi setup hooks`) is pending and currently exits with `CAPABILITY_GAP` before any effects.
48
+
49
+ ## Commands
50
+
51
+ `gitea-axi capabilities` lists all 124 retained baseline leaves, including explicit gaps.
52
+ Use `gitea-axi capabilities C038` to inspect a command's baseline outcome and captured flags.
53
+ Discussions, gists, projects, issue lock/transfer/subissue workflows, draft transitions, HEAD and jq/template compatibility remain unavailable.
54
+ `setup hooks`, `update` and `update --check` refuse before effects until their installation and distribution contracts are evidenced.
55
+ The generated [skill](generated/SKILL.md), help and host inventory share the executable registry.
56
+ Command groups support `--help` to list their subcommands; unknown-command errors identify the requested command.
57
+ See the [catalogue contract](docs/catalogue-contract.md) for investigated versions and pending checks.
58
+
59
+ ### Actions runs
60
+
61
+ `run list` reads Gitea 1.23.4 Actions task summaries with workflow, branch, status, event and commit filters across bounded pages.
62
+ Versions older than 1.24 may return an empty task list even when workflow runs exist; empty output discloses this limitation.
63
+ An unavailable or hidden task route reports `CAPABILITY_GAP`.
64
+ `--user` and unmapped fields fail explicitly.
65
+ Task IDs and status strings do not establish run identity, job conclusions or terminal run results.
66
+ All seven run commands and five workflow commands remain discoverable with `--help`.
67
+ Run detail, watch, logs, downloads and workflow controls report explicit capability gaps.
68
+ Mutating run and workflow controls remain unsupported pending contract commissioning.
69
+ Full workflow run APIs require Gitea 1.24 or later; the retained workflow, artifact and job-log routes remain pending commissioning here.
70
+
71
+ ### Search
72
+
73
+ `search issues|prs|repos|commits "text"` searches at most 20 pages and 1000 candidates per collection, then filters, sorts and applies the total output limit (default 1000).
74
+ Issue and PR searches support exact arbitrary author and assignee names and comma-separated label ANY matching; an unknown label alone matches nothing.
75
+ Use `--repo OWNER/NAME` for repository scope or `--owner OWNER` for issue, PR and repository searches.
76
+ Repository search supports `--language` and integer `--stars` comparisons.
77
+ Commit search requires `--repo`, resolves `--revision` (default repository branch) to a SHA and discloses that ancestry scope.
78
+ Results disclose truncation and scope; totals are unknown when candidate collection is incomplete.
79
+ Queries accept plain text; unsupported qualifiers, review predicates and `authorAssociation` fail explicitly.
80
+ `search code` remains unresolved because repository contents cannot substitute for indexed code search.
81
+
82
+ ### Reads, output and exit codes
34
83
 
35
84
  Command syntax:
36
85
 
@@ -48,6 +97,12 @@ gitea-axi config path
48
97
  gitea-axi doctor
49
98
  ```
50
99
 
100
+ Raw `api GET` contents reads support root and nested directories, returning directory arrays (including empty arrays) or individual file, symlink and submodule objects under `data`.
101
+ Nullable contents and link fields are accepted while non-null values retain their schema type checks.
102
+ Use literal slashes for nested paths such as `repos/OWNER/NAME/contents/src/main.ts`, `repos/OWNER/NAME/editorconfig/src/main.ts` and Git ref filters such as `repos/OWNER/NAME/git/refs/heads/main`.
103
+ This multi-segment support applies only to those GET routes; other route parameters and raw writes retain single-segment matching.
104
+ Encoded route separators and traversal remain refused.
105
+
51
106
  Anonymous reads require an explicit origin; repository selection follows the rules below.
52
107
  Anonymous HTTP reads require `--allow-insecure-http` for that selected origin.
53
108
  The tool follows no redirects and never imports tea or Git credentials.
@@ -55,22 +110,187 @@ Output uses TOON; structured errors use stdout, with exit 1 for operational fail
55
110
  Business reads share a 30-second deadline, except check watches use their explicit timeout; each request has a 2,000,000-byte response limit.
56
111
  `repo view` instead gives each of its server version, specification and repository requests a separate 30-second timeout.
57
112
  An explicit repository wins over a matching local origin remote; a remote never changes the selected profile.
113
+ `-R` aliases `--repo` after an explicit command, with the same duplicate-selector checks.
114
+ For the default dashboard, use `gitea-axi --repo OWNER/NAME`; root-level `-R` remains unsupported.
58
115
  Hosted repository reads require an explicit repository.
59
- Lists default to 30 returned records and bound collection scans to 20 pages or 1000 unique records.
116
+ Lists default to 30 returned records, except `run list` and `release list` default to 10; collection scans stop at 20 pages or at least 1000 unique records and retain at most 1000.
117
+ Automatic page queries apply only to routes whose retained Gitea specification declares a `page` parameter.
118
+ Unpaginated collections, including inline review comments and attachment metadata, are complete unless the server supplies a continuation link or the collection exceeds the record bound; a response of 50 records alone does not imply another page.
60
119
  Issue and PR lists filter before globally sorting the collected records and applying the return limit.
61
120
  Milestone selectors use exact names, including numeric-looking names.
62
- List output distinguishes unknown vendor totals from known totals and reports continuation and truncation.
63
- Dashboard sections report permission failures as partial results.
64
- Bodies default to 1000 characters, diffs to 10000 characters, and rendered output to 100000 characters.
121
+ List output distinguishes unknown totals from known totals and reports continuation and truncation.
122
+ For an unpaginated collection without a continuation link, a missing server total is inferred from the unique records received.
123
+ Issue, PR and run lists expose `unfiltered_total` as the server count before client-side filtering; `total` is the filtered count when the bounded read is complete, otherwise null.
124
+ Truncated output includes guidance to raise `--limit` (maximum 1000), or use raw API pagination when the bounded read is incomplete.
125
+ The dashboard shows a compact repository projection (ID, full name, visibility, default branch, open issue and PR counts, and update time), plus up to five issues, PRs, labels, releases and task summaries per section.
126
+ Dashboard sections report permission failures as partial results and point truncated sections to the corresponding standalone list command with `--repo` and `--limit`.
127
+ Issue and PR bodies default to 1000 characters, diffs to 10000 characters, and rendered output to 100000 characters.
65
128
  `--full` expands bodies and diffs within the response and output ceilings.
66
129
  `pr checks` exits 8 for pending checks and 1 for failed or absent checks; watches require an explicit deadline and accept cancellation.
130
+ Commits without statuses return an empty check result; watches continue waiting for checks to register until the deadline or cancellation.
67
131
  `--fields` refuses unknown names and fields without an evidenced Gitea equivalent, and cannot combine with detail expansion flags.
68
- Raw requests accept `api [GET] <relative-path>` for JSON metadata and refuse absolute URLs, traversal, secret query fields, credential-bearing routes, administrative routes, text or binary downloads and unclassified routes.
132
+ PR statusCheckRollup explicitly identifies partial commit statuses, and `fullDatabaseId` is a numeric vendor ID rather than a GraphQL node ID.
133
+ Raw reads accept `api [GET] <relative-path>` for JSON metadata and refuse absolute URLs, traversal, secret query fields, credential-bearing routes, administrative routes, text or binary downloads and unclassified routes.
134
+ Secret query fields are usage errors with exit code 2.
135
+ Encoded slash and backslash separators are refused in API paths; encoded query text remains supported.
69
136
  HTTP 401, 403 and ambiguous 404 responses remain distinct failures, and rate-limit retry hints never cause automatic retries.
137
+ Transport failures include a short safe cause, such as `ECONNRESET` or `TLS error`, for API requests and attachment downloads.
138
+
139
+ ### Issues and labels
140
+
141
+ Issue mutations include `create`, `edit`, `close`, `reopen`, `comment`, `delete`, `pin` and `unpin`.
142
+ Label mutations include `create`, `edit` and `delete`; edit and delete accept an exact label name or ID.
143
+ Create accepts repeatable `--assignee` and `--label` names and an exact `--milestone` title.
144
+ Edit uses repeatable `--add-assignee`, `--remove-assignee`, `--add-label` and `--remove-label`, preserving unrelated memberships.
145
+ Create, edit and comment accept `--body` or `--body-file`; body files must be owned, mode 0600, and at most 1000000 bytes.
146
+ Use `--body-file -` for bounded stdin.
147
+ Close accepts an optional `--comment`, recorded as a separate write step.
148
+ GitHub-only type, project, close-reason and duplicate flags are refused before mutation.
149
+ Create, edit and comment accept repeatable `--attach FILE` with an additional attachment operation grant.
150
+ Comment attachments bind the newly created comment ID; creation attachments bind the newly created parent identity.
151
+ An attachment failure reports the existing parent ID and number for reconciliation without repeating creation.
152
+ Label colours normalize to six lowercase hexadecimal digits, and renaming preserves the label ID.
153
+ Issue and label deletion require a complete collection and at least one remaining control record before dispatch.
154
+
155
+ ### Pull requests
156
+
157
+ Pull request mutations include `create`, `edit`, `close`, `reopen`, `comment`, `update-branch` and `merge`.
158
+ Creation requires explicit `--title`, `--base` and `--head` branch names in the selected repository.
159
+ Cross-repository heads, draft and project inputs remain unavailable.
160
+ Create accepts repeatable `--reviewer` user names and `--team-reviewer` team names, plus issue-style assignees, labels and milestone selectors.
161
+ Edit accepts `--base`, text fields and assignee/label deltas, plus repeatable `--add-reviewer`, `--remove-reviewer`, `--add-team-reviewer` and `--remove-team-reviewer` deltas.
162
+ Existing reviewers and teams remain present unless explicitly removed.
163
+ Reviewer edits run before field edits, so an upstream reviewer rejection leaves requested field edits unapplied.
164
+ Create requires the `repoCreatePullRequest` grant.
165
+ When reviewers are requested, create resolves reviewer user identities first, then requests reviewers after creation as a separate guarded step requiring the additional `repoCreatePullReviewRequests` grant.
166
+ If the reviewer step fails after creation, the error reports the created PR ID and number for reconciliation without retrying.
167
+ Close with `--comment` records closure and comment as separate steps and reports partial outcomes without retrying.
168
+ Successful writes verify the same PR identity, expected fields and memberships through fresh reads.
169
+ Update-branch verifies that the resulting head contains both its previous head and base commits; conflicts never report completion.
170
+ Merge requires exactly one of `--merge`, `--squash`, `--rebase` or `--method merge|squash|rebase`, and a full `--match-head-commit` SHA.
171
+ It sends the vendor `head_commit_id` precondition and verifies the closed, merged PR, base branch tip and final commit topology.
172
+ Merge commits retain the previous base and checked head as parents; squash uses one new commit on the previous base; rebase verifies the rewritten linear chain and original commit messages.
173
+ These are offline protocol and topology checks, not live vendor atomicity or content-equivalence commissioning.
174
+ `--auto`, `--admin` and `--delete-branch` each report a capability gap because their semantics remain unverified.
175
+ `pr review` reports a compatibility gap before workflow validation or posting; see the [catalogue contract](docs/catalogue-contract.md) for C043 and CX-007.
176
+ Supplying `--match-head-commit` does not enable reviews or weaken this refusal to a warning.
177
+
178
+ ### Repositories
179
+
180
+ Repository mutations include API-only `repo create NAME|OWNER/NAME`, `repo edit` and `repo fork`.
181
+ Create requires exactly one of `--public` or `--private`, and accepts `--description` and `--template OWNER/NAME`.
182
+ An unqualified name uses the authenticated user; another owner selects an organization.
183
+ The operation grants are `createCurrentUserRepo`, `createOrgRepo`, `generateRepo`, `repoEdit` and `createFork` respectively.
184
+ Template creation uses the generate endpoint with Git content enabled, then compares the complete resulting Git tree with the template.
185
+ Templates containing `.gitea/template` are refused before mutation with `CAPABILITY_GAP`: `Transforming templates not yet supported`.
186
+ Edit supports `--description`, `--visibility public|private`, `--default-branch`, `--enable-issues` and `--enable-wiki`.
187
+ Boolean flags accept explicit `=true` and `=false`, including disabling issues or wiki.
188
+ Fork takes a source through `--repo` or a positional OWNER/NAME and optionally `--org` for the destination.
189
+ Verification checks the destination identity, requested fields and fork parent.
190
+ Internal visibility refuses before mutation.
191
+ Workspace flags (`--source`, `--push`, `--remote`, `--clone`) use the standalone Git workflows below and require explicit `--workspace` selection.
192
+ Repository creation `--push` and `--remote` require `--source`.
193
+
194
+ ### Releases
195
+
196
+ Release commands include `list`, `view TAG`, `create TAG`, `edit TAG` and `delete TAG` with `--repo OWNER/NAME`.
197
+ Exact release and tag lookups require complete collections within the collection scan bounds above; incomplete lookups refuse.
198
+ Slash-containing release tags and branch refs are supported without weakening API path separator protections.
199
+ List supports `--exclude-drafts` and `--exclude-pre-releases`; list and dashboard release rows contain `id`, `tag_name`, `name`, `draft`, `prerelease` and `published_at`.
200
+ View shows up to 1500 note characters; `--full` expands this to 80000 within the output ceiling.
201
+ Create and edit accept `--title`, `--notes` (or `--body`), `--notes-file` (or `--body-file`), `--draft` and `--prerelease`, preserving explicit false values.
202
+ File inputs share the protected body-file contract; stdin uses `-`.
203
+ Create accepts `--target`, `--verify-tag` and `--notes-from-tag` for an existing annotated tag's message.
204
+ The grants are `repoCreateRelease`, `repoEditRelease` and `repoDeleteRelease`.
205
+ Deletion verifies release absence, preservation of other releases and the independently observed tag state; it does not delete a tag.
206
+ `--latest`, discussion settings and asset positionals refuse before creation.
207
+ An existing or uncertain release cannot trigger an automatic duplicate create.
208
+
209
+ ### Attachments
70
210
 
71
- `src/registry.ts` owns command metadata, help and the host inventory.
72
- `npm run generate` generates those artifacts plus the consumed response schemas, route types and classified raw-route registry from the retained Swagger.
73
- `npm run generate:check` rejects stale artifacts.
211
+ `issue upload NUMBER`, `pr upload NUMBER` and `release upload TAG` accept repeatable `--file FILE` and optional `--content-type` (default `application/octet-stream`).
212
+ Create and edit also accept repeatable `--attach FILE`; all uploads share the write policy below.
213
+ The attachment grants are `issueCreateIssueAttachment`, `issueCreateIssueCommentAttachment` and `repoCreateReleaseAttachment`.
214
+ Uploads bind exact bytes, MIME type and basename to confirmation, then verify new attachment identities, names and sizes through metadata reads.
215
+ Each file is limited to 10000000 bytes; symlinks, unsafe portable basenames and duplicate names are refused.
216
+
217
+ `issue download NUMBER`, `pr download NUMBER` and `release download TAG` select attachments with repeatable `--pattern` globs (`*` and `?`, default `*`) or exact `--asset-id`.
218
+ Use an existing `--dir`, or `--output FILE` for exactly one attachment.
219
+ `--sha256 DIGEST` verifies an independently known digest for one selected file; `--content-type TYPE` requires that MIME type.
220
+ Downloads verify metadata identity again before requesting `/attachments/UUID` on the selected origin, independently bound to 10000000 bytes per response.
221
+ This binary route is [source-derived](evidence/binary-transfer-contract.md); private-token compatibility, direct object storage and live commissioning remain pending.
222
+ Metadata URLs are never used as transport authority, and every redirect is refused.
223
+ Files are written privately and published only after complete size and optional digest verification.
224
+ Existing files require explicit `--clobber`; traversal, symlinks and duplicate basenames refuse.
225
+ No matches is an explicit error; partial transfers identify completed attachment IDs and retain only completed files.
226
+
227
+ ### Secrets and variables
228
+
229
+ `secret list|set|delete` and `variable list|set|delete` select a repository with `--repo OWNER/NAME` or an organization with `--org NAME`.
230
+ Set and delete take one name, normalized to uppercase for repository and organization operations, including raw secret writes; environment scopes are unsupported.
231
+ `secret set NAME --input -` reads the exact value from protected stdin; `--input FILE` uses an owned mode 0600 file, bounded to 1000000 bytes.
232
+ Secret values are write-only: previews omit them, mutation response bodies are discarded, and verification reads name metadata only.
233
+ Secret listing returns names without invented timestamps; a successful set confirms name existence, not independently commissioned value consumption.
234
+ Variable listing returns names and stored values, including empty strings.
235
+ `variable set NAME --body VALUE` or `--input FILE|-` creates absent variables and updates existing ones, preserving empty strings and verifying the exact value and unrelated variables.
236
+ Use `updateRepoSecret`, `deleteRepoSecret`, `updateOrgSecret` or `deleteOrgSecret` for secret grants.
237
+ Variable grants are `createRepoVariable`, `updateRepoVariable`, `deleteRepoVariable` and their organization counterparts `createOrgVariable`, `updateOrgVariable`, `deleteOrgVariable`.
238
+
239
+ `release create --generate-notes` computes notes locally from exact commit ancestry and declares a `local-composite` result with `from`, `to` and commit identities.
240
+ `--notes-start-tag` excludes that tag and all its ancestors, requires it to be an ancestor of the target, and otherwise defaults to the most recent nondraft release's tag.
241
+ With no previous release, notes include the complete target ancestry.
242
+ The traversal includes merged branches, is bounded to 1000 commits per ancestry, and refuses incomplete ranges.
243
+ For a new tag, the generated target is pinned to its resolved SHA in the write payload.
244
+ `--fail-on-no-commits` refuses an empty generated range; supplied notes precede generated notes.
245
+ These notes do not claim Gitea's native release-note generation or GitHub formatting parity.
246
+
247
+ ## Safety
248
+
249
+ Writes are disabled unless `--allow-write` names every exact vendor operation needed by the command.
250
+ A profile's optional `writes` object can narrow this grant with `allowWrites` and an `operations` array.
251
+ When set, `GITEA_AXI_READ_ONLY` refuses writes unless its value is exactly `false`.
252
+ A hosted operation allow-list and read-only policy always take precedence over command-line grants.
253
+ Preview and confirm the same arguments:
254
+
255
+ ```sh
256
+ gitea-axi issue create --repo OWNER/NAME --title 'Fix login' --body 'Reproduction steps' --allow-write issueCreateIssue --preview
257
+ gitea-axi issue create --repo OWNER/NAME --title 'Fix login' --body 'Reproduction steps' --allow-write issueCreateIssue --confirm CONFIRMATION_ID
258
+ gitea-axi issue close 42 --repo OWNER/NAME --comment 'Resolved' --allow-write issueEditIssue,issueCreateComment --preview
259
+ gitea-axi label create --repo OWNER/NAME --name bug --color ff0000 --allow-write issueCreateLabel --preview
260
+ ```
261
+
262
+ The preview identifies the exact operations, payload and current preconditions without sending a mutation.
263
+ Confirmation binds the credential revision, origin, target, operations, canonical payload and preconditions.
264
+ Changing any binding requires a fresh preview.
265
+ Organization casing must match the preview exactly.
266
+ For issues that remain present, successful named writes verify identity and the expected final title, body, assignees, labels, milestone and state through fresh reads.
267
+ Pin changes verify the complete expected pin membership; deletion verifies absence of the selected identity and preservation of all remaining records.
268
+ A mismatched or failed verification reports an unknown outcome.
269
+
270
+ Raw POST, PUT, PATCH and DELETE requests use this same policy and journal.
271
+ Use a positional method or `-X METHOD`, repeatable `--field key=value` for typed numbers, booleans and null, or `--raw-field key=value` for strings.
272
+ Protected JSON `--input FILE` and `--input -` are alternatives to argv fields, with the same 1000000-byte bound.
273
+ Raw secret PUT requests require protected JSON `--input` containing only a string `data` field.
274
+ Secret `--field data=...` and `--raw-field data=...` refuse before dispatch without echoing the value.
275
+ Other credential-bearing routes remain refused.
276
+ Integer route parameters on writes require canonical decimal spelling, so an issue index such as `01` is refused.
277
+
278
+ The journal defaults to `$XDG_STATE_HOME/gitea-axi/writes.log`, otherwise `~/.local/state/gitea-axi/writes.log` on Linux.
279
+ `GITEA_AXI_WRITE_LOG` selects another absolute path; private confirmation receipts live beside it in `write-receipts`.
280
+ The journal records hashes, operation identities and step states, without credentials or request bodies.
281
+ Each attempt is persisted before dispatch.
282
+ API timeouts, interrupted requests and partial failures return `WRITE_OUTCOME_UNKNOWN`, completed/pending step information and an exact `gitea-axi write reconcile CONFIRMATION_ID` command.
283
+ Dispatched local Git failures retain their specific error code and include reconciliation information; stack recovery follows the [metadata and recovery contract](docs/local-stack-contract.md).
284
+ Reconciliation performs complete metadata reads for inspection and never repeats a mutation.
285
+ An unresolved dispatched attempt cannot be replayed by reusing its confirmation or obtaining another preview for the same effect.
286
+ Replay guards also treat repository owner, repository name and organization name case variants as the same identity for named and raw writes.
287
+ Unresolved organization attempts from older versions conservatively block all writes to the same organization at the same origin, including different names or payloads, and return the original attempt's reconciliation command.
288
+ Keep its private receipts until the outcome has been investigated.
289
+ Native Windows write protection and live write commissioning remain unverified; Windows writes are refused.
290
+
291
+ ## Configuration
292
+
293
+ See the [catalogue contract](docs/catalogue-contract.md#generation-and-verification) for discovery generation and freshness commands.
74
294
  `evidence/README.md` records retained source paths, provenance and hashes.
75
295
  Credentialed reads use the shipped `axi-credential.schema.json` contract.
76
296
  The config file is `$XDG_CONFIG_HOME/gitea-axi/config.json`, otherwise `~/.config/gitea-axi/config.json` on Linux, and `%APPDATA%\gitea-axi\config.json` on Windows.
@@ -78,8 +298,13 @@ The config file is `$XDG_CONFIG_HOME/gitea-axi/config.json`, otherwise `~/.confi
78
298
  `--config` requires an absolute path.
79
299
  The config object contains `version: 1`, a `profiles` object keyed by profile name, and an optional `defaultProfile` name.
80
300
  Without `--profile` or `GITEA_AXI_PROFILE`, setting any environment-profile field selects the environment profile instead of the config file; incomplete profiles are refused.
81
- File selection uses the explicit profile, otherwise the file's `defaultProfile` or its sole profile.
82
- Multiple file profiles without a selection or default are refused.
301
+ Incomplete environment profiles report missing variables; profile schema errors identify the invalid or missing field and its correction, including during hostname selection.
302
+ `--hostname` takes precedence over `GH_HOST` and requires the selected profile's origin to match its host and port before secret resolution.
303
+ For file profiles, an explicit profile must match; otherwise exactly one matching profile is required, ignoring `defaultProfile`.
304
+ Without a host selector, file selection uses the explicit profile, otherwise the file's `defaultProfile` or its sole profile; multiple profiles without a selection or default are refused.
305
+ A host selector cannot construct an anonymous origin, bypass tokenOrigin binding or enable HTTP.
306
+ Search commands currently reject `--hostname`; use `GH_HOST` with a matching profile instead.
307
+ Hosted invocations refuse credential/configuration overrides and ignore ambient `GH_HOST` for the injected Artium profile.
83
308
  On Linux, the config file must be owned by the invoking account and must not be group- or world-writable.
84
309
  Config files and file SecretRefs must be regular files; symlinks are refused.
85
310
  See [the credential schema](axi-credential.schema.json) for profile fields and env/file SecretRef shapes.
@@ -87,6 +312,7 @@ See [the credential schema](axi-credential.schema.json) for profile fields and e
87
312
  Inline tokens and secret arguments are refused.
88
313
  Resolved credentials are redacted from response and error string values before TOON serialization, including their JSON-escaped and URL-encoded forms.
89
314
  Redaction also masks recognized secret fields, secret query values, URL credentials, JWTs, private keys and account keys.
315
+ Generated confirmation IDs and fixed confirmation/reconciliation instructions remain intact when they overlap a protected value; payloads and upstream response text remain subject to redaction.
90
316
  The complete environment profile uses `GITEA_AXI_ORIGIN`, `GITEA_AXI_TOKEN_ORIGIN` and a JSON SecretRef in `GITEA_AXI_TOKEN`.
91
317
  Set `GITEA_AXI_ALLOW_INSECURE_HTTP=true` to permit HTTP for that environment profile; only the exact values `true` and `false` are accepted.
92
318
  HTTP requires `allowInsecureHttp: true` on that exact profile; a runtime origin override cannot reuse the token for another origin.
@@ -94,16 +320,108 @@ Keep file references owned by the invoking account with mode 0600 on Linux.
94
320
  Windows file protection is PENDING; file SecretRefs are refused until native ACL verification is available.
95
321
  Use env SecretRefs on Windows.
96
322
  `auth status` resolves the selected credentials locally; its `authenticated` field reports token availability without checking the server.
97
- `doctor` checks the current identity through Gitea's `/api/v1/user` endpoint.
323
+ `doctor` checks the current identity through Gitea's `/api/v1/user` endpoint and displays only the selected origin, account ID and login.
324
+
325
+
326
+ ## Local Git workflows and owned stacks
327
+
328
+ Local workflows require an explicit absolute `--workspace` and use native Git argument arrays.
329
+ Select the native repository root, or an existing parent directory for cloning.
330
+ `repo clone OWNER/NAME` clones into a new child directory of the selected parent and checks out the previewed default commit.
331
+ `repo create --source /absolute/repo --push --remote NAME` publishes the current source branch with its complete history.
332
+ `repo fork --clone` clones the verified fork, while `repo fork --remote NAME` configures a new remote in the workspace.
333
+ Existing remotes are never overwritten.
334
+ Use a repository-local Git user name and email for workflows that create commits.
335
+ Git credential helpers, hooks, transport rewrites and redirects are disabled or refused.
336
+ Pushes publish only the explicit branch refspecs, with tag following and submodule recursion disabled regardless of repository configuration.
337
+ Tokens travel through an origin-scoped environment configuration and never enter Git arguments or remote URLs.
338
+
339
+ `pr checkout NUMBER` fetches the PR's actual head repository and branch and checks out its exact previewed SHA with HEAD detached.
340
+ Dirty workspaces refuse before checkout.
341
+ `pr revert NUMBER` first prepares a local revert of a two-parent merge.
342
+ Repeat its preview/confirmation cycle to publish the recorded generated commit and create the revert PR.
343
+ Conflicts stop and retain native Git's recovery state.
344
+
345
+ Each local write uses the existing `--preview` and `--confirm ID` policy with an explicit local grant such as `--allow-write gitea.local.checkout`.
346
+ API composites also require their vendor operation grants, for example `createCurrentUserRepo,gitea.local.composite` or `gitea.local.revert,repoCreatePullRequest`.
347
+ Preview creates only policy state; it changes no worktree, refs, stack metadata or upstream business state.
348
+
349
+ One owned stack is recorded per native Git common directory, shared by its worktrees.
350
+ Initialize ordered existing branches with `stack init one two three --base main`, then navigate with checkout/up/down/top/bottom/trunk.
351
+ All commands require `--workspace`; stack commands reject `--repo`, `-R` and `GH_REPO`.
352
+ Check out a named branch before stack mutations; recorded rebase recovery remains available during a native conflict with HEAD detached.
353
+ Use `stack link` with existing branches, PR numbers or same-repository PR URLs to reconcile PR bases without creating duplicates.
354
+ `stack push --remote NAME` uses atomic updates and leases bound to the previewed remote tips.
355
+ `stack merge STACK --merge-method merge` checks heads, merges dependencies in order, retargets descendants to trunk and stops on failure.
356
+ `--merge`, `--squash` and `--rebase` select the other captured method spellings; reviewed confirmation supplies the workflow's noninteractive yes semantics.
357
+ `stack sync --prune` preserves trunk, checked-out and unrelated branches and requires verified merged history before deleting owned branches.
358
+ `stack view` exposes pending checkpoints for inspection after interruptions.
359
+ `stack unstack --local` removes only local relationships; without `--local`, linked PR bases are restored to trunk first.
360
+
361
+ Rebase supports selected branches, `--downstack`, `--upstack`, `--no-trunk`, `--committer-date-is-author-date`, and recorded `--continue`/`--abort` recovery.
362
+ See the [metadata and recovery contract](docs/local-stack-contract.md) for stack-add staging rules and recovery prerequisites.
363
+
364
+ `stack submit` reports an explicit compatibility gap before effects because its required automatic merge scheduling is not yet verified.
365
+ `stack rebase --preserve-dates` remains pending until upstream date semantics are evidenced.
366
+ Submit/link `--open` refuse before effects because browser launch is prohibited outside authentication.
367
+ Artium admission refuses every stack command and all local checkout/composite options.
368
+ Run `node scripts/verify.mjs --task S6.1` and `node scripts/verify.mjs --task S6.2` for the installed-package fake Git/Gitea journeys.
369
+ Artium checks remain opt-in through the frozen fixture selectors in [Development](#development); native Windows and live commissioning remain pending.
98
370
 
371
+ ## Limitations
372
+
373
+ Linux live testing against Gitea 1.23.4 motivated the read fixes covered by `test/fixtures/live-reads.json`.
374
+ Those fixtures retain minimal recorded response shapes with synthetic identities and content; automated verification replays them offline and does not establish broader live commissioning.
375
+ Route classification covers all captured methods; this does not claim completion of every business workflow.
99
376
  Stock Artium support is not claimed.
100
377
  Full profile management and native Windows permission verification remain pending.
378
+ Windows writes are refused, and Windows file SecretRefs are refused until native ACL verification is available.
379
+ `gitea-axi capabilities` reports every command's outcome, including explicit gaps; see the [catalogue contract](docs/catalogue-contract.md) for investigated versions and pending checks.
380
+
381
+ ## Development
382
+
383
+ Use Node 24.14.0 (minimum 24.11.0) and npm 11.9.0.
384
+ Dependencies are exact in `package.json` and transitive versions are locked in `package-lock.json`.
385
+ To build from source:
101
386
 
102
- `npm test` runs the installed-package checks with local Gitea fakes for S1.1 and S2.1 through S2.4.
387
+ ```sh
388
+ npm ci --ignore-scripts
389
+ npm run build
390
+ node dist/cli.js repo view --help
391
+ node scripts/verify.mjs --task S1.1
392
+ ```
393
+
394
+ The verification driver builds and packs the CLI, installs that tarball into an empty temporary prefix and invokes its installed bin from outside the source tree.
395
+ Dependency installation can access npm; Gitea requests use only local fakes.
396
+ Only the local HTTP fake replaces the vendor boundary.
397
+ The S1.1 driver checks AC-001, AC-002 and AC-004, package contents, registry freshness, type checking, linting and refusal cases.
398
+ Its report contains exact process invocations, assertions, runtime pins and the package SHA-256.
399
+ The S1, S2, S3, S4 and S6 drivers print their report paths.
400
+ Set `GITEA_AXI_REPORT` to retain their reports at a chosen path.
401
+ Their default reports go to unique files in the OS temporary directory; temporary installation files are removed.
402
+ For S5, set `GITEA_AXI_REPORT_DIR` to an existing directory to retain `S5.1.json` or `S5.2.json`; otherwise only a console summary is emitted.
403
+
404
+ `npm test` first runs the package-contract check in `scripts/verify-package.ts`, then the offline live-read regressions in `scripts/verify-live-reads.ts`, then the installed-package checks with local Gitea fakes for S1.1, S2.1 through S2.4, S3.1 through S3.5, S4.1 through S4.3, S5.1, S5.2 and S6.1 through S6.4 under an inherited outbound network guard.
405
+ The package-contract check builds, packs and installs offline, validates the tarball file list and bin entry, smoke-tests the installed `--help` and `--version`, parses the consumed JSON artifacts and imports the modules Artium consumes from the installed package.
406
+ Run it independently with `node --import ./scripts/deny-network.ts scripts/verify-package.ts`.
103
407
  Each S2 task also has an independent entrypoint, for example `node scripts/verify.mjs --task S2.3`.
408
+ Run each S3 journey independently with `node scripts/verify.mjs --task S3.1`, `S3.2`, `S3.3`, `S3.4` or `S3.5`.
409
+ Run the release journey with `node scripts/verify.mjs --task S4.1`.
410
+ Run attachments with `node scripts/verify.mjs --task S4.2` and protected values with `node scripts/verify.mjs --task S4.3`.
411
+ Run the S5 journeys independently with `node scripts/verify.mjs --task S5.1` and `node scripts/verify.mjs --task S5.2`.
104
412
  The S2 fixtures contain synthetic non-empty records authored against the retained schemas, with independent expected projections for supported selected PR fields and explicit capability refusals for the remaining fields.
413
+ Run Artium hosted proof once per release or in one nightly batch; individual slices merge on this repository's gate, including the package-contract check and outbound network deny.
105
414
  Set `GITEA_AXI_ARTIUM_ROOT` to a built admission fixture containing this package's read inventory to repeat business reads through a real local Artium server, client, credential writer and runner.
106
415
  Set `GITEA_AXI_ARTIUM_MANIFEST` to that fixture's frozen manifest; the S2 proof records its revision and manifest digest alongside the tested source head and package digest.
107
- The S2 driver reports whether Artium was selected; a standalone pass does not establish hosted admission.
416
+ The S2 and S5 drivers report whether Artium was selected, with an explicit skip reason when absent; a standalone pass does not establish hosted admission.
108
417
  Hosted CLI behavior scenarios must execute and match the expected exit code and standalone result; expected admission refusals are checked separately for their exact error code and recorded in `hostedRefusals`.
109
418
  The fixture must admit item positionals, raw GET paths and the declared read flags while continuing to block credential and endpoint overrides.
419
+ The S3 and S4 drivers use the same fixture selectors for opt-in hosted write journeys through the real local client, server, credential writer and runner.
420
+ They report an explicit skip reason when the fixture is absent.
421
+ The S4.2 and S4.3 journeys additionally require admission for managed uploads, protected inputs and dedicated authenticated artifact transfers.
422
+ Ordinary hosted download calls and arbitrary host file paths remain refused.
423
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution rules.
424
+
425
+ ## License
426
+
427
+ This project is licensed under the MIT License. See [LICENSE](LICENSE).
@@ -14,7 +14,14 @@
14
14
  { "type": "object", "additionalProperties": false, "required": ["file"], "properties": { "file": { "type": "string", "pattern": "^(?:/|[A-Za-z]:[\\\\/])" } } }
15
15
  ]
16
16
  },
17
- "allowInsecureHttp": { "type": "boolean", "default": false }
17
+ "allowInsecureHttp": { "type": "boolean", "default": false },
18
+ "writes": {
19
+ "type": "object", "additionalProperties": false, "required": ["allowWrites", "operations"],
20
+ "properties": {
21
+ "allowWrites": { "type": "boolean" },
22
+ "operations": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
23
+ }
24
+ }
18
25
  },
19
26
  "if": { "properties": { "origin": { "pattern": "^http:" } } },
20
27
  "then": { "required": ["allowInsecureHttp"], "properties": { "allowInsecureHttp": { "const": true } } }
@@ -0,0 +1,37 @@
1
+ import { collect, object, read } from './api.js';
2
+ import { CliError } from './error.js';
3
+ export async function actionValues(client, path) {
4
+ const page = await collect(client, path);
5
+ if (page.truncated)
6
+ throw new CliError('INCOMPLETE_READ', 'A complete collection is required for exact value identity');
7
+ if (path.startsWith('/api/v1/repos/') && path.endsWith('/variables')) {
8
+ for (const row of page.items) {
9
+ const detail = object(await read(client, path + '/' + encodeURIComponent(String(row.name))));
10
+ row.data = detail.data;
11
+ }
12
+ }
13
+ return page.items.map(row => path.endsWith('/secrets') ? { name: row.name, ...(row.created_at ? { created_at: row.created_at } : {}) } : { name: row.name, data: row.data });
14
+ }
15
+ export async function valuePlan(client, path, name, action, value) {
16
+ name = name.toUpperCase();
17
+ const before = await actionValues(client, path);
18
+ const current = before.find(row => row.name === name);
19
+ const secret = path.endsWith('/secrets');
20
+ if (action === 'delete' && !current)
21
+ throw new CliError('NOT_FOUND_OR_HIDDEN', 'The selected name is missing or hidden');
22
+ const method = action === 'delete' ? 'DELETE' : secret || current ? 'PUT' : 'POST';
23
+ const payload = action === 'delete' ? undefined : secret ? { data: value } : { value };
24
+ return { target: path + '/' + encodeURIComponent(name), steps: [{ method, path: path + '/' + encodeURIComponent(name), ...(payload ? { payload } : {}) }],
25
+ reads: [path], preconditions: before, sensitive: secret, value: { path, name, action, ...(secret || value === undefined ? {} : { expected: value }) } };
26
+ }
27
+ export function verifyValue(plan, observations) {
28
+ const value = plan.value;
29
+ const after = observations.find(row => row.path === value.path).data.map(object);
30
+ const before = plan.preconditions;
31
+ const selected = after.find(row => row.name === value.name);
32
+ const controls = (rows) => rows.filter(row => row.name !== value.name).map(row => ({ name: row.name, ...(value.path.endsWith('/variables') ? { data: row.data } : {}) })).sort((a, b) => String(a.name).localeCompare(String(b.name)));
33
+ if (JSON.stringify(controls(before)) !== JSON.stringify(controls(after)) ||
34
+ (value.action === 'delete' ? selected !== undefined : !selected || value.expected !== undefined && selected.data !== value.expected)) {
35
+ throw new CliError('READBACK_MISMATCH', 'Name identity or variable readback differs from the requested change');
36
+ }
37
+ }