From ab3c6848115b84d6843dbaf9f6c499c2382bec8f Mon Sep 17 00:00:00 2001 From: Torsten Mahr Date: Sun, 20 Sep 2026 11:06:48 +0200 Subject: [PATCH 1/3] docs: assess against standard 1.14.0 (Needs work: P09 fail) Reassessed against v1.14.0 (was 1.11.1): W05/W06 retired, W09 added, R05 now needs a recorded smoke test of the published file, P09 is a fail where a runner exists. Also fixes what the assessment found: the site still described 1.2.0, and AGENTS.md omitted the plugin. Adds the generated conformance badge. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EGjbE4ZXFZyXFd2GQ7xRAp --- .github/badges/conformance.svg | 10 ++++ .github/conformance.yml | 17 +++--- AGENTS.md | 8 +++ docs/index.html | 14 ++--- docs/self-assessment.md | 95 ++++++++++++++++++++++------------ 5 files changed, 95 insertions(+), 49 deletions(-) create mode 100644 .github/badges/conformance.svg diff --git a/.github/badges/conformance.svg b/.github/badges/conformance.svg new file mode 100644 index 0000000..9bdd92a --- /dev/null +++ b/.github/badges/conformance.svg @@ -0,0 +1,10 @@ + + trsdn standard: v1.14.0 - Needs work + + + + + trsdn standard + v1.14.0 - Needs work + + diff --git a/.github/conformance.yml b/.github/conformance.yml index ce83cce..4bd00e6 100644 --- a/.github/conformance.yml +++ b/.github/conformance.yml @@ -4,8 +4,8 @@ # Criteria: https://github.com/trsdn/.github/blob/main/docs/repository-quality-standard.md # Evidence: docs/self-assessment.md (only for results that aren't a clean pass) -standard_version: "1.11.1" -assessed_on: "2026-09-17" +standard_version: "1.14.0" +assessed_on: "2026-09-20" state: "Needs work" evidence: "docs/self-assessment.md" @@ -34,13 +34,13 @@ criteria: P06: pass P07: pass P08: partial - P09: partial + P09: fail P10: pass P11: pass S01: pass S02: pass - S03: pass - S04: pass + S03: partial + S04: partial S05: pass S06: pass S07: pass @@ -60,7 +60,7 @@ criteria: R02: partial R03: pass R04: pass - R05: pass + R05: partial R06: pass R07: pass R08: pass @@ -79,10 +79,11 @@ criteria: W02: pass W03: pass W04: pass - W05: pass - W06: pass + W05: na + W06: na W07: pass W08: pass + W09: pass G01: pass G02: pass G03: pass diff --git a/AGENTS.md b/AGENTS.md index 106a26d..c1aa970 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,9 @@ swift build # Unit tests (swift-testing: @Test / #expect, no XCTest) swift test +# Stream Deck plugin tests (Node 20+; not run in CI) +(cd Tools/openpromptr-streamdeck && npm test) + # Formatting check (must be clean; CI enforces this) swift format lint --strict --recursive Sources Tests Package.swift @@ -148,6 +151,11 @@ Sources/ process, main.swift's dispatch between the two, Update/ (AppUpdater integration, see #7), and LocalAPI/ (the loopback HTTP control API, see #4). + +Tools/ +└── openpromptr-streamdeck/ OpenDeck / Stream Deck plugin (plain JavaScript, + no dependencies, no build step). A client of the local + API; installed with its own `install.sh`. ``` Three source types feed one output pipeline: a private virtual display, a diff --git a/docs/index.html b/docs/index.html index 25cbf94..f230e8f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -303,13 +303,13 @@

OpenPromptr

Maintained - v1.2.0 + v1.3.0 macOS 13+ MIT licence
- - Download OpenPromptr 1.2.0 + + Download OpenPromptr 1.3.0 Build from source @@ -388,9 +388,9 @@

Get it

Download and open the disk image.

- OpenPromptr-v1.2.0-macOS-arm64.dmg + OpenPromptr-v1.3.0-macOS-arm64.dmg — verify against its - published checksum + published checksum if you'd like — then drag OpenPromptr into Applications.

@@ -479,8 +479,8 @@

Project

  • Changelog
  • - Describes version 1.2.0. Page last - reviewed . + Describes version 1.3.0. Page last + reviewed .

    diff --git a/docs/self-assessment.md b/docs/self-assessment.md index 641bea0..bc8f5b8 100644 --- a/docs/self-assessment.md +++ b/docs/self-assessment.md @@ -2,11 +2,16 @@ Assessed against the [trsdn Repository Quality Standard](https://github.com/trsdn/.github/blob/main/docs/repository-quality-standard.md) -v1.11.1 on 2026-09-17. The machine-readable result is +v1.14.0 on 2026-09-20, by an AI agent reading the repository, the GitHub API and +the published v1.3.0 release. The machine-readable result is [`.github/conformance.yml`](../.github/conformance.yml); this document is the evidence for every criterion that isn't a clean pass. A clean pass isn't repeated here — see `standard.yml`'s catalog for what each ID means. +Overall state: **Needs work**. The only failing criterion is `P09`, which the +record cannot excuse and which is low impact; there is no critical or +high-priority gap. Nine criteria are partial. + ## Profiles claimed Baseline, Public, Software, Package And Release, Product Identity, Agent @@ -21,47 +26,71 @@ Not claimed: Documentation (the primary product is the app, not documentation or content), Deployable (a distributed desktop app, not a deployed service), Archived (the repository is active). +## Fail + +- **P09** — no repository-activity card exists. The standard now says a + repository with a runner and no card has not met the criterion (before 1.12 + this was recorded as partial on the grounds that it was unclear whether it + was expected). The card has to be regenerated by a scheduled workflow, which + needs a write-capable token; `AGENTS.md` forbids adding a write permission to + a workflow here, so this is a decision to make, not just work to do. + ## Partial - **B05** — `AGENTS.md` and the README document the individual commands - (`swift build`, `swift test`, `swift format lint`, `./build-app.sh`), but - there's no single combined gate script. Running all of them is a few lines, - not one command. + (`swift build`, `swift test`, `swift format lint`, `./build-app.sh`, and + `npm test` for the plugin), but there's no single combined gate script. - **I03** — the app bundle's `NSHumanReadableCopyright` names the copyright holder, but no license identifier (e.g. "MIT") is embedded in the bundle itself, only in the repository's `LICENSE` file. - **L03** — the primary language is English in practice (`CONTRIBUTING.md` states it for contributions) and there is exactly one locale, but nothing states outright "this app is English-only, no localization is planned." -- **P08** — README badges (CI, License, Platform, Swift) exist but haven't - been diffed against the org's specific badge convention document. -- **P09** — no self-hosted, generated repository-activity visualization - exists; unclear whether this specific evidence is expected for a repo this - size, so recorded as partial rather than guessed at either way. -- **R01, R02** — the README states the current version and macOS - compatibility informally; there's no separate package manifest (this isn't - a distributed package) and no explicit written compatibility/versioning - policy beyond "tags are semver, see CHANGELOG." +- **P08** — the badge block is CI, License, Platform, Swift. The standard's + block is license, platform, CI, latest release, conformance, in that order: + the order differs, the release and conformance badges are missing (the + conformance badge now exists under `.github/badges/`), and the Swift badge has + no stated reason. +- **R01** — SwiftPM has no manifest fields for description, licence or + repository URL, so under 1.13 they belong in the app's `Info.plist`. The + plist carries the name, version and copyright, but not the licence + identifier (see `I03`), and no document says which property lives where. +- **R02** — the README states the current version and macOS compatibility + informally; there's no written versioning and compatibility policy beyond + "tags are semver, see CHANGELOG." +- **R05** — the standard now asks for a *recorded* smoke test of the published + file. On 2026-09-20 the published `OpenPromptr-v1.3.0-macOS-arm64.dmg` was + mounted: `spctl` accepts the app as Notarized Developer ID, `codesign + --verify --deep --strict` passes, and the binary launches and prints + `OpenPromptr 1.3.0 (64)`. The core function (capturing a source and showing + it on a target display) was **not** exercised: that needs a display and a + Screen Recording grant, and nobody has run 1.3.0 that way. A + maintainer installed 1.2.0 on their own machine, but no record says which + version was tested when, or what was exercised. +- **S03** — Swift is checked (`swift format lint --strict` in CI), but the + plugin's JavaScript under `Tools/` has no formatter or linter. +- **S04** — CI builds and tests the app on macOS. The plugin's tests + (`npm test` in `Tools/openpromptr-streamdeck`, Node 20+) exist and pass + locally but no workflow runs them, so a supported runtime is not covered by CI. ## Resolved since the last pass -- **R03–R08** — `v1.2.0` (2026-09-17) is a real, signed, notarized release: - `trsdn/macos-notarization-broker`'s `openpromptr` profile now builds the - correct source (#49, #50) and produces `OpenPromptr-v{version}-macOS-arm64.{zip,dmg}` - plus the AppUpdater-required `OpenPromptr-{version}.dmg` copy (R03, R04). - The broker's own preflight/`validate_app_tree` smoke-tests the bundle - before signing (R05). Release notes come from the CHANGELOG entry (R06). - As of `scripts/request.sh` on the broker (PR #53), this is enforced, not - just practiced: `--publish` fetches `CHANGELOG.md` at the tag through the - API and fails the release outright if the entry for that version is - missing, empty, or still sitting under `## Unreleased` (R07 — the trsdn - standard's decision 0010 gate, mandatory for every profile on that - broker, not just this one). `provenance.json` is uploaded as a release - asset, recording the source commit, tag, and signing identity, so a - consumer can verify where the artifact came from (R08). Verified live: - `xcrun stapler validate` and `spctl --assess` both accept the published - DMG as "Notarized Developer ID", and the R07 gate was tested against - `v1.2.0`'s real `CHANGELOG.md` entry before merging. +- **R03–R08** — `v1.2.0` and `v1.3.0` are real, signed, notarized releases + produced by `trsdn/macos-notarization-broker`'s `openpromptr` profile, started + for the tag with the command in `RELEASE_CHECKLIST.md` (R03). Tag, version and + release title agree (R04). Release notes are the CHANGELOG entry for the + version, and the broker refuses to publish without it (R06, R07). + `provenance.json` is a release asset, and `stapler validate` and `spctl` both + accept the DMG (R08). `R05` moved to partial only because 1.14.0 asks for more + than the broker's own preflight (see above). +- **W03** — the site still described v1.2.0 after v1.3.0 was published. Fixed in + the same change as this assessment (download links, version badge, "describes + version" line). +- **G02** — `AGENTS.md`'s layout and command list did not mention + `Tools/openpromptr-streamdeck` or its `npm test`. Fixed in the same change. +- **W05, W06** — retired in standard 1.12.0 and recorded `na`. **W09** (a site + designed for the project) passes: `docs/index.html` is a bespoke page, see + [decision 0013](https://github.com/trsdn/.github/blob/main/docs/decisions/0013-sites-are-designed-not-templated.md). ## Not applicable @@ -82,12 +111,10 @@ Archived (the repository is active). ## Worth noting on otherwise-passing criteria - **X01** (keyboard operability) — verified by code review, not live - end-to-end testing: every interactive control in `ControlView.swift` is a + end-to-end testing: every interactive control in `ControlView.swift` and `SettingsView.swift` is a native SwiftUI `Button`/`Toggle`/`Picker`, which macOS gives standard Tab focus order and a focus ring for free, and there's no custom-drawn hit-testing that would bypass it. Not tested with an actual screen reader or a physical keyboard walkthrough. - **W01** — "repeatable, documented process" here is simply: GitHub Pages - serves `docs/` from `main` directly, no build step. That's the whole - process; there's nothing to document beyond what `docs/assets/VENDORED.md` - already says about updating the vendored assets. + serves `docs/` from `main` directly, no build step and no vendored assets. From 209138786d5039ebd12b7862c53cf73cc71c2c95 Mon Sep 17 00:00:00 2001 From: Torsten Mahr Date: Sun, 20 Sep 2026 21:40:01 +0200 Subject: [PATCH 2/3] docs: reassess against standard 1.15.0; add a smoke kit for published releases 1.15.0 asks for a smoke kit (R05), accepts SemVer as the versioning policy (R02), and grades stated-but-not-allowed and restated facts. Adds Scripts/smoke-published.sh, run against v1.3.0 (9/9), states SemVer and English-only in the README, and re-records the results: R05, R02, L03, B05 pass; S02 and B13 partial. Still Needs work because of P09. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EGjbE4ZXFZyXFd2GQ7xRAp --- .github/badges/conformance.svg | 6 +-- .github/conformance.yml | 14 +++--- AGENTS.md | 3 ++ README.md | 12 +++++ RELEASE_CHECKLIST.md | 23 +++++++++ Scripts/smoke-published.sh | 83 ++++++++++++++++++++++++++++++++ docs/self-assessment.md | 86 +++++++++++++++++++--------------- 7 files changed, 180 insertions(+), 47 deletions(-) create mode 100755 Scripts/smoke-published.sh diff --git a/.github/badges/conformance.svg b/.github/badges/conformance.svg index 9bdd92a..585d29e 100644 --- a/.github/badges/conformance.svg +++ b/.github/badges/conformance.svg @@ -1,10 +1,10 @@ - - trsdn standard: v1.14.0 - Needs work + + trsdn standard: v1.15.0 - Needs work trsdn standard - v1.14.0 - Needs work + v1.15.0 - Needs work diff --git a/.github/conformance.yml b/.github/conformance.yml index 4bd00e6..96a252f 100644 --- a/.github/conformance.yml +++ b/.github/conformance.yml @@ -4,7 +4,7 @@ # Criteria: https://github.com/trsdn/.github/blob/main/docs/repository-quality-standard.md # Evidence: docs/self-assessment.md (only for results that aren't a clean pass) -standard_version: "1.14.0" +standard_version: "1.15.0" assessed_on: "2026-09-20" state: "Needs work" evidence: "docs/self-assessment.md" @@ -14,7 +14,7 @@ criteria: B02: pass B03: pass B04: pass - B05: partial + B05: pass B06: pass B07: pass B08: pass @@ -22,7 +22,7 @@ criteria: B10: pass B11: pass B12: pass - B13: pass + B13: partial B14: na B15: pass B16: pass @@ -38,7 +38,7 @@ criteria: P10: pass P11: pass S01: pass - S02: pass + S02: partial S03: partial S04: partial S05: pass @@ -57,10 +57,10 @@ criteria: D05: na D06: na R01: partial - R02: partial + R02: pass R03: pass R04: pass - R05: partial + R05: pass R06: pass R07: pass R08: pass @@ -94,7 +94,7 @@ criteria: G08: na L01: pass L02: pass - L03: partial + L03: pass L04: na L05: na L06: na diff --git a/AGENTS.md b/AGENTS.md index c1aa970..4e2714d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,9 @@ swift format lint --strict --recursive Sources Tests Package.swift # Build, bundle, and sign -> dist/OpenPromptr.app ./build-app.sh + +# Smoke-test a published release (downloads it; no operator needed) +Scripts/smoke-published.sh v ``` There is no Xcode project. Everything goes through SwiftPM; the app bundle is diff --git a/README.md b/README.md index e184e2f..53dc9f3 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,18 @@ the invisible virtual display. - Xcode or Command Line Tools with Swift 6 - Screen Recording permission for the built app bundle +## Versioning and language + +Releases are tagged `vMAJOR.MINOR.PATCH` and follow +[Semantic Versioning](https://semver.org/spec/v2.0.0.html): a patch release only +fixes bugs, a minor release adds behavior without breaking what exists, and a +major release may change or remove it. That covers the app, its local HTTP API +and the Stream Deck plugin. Every version's changes are in +[`CHANGELOG.md`](CHANGELOG.md). The app supports macOS 13 and later. + +The app's interface is **English only**; there are no localizations and none are +planned. + ## Building and testing ```bash diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index 997a3ab..c34eda8 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -87,6 +87,29 @@ shasum -a 256 -c OpenPromptr-v-macOS-arm64.dmg.sha256 Then mount it, drag the app to `/Applications`, and confirm on a machine that has never run it that it starts without a Gatekeeper warning. +## Smoke test of the published release + +`Scripts/smoke-published.sh` is the smoke kit. It needs nobody at the machine: + +```bash +Scripts/smoke-published.sh v +``` + +It downloads the published DMGs, zip and checksums, and checks the checksums, +that the AppUpdater copy is the same file, that the notarization ticket is +stapled, that Gatekeeper accepts the DMG and the app, that the signature +verifies, and that the app launches and reports the version the tag names. Exit +status 0 is a pass. It does **not** capture a source or draw on a display; that +needs a display and a Screen Recording grant, so it stays the manual check in +step 4 above. + +Run it after every release and add a row. A record stands for later releases +until one changes how the app is built, signed or packaged. + +| Version | Date | Result | Run by | +| --- | --- | --- | --- | +| v1.3.0 | 2026-09-20 | pass — 9 of 9 checks; app reports `OpenPromptr 1.3.0 (64)` | AI agent | + ## Testing the updater Existing installs of the pre-rename *Teleprompter Mirror* have no updater at diff --git a/Scripts/smoke-published.sh b/Scripts/smoke-published.sh new file mode 100755 index 0000000..0645e6d --- /dev/null +++ b/Scripts/smoke-published.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# +# Smoke kit: checks a published OpenPromptr release as a consumer receives it, +# with nobody operating the app. +# +# Scripts/smoke-published.sh v1.3.0 +# +# Downloads the release's DMGs and checksums from GitHub, then verifies that the +# published files are intact, notarized and signed, that the copy AppUpdater +# fetches is the same file, and that the app inside launches and reports the +# version the tag names. Exit status 0 means every check passed. +# +# What it does NOT do: capture a source or draw on a display. That needs a +# display and a Screen Recording grant, i.e. someone at the machine. + +set -euo pipefail + +tag="${1:-}" +[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { + echo "Usage: $0 vX.Y.Z" >&2 + exit 2 +} +version="${tag#v}" +repo="${OPENPROMPTR_REPO:-trsdn/OpenPromptr}" +dmg="OpenPromptr-v${version}-macOS-arm64.dmg" +updater_dmg="OpenPromptr-${version}.dmg" +zip="OpenPromptr-v${version}-macOS-arm64.zip" + +work="$(mktemp -d)" +mount_point="$work/mnt" +cleanup() { + hdiutil detach -quiet "$mount_point" 2>/dev/null || true + rm -rf "$work" +} +trap cleanup EXIT + +pass() { echo " ok $1"; } +fail() { echo " FAIL $1" >&2; exit 1; } + +echo "Smoke test of $repo $tag" + +gh release download "$tag" -R "$repo" -D "$work" \ + -p "$dmg" -p "$dmg.sha256" -p "$updater_dmg" -p "$updater_dmg.sha256" \ + -p "$zip" -p "$zip.sha256" >/dev/null || fail "download of the release assets" +pass "release assets downloaded" + +(cd "$work" && shasum -a 256 -c "$dmg.sha256" "$zip.sha256" "$updater_dmg.sha256" >/dev/null) \ + || fail "published checksums" +pass "checksums match" + +cmp -s "$work/$dmg" "$work/$updater_dmg" || fail "the AppUpdater copy differs from the DMG" +pass "the AppUpdater copy is the same file as the DMG" + +xcrun stapler validate "$work/$dmg" >/dev/null 2>&1 || fail "notarization ticket is not stapled" +pass "notarization ticket stapled" + +spctl --assess --type open --context context:primary-signature "$work/$dmg" 2>/dev/null \ + || fail "Gatekeeper does not accept the DMG" +pass "Gatekeeper accepts the DMG" + +mkdir -p "$mount_point" +hdiutil attach -nobrowse -readonly -mountpoint "$mount_point" "$work/$dmg" >/dev/null \ + || fail "the DMG does not mount" +app="$mount_point/OpenPromptr.app" +[[ -d "$app" ]] || fail "OpenPromptr.app is not in the DMG" +pass "DMG mounts and holds OpenPromptr.app" + +codesign --verify --deep --strict "$app" 2>/dev/null || fail "code signature" +spctl --assess --type execute "$app" 2>/dev/null || fail "Gatekeeper does not accept the app" +pass "app signature valid and accepted by Gatekeeper" + +plist="$app/Contents/Info.plist" +[[ "$(plutil -extract CFBundleIdentifier raw "$plist")" == "com.github.trsdn.OpenPromptr" ]] \ + || fail "bundle identifier" +[[ "$(plutil -extract CFBundleShortVersionString raw "$plist")" == "$version" ]] \ + || fail "CFBundleShortVersionString is not $version" +pass "bundle identifier and version $version in Info.plist" + +reported="$("$app/Contents/MacOS/OpenPromptr" --version)" || fail "the app does not launch" +[[ "$reported" == "OpenPromptr $version "* ]] || fail "--version reported \"$reported\"" +pass "app launches and reports: $reported" + +echo "All checks passed for $tag (core function not exercised; see the header)." diff --git a/docs/self-assessment.md b/docs/self-assessment.md index bc8f5b8..7ea90ae 100644 --- a/docs/self-assessment.md +++ b/docs/self-assessment.md @@ -2,15 +2,15 @@ Assessed against the [trsdn Repository Quality Standard](https://github.com/trsdn/.github/blob/main/docs/repository-quality-standard.md) -v1.14.0 on 2026-09-20, by an AI agent reading the repository, the GitHub API and +v1.15.0 on 2026-09-20, by an AI agent reading the repository, the GitHub API and the published v1.3.0 release. The machine-readable result is [`.github/conformance.yml`](../.github/conformance.yml); this document is the evidence for every criterion that isn't a clean pass. A clean pass isn't repeated here — see `standard.yml`'s catalog for what each ID means. -Overall state: **Needs work**. The only failing criterion is `P09`, which the -record cannot excuse and which is low impact; there is no critical or -high-priority gap. Nine criteria are partial. +Overall state: **Needs work**. The only failing criterion is `P09`, which +`scripts/conformance.py` will not let a `Fail` sit under `Healthy`. It is low +impact; there is no critical criterion failing. Seven criteria are partial. ## Profiles claimed @@ -28,50 +28,48 @@ Archived (the repository is active). ## Fail -- **P09** — no repository-activity card exists. The standard now says a - repository with a runner and no card has not met the criterion (before 1.12 - this was recorded as partial on the grounds that it was unclear whether it - was expected). The card has to be regenerated by a scheduled workflow, which - needs a write-capable token; `AGENTS.md` forbids adding a write permission to - a workflow here, so this is a decision to make, not just work to do. +- **P09** — no repository-activity card exists, and the repository has a runner. + Since 1.15.0 the card may be published to a dedicated branch (e.g. `stats`) by + a scheduled workflow, which needs a write-capable token for that job. + `AGENTS.md` forbids adding a write permission to any workflow here, so this is + a decision to make: either allow that one job, or accept the `Fail`. ## Partial -- **B05** — `AGENTS.md` and the README document the individual commands - (`swift build`, `swift test`, `swift format lint`, `./build-app.sh`, and - `npm test` for the plugin), but there's no single combined gate script. +- **B13** — the build commands (`swift build`, `swift test`, `./build-app.sh`) + are restated by hand in `README.md`, `CONTRIBUTING.md` and `AGENTS.md`. The + copies agree today, which is `Partial` under the standard, and none links to a + single home. - **I03** — the app bundle's `NSHumanReadableCopyright` names the copyright holder, but no license identifier (e.g. "MIT") is embedded in the bundle - itself, only in the repository's `LICENSE` file. -- **L03** — the primary language is English in practice (`CONTRIBUTING.md` - states it for contributions) and there is exactly one locale, but nothing - states outright "this app is English-only, no localization is planned." + itself, only in the repository's `LICENSE` file. Criterion has two parts; one + is met. - **P08** — the badge block is CI, License, Platform, Swift. The standard's block is license, platform, CI, latest release, conformance, in that order: the order differs, the release and conformance badges are missing (the conformance badge now exists under `.github/badges/`), and the Swift badge has no stated reason. - **R01** — SwiftPM has no manifest fields for description, licence or - repository URL, so under 1.13 they belong in the app's `Info.plist`. The - plist carries the name, version and copyright, but not the licence - identifier (see `I03`), and no document says which property lives where. -- **R02** — the README states the current version and macOS compatibility - informally; there's no written versioning and compatibility policy beyond - "tags are semver, see CHANGELOG." -- **R05** — the standard now asks for a *recorded* smoke test of the published - file. On 2026-09-20 the published `OpenPromptr-v1.3.0-macOS-arm64.dmg` was - mounted: `spctl` accepts the app as Notarized Developer ID, `codesign - --verify --deep --strict` passes, and the binary launches and prints - `OpenPromptr 1.3.0 (64)`. The core function (capturing a source and showing - it on a target display) was **not** exercised: that needs a display and a - Screen Recording grant, and nobody has run 1.3.0 that way. A - maintainer installed 1.2.0 on their own machine, but no record says which - version was tested when, or what was exercised. -- **S03** — Swift is checked (`swift format lint --strict` in CI), but the - plugin's JavaScript under `Tools/` has no formatter or linter. -- **S04** — CI builds and tests the app on macOS. The plugin's tests - (`npm test` in `Tools/openpromptr-streamdeck`, Node 20+) exist and pass - locally but no workflow runs them, so a supported runtime is not covered by CI. + repository URL, so they belong in the app's `Info.plist`. The plist carries + the name, version, copyright, repository URL (`OPRRepositoryURL`) and issue + tracker URL, but no description and no licence identifier, and no document + says which property lives where. +- **S02** — reading applied: the main entry point of a graphical application is + the logic behind its action, reached without its views. The 45 tests + (`OpenPromptrCoreTests`) cover the pure logic — aspect fit, capture sizing, + the recovery policy, display identity matching, the local API's parsing and + token check — including failure paths (rejected tokens, invalid settings, + exhausted retries). They do not cover the capture pipeline, `AppModel` or the + virtual display, which need a real display and a Screen Recording grant. + `AGENTS.md` says so. A suite that covers a supporting part of the action is a + `Partial`. +- **S03** — Swift is checked (`swift format lint --strict` in CI, and compiling + is the type check), but the plugin's JavaScript under `Tools/` has no + formatter or linter. +- **S04** — the app's claim (macOS 13 or later) is covered by CI on the runner's + macOS. The plugin claims Node 20 or newer (README, manifest); its tests + (`npm test` in `Tools/openpromptr-streamdeck`) pass locally but no workflow + runs them, so a claimed runtime has no job. ## Resolved since the last pass @@ -83,6 +81,20 @@ Archived (the repository is active). `provenance.json` is a release asset, and `stapler validate` and `spctl` both accept the DMG (R08). `R05` moved to partial only because 1.14.0 asks for more than the broker's own preflight (see above). +- **R05** — 1.15.0 asks for a smoke kit, a documented command that checks the + published artifact without an operator, run and recorded. `Scripts/smoke-published.sh` + is that kit and is documented in `RELEASE_CHECKLIST.md`; it was run against + v1.3.0 on 2026-09-20 (9 of 9 checks: checksums, updater copy identical, + notarization stapled, Gatekeeper accepts DMG and app, signature verifies, + bundle identifier and version, app launches and reports `OpenPromptr 1.3.0 + (64)`), and the result is in the log there. The core function is not + exercised, and the standard no longer requires it. +- **R02** — the README now states the SemVer scheme and what a consumer can rely + on across versions. +- **L03** — the README now declares the interface English-only, with no + localizations planned. +- **B05** — the commands are documented in `AGENTS.md`, and the latest run of + them on `main` (CI, 2026-09-20) is green; 1.15.0 counts that as run. - **W03** — the site still described v1.2.0 after v1.3.0 was published. Fixed in the same change as this assessment (download links, version badge, "describes version" line). From f9be3374b6c124da8324d8565a27b5e2b480f3d3 Mon Sep 17 00:00:00 2001 From: Torsten Mahr Date: Sun, 20 Sep 2026 21:46:49 +0200 Subject: [PATCH 3/3] Fix the assessment's gaps: stats card, plugin CI, single homes, embedded metadata - stats.yml: daily card to the stats branch (P09); AGENTS.md now allows a write permission for that one workflow only. - CI: Stream Deck plugin job (npm test incl. a dependency-free style check) and a check that the README platform badge matches Package.swift (S03, S04, P08). - README badges in the standard's order; commands and requirements have one home and CONTRIBUTING links to them (B13, P08). - Info.plist embeds the licence identifier and product description; the checklist states where each metadata property lives (I03, R01). - Record: Healthy, P09 and S02 partial. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EGjbE4ZXFZyXFd2GQ7xRAp --- .github/badges/conformance.svg | 10 +- .github/conformance.yml | 16 ++-- .github/workflows/ci.yml | 16 ++++ .github/workflows/stats.yml | 29 ++++++ AGENTS.md | 22 +++-- CHANGELOG.md | 7 ++ CONTRIBUTING.md | 35 ++----- Config/Info.plist | 4 + README.md | 30 +++--- RELEASE_CHECKLIST.md | 16 ++++ Tools/openpromptr-streamdeck/package.json | 2 +- Tools/openpromptr-streamdeck/test/smoke.js | 12 ++- Tools/openpromptr-streamdeck/test/style.js | 104 +++++++++++++++++++++ docs/self-assessment.md | 69 +++++++------- 14 files changed, 269 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/stats.yml create mode 100644 Tools/openpromptr-streamdeck/test/style.js diff --git a/.github/badges/conformance.svg b/.github/badges/conformance.svg index 585d29e..374f759 100644 --- a/.github/badges/conformance.svg +++ b/.github/badges/conformance.svg @@ -1,10 +1,10 @@ - - trsdn standard: v1.15.0 - Needs work + + trsdn standard: v1.15.0 - Healthy - - + + trsdn standard - v1.15.0 - Needs work + v1.15.0 - Healthy diff --git a/.github/conformance.yml b/.github/conformance.yml index 96a252f..e726b0c 100644 --- a/.github/conformance.yml +++ b/.github/conformance.yml @@ -6,7 +6,7 @@ standard_version: "1.15.0" assessed_on: "2026-09-20" -state: "Needs work" +state: "Healthy" evidence: "docs/self-assessment.md" criteria: @@ -22,7 +22,7 @@ criteria: B10: pass B11: pass B12: pass - B13: partial + B13: pass B14: na B15: pass B16: pass @@ -33,14 +33,14 @@ criteria: P05: pass P06: pass P07: pass - P08: partial - P09: fail + P08: pass + P09: partial P10: pass P11: pass S01: pass S02: partial - S03: partial - S04: partial + S03: pass + S04: pass S05: pass S06: pass S07: pass @@ -56,7 +56,7 @@ criteria: D04: na D05: na D06: na - R01: partial + R01: pass R02: pass R03: pass R04: pass @@ -66,7 +66,7 @@ criteria: R08: pass I01: pass I02: pass - I03: partial + I03: pass I04: pass I05: pass I06: pass diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9260c9..422d103 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,22 @@ jobs: run: swift test - name: Lint formatting run: swift format lint --strict --recursive Sources Tests Package.swift + - name: Check the README platform badge against Package.swift + run: | + v="$(sed -n 's/.*\.macOS(\.v\([0-9]*\)).*/\1/p' Package.swift)" + test -n "$v" + grep -q "Platform-macOS%20${v}%2B" README.md + + plugin: + name: Stream Deck plugin + runs-on: macos-15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Node version + run: node --version + - name: Format, static checks and tests + working-directory: Tools/openpromptr-streamdeck + run: npm test bundle: name: App bundle diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml new file mode 100644 index 0000000..3e21b6d --- /dev/null +++ b/.github/workflows/stats.yml @@ -0,0 +1,29 @@ +name: Repository stats + +# Renders the repository statistics card (standard criterion P09) and commits it +# to the generated `stats` branch, because `main` is protected. This is the only +# workflow here that may write, and it never runs for a pull request. + +on: + workflow_dispatch: + schedule: + - cron: "23 5 * * *" + push: + branches: + - main + paths: + - .github/workflows/stats.yml + +permissions: {} + +jobs: + stats: + permissions: + contents: write + uses: trsdn/.github/.github/workflows/repo-stats.yml@38969dfb6575240ff1c7096672e503022aa0910a # main + with: + output-dir: .github/stats + theme: both + cards: repo-card + branch: stats + commit-message: "chore(stats): update repository stats" diff --git a/AGENTS.md b/AGENTS.md index 4e2714d..9154f7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ swift build # Unit tests (swift-testing: @Test / #expect, no XCTest) swift test -# Stream Deck plugin tests (Node 20+; not run in CI) +# Stream Deck plugin: format, static checks and tests (Node 20+; run in CI too) (cd Tools/openpromptr-streamdeck && npm test) # Formatting check (must be clean; CI enforces this) @@ -34,12 +34,8 @@ assembled manually in `build-app.sh`. (`git fetch --tags` / a non-shallow checkout) to produce a meaningful version; outside a git checkout it falls back to whatever is in `Config/Info.plist`. -Optional runtime self-test, once the built app already holds Screen Recording -permission: - -```bash -open "dist/OpenPromptr.app" --args --self-test -``` +The optional runtime self-test is described in the +[README](README.md#optional-runtime-self-test). `--version` prints the version/build and exits — no window is created. @@ -51,9 +47,15 @@ open "dist/OpenPromptr.app" --args --self-test Connect key to this repository**, in any form. Distributable, notarized builds go through `trsdn/macos-notarization-broker` specifically so this never has to happen. -- **Add a secret or a write permission to any workflow here.** `ci.yml` runs - with `contents: read` and no secrets, which is what makes it safe to run - against any pull request, including from a fork. +- **Add a secret to any workflow, or a write permission to any workflow other + than `stats.yml`.** `ci.yml` runs with `contents: read` and no secrets, which + is what makes it safe to run against any pull request, including from a + fork. `stats.yml` is the one exception: it renders the repository statistics + card (criterion `P09`) and may hold `contents: write`, declared on its job + only, because it commits to the generated `stats` branch. It must stay that + narrow: no secrets, no `pull_request` trigger, only `schedule`, + `workflow_dispatch` and a `push` to `main` of its own file, and it writes + only the `stats` branch, never `main`. - **Rewrite published history.** No `git rebase`, `commit --amend`, or `push --force` against `main`. A ruleset blocks force pushes and deletion of `main`; branches with an open pull request are on trust. diff --git a/CHANGELOG.md b/CHANGELOG.md index eba1fb0..099d10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- The app bundle now embeds its licence identifier and product description in + `Info.plist`. +- A repository statistics card, generated daily to the `stats` branch, in the + README. + ## [1.3.0] - 2026-09-20 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d258b19..4723b81 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,42 +6,25 @@ section before making larger changes. ## Requirements -- macOS 13 or newer (developed and tested on current versions) -- Xcode Command Line Tools with Swift 6.1 or newer - (`swift-tools-version: 6.1`, see `Package.swift`) +- The platform and toolchain requirements in the + [README](README.md#requirements) - For signed builds: a "Developer ID Application" or "Apple Development" certificate in the keychain -There are no third-party dependencies. `swift build` is enough. +The only third-party dependencies are `AppUpdater` and `Swifter`, resolved by +SwiftPM; `swift build` is enough. ## Development workflow -```bash -swift build # build -swift test # run unit tests -./build-app.sh # create signed .app bundle in dist/ -``` +The build, test, format and bundle commands are in +[`AGENTS.md`](AGENTS.md#build--validate), which is their one home. -The app icon is generated from code and is present in the repository as -`Resources/AppIcon.icns`. Regenerate it after changes to -`Scripts/make-icon.swift`: - -```bash -swift Scripts/make-icon.swift -``` - -There is a self-test for a smoke test without a real target display: - -```bash -open "dist/OpenPromptr.app" --args --self-test -``` - -It reports `SELF_TEST_PASS` when capture setup and output work. +The app icon and the runtime self-test are described in the +[README](README.md#app-icon) and [its self-test section](README.md#optional-runtime-self-test). ## Before the pull request -- `swift build` completes without warnings. -- `swift test` is green. +- The validation commands in [`AGENTS.md`](AGENTS.md#build--validate) pass. - The change was checked manually with at least one source. - Behavior changes are described in `README.md`. diff --git a/Config/Info.plist b/Config/Info.plist index 5d771dc..dfa9e81 100644 --- a/Config/Info.plist +++ b/Config/Info.plist @@ -41,5 +41,9 @@ https://github.com/trsdn/OpenPromptr/issues OPRRepositoryURL https://github.com/trsdn/OpenPromptr + OPRLicenseIdentifier + MIT + OPRProductDescription + Native macOS app that mirrors and rotates a virtual display, a physical display, or a single window onto a teleprompter screen. diff --git a/README.md b/README.md index 53dc9f3..7698da6 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@ # OpenPromptr -[![CI](https://github.com/trsdn/OpenPromptr/actions/workflows/ci.yml/badge.svg)](https://github.com/trsdn/OpenPromptr/actions/workflows/ci.yml) -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![License: MIT](https://img.shields.io/github/license/trsdn/OpenPromptr)](LICENSE) [![Platform: macOS 13+](https://img.shields.io/badge/Platform-macOS%2013%2B-lightgrey.svg)](#requirements) -[![Swift 6.1](https://img.shields.io/badge/Swift-6.1-orange.svg)](Package.swift) +[![CI](https://github.com/trsdn/OpenPromptr/actions/workflows/ci.yml/badge.svg)](https://github.com/trsdn/OpenPromptr/actions/workflows/ci.yml) +[![Latest release](https://img.shields.io/github/v/release/trsdn/OpenPromptr)](https://github.com/trsdn/OpenPromptr/releases/latest) +[![Conformance](.github/badges/conformance.svg)](.github/conformance.yml)
    @@ -99,21 +100,16 @@ Releases are tagged `vMAJOR.MINOR.PATCH` and follow fixes bugs, a minor release adds behavior without breaking what exists, and a major release may change or remove it. That covers the app, its local HTTP API and the Stream Deck plugin. Every version's changes are in -[`CHANGELOG.md`](CHANGELOG.md). The app supports macOS 13 and later. +[`CHANGELOG.md`](CHANGELOG.md). The app's interface is **English only**; there are no localizations and none are planned. ## Building and testing -```bash -git clone https://github.com/trsdn/OpenPromptr.git -cd OpenPromptr -swift test -./build-app.sh -``` - -The script creates `dist/OpenPromptr.app`. It automatically prefers an +Clone the repository, then build and validate with the commands in +[`AGENTS.md`](AGENTS.md#build--validate) — that is their one home. +`./build-app.sh` creates `dist/OpenPromptr.app`. It automatically prefers an available identity of type **Developer ID Application**, falls back to **Apple Development**, and only falls back to an ad-hoc signature with a warning if no stable identity is available. There is no hard-coded team or certificate @@ -390,6 +386,16 @@ now also carries an icon. This has not been tested end-to-end with VoiceOver. Automatic-recovery and status changes are conveyed visually and in text only; there are no sound cues. +## Repository statistics + + + + Repository statistics + + +Generated daily by [`stats.yml`](.github/workflows/stats.yml) and committed to +the `stats` branch, because `main` is protected. + ## Contributing Contributions are welcome. The development workflow, language and commit diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index c34eda8..4871c0c 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -23,6 +23,22 @@ layout, architecture, entitlements, or minimum macOS version — is not a local decision. It requires a reviewed pull request against the broker's `profiles/apps.json`, and the release fails until that lands. +## Where package metadata lives + +SwiftPM's `Package.swift` has no fields for a description, a licence or a +repository URL, so those live in the file the app itself carries, +`Config/Info.plist`, and the GitHub repository settings mirror them: + +| Property | Home | Mirrored in | +| --- | --- | --- | +| Name | `CFBundleName`, `CFBundleDisplayName` | repository name | +| Version | the git tag, stamped into `CFBundleShortVersionString` and `CFBundleVersion` by the build | release title | +| Description | `OPRProductDescription` | GitHub repository description | +| Licence | `OPRLicenseIdentifier` (and `NSHumanReadableCopyright` for the holder) | `LICENSE`, GitHub licence | +| Repository, issue tracker | `OPRRepositoryURL`, `OPRIssueTrackerURL` | GitHub | + +When one changes, change the others in the same pull request. + ## Per release 1. Update `CHANGELOG.md`: move entries out of *Unreleased* into a new diff --git a/Tools/openpromptr-streamdeck/package.json b/Tools/openpromptr-streamdeck/package.json index 8958c65..377d1f2 100644 --- a/Tools/openpromptr-streamdeck/package.json +++ b/Tools/openpromptr-streamdeck/package.json @@ -5,7 +5,7 @@ "description": "A plugin for OpenDeck and the Elgato Stream Deck, so hardware keys can start and stop OpenPromptr's output and change its rotation and mirroring.", "type": "module", "scripts": { - "test": "node test/manifest.js && node test/deck-socket.js && node test/logic.js && node test/client.js && node test/smoke.js", + "test": "node test/style.js && node test/manifest.js && node test/deck-socket.js && node test/logic.js && node test/client.js && node test/smoke.js", "install-plugin": "./install.sh" }, "license": "MIT" diff --git a/Tools/openpromptr-streamdeck/test/smoke.js b/Tools/openpromptr-streamdeck/test/smoke.js index 9373c72..3186ac0 100644 --- a/Tools/openpromptr-streamdeck/test/smoke.js +++ b/Tools/openpromptr-streamdeck/test/smoke.js @@ -106,8 +106,10 @@ check("the plugin registers with the deck app", async () => { }); check("a key that appears is painted from the app's state, with a custom label", async () => { - tell({ event: "willAppear", context: "out", action: "com.trsdn.openpromptr.output", payload: { settings: { title: "Prompter" } } }); - tell({ event: "willAppear", context: "rot", action: "com.trsdn.openpromptr.rotation", payload: { settings: {} } }); + const output = "com.trsdn.openpromptr.output"; + const rotation = "com.trsdn.openpromptr.rotation"; + tell({ event: "willAppear", context: "out", action: output, payload: { settings: { title: "Prompter" } } }); + tell({ event: "willAppear", context: "rot", action: rotation, payload: { settings: {} } }); await until(() => last("setTitle", "out") && last("setTitle", "rot"), "first paint"); assert.equal(last("setTitle", "out").payload.title, "Prompter"); assert.equal(last("setState", "out").payload.state, 0); @@ -121,9 +123,11 @@ check("a change in the app repaints the key", async () => { check("a press becomes the matching request", async () => { requests.length = 0; - tell({ event: "keyDown", context: "out", action: "com.trsdn.openpromptr.output", payload: { settings: { title: "Prompter" } } }); + const output = "com.trsdn.openpromptr.output"; + tell({ event: "keyDown", context: "out", action: output, payload: { settings: { title: "Prompter" } } }); await until(() => requests.includes("POST /v1/output/stop"), "the stop request"); - tell({ event: "keyDown", context: "rot", action: "com.trsdn.openpromptr.rotation", payload: { settings: {} } }); + const rotation = "com.trsdn.openpromptr.rotation"; + tell({ event: "keyDown", context: "rot", action: rotation, payload: { settings: {} } }); await until(() => requests.filter((r) => r === "POST /v1/transform").length === 1, "the transform request"); }); diff --git a/Tools/openpromptr-streamdeck/test/style.js b/Tools/openpromptr-streamdeck/test/style.js new file mode 100644 index 0000000..982eb81 --- /dev/null +++ b/Tools/openpromptr-streamdeck/test/style.js @@ -0,0 +1,104 @@ +#!/usr/bin/env node +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +/** + * The plugin's format and static checks, without a linter to install. + * + * The plugin has no dependencies and no build step on purpose, so a check that + * needs `npm install` would be the only thing that did. This is the small part + * of one that has caught real problems here: source that does not parse, tabs, + * trailing whitespace, a missing final newline, and lines too long to read in a + * diff. + */ + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); +const MAX_LINE = 120; + +function files(dir) { + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + if (entry.name === "node_modules" || entry.name.startsWith(".")) return []; + const full = path.join(dir, entry.name); + return entry.isDirectory() ? files(full) : [full]; + }); +} + +const sources = files(root).filter((file) => /\.(js|json|html|css|sh)$/.test(file)); +const scripts = sources.filter((file) => file.endsWith(".js")); + +const checks = []; +const check = (name, body) => checks.push([name, body]); +const relative = (file) => path.relative(root, file); + +check("every script parses", () => { + for (const file of scripts) { + const result = spawnSync(process.execPath, ["--check", file], { encoding: "utf8" }); + assert.equal(result.status, 0, `${relative(file)} does not parse:\n${result.stderr}`); + } +}); + +check("no tabs and no trailing whitespace", () => { + for (const file of sources) { + fs.readFileSync(file, "utf8") + .split("\n") + .forEach((line, index) => { + assert.ok(!line.includes("\t"), `${relative(file)}:${index + 1} has a tab`); + assert.ok(!/\s$/.test(line), `${relative(file)}:${index + 1} has trailing whitespace`); + }); + } +}); + +check("every file ends with exactly one newline", () => { + for (const file of sources) { + const text = fs.readFileSync(file, "utf8"); + const single = text.endsWith("\n") && !text.endsWith("\n\n"); + assert.ok(single, `${relative(file)} does not end in one newline`); + } +}); + +check(`no script line is longer than ${MAX_LINE} characters`, () => { + for (const file of scripts) { + fs.readFileSync(file, "utf8") + .split("\n") + .forEach((line, index) => { + assert.ok(line.length <= MAX_LINE, `${relative(file)}:${index + 1} is ${line.length} characters`); + }); + } +}); + +check("scripts are modules that import only what they use", () => { + // A cheap stand-in for a no-unused-imports rule: a named import that never + // appears again in the file is dead weight and usually a leftover. + for (const file of scripts) { + const text = fs.readFileSync(file, "utf8"); + for (const match of text.matchAll(/^import\s+(?:(\w+)|\{([^}]+)\})\s+from/gm)) { + const listed = match[2]?.split(",").map((name) => name.trim().split(/\s+as\s+/).pop()); + const names = match[1] ? [match[1]] : listed; + for (const name of names.filter(Boolean)) { + const uses = text.match(new RegExp(`\\b${name}\\b`, "g")).length; + assert.ok(uses > 1, `${relative(file)} imports ${name} and never uses it`); + } + } + } +}); + +const failures = []; +for (const [name, body] of checks) { + try { + body(); + console.log(` ok ${name}`); + } catch (error) { + failures.push(name); + console.log(` FAIL ${name}\n ${error.message}`); + } +} +console.log( + failures.length + ? `\n${failures.length} of ${checks.length} checks failed` + : `\n${checks.length}/${checks.length} checks passed`, +); +process.exit(failures.length ? 1 : 0); diff --git a/docs/self-assessment.md b/docs/self-assessment.md index 7ea90ae..627ed19 100644 --- a/docs/self-assessment.md +++ b/docs/self-assessment.md @@ -8,9 +8,8 @@ the published v1.3.0 release. The machine-readable result is evidence for every criterion that isn't a clean pass. A clean pass isn't repeated here — see `standard.yml`'s catalog for what each ID means. -Overall state: **Needs work**. The only failing criterion is `P09`, which -`scripts/conformance.py` will not let a `Fail` sit under `Healthy`. It is low -impact; there is no critical criterion failing. Seven criteria are partial. +Overall state: **Healthy**: no criterion is `Fail`. Two are `Partial`, and both +are stated below. ## Profiles claimed @@ -26,34 +25,16 @@ Not claimed: Documentation (the primary product is the app, not documentation or content), Deployable (a distributed desktop app, not a deployed service), Archived (the repository is active). -## Fail - -- **P09** — no repository-activity card exists, and the repository has a runner. - Since 1.15.0 the card may be published to a dedicated branch (e.g. `stats`) by - a scheduled workflow, which needs a write-capable token for that job. - `AGENTS.md` forbids adding a write permission to any workflow here, so this is - a decision to make: either allow that one job, or accept the `Fail`. - ## Partial -- **B13** — the build commands (`swift build`, `swift test`, `./build-app.sh`) - are restated by hand in `README.md`, `CONTRIBUTING.md` and `AGENTS.md`. The - copies agree today, which is `Partial` under the standard, and none links to a - single home. -- **I03** — the app bundle's `NSHumanReadableCopyright` names the copyright - holder, but no license identifier (e.g. "MIT") is embedded in the bundle - itself, only in the repository's `LICENSE` file. Criterion has two parts; one - is met. -- **P08** — the badge block is CI, License, Platform, Swift. The standard's - block is license, platform, CI, latest release, conformance, in that order: - the order differs, the release and conformance badges are missing (the - conformance badge now exists under `.github/badges/`), and the Swift badge has - no stated reason. -- **R01** — SwiftPM has no manifest fields for description, licence or - repository URL, so they belong in the app's `Info.plist`. The plist carries - the name, version, copyright, repository URL (`OPRRepositoryURL`) and issue - tracker URL, but no description and no licence identifier, and no document - says which property lives where. +- **P09** — the card exists as a mechanism, not yet as a rendered image. + `.github/workflows/stats.yml` runs `trsdn/.github`'s reusable `repo-stats` + workflow (pinned to a commit) daily and on dispatch, writing the card to the + `stats` branch, and the README embeds it with a `` element. A + workflow can only be dispatched from the default branch, so the first run + happens after this change is merged; until then the README image does not + resolve. Becomes `Pass` once the run has produced `repo-card.svg` and + `repo-card-dark.svg` on `stats`. - **S02** — reading applied: the main entry point of a graphical application is the logic behind its action, reached without its views. The 45 tests (`OpenPromptrCoreTests`) cover the pure logic — aspect fit, capture sizing, @@ -62,14 +43,9 @@ Archived (the repository is active). exhausted retries). They do not cover the capture pipeline, `AppModel` or the virtual display, which need a real display and a Screen Recording grant. `AGENTS.md` says so. A suite that covers a supporting part of the action is a - `Partial`. -- **S03** — Swift is checked (`swift format lint --strict` in CI, and compiling - is the type check), but the plugin's JavaScript under `Tools/` has no - formatter or linter. -- **S04** — the app's claim (macOS 13 or later) is covered by CI on the runner's - macOS. The plugin claims Node 20 or newer (README, manifest); its tests - (`npm test` in `Tools/openpromptr-streamdeck`) pass locally but no workflow - runs them, so a claimed runtime has no job. + `Partial`. Closing this needs the capture path split so its decisions can run + without ScreenCaptureKit, which is a change to code that `swift test` cannot + verify end to end, so it was not attempted here. ## Resolved since the last pass @@ -95,6 +71,25 @@ Archived (the repository is active). localizations planned. - **B05** — the commands are documented in `AGENTS.md`, and the latest run of them on `main` (CI, 2026-09-20) is green; 1.15.0 counts that as run. +- **P08** — the badge block is now license, platform, CI, latest release, + conformance, in the standard's order. License and release come from GitHub + through shields.io, CI is GitHub's own badge, conformance is the committed + badge regenerated by `conformance.py`, and the platform badge is checked + against `Package.swift` by a CI step, so it cannot drift. The Swift badge was + dropped; the toolchain requirement lives in the README's Requirements. +- **S03, S04** — the Stream Deck plugin now has a CI job (`Stream Deck plugin`) + that runs `npm test`: a syntax check, a format check (no tabs, no trailing + whitespace, final newline, line length) and an unused-import check, written + without dependencies because the plugin has none, plus its functional tests. + Its claim of Node 20 or newer is covered by the runner's Node. +- **B13** — the build and validation commands, the self-test, the icon + generation and the platform requirements each have one home (`AGENTS.md`, + README) and `CONTRIBUTING.md` links to them. `CONTRIBUTING.md` had also + claimed there were no third-party dependencies, which stopped being true with + AppUpdater and Swifter. +- **I03, R01** — `Config/Info.plist` now embeds `OPRLicenseIdentifier` and + `OPRProductDescription`; `RELEASE_CHECKLIST.md` states which property lives + where and what mirrors it. - **W03** — the site still described v1.2.0 after v1.3.0 was published. Fixed in the same change as this assessment (download links, version badge, "describes version" line).