bigfix-root-mcp 0.1.4__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.
- bigfix_root_mcp-0.1.4/.editorconfig +23 -0
- bigfix_root_mcp-0.1.4/.github/dependabot.yml +42 -0
- bigfix_root_mcp-0.1.4/.github/workflows/pre-commit.yaml +130 -0
- bigfix_root_mcp-0.1.4/.github/workflows/tag_and_release.yaml +234 -0
- bigfix_root_mcp-0.1.4/.github/workflows/test.yaml +111 -0
- bigfix_root_mcp-0.1.4/.gitignore +225 -0
- bigfix_root_mcp-0.1.4/.mcp.json +13 -0
- bigfix_root_mcp-0.1.4/.pre-commit-config.yaml +224 -0
- bigfix_root_mcp-0.1.4/.yamllint.yaml +14 -0
- bigfix_root_mcp-0.1.4/LICENSE +21 -0
- bigfix_root_mcp-0.1.4/PKG-INFO +286 -0
- bigfix_root_mcp-0.1.4/README.md +262 -0
- bigfix_root_mcp-0.1.4/docs/besapi-notes.md +110 -0
- bigfix_root_mcp-0.1.4/docs/besapi-proposals.md +149 -0
- bigfix_root_mcp-0.1.4/docs/client-query.md +148 -0
- bigfix_root_mcp-0.1.4/docs/design-decisions.md +198 -0
- bigfix_root_mcp-0.1.4/docs/rest-endpoints.md +116 -0
- bigfix_root_mcp-0.1.4/docs/security-review.md +311 -0
- bigfix_root_mcp-0.1.4/fastmcp.json +17 -0
- bigfix_root_mcp-0.1.4/pyproject.toml +161 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/__init__.py +11 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/__main__.py +6 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/actions.py +42 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/besxml.py +85 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/clientquery.py +237 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/connection.py +147 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/content.py +268 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/errors.py +181 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/prompts.py +93 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/resources/client-cookbook.md +69 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/resources/session-cookbook.md +119 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/resources/tool-guide.md +68 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/response.py +93 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/server.py +861 -0
- bigfix_root_mcp-0.1.4/src/bigfix_root_mcp/writes.py +108 -0
- bigfix_root_mcp-0.1.4/tests/conftest.py +108 -0
- bigfix_root_mcp-0.1.4/tests/test_actions.py +44 -0
- bigfix_root_mcp-0.1.4/tests/test_besxml.py +76 -0
- bigfix_root_mcp-0.1.4/tests/test_clientquery.py +244 -0
- bigfix_root_mcp-0.1.4/tests/test_connection.py +126 -0
- bigfix_root_mcp-0.1.4/tests/test_content.py +253 -0
- bigfix_root_mcp-0.1.4/tests/test_hints.py +47 -0
- bigfix_root_mcp-0.1.4/tests/test_resources.py +71 -0
- bigfix_root_mcp-0.1.4/tests/test_response.py +115 -0
- bigfix_root_mcp-0.1.4/tests/test_tools.py +412 -0
- bigfix_root_mcp-0.1.4/tests/test_writes.py +171 -0
- bigfix_root_mcp-0.1.4/uv.lock +2586 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# EditorConfig is awesome: https://EditorConfig.org
|
|
2
|
+
# Requires plugin for VSCode:
|
|
3
|
+
# - https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig
|
|
4
|
+
|
|
5
|
+
# top-most EditorConfig file
|
|
6
|
+
root = true
|
|
7
|
+
|
|
8
|
+
[*]
|
|
9
|
+
charset = utf-8
|
|
10
|
+
trim_trailing_whitespace = true
|
|
11
|
+
insert_final_newline = true
|
|
12
|
+
|
|
13
|
+
[*.py]
|
|
14
|
+
# the following is required for compatibility with black:
|
|
15
|
+
indent_style = space
|
|
16
|
+
indent_size = 4
|
|
17
|
+
|
|
18
|
+
[*.bes]
|
|
19
|
+
indent_style = tab
|
|
20
|
+
# should tabs be 2, 3, or 4 spaces?
|
|
21
|
+
# indent_size = 4
|
|
22
|
+
# actionscript within the XML of BES files MUST have windows line endings:
|
|
23
|
+
end_of_line = crlf
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Dependency-update alerts and PRs.
|
|
3
|
+
#
|
|
4
|
+
# The 7-day cooldown is the point, not a courtesy: a release published minutes
|
|
5
|
+
# ago is the likeliest to be a compromised one. It matches `exclude-newer` in
|
|
6
|
+
# pyproject.toml and is zizmor's minimum.
|
|
7
|
+
version: 2
|
|
8
|
+
updates:
|
|
9
|
+
# "uv", not "pip": uv.lock is what CI installs from (uv sync --locked), and
|
|
10
|
+
# "pip" would read pyproject.toml without updating the lock.
|
|
11
|
+
- package-ecosystem: "uv"
|
|
12
|
+
directory: "/"
|
|
13
|
+
schedule:
|
|
14
|
+
interval: "weekly"
|
|
15
|
+
cooldown:
|
|
16
|
+
default-days: 7
|
|
17
|
+
open-pull-requests-limit: 5
|
|
18
|
+
labels:
|
|
19
|
+
- "dependencies"
|
|
20
|
+
|
|
21
|
+
- package-ecosystem: "github-actions"
|
|
22
|
+
directory: "/"
|
|
23
|
+
schedule:
|
|
24
|
+
interval: "weekly"
|
|
25
|
+
cooldown:
|
|
26
|
+
default-days: 7
|
|
27
|
+
open-pull-requests-limit: 5
|
|
28
|
+
labels:
|
|
29
|
+
- "dependencies"
|
|
30
|
+
|
|
31
|
+
# Scans .pre-commit-config.yaml and updates each hook's `rev:` (SHA-pinned
|
|
32
|
+
# revs get their version comment updated too, same as the github-actions
|
|
33
|
+
# entries above). No `cooldown` here: as of 2026-03, GitHub does not support
|
|
34
|
+
# the cooldown option for this ecosystem, so these PRs land without the
|
|
35
|
+
# 7-day delay the other two ecosystems get - review before merging.
|
|
36
|
+
- package-ecosystem: "pre-commit"
|
|
37
|
+
directory: "/"
|
|
38
|
+
schedule:
|
|
39
|
+
interval: "weekly"
|
|
40
|
+
open-pull-requests-limit: 5
|
|
41
|
+
labels:
|
|
42
|
+
- "dependencies"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pre-commit
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
# so a future release workflow can gate on this exact job rather than
|
|
11
|
+
# polling for other checks to go green
|
|
12
|
+
workflow_call:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: pre-commit-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
pre-commit:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
# no-commit-to-branch guards a developer's local `git commit`; it has nothing
|
|
25
|
+
# to check on a runner. On a push to main, actions/checkout leaves HEAD on
|
|
26
|
+
# the main branch, so the hook fails the whole run on a merged PR. (On a
|
|
27
|
+
# pull_request event HEAD is detached at the merge ref, where it passes -
|
|
28
|
+
# which is why this only shows up after the merge.) SKIP is read by
|
|
29
|
+
# pre-commit itself, so it applies to both steps below. It is set even
|
|
30
|
+
# though that hook is currently commented out in .pre-commit-config.yaml,
|
|
31
|
+
# so uncommenting it there does not silently break CI.
|
|
32
|
+
#
|
|
33
|
+
# GH_TOKEN puts zizmor in online mode (offline is the default without it):
|
|
34
|
+
# it can then fetch the referenced actions themselves for the audits that
|
|
35
|
+
# need that - see the comment above the zizmor hook in
|
|
36
|
+
# .pre-commit-config.yaml. The runner's own GITHUB_TOKEN is enough for a
|
|
37
|
+
# public repo; this is read-only per the `permissions:` block above and
|
|
38
|
+
# never touches git credentials (actions/checkout below already sets
|
|
39
|
+
# persist-credentials: false).
|
|
40
|
+
env:
|
|
41
|
+
SKIP: no-commit-to-branch
|
|
42
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
45
|
+
with:
|
|
46
|
+
# required to grab the history of the PR for pre-commit
|
|
47
|
+
fetch-depth: 0
|
|
48
|
+
persist-credentials: false
|
|
49
|
+
|
|
50
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.12"
|
|
53
|
+
|
|
54
|
+
# single source of truth for the uv version: the `rev` of the
|
|
55
|
+
# astral-sh/uv-pre-commit repo in .pre-commit-config.yaml, which
|
|
56
|
+
# `pre-commit autoupdate` already keeps current. Read with awk rather
|
|
57
|
+
# than a yaml parser so it needs nothing installed first. `rev` is
|
|
58
|
+
# SHA-pinned (Dependabot's pre-commit ecosystem keeps the trailing
|
|
59
|
+
# `# X.Y.Z` comment in sync when it bumps the SHA - same convention as
|
|
60
|
+
# the actions/* pins above), so the version comes from that comment;
|
|
61
|
+
# fall back to the bare `rev` value for a tag-pinned rev with no
|
|
62
|
+
# comment.
|
|
63
|
+
- name: read uv version from .pre-commit-config.yaml
|
|
64
|
+
run: |
|
|
65
|
+
UV_VERSION="$(awk '
|
|
66
|
+
/^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
|
|
67
|
+
found && $1 == "rev:" {
|
|
68
|
+
if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
|
|
69
|
+
ver = substr($0, RSTART, RLENGTH)
|
|
70
|
+
sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
|
|
71
|
+
} else {
|
|
72
|
+
ver = $2
|
|
73
|
+
gsub(/["'\'']/, "", ver)
|
|
74
|
+
sub(/^[vV]/, "", ver)
|
|
75
|
+
}
|
|
76
|
+
print ver
|
|
77
|
+
exit
|
|
78
|
+
}
|
|
79
|
+
' .pre-commit-config.yaml)"
|
|
80
|
+
if [ -z "$UV_VERSION" ]; then
|
|
81
|
+
echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
|
|
85
|
+
echo "uv version: $UV_VERSION"
|
|
86
|
+
|
|
87
|
+
# the local uv-lock-check / uv-build-wheel / pytest / mypy hooks are
|
|
88
|
+
# language: system and shell out to `uv`, which is not on a runner by
|
|
89
|
+
# default - without this they all fail
|
|
90
|
+
- name: install uv (needed by the local pre-commit hooks)
|
|
91
|
+
run: python -m pip install "uv==${UV_VERSION}"
|
|
92
|
+
|
|
93
|
+
# the local hooks run `uv run --frozen`, which needs the project's
|
|
94
|
+
# environment to already exist
|
|
95
|
+
- name: uv sync --locked
|
|
96
|
+
run: uv sync --locked
|
|
97
|
+
|
|
98
|
+
- name: pre-commit local action test
|
|
99
|
+
if: github.event_name == 'pull_request' || github.event_name == 'push'
|
|
100
|
+
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
|
101
|
+
with:
|
|
102
|
+
extra_args: >-
|
|
103
|
+
--color=always
|
|
104
|
+
--from-ref ${{ github.event.pull_request.base.sha || github.event.before }}
|
|
105
|
+
--to-ref ${{ github.event.pull_request.head.sha || github.sha }}
|
|
106
|
+
--hook-stage manual
|
|
107
|
+
|
|
108
|
+
# manual dispatch, and a workflow_call: run every hook (including the
|
|
109
|
+
# manual-stage ones - validate-pyproject, pyroma, the pytest and mypy
|
|
110
|
+
# wrappers, the check-useless-excludes/check-hooks-apply meta hooks)
|
|
111
|
+
# against the full tree, not just the diff range a PR/push would use.
|
|
112
|
+
# workflow_call has no pull_request/before ref to diff against anyway -
|
|
113
|
+
# github.event there is the caller's payload, not a diff range.
|
|
114
|
+
- name: pre-commit local action test (manual dispatch)
|
|
115
|
+
if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call'
|
|
116
|
+
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
|
117
|
+
with:
|
|
118
|
+
extra_args: --color=always --all-files --hook-stage manual
|
|
119
|
+
|
|
120
|
+
# `restore-keys` in the shared action means a config change restores the
|
|
121
|
+
# previous cache and then saves it back under the new key - so every hook
|
|
122
|
+
# rev bump would otherwise leave its old environment in the cache forever.
|
|
123
|
+
# gc deletes exactly the environments no live config references. Last step
|
|
124
|
+
# in the job on purpose: actions/cache saves in its post step, which runs
|
|
125
|
+
# after this, so what gets uploaded is the pruned store. No `if:` - the
|
|
126
|
+
# default success() matches the cache action's own `post-if: success()`,
|
|
127
|
+
# so this runs in precisely the cases where a save follows. `pre-commit`
|
|
128
|
+
# is on PATH from the shared action's `python -m pip install pre-commit`.
|
|
129
|
+
- name: prune unused hook environments before the cache is saved
|
|
130
|
+
run: pre-commit gc
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Tags and releases on a version bump. Nothing here bumps the version: the flow
|
|
3
|
+
# is to change `[project] version` in pyproject.toml in a PR, merge it to main,
|
|
4
|
+
# and this workflow does the rest (tag, build, checksums, GitHub Release, and -
|
|
5
|
+
# once deliberately enabled, see the publish step - PyPI).
|
|
6
|
+
name: Tag and Release
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches:
|
|
11
|
+
- main
|
|
12
|
+
paths:
|
|
13
|
+
- "pyproject.toml"
|
|
14
|
+
- "uv.lock"
|
|
15
|
+
- "src/**"
|
|
16
|
+
- ".github/workflows/tag_and_release.yaml"
|
|
17
|
+
- ".github/workflows/test.yaml"
|
|
18
|
+
- ".github/workflows/pre-commit.yaml"
|
|
19
|
+
# a re-run path for when a release run failed after the version was already
|
|
20
|
+
# merged; the tag check below still makes it a no-op if the tag exists
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
concurrency:
|
|
27
|
+
# deliberately no cancel-in-progress: never kill a release mid-flight
|
|
28
|
+
group: release-${{ github.ref }}
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
# Decides whether there is anything to do, so the expensive jobs below can be
|
|
32
|
+
# skipped outright. A push that touches src/** without bumping the version
|
|
33
|
+
# gets a green, near-instant run and no release.
|
|
34
|
+
version:
|
|
35
|
+
name: Check for an untagged version
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
# fork guard: a fork that pushes to its own main should not try to release
|
|
38
|
+
if: github.repository == 'jgstew/bigfix-root-mcp'
|
|
39
|
+
outputs:
|
|
40
|
+
version: ${{ steps.getversion.outputs.version }}
|
|
41
|
+
release_needed: ${{ steps.tagged.outputs.release_needed }}
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
+
with:
|
|
45
|
+
# full history + tags: the tag check below needs every existing tag
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
fetch-tags: true
|
|
48
|
+
persist-credentials: false
|
|
49
|
+
|
|
50
|
+
# `[project] version` in pyproject.toml is the single source of truth -
|
|
51
|
+
# src/bigfix_root_mcp/__init__.py reads __version__ back out of the
|
|
52
|
+
# installed package metadata, so there is no second place to bump.
|
|
53
|
+
# tomllib is stdlib on the 3.11+ this package requires, and the runner's
|
|
54
|
+
# preinstalled python is new enough, so no setup-python step is needed.
|
|
55
|
+
- name: Read version from pyproject.toml
|
|
56
|
+
id: getversion
|
|
57
|
+
run: |
|
|
58
|
+
version="$(python3 -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
59
|
+
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-]?[0-9A-Za-z.]+)?$'; then
|
|
60
|
+
echo "::error::unexpected version string in pyproject.toml: '$version'"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
64
|
+
echo "version: $version"
|
|
65
|
+
|
|
66
|
+
# Release only if this version has no tag yet. This is what makes the
|
|
67
|
+
# workflow idempotent and safe to re-run.
|
|
68
|
+
- name: Check whether v<version> is already tagged
|
|
69
|
+
id: tagged
|
|
70
|
+
env:
|
|
71
|
+
VERSION: ${{ steps.getversion.outputs.version }}
|
|
72
|
+
run: |
|
|
73
|
+
if git show-ref --tags --verify --quiet -- "refs/tags/v${VERSION}"; then
|
|
74
|
+
echo "release_needed=false" >> "$GITHUB_OUTPUT"
|
|
75
|
+
echo "v${VERSION} is already tagged - nothing to release"
|
|
76
|
+
else
|
|
77
|
+
echo "release_needed=true" >> "$GITHUB_OUTPUT"
|
|
78
|
+
echo "v${VERSION} has no tag yet - will release"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# The gates. Reusing the test and pre-commit workflows means the release is
|
|
82
|
+
# blocked by the exact same jobs that guard PRs, with no third-party
|
|
83
|
+
# wait-for-checks action and no polling. Both run in parallel off `version`
|
|
84
|
+
# so a release is no slower than the slower of the two.
|
|
85
|
+
test:
|
|
86
|
+
name: Test
|
|
87
|
+
needs: version
|
|
88
|
+
if: needs.version.outputs.release_needed == 'true'
|
|
89
|
+
uses: ./.github/workflows/test.yaml
|
|
90
|
+
|
|
91
|
+
pre-commit:
|
|
92
|
+
name: pre-commit
|
|
93
|
+
needs: version
|
|
94
|
+
if: needs.version.outputs.release_needed == 'true'
|
|
95
|
+
uses: ./.github/workflows/pre-commit.yaml
|
|
96
|
+
|
|
97
|
+
release:
|
|
98
|
+
name: Tag and Release
|
|
99
|
+
needs: [version, test, pre-commit]
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
# Deliberately gates the whole release - tag, GitHub release, and PyPI
|
|
102
|
+
# upload - not just the publish step, so an environment protection rule
|
|
103
|
+
# (required reviewer, wait timer, branch restriction) stops a release
|
|
104
|
+
# before the tag is cut rather than after. The name must match the
|
|
105
|
+
# `Environment name` field of the PyPI trusted publisher exactly: PyPI
|
|
106
|
+
# checks the `environment` claim in the OIDC token, and a mismatch fails
|
|
107
|
+
# the publish. Create it under Settings > Environments; an environment
|
|
108
|
+
# with no protection rules is valid and simply records the deployment.
|
|
109
|
+
environment: pypi
|
|
110
|
+
permissions:
|
|
111
|
+
# contents: write - create the tag and the release, and upload assets.
|
|
112
|
+
# id-token: write - OIDC for the PyPI trusted-publishing step at the end
|
|
113
|
+
# of this job. Both live here rather than in a separate publish job so
|
|
114
|
+
# the built dist/ does not have to be handed between jobs through
|
|
115
|
+
# upload-artifact/download-artifact.
|
|
116
|
+
contents: write
|
|
117
|
+
id-token: write
|
|
118
|
+
steps:
|
|
119
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
120
|
+
with:
|
|
121
|
+
persist-credentials: false
|
|
122
|
+
|
|
123
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
124
|
+
with:
|
|
125
|
+
python-version: "3.13"
|
|
126
|
+
|
|
127
|
+
# same awk-from-.pre-commit-config.yaml source of truth as
|
|
128
|
+
# .github/workflows/test.yaml and pre-commit.yaml
|
|
129
|
+
- name: read uv version from .pre-commit-config.yaml
|
|
130
|
+
run: |
|
|
131
|
+
UV_VERSION="$(awk '
|
|
132
|
+
/^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
|
|
133
|
+
found && $1 == "rev:" {
|
|
134
|
+
if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
|
|
135
|
+
ver = substr($0, RSTART, RLENGTH)
|
|
136
|
+
sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
|
|
137
|
+
} else {
|
|
138
|
+
ver = $2
|
|
139
|
+
gsub(/["'\'']/, "", ver)
|
|
140
|
+
sub(/^[vV]/, "", ver)
|
|
141
|
+
}
|
|
142
|
+
print ver
|
|
143
|
+
exit
|
|
144
|
+
}
|
|
145
|
+
' .pre-commit-config.yaml)"
|
|
146
|
+
if [ -z "$UV_VERSION" ]; then
|
|
147
|
+
echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
150
|
+
echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
|
|
151
|
+
echo "uv version: $UV_VERSION"
|
|
152
|
+
|
|
153
|
+
- name: install uv
|
|
154
|
+
run: python -m pip install "uv==${UV_VERSION}"
|
|
155
|
+
|
|
156
|
+
- name: uv build (wheel + sdist)
|
|
157
|
+
run: uv build
|
|
158
|
+
|
|
159
|
+
# `sha256sum -c SHA256SUMS.txt`-verifiable, and with no dist/ prefix in
|
|
160
|
+
# it, so it checks out wherever the assets are downloaded to. Globs are
|
|
161
|
+
# listed explicitly rather than `./*` so the output file can never end up
|
|
162
|
+
# listing itself.
|
|
163
|
+
- name: Compute SHA-256 checksums
|
|
164
|
+
working-directory: dist
|
|
165
|
+
run: |
|
|
166
|
+
sha256sum ./*.whl ./*.tar.gz > SHA256SUMS.txt
|
|
167
|
+
cat SHA256SUMS.txt
|
|
168
|
+
|
|
169
|
+
# Release notes: GitHub's own generated notes (same content the Releases
|
|
170
|
+
# UI would produce) with the checksums appended, so the hashes are
|
|
171
|
+
# readable in the release body as well as downloadable as an asset. The
|
|
172
|
+
# notes are built here instead of passing --generate-notes to `gh release
|
|
173
|
+
# create` so the appended block is not at the mercy of how that flag
|
|
174
|
+
# combines with --notes.
|
|
175
|
+
- name: Build release notes
|
|
176
|
+
env:
|
|
177
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
178
|
+
VERSION: ${{ needs.version.outputs.version }}
|
|
179
|
+
run: |
|
|
180
|
+
notes_file="${RUNNER_TEMP}/release-notes.md"
|
|
181
|
+
if ! gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \
|
|
182
|
+
--method POST \
|
|
183
|
+
-f "tag_name=v${VERSION}" \
|
|
184
|
+
-f "target_commitish=${GITHUB_SHA}" \
|
|
185
|
+
--jq '.body' > "$notes_file"; then
|
|
186
|
+
echo "::warning::could not generate release notes; falling back to a plain body"
|
|
187
|
+
printf 'Release v%s\n' "${VERSION}" > "$notes_file"
|
|
188
|
+
fi
|
|
189
|
+
{
|
|
190
|
+
printf '\n## SHA-256\n\n'
|
|
191
|
+
printf 'Verify with `sha256sum -c SHA256SUMS.txt`:\n\n'
|
|
192
|
+
printf '```\n'
|
|
193
|
+
cat dist/SHA256SUMS.txt
|
|
194
|
+
printf '```\n'
|
|
195
|
+
} >> "$notes_file"
|
|
196
|
+
echo "NOTES_FILE=$notes_file" >> "$GITHUB_ENV"
|
|
197
|
+
cat "$notes_file"
|
|
198
|
+
|
|
199
|
+
# `gh release create --target` creates the tag itself, so this needs no
|
|
200
|
+
# git push and the checkout above can stay credential-less. Using the gh
|
|
201
|
+
# CLI (preinstalled on the runner) also means one less third-party action
|
|
202
|
+
# to pin and keep updated.
|
|
203
|
+
- name: Create the GitHub release
|
|
204
|
+
env:
|
|
205
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
206
|
+
VERSION: ${{ needs.version.outputs.version }}
|
|
207
|
+
run: |
|
|
208
|
+
gh release create "v${VERSION}" \
|
|
209
|
+
--target "${GITHUB_SHA}" \
|
|
210
|
+
--title "v${VERSION}" \
|
|
211
|
+
--notes-file "${NOTES_FILE}" \
|
|
212
|
+
dist/*.whl dist/*.tar.gz dist/SHA256SUMS.txt
|
|
213
|
+
|
|
214
|
+
# Inert until all of these are true, so nothing can publish by accident:
|
|
215
|
+
# 1. a PyPI trusted publisher exists for this repo with workflow name
|
|
216
|
+
# `tag_and_release.yaml` and environment name `pypi`, matching the
|
|
217
|
+
# `environment:` above (https://docs.pypi.org/trusted-publishers/)
|
|
218
|
+
# 2. the repository variable PYPI_PUBLISH is set to `true`
|
|
219
|
+
# (Settings > Secrets and variables > Actions > Variables)
|
|
220
|
+
# 3. whatever protection rules the `pypi` environment carries have been
|
|
221
|
+
# satisfied - those gate this entire job, tagging included
|
|
222
|
+
# `uv publish` speaks OIDC natively given the id-token: write permission
|
|
223
|
+
# above, so there is no long-lived API token to store and no
|
|
224
|
+
# pypa/gh-action-pypi-publish pin to maintain. The release is already
|
|
225
|
+
# created at this point: a failure here leaves the GitHub release intact
|
|
226
|
+
# and is re-run by hand, not by re-running the whole workflow.
|
|
227
|
+
#
|
|
228
|
+
# NOTE: bigfix-root-mcp is not on PyPI yet, so the very first publish also
|
|
229
|
+
# needs the project name to exist there or a pending publisher configured.
|
|
230
|
+
- name: Publish to PyPI (trusted publishing)
|
|
231
|
+
if: vars.PYPI_PUBLISH == 'true'
|
|
232
|
+
# the explicit globs keep SHA256SUMS.txt (which lives in dist/ too) out
|
|
233
|
+
# of the upload; uv publish would otherwise take everything in dist/
|
|
234
|
+
run: uv publish --trusted-publishing always dist/*.whl dist/*.tar.gz
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
# so a future release workflow can gate on this exact job rather than
|
|
11
|
+
# polling for other checks to go green
|
|
12
|
+
workflow_call:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: test-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
test:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
# Single bump point for the version this package is tested against; keep
|
|
30
|
+
# it in step with the `classifiers` list in pyproject.toml. Pinned rather
|
|
31
|
+
# than "3" so a new CPython release does not silently change what CI runs.
|
|
32
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.13"
|
|
35
|
+
|
|
36
|
+
# single source of truth for the uv version: the `rev` of the
|
|
37
|
+
# astral-sh/uv-pre-commit repo in .pre-commit-config.yaml, which
|
|
38
|
+
# `pre-commit autoupdate` already keeps current. Read with awk rather
|
|
39
|
+
# than a yaml parser so it needs nothing installed first. `rev` is
|
|
40
|
+
# SHA-pinned (Dependabot's pre-commit ecosystem keeps the trailing
|
|
41
|
+
# `# X.Y.Z` comment in sync when it bumps the SHA - same convention as
|
|
42
|
+
# the actions/* pins above), so the version comes from that comment;
|
|
43
|
+
# fall back to the bare `rev` value for a tag-pinned rev with no
|
|
44
|
+
# comment. Same step as in .github/workflows/pre-commit.yaml.
|
|
45
|
+
- name: read uv version from .pre-commit-config.yaml
|
|
46
|
+
run: |
|
|
47
|
+
UV_VERSION="$(awk '
|
|
48
|
+
/^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
|
|
49
|
+
found && $1 == "rev:" {
|
|
50
|
+
if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
|
|
51
|
+
ver = substr($0, RSTART, RLENGTH)
|
|
52
|
+
sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
|
|
53
|
+
} else {
|
|
54
|
+
ver = $2
|
|
55
|
+
gsub(/["'\'']/, "", ver)
|
|
56
|
+
sub(/^[vV]/, "", ver)
|
|
57
|
+
}
|
|
58
|
+
print ver
|
|
59
|
+
exit
|
|
60
|
+
}
|
|
61
|
+
' .pre-commit-config.yaml)"
|
|
62
|
+
if [ -z "$UV_VERSION" ]; then
|
|
63
|
+
echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
|
|
67
|
+
echo "uv version: $UV_VERSION"
|
|
68
|
+
|
|
69
|
+
- name: install uv
|
|
70
|
+
run: python -m pip install "uv==${UV_VERSION}"
|
|
71
|
+
|
|
72
|
+
# no --all-extras: this package declares no optional extras, and the dev
|
|
73
|
+
# tooling lives in [dependency-groups] dev, which uv syncs by default
|
|
74
|
+
- name: uv sync --locked
|
|
75
|
+
run: uv sync --locked
|
|
76
|
+
|
|
77
|
+
# flags live in [tool.pytest.ini_options] in pyproject.toml, not here
|
|
78
|
+
- name: pytest
|
|
79
|
+
run: uv run --frozen pytest
|
|
80
|
+
|
|
81
|
+
- name: uv build (wheel + sdist)
|
|
82
|
+
run: uv build
|
|
83
|
+
|
|
84
|
+
# Exercise the artifact a release would actually ship, not just the
|
|
85
|
+
# source tree: install the wheel into a throwaway venv and import it.
|
|
86
|
+
# The resource read is the point - the MCP resources are markdown files
|
|
87
|
+
# loaded at runtime via importlib.resources, so a packaging change that
|
|
88
|
+
# drops them breaks the server without breaking a source-tree test run.
|
|
89
|
+
# There is no --help smoke check because the one console script
|
|
90
|
+
# (bigfix-root-mcp -> server:main) takes no arguments and goes straight
|
|
91
|
+
# to serving MCP over stdio.
|
|
92
|
+
- name: install the built wheel and smoke-import it
|
|
93
|
+
run: |
|
|
94
|
+
uv venv "${RUNNER_TEMP}/wheeltest"
|
|
95
|
+
VIRTUAL_ENV="${RUNNER_TEMP}/wheeltest" uv pip install dist/*.whl
|
|
96
|
+
"${RUNNER_TEMP}/wheeltest/bin/python" - <<'PY'
|
|
97
|
+
import importlib.resources as r
|
|
98
|
+
|
|
99
|
+
import bigfix_root_mcp
|
|
100
|
+
from bigfix_root_mcp import server # noqa: F401 (import side effects register the tools)
|
|
101
|
+
|
|
102
|
+
print("version:", bigfix_root_mcp.__version__)
|
|
103
|
+
for name in ("tool-guide.md", "session-cookbook.md", "client-cookbook.md"):
|
|
104
|
+
text = (r.files("bigfix_root_mcp") / "resources" / name).read_text()
|
|
105
|
+
assert len(text) > 500, name
|
|
106
|
+
print(name, len(text), "bytes")
|
|
107
|
+
PY
|
|
108
|
+
|
|
109
|
+
# cheap, and makes a test run's hashes comparable to any released ones
|
|
110
|
+
- name: sha256 of the built artifacts
|
|
111
|
+
run: sha256sum dist/*
|