axi-toolkit 0.2.0__tar.gz

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.
Files changed (65) hide show
  1. axi_toolkit-0.2.0/.githooks/commit-msg +25 -0
  2. axi_toolkit-0.2.0/.githooks/pre-commit +17 -0
  3. axi_toolkit-0.2.0/.github/workflows/ci.yml +183 -0
  4. axi_toolkit-0.2.0/.github/workflows/hygiene.yml +91 -0
  5. axi_toolkit-0.2.0/.github/workflows/release.yml +162 -0
  6. axi_toolkit-0.2.0/.gitignore +16 -0
  7. axi_toolkit-0.2.0/.release-please-manifest.json +3 -0
  8. axi_toolkit-0.2.0/AGENTS.md +201 -0
  9. axi_toolkit-0.2.0/CHANGELOG.md +13 -0
  10. axi_toolkit-0.2.0/CLAUDE.md +1 -0
  11. axi_toolkit-0.2.0/LICENSE +21 -0
  12. axi_toolkit-0.2.0/PKG-INFO +162 -0
  13. axi_toolkit-0.2.0/README.md +135 -0
  14. axi_toolkit-0.2.0/metaobjects/meta.axi-toolkit.yaml +488 -0
  15. axi_toolkit-0.2.0/pyproject.toml +74 -0
  16. axi_toolkit-0.2.0/release-please-config.json +13 -0
  17. axi_toolkit-0.2.0/scripts/commitcheck.py +1611 -0
  18. axi_toolkit-0.2.0/scripts/install-hooks.sh +12 -0
  19. axi_toolkit-0.2.0/scripts/leakcheck.py +1017 -0
  20. axi_toolkit-0.2.0/scripts/reqgen.py +547 -0
  21. axi_toolkit-0.2.0/src/axi_toolkit/__init__.py +28 -0
  22. axi_toolkit-0.2.0/src/axi_toolkit/envconfig.py +243 -0
  23. axi_toolkit-0.2.0/src/axi_toolkit/errors.py +458 -0
  24. axi_toolkit-0.2.0/src/axi_toolkit/redact.py +173 -0
  25. axi_toolkit-0.2.0/src/axi_toolkit/render/__init__.py +7 -0
  26. axi_toolkit-0.2.0/src/axi_toolkit/render/cli.py +191 -0
  27. axi_toolkit-0.2.0/src/axi_toolkit/render/prose.py +100 -0
  28. axi_toolkit-0.2.0/src/axi_toolkit/toon.py +349 -0
  29. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/LICENSE +21 -0
  30. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/PROVENANCE.md +69 -0
  31. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/__init__.py +224 -0
  32. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/checksums.txt +9 -0
  33. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-nested.json +132 -0
  34. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-objects.json +191 -0
  35. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-primitive.json +111 -0
  36. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/arrays-tabular.json +182 -0
  37. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/delimiters.json +253 -0
  38. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/objects-keyed.json +155 -0
  39. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/objects.json +297 -0
  40. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/primitives.json +278 -0
  41. axi_toolkit-0.2.0/src/axi_toolkit/toon_spec/encode/whitespace.json +51 -0
  42. axi_toolkit-0.2.0/tests/conformance/__init__.py +9 -0
  43. axi_toolkit-0.2.0/tests/conformance/capture.json +2066 -0
  44. axi_toolkit-0.2.0/tests/conformance/projections.py +930 -0
  45. axi_toolkit-0.2.0/tests/conformance/relations.py +187 -0
  46. axi_toolkit-0.2.0/tests/conformance/specs.py +96 -0
  47. axi_toolkit-0.2.0/tests/conformance/test_requirements_generated.py +461 -0
  48. axi_toolkit-0.2.0/tests/conftest.py +16 -0
  49. axi_toolkit-0.2.0/tests/test_envconfig.py +285 -0
  50. axi_toolkit-0.2.0/tests/test_errors.py +208 -0
  51. axi_toolkit-0.2.0/tests/test_purity.py +154 -0
  52. axi_toolkit-0.2.0/tests/test_redact.py +196 -0
  53. axi_toolkit-0.2.0/tests/test_relations.py +138 -0
  54. axi_toolkit-0.2.0/tests/test_render_cli.py +199 -0
  55. axi_toolkit-0.2.0/tests/test_render_prose.py +133 -0
  56. axi_toolkit-0.2.0/tests/test_reqgen.py +332 -0
  57. axi_toolkit-0.2.0/tests/test_toon.py +227 -0
  58. axi_toolkit-0.2.0/tests/test_toon_conformance.py +98 -0
  59. axi_toolkit-0.2.0/vendor/conventional-commits-parser/LICENSE +14 -0
  60. axi_toolkit-0.2.0/vendor/conventional-commits-parser/PROVENANCE.md +65 -0
  61. axi_toolkit-0.2.0/vendor/conventional-commits-parser/checksums.txt +5 -0
  62. axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/codes.js +10 -0
  63. axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/parser.js +431 -0
  64. axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/scanner.js +79 -0
  65. axi_toolkit-0.2.0/vendor/conventional-commits-parser/lib/type-checks.js +25 -0
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env sh
2
+ # Two things a commit message can get wrong, both invisible until much later.
3
+ #
4
+ # 1. It can publish library-specific data. File content and commit messages are
5
+ # separate channels; --staged only covers the former, and a message is just
6
+ # as public.
7
+ # 2. It can be one release-please cannot parse. That one is worse, because it
8
+ # fails silently and green: the release workflow logs "commit could not be
9
+ # parsed" at debug level, considers zero commits, skips the release and exits
10
+ # 0, and the fix sits on main unpublished. It has already cost one release
11
+ # here. See scripts/commitcheck.py for the exact grammar rule.
12
+ #
13
+ # Install with: scripts/install-hooks.sh
14
+ set -e
15
+
16
+ root=$(git rev-parse --show-toplevel)
17
+ python=$(command -v python3 || command -v python || true)
18
+
19
+ if [ -z "$python" ]; then
20
+ echo "commit-msg: no python interpreter found; cannot run the checks" >&2
21
+ exit 1
22
+ fi
23
+
24
+ "$python" "$root/scripts/leakcheck.py" --commit-msg "$1"
25
+ "$python" "$root/scripts/commitcheck.py" --commit-msg "$1" --root "$root"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env sh
2
+ # Block a commit that would publish installation-specific data.
3
+ #
4
+ # Install with: scripts/install-hooks.sh
5
+ # The same scan runs in CI, so bypassing this hook only delays the failure.
6
+ set -e
7
+
8
+ root=$(git rev-parse --show-toplevel)
9
+ python=$(command -v python3 || command -v python || true)
10
+
11
+ if [ -z "$python" ]; then
12
+ echo "pre-commit: no python interpreter found; cannot run the leak check" >&2
13
+ echo "pre-commit: install python3, or run the scan manually before pushing" >&2
14
+ exit 1
15
+ fi
16
+
17
+ "$python" "$root/scripts/leakcheck.py" --staged --root "$root"
@@ -0,0 +1,183 @@
1
+ # Local CI — the heavy validation matrix, on the maintainer's self-hosted runner.
2
+ #
3
+ # SECURITY (public repository + self-hosted runner): this workflow must NEVER
4
+ # gain a `pull_request` trigger. The runner is the maintainer's own workstation,
5
+ # and a job on it executes repository code as a real user with that user's files
6
+ # and network. Every trigger below is reachable only by someone who already has
7
+ # write access — a push to `main`, the schedule, or a manual dispatch — so
8
+ # fork-submitted code cannot reach the machine. `pull_request` would hand any
9
+ # contributor on the internet code execution on it, in one line and with no
10
+ # other visible symptom. Add coverage for pull requests in `hygiene.yml`, which
11
+ # is GitHub-hosted, or not at all.
12
+ #
13
+ # WHERE VALIDATION ACTUALLY HAPPENS: every change here goes through the local
14
+ # no-mistakes gate — review, tests, lint, docs — before a PR is ever opened.
15
+ # GitHub-hosted CI is not the primary quality signal for this repository and
16
+ # should not be treated as one, which is why a pull request shows a single cheap
17
+ # check (the leak scan, the one gate that must run before a human reads a diff)
18
+ # rather than a matrix. This workflow is the full matrix; it runs on `main`,
19
+ # nightly, and on demand.
20
+ name: ci (self-hosted)
21
+
22
+ on:
23
+ push:
24
+ branches: [main]
25
+ # Nightly. Deliberately not 08:17 or 09:41 UTC: two sibling projects hold
26
+ # those slots on the same workstation.
27
+ schedule:
28
+ - cron: '23 10 * * *'
29
+ workflow_dispatch:
30
+
31
+ permissions:
32
+ contents: read
33
+
34
+ concurrency:
35
+ group: ci-${{ github.ref }}
36
+ cancel-in-progress: true
37
+
38
+ jobs:
39
+ leakcheck:
40
+ name: leak check
41
+ runs-on: [self-hosted, linux]
42
+ timeout-minutes: 10
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ with:
46
+ # The workspace on a self-hosted runner outlives the job; do not leave
47
+ # a credential behind in its .git/config.
48
+ persist-credentials: false
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: "3.12"
52
+ # Prove the scanner still detects what it claims to before trusting it.
53
+ - name: Self-test against a synthetic dirty fixture
54
+ run: python3 scripts/leakcheck.py --demo
55
+ - name: Scan every tracked file
56
+ run: python3 scripts/leakcheck.py
57
+
58
+ # A commit message release-please cannot parse is dropped from the changelog
59
+ # and the version bump while the release workflow exits 0. `release.yml` fails
60
+ # on that too, but it only runs on `main`; here it also runs nightly, so an
61
+ # allowance that has outlived its cause surfaces without waiting for a merge.
62
+ commits:
63
+ name: commit messages are readable
64
+ runs-on: [self-hosted, linux]
65
+ timeout-minutes: 10
66
+ permissions:
67
+ contents: read
68
+ # A pull request body can replace a commit message entirely, so the audit
69
+ # reads it. Read-only.
70
+ pull-requests: read
71
+ steps:
72
+ - uses: actions/checkout@v4
73
+ with:
74
+ fetch-depth: 0
75
+ persist-credentials: false
76
+ - uses: actions/setup-python@v5
77
+ with:
78
+ python-version: "3.12"
79
+ - uses: actions/setup-node@v4
80
+ with:
81
+ node-version: "20"
82
+ - name: Self-test the checker
83
+ run: python3 scripts/commitcheck.py --demo
84
+ - name: Audit every commit since the last release
85
+ env:
86
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87
+ run: python3 scripts/commitcheck.py --since-release --pull-requests require
88
+
89
+ # EVERY job that needs third-party packages installs into a fresh `.venv` and
90
+ # calls the tools by path out of it. That is not ceremony. This runner runs as
91
+ # the maintainer's own user, so `~/.local/lib/python3.X/site-packages` and
92
+ # `~/.local/bin` are on the interpreter's path and ahead of it on `PATH` — a
93
+ # bare `pytest` or `ruff` resolves to whatever the maintainer happens to have
94
+ # installed, against `/usr/bin/python3.X` rather than the interpreter this job
95
+ # set up. A venv excludes the user site by construction, so a job sees only
96
+ # what it just installed.
97
+ lint:
98
+ name: lint
99
+ runs-on: [self-hosted, linux]
100
+ timeout-minutes: 10
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+ with:
104
+ persist-credentials: false
105
+ - uses: actions/setup-python@v5
106
+ with:
107
+ python-version: "3.12"
108
+ # --clear because the workspace outlives the job on a self-hosted runner.
109
+ - run: python -m venv --clear .venv
110
+ - run: .venv/bin/python -m pip install --upgrade pip
111
+ - run: .venv/bin/pip install -e ".[dev]"
112
+ - run: .venv/bin/ruff check .
113
+ - run: .venv/bin/ruff format --check .
114
+
115
+ test:
116
+ name: test (py${{ matrix.python-version }})
117
+ runs-on: [self-hosted, linux]
118
+ timeout-minutes: 20
119
+ strategy:
120
+ fail-fast: false
121
+ matrix:
122
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
123
+ steps:
124
+ - uses: actions/checkout@v4
125
+ with:
126
+ persist-credentials: false
127
+ # The runner's host provides only some of these; setup-python downloads
128
+ # the rest into the persistent tool cache, so only the first run pays.
129
+ - uses: actions/setup-python@v5
130
+ with:
131
+ python-version: ${{ matrix.python-version }}
132
+ - run: python -m venv --clear .venv
133
+ - run: .venv/bin/python -m pip install --upgrade pip
134
+ - run: .venv/bin/pip install -e ".[dev]"
135
+ # No credentials, no source checkouts, no network. The conformance layer
136
+ # reads the committed capture, which is the property that lets the whole
137
+ # suite run here at all — and the floor is 3.9 precisely because the
138
+ # metadata toolchain (3.11+) is not needed to run what it generated.
139
+ - run: .venv/bin/pytest
140
+
141
+ # The declaration is the source of truth, so a generated file that no longer
142
+ # matches it is a failure rather than a stale artefact somebody will notice.
143
+ # Separate from `test` because the metadata toolchain's floor is 3.11 while
144
+ # the matrix goes down to 3.9: the generator needs it, the generated checks
145
+ # never do.
146
+ requirements:
147
+ name: requirements are current
148
+ runs-on: [self-hosted, linux]
149
+ timeout-minutes: 10
150
+ steps:
151
+ - uses: actions/checkout@v4
152
+ with:
153
+ persist-credentials: false
154
+ - uses: actions/setup-python@v5
155
+ with:
156
+ python-version: "3.12"
157
+ - run: python -m venv --clear .venv
158
+ - run: .venv/bin/python -m pip install --upgrade pip
159
+ - run: .venv/bin/pip install -e ".[dev,reqgen]"
160
+ # Loads the declaration under the sealed-registry discipline and applies
161
+ # the binding rules — an invented attribute, a requirement with no check,
162
+ # a reference that no longer resolves, all fail here.
163
+ - run: .venv/bin/python scripts/reqgen.py list
164
+ - run: .venv/bin/python scripts/reqgen.py check
165
+
166
+ # Nothing watches a workflow that runs on main and overnight, so a failure has
167
+ # to come and find someone.
168
+ notify:
169
+ needs: [leakcheck, commits, lint, test, requirements]
170
+ if: always() && contains(needs.*.result, 'failure')
171
+ runs-on: [self-hosted, linux]
172
+ timeout-minutes: 5
173
+ steps:
174
+ - env:
175
+ # env-indirection: a commit message is untrusted text and must never be
176
+ # interpolated with ${{ }} directly into a script body.
177
+ HEAD_MSG: ${{ github.event.head_commit.message }}
178
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
179
+ run: |
180
+ export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
181
+ notify-send -u critical "ci FAILED: axi-toolkit" \
182
+ "$(printf '%s' "$HEAD_MSG" | head -n1)
183
+ $RUN_URL" || echo "notify-send unavailable; GitHub email still fires"
@@ -0,0 +1,91 @@
1
+ # The one check a pull request gets on a GitHub-hosted runner.
2
+ #
3
+ # The leak scan takes seconds and it is the gate that must run before a human
4
+ # looks at a diff: this repository is public and it is built by reading two
5
+ # tools and the installations they were developed against, so an address, a
6
+ # token or a real entity name in a diff is the failure that matters. Everything
7
+ # heavier — lint, the Python matrix, the requirements gate — runs in `ci.yml` on
8
+ # the maintainer's self-hosted runner, on `main` rather than on pull requests.
9
+ #
10
+ # That is not a reduction in rigour, and a thin-looking PR check should not be
11
+ # read as one: every change goes through the local no-mistakes gate — review,
12
+ # tests, lint, docs — before the PR exists. GitHub-hosted CI is not the primary
13
+ # quality signal here. Keep this workflow to the one cheap job; adding the
14
+ # matrix back would restore the duplicate-run waste it was split up to end.
15
+ #
16
+ # The two steps that read the pull request itself are the one exception to
17
+ # that, and they are not coverage for their own sake: they check something
18
+ # that exists ONLY on a pull request. A title and a body are in no checkout,
19
+ # pass under no hook, and can be edited after every other check has run — so
20
+ # `edited` is in the trigger list, and two separate guards depend on it
21
+ # being there.
22
+ #
23
+ # The first is a leak scan of the title and body. This repository is public and
24
+ # the pipeline's own document step writes into the body, pasting captured pytest
25
+ # output that carries absolute paths on two lines: the header's `rootdir:` line,
26
+ # and the warnings summary, which prints the site-packages path of the
27
+ # interpreter that raised the warning — a path no choice of capture directory
28
+ # moves. The first line published a home directory three times across two
29
+ # sibling repositories, with every check green each time, because nothing
30
+ # scanned the one artefact that is neither a tracked file nor a commit message.
31
+ # The guard went in here from the first commit for exactly that, rather than
32
+ # after this repository's first leak — which came anyway, through the second
33
+ # line, in a body whose capture had been run from a scratch directory precisely
34
+ # so the `rootdir:` line would read harmless. The guard caught it, and `edited`
35
+ # re-ran it green on the fixed body. `edited` is what makes the check work at
36
+ # all: the document step writes the evidence in *after* the pull request is
37
+ # opened, so a check that fired only on `opened` would pass the empty original
38
+ # body.
39
+ #
40
+ # The second is release-please's: a body that names the commit-override marker
41
+ # replaces the merged commit message with whatever follows the word, and a
42
+ # paragraph *about* the mechanism is enough to do it. It has already cost a
43
+ # sibling project a release. Still one job, still seconds.
44
+ name: hygiene
45
+
46
+ on:
47
+ pull_request:
48
+ types: [opened, synchronize, reopened, edited]
49
+
50
+ permissions:
51
+ contents: read
52
+
53
+ concurrency:
54
+ group: hygiene-${{ github.ref }}
55
+ cancel-in-progress: true
56
+
57
+ jobs:
58
+ leakcheck:
59
+ name: leak check
60
+ runs-on: ubuntu-latest
61
+ timeout-minutes: 10
62
+ permissions:
63
+ contents: read
64
+ pull-requests: read
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+ - uses: actions/setup-python@v5
68
+ with:
69
+ python-version: "3.12"
70
+ # Prove the scanner still detects what it claims to before trusting it: a
71
+ # scanner that quietly stopped matching would otherwise pass every diff.
72
+ - name: Self-test against a synthetic dirty fixture
73
+ run: python3 scripts/leakcheck.py --demo
74
+ - name: Scan every tracked file
75
+ run: python3 scripts/leakcheck.py
76
+ # The surface no hook and no checkout can reach. Fails closed: without a
77
+ # token, or on a fetch that does not answer, it fails the check rather than
78
+ # reporting a clean it cannot support. It reports the field, line and
79
+ # offset of a match and never the match itself — this log is public.
80
+ - name: Scan the pull request title and body
81
+ env:
82
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83
+ run: python3 scripts/leakcheck.py --pull-request "${{ github.event.pull_request.number }}"
84
+ # Same discipline as the scan above: prove the checker still tells the
85
+ # shapes apart before believing its verdict on this pull request.
86
+ - name: Self-test the commit checker
87
+ run: python3 scripts/commitcheck.py --demo
88
+ - name: Check what a merge would hand release-please
89
+ env:
90
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
91
+ run: python3 scripts/commitcheck.py --pull-request "${{ github.event.pull_request.number }}"
@@ -0,0 +1,162 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ # release-please reports success when it cannot read a commit. It catches the
13
+ # parse error, logs `commit could not be parsed` at debug level, carries on
14
+ # with whatever it *could* read, and exits 0 — so a message its parser chokes
15
+ # on is dropped from the changelog and from the version bump while the run goes
16
+ # green. That is not hypothetical here: it is how a merged fix sat on `main`
17
+ # unreleased while this workflow reported success, with `Considering: 0
18
+ # commits` buried in the log.
19
+ #
20
+ # Every commit since the last release tag has to be readable by the parser
21
+ # release-please actually runs — a byte-for-byte copy of it lives in
22
+ # `vendor/conventional-commits-parser/`, so this needs no npm install and no
23
+ # network. A commit it cannot read is one release-please silently dropped, and
24
+ # zero readable commits with commits waiting is the exact state that shipped
25
+ # nothing; both fail here instead of passing quietly. Nothing since the tag is
26
+ # the healthy no-op and still passes, which is the distinction the log did not
27
+ # make.
28
+ #
29
+ # `--pull-requests require` is the correction to the first version of this job,
30
+ # which passed — in the sibling project — while release-please dropped every
31
+ # commit in the range. The message a commit carries is not necessarily the
32
+ # message release-please reads: a pull request body that names the
33
+ # commit-override marker — a paragraph *about* the mechanism is enough —
34
+ # replaces the whole message with whatever follows the word. That artefact is
35
+ # on GitHub and not in the checkout, so the audit needs a token, and `require`
36
+ # makes a missing one fail rather than quietly send it back to checking the
37
+ # wrong string.
38
+ #
39
+ # This job deliberately does NOT `needs:` the one below. It has to fail on its
40
+ # own account, including on a run where release-please itself errored, and its
41
+ # verdict does not depend on the outcome it is auditing.
42
+ commit-audit:
43
+ name: every commit is readable
44
+ runs-on: ubuntu-latest
45
+ timeout-minutes: 10
46
+ permissions:
47
+ contents: read
48
+ # The pull request body can replace the commit message, so the audit has
49
+ # to read it. Read-only: this job never writes anything.
50
+ pull-requests: read
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ with:
54
+ # The audit compares `main` against the last release tag, so it needs
55
+ # both the tags and the commits after them.
56
+ fetch-depth: 0
57
+ - uses: actions/setup-python@v5
58
+ with:
59
+ python-version: "3.12"
60
+ - uses: actions/setup-node@v4
61
+ with:
62
+ node-version: "20"
63
+ # A checker that stopped detecting anything reads exactly like a checker
64
+ # with nothing to report, so prove it still works before trusting it.
65
+ - name: Self-test the checker
66
+ run: python3 scripts/commitcheck.py --demo
67
+ - name: Audit every commit since the last release
68
+ env:
69
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70
+ run: python3 scripts/commitcheck.py --since-release --pull-requests require
71
+
72
+ # Drives version bumps and the changelog from conventional commits, and opens
73
+ # a release PR. Merging that PR is what cuts a release.
74
+ release-please:
75
+ name: release PR
76
+ runs-on: ubuntu-latest
77
+ permissions:
78
+ contents: write
79
+ pull-requests: write
80
+ outputs:
81
+ release_created: ${{ steps.release.outputs.release_created }}
82
+ tag_name: ${{ steps.release.outputs.tag_name }}
83
+ steps:
84
+ - uses: googleapis/release-please-action@v4
85
+ id: release
86
+ with:
87
+ config-file: release-please-config.json
88
+ manifest-file: .release-please-manifest.json
89
+
90
+ # What the action decided, in the run summary rather than only in the log.
91
+ # "No release PR and no release" is a legitimate outcome and also what a
92
+ # silent parse failure looks like; `commit-audit` is what tells the two
93
+ # apart, so say here which one this run was.
94
+ - name: Say what release-please decided
95
+ env:
96
+ RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
97
+ PRS_CREATED: ${{ steps.release.outputs.prs_created }}
98
+ TAG_NAME: ${{ steps.release.outputs.tag_name }}
99
+ run: |
100
+ {
101
+ echo "### release-please"
102
+ echo ""
103
+ echo "| | |"
104
+ echo "| --- | --- |"
105
+ echo "| release created | ${RELEASE_CREATED:-false} |"
106
+ echo "| release PR created or updated | ${PRS_CREATED:-false} |"
107
+ echo "| tag | ${TAG_NAME:-none} |"
108
+ echo ""
109
+ echo "A run with neither a release nor a release PR means nothing"
110
+ echo "releasable has landed. The \`every commit is readable\` job is"
111
+ echo "what proves that is the truth rather than a parse failure."
112
+ } >> "$GITHUB_STEP_SUMMARY"
113
+
114
+ # NOT YET REACHABLE, on purpose. The PyPI project does not exist: registering
115
+ # the name is a one-time account action only the maintainer can perform, and
116
+ # nothing here should attempt it. The job is written now and gated behind
117
+ # `release_created`, which is only true after a release PR merges, so it
118
+ # cannot fire before the name is claimed. Trusted publishing needs the
119
+ # `pypi` environment and a PyPI publisher configured for this repository
120
+ # before the first release; until then, merging a release PR would fail this
121
+ # job and nothing else.
122
+ publish:
123
+ name: publish to PyPI
124
+ needs: release-please
125
+ if: needs.release-please.outputs.release_created == 'true'
126
+ runs-on: ubuntu-latest
127
+ environment:
128
+ name: pypi
129
+ url: https://pypi.org/p/axi-toolkit
130
+ permissions:
131
+ # The only credential involved: a short-lived OIDC token minted for this
132
+ # run. No long-lived PyPI token exists in this repository or anywhere else.
133
+ id-token: write
134
+ steps:
135
+ - uses: actions/checkout@v4
136
+ - uses: actions/setup-python@v5
137
+ with:
138
+ python-version: "3.12"
139
+ - run: python -m pip install --upgrade pip build
140
+ - run: python -m build
141
+
142
+ # Publishing a broken artifact is worse than not publishing. This package
143
+ # has no entry point to run, so the smoke test is the thing a consumer
144
+ # will actually do: install the wheel on its own, import every module, and
145
+ # run the vendored conformance rig out of the installed copy. That last
146
+ # part is why the fixtures ship inside the package — a wheel whose
147
+ # encoder and fixtures disagreed would go out green otherwise.
148
+ - name: Smoke-test the built wheel
149
+ run: |
150
+ python -m venv /tmp/verify
151
+ /tmp/verify/bin/pip install --quiet dist/*.whl
152
+ /tmp/verify/bin/python -c "
153
+ import axi_toolkit, axi_toolkit.errors, axi_toolkit.redact, axi_toolkit.envconfig
154
+ import axi_toolkit.toon, axi_toolkit.toon_spec
155
+ from axi_toolkit.render import cli, prose
156
+ report = axi_toolkit.toon_spec.run(axi_toolkit.toon.encode)
157
+ assert not report.failures, report.failures[:3]
158
+ assert not axi_toolkit.toon_spec.digest_mismatches()
159
+ print('axi-toolkit', axi_toolkit.__version__, 'TOON', report.score)
160
+ "
161
+
162
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+
12
+ # Agent-local settings, which routinely contain absolute home paths. Keeping
13
+ # this out of .git/info/exclude matters: that file is local to one clone and
14
+ # never travels with the repository.
15
+ .claude/
16
+ .codex/
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.0"
3
+ }