diff --git a/.github/assets/logo/logo-dark.png b/.github/assets/logo/logo-dark.png new file mode 100644 index 0000000..916a66d Binary files /dev/null and b/.github/assets/logo/logo-dark.png differ diff --git a/.github/assets/logo/logo-light.png b/.github/assets/logo/logo-light.png new file mode 100644 index 0000000..916a66d Binary files /dev/null and b/.github/assets/logo/logo-light.png differ diff --git a/.github/assets/social-preview/social-preview.png b/.github/assets/social-preview/social-preview.png new file mode 100644 index 0000000..fba8426 Binary files /dev/null and b/.github/assets/social-preview/social-preview.png differ diff --git a/.github/brand/brand.lock.json b/.github/brand/brand.lock.json deleted file mode 100644 index 999a16a..0000000 --- a/.github/brand/brand.lock.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "schema_version": 2, - "authority": "https://github.com/computer-mcp/.github", - "revision": "2d9913a442afcb22faca90f5272d3988eb961be6e858b71c5067303cf7c627d6", - "consumer": "plugin-swift-format", - "files": { - "Documentation/Brand/header-light.png": "df3dc66b262d521640b2e11f8ee85d210e53a7f823136f65a7e197be288e5d4a", - "Documentation/Brand/header-dark.png": "ab670577f37e1e2eacef7b5d9973b51ebe536dbd585f3aa6dd380e3c6eb36129", - ".github/brand/social.png": "0670f9bc4c04ed705b45190654cefd04a43d986d4821ea4def1c472546694665" - }, - "entries": { - "README.md": [ - "Swift Format", - "Documentation/Brand/header-light.png", - "Documentation/Brand/header-dark.png" - ] - } -} diff --git a/.github/brand/social.png b/.github/brand/social.png deleted file mode 100644 index bd56802..0000000 Binary files a/.github/brand/social.png and /dev/null differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc3dec6..6b6ac1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,14 @@ name: CI on: pull_request: + types: [opened, synchronize, reopened] push: branches: [master] workflow_dispatch: permissions: contents: read concurrency: - group: ci-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: package: @@ -29,7 +30,7 @@ jobs: /usr/bin/swift test --disable-automatic-resolution /usr/bin/swift build -c release --disable-automatic-resolution python3 -m unittest discover -s Tests -p 'test_*.py' - python3 Scripts/build_package.py dist > package-receipt.json + python3 scripts/build_package.py dist > package-receipt.json - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: swift-format-plugin-${{ github.sha }} @@ -51,6 +52,10 @@ jobs: - name: Verify the oldest supported package interpreter run: | python3 -m unittest discover -s Tests -p 'test_*.py' - python3 Scripts/build_package.py dist - brand: - uses: computer-mcp/.github/.github/workflows/brand-check.yml@f402911abcdfb87ef0c41a2759e8c51387b18935 # master + python3 scripts/build_package.py dist + validate: + if: always() + needs: [package, minimum-python] + uses: computer-mcp/github-workflows/.github/workflows/result.yml@d65bc20504f39c3a30232d67043abeca43d2c7b1 + with: + needs-json: ${{ toJSON(needs) }} diff --git a/.github/workflows/notify-catalog.yml b/.github/workflows/notify-catalog.yml index 4739b0b..274e8f7 100644 --- a/.github/workflows/notify-catalog.yml +++ b/.github/workflows/notify-catalog.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Request complete catalog reconciliation id: catalog - uses: computer-mcp/computer-mcp.github.io/.github/actions/notify-catalog@cda692839487f03a2b4740d945f9a8bd818554e5 + uses: computer-mcp/github-workflows/actions/notify-catalog@d65bc20504f39c3a30232d67043abeca43d2c7b1 with: client-id: ${{ vars.CATALOG_APP_CLIENT_ID }} private-key: ${{ secrets.CATALOG_APP_PRIVATE_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aac394e..fee0909 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,11 @@ jobs: permissions: contents: write actions: read - uses: computer-mcp/.github/.github/workflows/plugin-release.yml@35766b350427ed900b456aa3c39098e97d9d3aa1 # master + uses: computer-mcp/github-workflows/.github/workflows/plugin-release.yml@d65bc20504f39c3a30232d67043abeca43d2c7b1 with: ref: ${{ inputs.ref }} publish: ${{ inputs.publish }} - build: python3 Scripts/build_package.py + build: python3 scripts/build_package.py ci_artifact: swift-format-plugin-${commit} notify-catalog: needs: release diff --git a/.github/workflows/source-policy.yml b/.github/workflows/source-policy.yml new file mode 100644 index 0000000..6179997 --- /dev/null +++ b/.github/workflows/source-policy.yml @@ -0,0 +1,17 @@ +name: Source policy checks +on: + pull_request: + types: [opened, synchronize, reopened, edited] + push: + branches: [master] + merge_group: +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + policy: + uses: computer-mcp/github-workflows/.github/workflows/policy.yml@d65bc20504f39c3a30232d67043abeca43d2c7b1 + with: + declaration-revision: 5c54a40cb7953db7472ebbefd3db608036b7b3ab diff --git a/AGENTS.md b/AGENTS.md index d38a09b..17d745f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -152,13 +152,3 @@ source of truth instead of a literal value. ## Repository Guardrails Read Package.swift and computer-mcp-plugin.toml before editing. Keep the exporter separate from the vendor formatter. Generate cli-tree.json from verified native input; preserve upstream notices and never hand-edit generated output. Run swift build, swift test and swift-format lint before handoff. Do not install vendor binaries or change global PATH. - -## Brand delivery - -The organization `.github` repository owns the design system and renders every -image; the main Computer MCP repository owns ProductIdentity. Imported -`Documentation/Brand/header-light.png` and `header-dark.png` travel with the -packaged manual; `.github/brand/social.png` supplies the repository preview. -`.github/brand/brand.lock.json` binds them to the organization exports, and CI -verifies it with the shared brand-check workflow. Update imports with the -organization repository's `python3 Brand/brand.py sync`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ca61ec..96e8abb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,10 +14,10 @@ swift format lint --strict --recursive --configuration .swift-format Package.swi swift test --disable-automatic-resolution swift build -c release --disable-automatic-resolution python3 -m unittest discover -s Tests -p 'test_*.py' -python3 Scripts/build_package.py OUTPUT_DIRECTORY +python3 scripts/build_package.py OUTPUT_DIRECTORY ``` -CI also repeats the Python tests and package build on Python 3.11, and runs the organization brand check. Exercise help, valid input and invalid input from outside the checkout. Keep command output contracts and generated resources covered by tests. +CI also repeats the Python tests and package build on Python 3.11. Exercise help, valid input and invalid input from outside the checkout. Keep command output contracts and generated resources covered by tests. ## Documentation diff --git a/Documentation/Architecture/Package.md b/Documentation/Architecture/Package.md index e3a3892..a74856e 100644 --- a/Documentation/Architecture/Package.md +++ b/Documentation/Architecture/Package.md @@ -19,14 +19,14 @@ is a separately resolved host dependency, never bundled or installed here. ## Boundaries -The host owns workspace access, caller permissions, executable bindings and +The host owns project access, caller permissions, executable bindings and runtime output limits. Version/interface checks distinguish the reviewed tree from an incompatible external installation. Current mapping and intentional coverage limits are described in [Interface](../Reference/Interface.md). ## Distribution -`Scripts/build_package.py` packages regular, bounded source files into a +`scripts/build_package.py` packages regular, bounded source files into a deterministic ZIP. It includes the host-consumed declaration and CLI Tree, manuals and notices, while build products and maintainer source stay in the repository. The Python standard-library builder requires no vendor process. diff --git a/Documentation/Brand/header-dark.png b/Documentation/Brand/header-dark.png deleted file mode 100644 index 7f9ca89..0000000 Binary files a/Documentation/Brand/header-dark.png and /dev/null differ diff --git a/Documentation/Brand/header-light.png b/Documentation/Brand/header-light.png deleted file mode 100644 index 2ea0c8c..0000000 Binary files a/Documentation/Brand/header-light.png and /dev/null differ diff --git a/Documentation/Reference/Release.md b/Documentation/Reference/Release.md index c4782ae..220a57d 100644 --- a/Documentation/Reference/Release.md +++ b/Documentation/Reference/Release.md @@ -9,7 +9,7 @@ The organization [VERSIONING](https://github.com/computer-mcp/.github/blob/maste owns version rules, including tags, version choice and the changelog. This reference records this repository's version sources, compatibility, release procedure and checks. The shared -[release workflow guide](https://github.com/computer-mcp/.github/blob/master/Documentation/Reference/PluginRelease.md) +[release workflow guide](https://github.com/computer-mcp/github-workflows/blob/master/docs/plugin-release.md) describes workflow verification and recovery. ## Components and Version Sources @@ -22,7 +22,8 @@ The `export-swift-format-tree` executable is a maintainer tool that regenerates `cli-tree.json`. It is not shipped and has no version of its own. The archive contains the manifest, `cli-tree.json`, the README, contribution -guide, license, notices, `ThirdPartyNotices/` and `Documentation/`. +guide, license, notices, `ThirdPartyNotices/`, `Documentation/`, and the README +images `.github/assets/logo/logo-light.png` and `.github/assets/logo/logo-dark.png`. The exporter, tests, build scripts and workflows are excluded from the archive. ## Compatibility Surface @@ -55,8 +56,9 @@ host installation and real format/lint calls are separate manual acceptance. ## Derived Metadata and Drift Checks -`Scripts/build_package.py` verifies the plugin ID, parses the CLI tree, skips -hidden files and Python caches, rejects links and special files, and produces +`scripts/build_package.py` verifies the plugin ID, parses the CLI tree, skips +hidden files and Python caches during directory traversal, explicitly includes +the two README logo files, rejects links and special files, and produces the same archive bytes from the same source. The builder receipt prints the ID, version and SHA-256. @@ -78,7 +80,7 @@ The shared workflow checks that setting before writing a Release. and that the reviewed `master` commit has a successful `ci.yml` push run. 2. Dispatch `release.yml` from `master` with `ref` set to that full commit SHA and `publish` set to `false` (the default). The pinned reusable workflow runs - `python3 Scripts/build_package.py` with an output directory and requires its + `python3 scripts/build_package.py` with an output directory and requires its `swift-format.zip` to match the archive in the exact `swift-format-plugin-` CI artifact byte for byte. Inspect the `plugin-release--` workflow artifact: `assets/` contains the @@ -117,9 +119,9 @@ The job checks this repository's immutable ID, does not check out package code, and grants its own token no repository permissions. Missing or rejected authority fails the run visibly. -The website's notification action is pinned to a reviewed full commit. Publish -that website commit first, then update the pin here when adopting a change to -the notification contract. +The shared notification action in `computer-mcp/github-workflows` is pinned to a +reviewed full commit. Update the pin here when adopting a reviewed change to the +notification contract. A release published with a repository `GITHUB_TOKEN` does not trigger release-event workflows. `release.yml` calls `notify-catalog.yml` as a dependent @@ -147,8 +149,8 @@ acceptance record. | Operation | Existing command or explicit manual procedure | Required access | | --- | --- | --- | | Version update and check | Edit `computer-mcp-plugin.toml`; `python3 -m unittest discover -s Tests -p 'test_*.py'` | Local checkout | -| Candidate validation and build | `ci.yml`; locally the commands in [CONTRIBUTING](../../CONTRIBUTING.md#validation), then `python3 Scripts/build_package.py dist` | CI or local checkout with Swift toolchain and resolved dependencies | -| Native and host acceptance | Validate and install the exact archive with the selected host, bind the supported formatter and exercise format/lint calls | Selected host, formatter and authorized workspace | +| Candidate validation and build | `ci.yml`; locally the commands in [CONTRIBUTING](../../CONTRIBUTING.md#validation), then `python3 scripts/build_package.py dist` | CI or local checkout with Swift toolchain and resolved dependencies | +| Native and host acceptance | Validate and install the exact archive with the selected host, bind the supported formatter and exercise format/lint calls | Selected host, formatter and authorized project | | Acceptance and publication | Manual checks above; dispatch `release.yml`, dry run first | Repository Actions; publication also needs a signing key and release write access | | Status and interrupted-run recovery | Inspect CI or `release.yml`; follow [Retry](#retry) | Repository Actions | | Cleanup | Delete obsolete local output directories and reproducible `.build/` products | Local checkout | @@ -161,7 +163,7 @@ retention. ### Retry For a failed release run, use **Re-run failed jobs**. The shared workflow's -[recovery procedure](https://github.com/computer-mcp/.github/blob/master/Documentation/Reference/PluginRelease.md#run-and-recover) +[recovery procedure](https://github.com/computer-mcp/github-workflows/blob/master/docs/plugin-release.md#run-and-recover) owns draft recovery, frozen provenance and expired artifacts. A notification failure needs only the notification job rerun or a manual notification dispatch. diff --git a/README.md b/README.md index 0836468..72d19e1 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,108 @@ - - - Computer MCP — Swift Format - +

+ + + + +

+ +

Swift Format

+ +

Computer MCP family plugin for verified Swift formatting and linting.

+ +

+ CI + swift-format from Swift 6.3.1 + License: FSL-1.1-ALv2 +

+ +

+ Install · + Usage · + Interface · + Documentation · + Contributing +

+ +## Overview + +This plugin gives [Computer MCP](https://computer-mcp.github.io/) typed tools for the +`swift-format` supplied with Swift 6.3.1: formatting, linting, configuration output and help. +The formatter stays yours. The plugin ships no Swift or vendor binaries and does not change +`PATH`; you bind the executable you already have. + +Before every authorized call, the Gateway checks the bound executable's version and native +interface digest against this plugin's verified baseline. A mismatch stops the call until you +select a compatible binding or install a reviewed plugin update. These checks run the vendor +executable with closed stdin in the registered working directory and environment, share a +bounded time budget, and never run during installation or a static doctor check. + +## Quick look + +The plugin adds one CLI contribution, `format`. Its root command formats, like the upstream +default, and it has these subcommands: + +| Command | Result | +| --- | --- | +| `format` | Formats files or stdin; `in_place: true` overwrites the listed files | +| `lint` | Reports style findings; `strict: true` makes findings fail the command | +| `dump-configuration` | Prints the effective or default configuration as JSON | +| `help` | Prints upstream help | + +Install it in the host, bind the `formatter` dependency, then call the generated tool. For +`format`, pass: -# Computer MCP — Swift Format - -Part of the [Computer MCP](https://computer-mcp.github.io/) family. -**Wherever you chat, your computer is there.** - -A Computer MCP plugin for the Swift Format supplied with Swift 6.3.1. It exposes -formatting, linting, configuration output, and help through the Gateway's typed -CLI tools. The installed formatter remains user-owned. +```json +{"paths":["-"],"source":"let value=1\n","color_diagnostics":false,"project_id":"YOUR_PROJECT"} +``` -## Setup +The result carries the real stdout, stderr, exit status and timeout information. -Add this checkout with Computer MCP's local plugin entry. In its settings, bind -the `formatter` dependency to the actual `swift-format` executable supplied with -Swift 6.3.1, verify its `--version`, then enable the `format` CLI contribution. -The Gateway requires a workspace and Full Shell permission for these tools. -Before every authorized invocation, the Gateway checks the executable's version -and native interface digest against this plugin's baseline. A mismatch stops the -invocation; select a compatible dependency binding or a reviewed plugin update. -These checks run vendor code with closed stdin in the registered cwd/environment, -share a bounded check budget, and do not run during installation or static doctor. -This package installs neither Swift nor vendor binaries and does not alter PATH. +## Install -Use `tools/list` to obtain generated tool names; titles contain the registration -and command path. For the `format` command, pass: +1. Build the package archive (see [Package archive](#package-archive)), or add this checkout with + Computer MCP's local plugin entry. +2. Install the archive through the host's plugin management UI or CLI. +3. In the plugin's settings, bind the `formatter` dependency to the actual `swift-format` from + Swift 6.3.1 and verify its `--version`. +4. Enable the `format` CLI contribution. The tools run inside a project and need Full Shell + permission. -```json -{"paths":["-"],"source":"let value=1\n","color_diagnostics":false,"workspace_id":"YOUR_WORKSPACE"} -``` +Disabling or removing the plugin revokes its contribution, not the external formatter. Host +overrides and the dependency binding belong to Computer MCP. -The result contains real stdout/stderr, exit status, and timeout information. -Use `paths` for files; `in_place: true` explicitly overwrites those files. Omitted -paths still select stdin but emit an upstream deprecation warning; `["-"]` is -the supported explicit stdin form. `lint` checks source and `strict: true` makes -findings fail the command. No artificial dry-run is supplied. +## Usage -## Interface coverage +Use `tools/list` to obtain the generated tool names; titles contain the registration and command +path. Pass files in `paths`; `["-"]` selects stdin explicitly. Omitting `paths` still reads stdin +but emits an upstream deprecation warning. No artificial dry run is supplied. -`cli-tree.json` is generated from the native machine export, not parsed help text -or a handwritten command list. Every argument of the five observed nodes is -accounted for, including hidden debug arguments. Preferred spellings represent -equivalent aliases; one Boolean represents the verified color/inverse pair. -The native export's missing default-subcommand and stdin semantics are supplemented -from the matching upstream source. See [Interface](Documentation/Reference/Interface.md). +`cli-tree.json` is generated from the formatter's native machine export, not parsed help text or +a handwritten list. Every argument of the five observed commands is accounted for, including +hidden debug arguments; preferred spellings stand for equivalent aliases, and one Boolean covers +the verified color/inverse pair. The export's missing default-subcommand and stdin semantics are +supplemented from the matching upstream source. Coverage is explicitly partial: experimental +parser-control entry points are not formatting tools, and value-dependent validation stays in the +formatter. See [Interface](Documentation/Reference/Interface.md). -Coverage is explicitly partial: experimental parser-control entry points are -not formatting tools, and value-dependent upstream validation remains in the -formatter. Unknown versions or a changed native export stop generation. Updating -this plugin's verified interface does not require vendor-specific Gateway code. +### Package archive -## Package archive +With Python 3.11 or newer: -With Python 3.11 or newer, run `python3 Scripts/build_package.py OUTPUT_DIRECTORY` -to produce `swift-format.zip` and print its ID, version and SHA-256. The archive -contains the declaration, generated tree, manuals, license and upstream notices; it -contains neither the maintainer exporter nor the vendor formatter. Identical -inputs produce identical bytes. An existing different artifact is preserved -and causes an error. Install the ZIP through the host's plugin management UI or -CLI, reviewing the dependency binding and grants independently. +```sh +python3 scripts/build_package.py OUTPUT_DIRECTORY +``` -`.github/workflows/ci.yml` validates the exporter and package on pull -requests, `master` pushes and manual runs, and repeats the package tests and -build on Python 3.11. Its downloadable workflow artifacts are -validation outputs, not a public plugin release or publisher verification. +This writes `swift-format.zip` and prints its ID, version and SHA-256. The archive contains the +declaration, generated tree, manuals, license and upstream notices; it contains neither the +maintainer exporter nor the vendor formatter. Identical inputs produce identical bytes, and an +existing different archive is preserved and reported as an error. Review the dependency binding +and grants when you install it. -## Regenerate and test +### Regenerate the interface -Requires Swift 6.2 or newer to build the exporter. It uses swift-argument-parser -for named options, validation and generated help. -The exporter is a maintainer tool, not a replacement formatter or runtime service. +The exporter is a maintainer tool, not a replacement formatter or runtime service. Build it with +Swift 6.2 or newer; it uses swift-argument-parser for options, validation and help. In a shell +with `set -o pipefail`: ```sh swift build @@ -81,21 +112,35 @@ SWIFT_FORMAT=/absolute/path/to/swift-format --executable-version "$("$SWIFT_FORMAT" --version)" --output cli-tree.json ``` -Use a shell with pipeline failure propagation (`set -o pipefail`). The native -export must match the pinned baseline. Review upstream source and tests before -supporting another version; do not bypass the drift check. The exporter writes -atomically after complete validation, and reports structured errors on stderr. -No global installation is needed; it runs by explicit path from any directory. +The native export must match the pinned baseline: an unknown version or a changed export stops +generation. Review upstream source and tests before supporting another version, and do not bypass +the drift check. The exporter writes atomically after complete validation, reports structured +errors on stderr, and runs by explicit path from any directory. Updating the verified interface +needs no vendor-specific Gateway code. + +## Requirements + +- Computer MCP. +- `swift-format` from Swift 6.3.1, bound in the plugin's settings. +- Python 3.11 or newer to build the package archive. +- Swift 6.2 or newer to build and test the exporter. + +## Documentation + +The [documentation index](Documentation/README.md) covers the architecture, the +[interface](Documentation/Reference/Interface.md) and the +[release process](Documentation/Reference/Release.md). CI validates the exporter and the package +on pull requests, `master` pushes and manual runs, and repeats the package tests and build on +Python 3.11. Its downloadable workflow artifacts are validation outputs, not a public plugin +release. -Disabling or removing the plugin revokes its contribution, not the external -formatter. Host overrides and the dependency binding belong to Computer MCP. +## Contributing -See the [documentation index](Documentation/README.md) and -[contributor guide](CONTRIBUTING.md) for architecture and validation guidance. +See the [contributor guide](CONTRIBUTING.md). ## License Computer MCP-owned code and resources use the -[Functional Source License 1.1, Apache 2.0 Future License](LICENSE) -(FSL-1.1-ALv2). [Third-party notices](THIRD_PARTY_NOTICES.md) identify the -separate rights covering upstream descriptions and dependencies. +[Functional Source License 1.1, Apache 2.0 Future License](LICENSE) (FSL-1.1-ALv2). +[Third-party notices](THIRD_PARTY_NOTICES.md) identify the separate rights covering upstream +descriptions and dependencies. diff --git a/Tests/test_build_package.py b/Tests/test_build_package.py index ef8689e..d0f860d 100644 --- a/Tests/test_build_package.py +++ b/Tests/test_build_package.py @@ -10,7 +10,7 @@ import zipfile ROOT = Path(__file__).resolve().parent.parent -SPEC = importlib.util.spec_from_file_location("build_package", ROOT / "Scripts/build_package.py") +SPEC = importlib.util.spec_from_file_location("build_package", ROOT / "scripts/build_package.py") BUILDER = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(BUILDER) @@ -28,11 +28,15 @@ def test_reproducible_declaration_and_notices_without_executables(self): self.assertIn("cli-tree.json", archive.namelist()) self.assertIn("ThirdPartyNotices/SwiftFormat-LICENSE.txt", archive.namelist()) for name in ("LICENSE", "THIRD_PARTY_NOTICES.md", "ThirdPartyNotices.txt", - "NOTICE.md", "ThirdPartyNotices/SwiftFormat-LICENSE.txt"): + "NOTICE.md", "ThirdPartyNotices/SwiftFormat-LICENSE.txt", + ".github/assets/logo/logo-light.png", ".github/assets/logo/logo-dark.png"): self.assertEqual(archive.read(name), (ROOT / name).read_bytes()) self.assertEqual(archive.read("cli-tree.json"), (ROOT / "cli-tree.json").read_bytes()) + self.assertEqual( + {name for name in archive.namelist() if name.startswith(".github/")}, + {".github/assets/logo/logo-light.png", ".github/assets/logo/logo-dark.png"}) for item in archive.infolist(): - self.assertFalse(item.filename.startswith((".git", ".build", "Sources/", "bin/"))) + self.assertFalse(item.filename.startswith((".git/", ".build/", "Sources/", "bin/"))) self.assertEqual(item.date_time, (1980, 1, 1, 0, 0, 0)) self.assertEqual((item.external_attr >> 16) & 0o777, 0o644) @@ -44,7 +48,7 @@ def test_rejects_unsafe_inputs(self): for name in ("computer-mcp-plugin.toml", "cli-tree.json", "README.md", "CONTRIBUTING.md", "NOTICE.md", "LICENSE", "THIRD_PARTY_NOTICES.md", "ThirdPartyNotices.txt"): shutil.copyfile(ROOT / name, root / name) - for name in ("ThirdPartyNotices", "Documentation"): + for name in ("ThirdPartyNotices", "Documentation", ".github/assets/logo"): shutil.copytree(ROOT / name, root / name) target = root / "Documentation" / "unsafe" if kind == "symlink": @@ -73,8 +77,9 @@ def test_skips_hidden_files_and_python_caches(self): actual, _ = BUILDER.package_bytes(root) self.assertEqual(actual, expected) - def test_requires_license_and_notices(self): - for name in ("LICENSE", "THIRD_PARTY_NOTICES.md", "ThirdPartyNotices.txt"): + def test_requires_license_notices_and_logos(self): + for name in ("LICENSE", "THIRD_PARTY_NOTICES.md", "ThirdPartyNotices.txt", + ".github/assets/logo/logo-light.png", ".github/assets/logo/logo-dark.png"): with self.subTest(name=name), tempfile.TemporaryDirectory() as temp: root = Path(temp) / "source" shutil.copytree(ROOT, root, ignore=shutil.ignore_patterns(".git", ".build", "__pycache__")) @@ -84,7 +89,7 @@ def test_requires_license_and_notices(self): def test_command_preserves_existing_artifacts(self): with tempfile.TemporaryDirectory() as temp: - command = [sys.executable, str(ROOT / "Scripts/build_package.py"), temp] + command = [sys.executable, str(ROOT / "scripts/build_package.py"), temp] first = subprocess.run(command, capture_output=True, timeout=10) second = subprocess.run(command, capture_output=True, timeout=10) self.assertEqual(first.returncode, 0, first.stderr) diff --git a/Scripts/build_package.py b/scripts/build_package.py similarity index 95% rename from Scripts/build_package.py rename to scripts/build_package.py index 8d6e060..ef2d13c 100644 --- a/Scripts/build_package.py +++ b/scripts/build_package.py @@ -15,7 +15,8 @@ def package_bytes(root): pending = [root / name for name in ( "computer-mcp-plugin.toml", "cli-tree.json", "README.md", "CONTRIBUTING.md", "LICENSE", "THIRD_PARTY_NOTICES.md", "ThirdPartyNotices.txt", - "NOTICE.md", "ThirdPartyNotices", "Documentation")] + "NOTICE.md", "ThirdPartyNotices", "Documentation", + ".github/assets/logo/logo-light.png", ".github/assets/logo/logo-dark.png")] contents = {} count = total = 0 while pending: