clauster 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. clauster-0.2.0/.bestpractices.json +136 -0
  2. clauster-0.2.0/.coderabbit.yaml +17 -0
  3. clauster-0.2.0/.dockerignore +29 -0
  4. clauster-0.2.0/.github/CODEOWNERS +9 -0
  5. clauster-0.2.0/.github/settings.yml +143 -0
  6. clauster-0.2.0/.github/workflows/ci.yml +167 -0
  7. clauster-0.2.0/.github/workflows/lint.yml +55 -0
  8. clauster-0.2.0/.github/workflows/pr-title.yml +41 -0
  9. clauster-0.2.0/.github/workflows/release-please.yml +60 -0
  10. clauster-0.2.0/.github/workflows/release.yml +125 -0
  11. clauster-0.2.0/.github/workflows/scorecard.yml +45 -0
  12. clauster-0.2.0/.github/workflows/security.yml +208 -0
  13. clauster-0.2.0/.gitignore +31 -0
  14. clauster-0.2.0/.markdownlint-cli2.yaml +48 -0
  15. clauster-0.2.0/.pre-commit-config.yaml +45 -0
  16. clauster-0.2.0/.release-please-manifest.json +3 -0
  17. clauster-0.2.0/.yamllint.yaml +33 -0
  18. clauster-0.2.0/CHANGELOG.md +60 -0
  19. clauster-0.2.0/CONTRIBUTING.md +46 -0
  20. clauster-0.2.0/Dockerfile +70 -0
  21. clauster-0.2.0/LICENSE +202 -0
  22. clauster-0.2.0/PKG-INFO +280 -0
  23. clauster-0.2.0/README.md +237 -0
  24. clauster-0.2.0/SECURITY.md +34 -0
  25. clauster-0.2.0/THIRD_PARTY_NOTICES.md +112 -0
  26. clauster-0.2.0/UPGRADING.md +53 -0
  27. clauster-0.2.0/clauster.spec +48 -0
  28. clauster-0.2.0/clauster.yml.example +75 -0
  29. clauster-0.2.0/codecov.yml +20 -0
  30. clauster-0.2.0/docker/entrypoint.sh +20 -0
  31. clauster-0.2.0/docs/screenshots/dashboard-dark.png +0 -0
  32. clauster-0.2.0/docs/screenshots/dashboard-light.png +0 -0
  33. clauster-0.2.0/docs/screenshots/login-dark.png +0 -0
  34. clauster-0.2.0/docs/screenshots/new-project-clone.png +0 -0
  35. clauster-0.2.0/package-lock.json +1348 -0
  36. clauster-0.2.0/package.json +13 -0
  37. clauster-0.2.0/pyproject.toml +135 -0
  38. clauster-0.2.0/release-please-config.json +35 -0
  39. clauster-0.2.0/renovate.json +94 -0
  40. clauster-0.2.0/scripts/build-binary.sh +17 -0
  41. clauster-0.2.0/scripts/lint-docs.sh +26 -0
  42. clauster-0.2.0/src/clauster/__init__.py +3 -0
  43. clauster-0.2.0/src/clauster/__main__.py +342 -0
  44. clauster-0.2.0/src/clauster/app.py +746 -0
  45. clauster-0.2.0/src/clauster/auth.py +311 -0
  46. clauster-0.2.0/src/clauster/bridge_log.py +80 -0
  47. clauster-0.2.0/src/clauster/claude_cli.py +36 -0
  48. clauster-0.2.0/src/clauster/claude_md.py +178 -0
  49. clauster-0.2.0/src/clauster/clone_jobs.py +121 -0
  50. clauster-0.2.0/src/clauster/config.py +313 -0
  51. clauster-0.2.0/src/clauster/discovery.py +75 -0
  52. clauster-0.2.0/src/clauster/environments.py +284 -0
  53. clauster-0.2.0/src/clauster/hooks/__init__.py +5 -0
  54. clauster-0.2.0/src/clauster/hooks/resume_recap.py +211 -0
  55. clauster-0.2.0/src/clauster/inspector.py +68 -0
  56. clauster-0.2.0/src/clauster/logstream.py +44 -0
  57. clauster-0.2.0/src/clauster/models.py +148 -0
  58. clauster-0.2.0/src/clauster/ops.py +470 -0
  59. clauster-0.2.0/src/clauster/pointers.py +63 -0
  60. clauster-0.2.0/src/clauster/procutil.py +141 -0
  61. clauster-0.2.0/src/clauster/provisioning.py +362 -0
  62. clauster-0.2.0/src/clauster/pty_keeper.py +205 -0
  63. clauster-0.2.0/src/clauster/recap.py +112 -0
  64. clauster-0.2.0/src/clauster/redact.py +64 -0
  65. clauster-0.2.0/src/clauster/runner.py +1039 -0
  66. clauster-0.2.0/src/clauster/state.py +93 -0
  67. clauster-0.2.0/src/clauster/static/alpine.LICENSE +21 -0
  68. clauster-0.2.0/src/clauster/static/alpine.min.js +5 -0
  69. clauster-0.2.0/src/clauster/static/clauster.css +340 -0
  70. clauster-0.2.0/src/clauster/static/favicon.svg +14 -0
  71. clauster-0.2.0/src/clauster/static/vendor/iconoir/LICENSE +21 -0
  72. clauster-0.2.0/src/clauster/static/vendor/iconoir/README.md +45 -0
  73. clauster-0.2.0/src/clauster/static/vendor/tabler/LICENSE +21 -0
  74. clauster-0.2.0/src/clauster/static/vendor/tabler/README.md +30 -0
  75. clauster-0.2.0/src/clauster/static/vendor/tabler/css/tabler.min.css +9 -0
  76. clauster-0.2.0/src/clauster/static/vendor/tabler/js/tabler.min.js +13 -0
  77. clauster-0.2.0/src/clauster/static/vendor/versions.txt +11 -0
  78. clauster-0.2.0/src/clauster/templates/_iconoir_sprite.html +82 -0
  79. clauster-0.2.0/src/clauster/templates/_project_card.html +166 -0
  80. clauster-0.2.0/src/clauster/templates/dashboard.html +760 -0
  81. clauster-0.2.0/src/clauster/templates/login.html +77 -0
  82. clauster-0.2.0/src/clauster/trust.py +117 -0
  83. clauster-0.2.0/src/clauster/usage.py +212 -0
  84. clauster-0.2.0/tests/E2E_CHECKLIST.md +118 -0
  85. clauster-0.2.0/tests/conftest.py +69 -0
  86. clauster-0.2.0/tests/fixtures/bridge-logs/test1-bridge-debug.log +8 -0
  87. clauster-0.2.0/tests/fixtures/fake_claude/claude +178 -0
  88. clauster-0.2.0/tests/fixtures/fake_claude/claude.cmd +5 -0
  89. clauster-0.2.0/tests/fixtures/fake_git/git +75 -0
  90. clauster-0.2.0/tests/fixtures/fake_git/git.cmd +5 -0
  91. clauster-0.2.0/tests/fixtures/pointers/dockerize2.bridge-pointer.json +1 -0
  92. clauster-0.2.0/tests/fixtures/pointers/test1.bridge-pointer.json +1 -0
  93. clauster-0.2.0/tests/fixtures/pointers/test2.bridge-pointer.json +1 -0
  94. clauster-0.2.0/tests/fixtures/transcripts/test1-session.jsonl +10 -0
  95. clauster-0.2.0/tests/test_app.py +36 -0
  96. clauster-0.2.0/tests/test_app_auth.py +364 -0
  97. clauster-0.2.0/tests/test_app_instances.py +80 -0
  98. clauster-0.2.0/tests/test_app_routes.py +439 -0
  99. clauster-0.2.0/tests/test_auth.py +288 -0
  100. clauster-0.2.0/tests/test_bridge_log.py +49 -0
  101. clauster-0.2.0/tests/test_claude_md.py +234 -0
  102. clauster-0.2.0/tests/test_clone_jobs.py +79 -0
  103. clauster-0.2.0/tests/test_config.py +159 -0
  104. clauster-0.2.0/tests/test_discovery.py +57 -0
  105. clauster-0.2.0/tests/test_environments.py +408 -0
  106. clauster-0.2.0/tests/test_fixtures.py +42 -0
  107. clauster-0.2.0/tests/test_inspector.py +51 -0
  108. clauster-0.2.0/tests/test_logstream.py +68 -0
  109. clauster-0.2.0/tests/test_logtail.py +51 -0
  110. clauster-0.2.0/tests/test_main.py +186 -0
  111. clauster-0.2.0/tests/test_ops.py +402 -0
  112. clauster-0.2.0/tests/test_pointers.py +48 -0
  113. clauster-0.2.0/tests/test_procutil.py +156 -0
  114. clauster-0.2.0/tests/test_provisioning.py +484 -0
  115. clauster-0.2.0/tests/test_pty_keeper.py +230 -0
  116. clauster-0.2.0/tests/test_recap.py +340 -0
  117. clauster-0.2.0/tests/test_redact.py +53 -0
  118. clauster-0.2.0/tests/test_runner.py +528 -0
  119. clauster-0.2.0/tests/test_runner_pty.py +266 -0
  120. clauster-0.2.0/tests/test_runner_recap.py +97 -0
  121. clauster-0.2.0/tests/test_spawn_controls.py +238 -0
  122. clauster-0.2.0/tests/test_state.py +67 -0
  123. clauster-0.2.0/tests/test_trust.py +128 -0
  124. clauster-0.2.0/tests/test_urls.py +59 -0
  125. clauster-0.2.0/tests/test_usage.py +284 -0
  126. clauster-0.2.0/uv.lock +1459 -0
@@ -0,0 +1,136 @@
1
+ {
2
+ "description_good_status": "Met",
3
+ "description_good_justification": "Clauster's purpose and capabilities are described in the README and the GitHub repository description.",
4
+ "interact_status": "Met",
5
+ "interact_justification": "CONTRIBUTING.md explains how to interact and contribute; GitHub Issues are open. https://github.com/schubydoo/clauster/blob/main/CONTRIBUTING.md",
6
+ "contribution_status": "Met",
7
+ "contribution_justification": "The contribution process (branch + PR, CI gates) is documented. https://github.com/schubydoo/clauster/blob/main/CONTRIBUTING.md",
8
+ "contribution_requirements_status": "Met",
9
+ "contribution_requirements_justification": "CONTRIBUTING.md states the requirements: pytest with a 96% coverage gate, ruff/pyright/doc-lint, and Conventional Commit PR titles. https://github.com/schubydoo/clauster/blob/main/CONTRIBUTING.md",
10
+ "floss_license_status": "Met",
11
+ "floss_license_justification": "Released under the Apache License 2.0. https://github.com/schubydoo/clauster/blob/main/LICENSE",
12
+ "floss_license_osi_status": "Met",
13
+ "floss_license_osi_justification": "Apache-2.0 is an OSI-approved open source license.",
14
+ "license_location_status": "Met",
15
+ "license_location_justification": "License is in the standard top-level LICENSE file. https://github.com/schubydoo/clauster/blob/main/LICENSE",
16
+ "documentation_basics_status": "Met",
17
+ "documentation_basics_justification": "README documents installation, configuration (clauster.yml), and usage; CONTRIBUTING, SECURITY, UPGRADING, and THIRD_PARTY_NOTICES are also provided.",
18
+ "documentation_interface_status": "Met",
19
+ "documentation_interface_justification": "The README documents the external interface: the CLI subcommands (clauster run/doctor/backup/...) and the clauster.yml configuration keys; the web UI is self-describing.",
20
+ "sites_https_status": "Met",
21
+ "sites_https_justification": "The project homepage/repository (GitHub) and distribution channels (PyPI, GHCR) are all served over HTTPS.",
22
+ "discussion_status": "Met",
23
+ "discussion_justification": "GitHub Issues is enabled and used for project discussion. https://github.com/schubydoo/clauster/issues",
24
+ "english_status": "Met",
25
+ "english_justification": "All documentation and code comments are in English.",
26
+ "maintained_status": "Met",
27
+ "maintained_justification": "The project is actively maintained (frequent commits, PRs, and reviews).",
28
+ "repo_public_status": "Met",
29
+ "repo_public_justification": "Public git repository hosted on GitHub. https://github.com/schubydoo/clauster",
30
+ "repo_track_status": "Met",
31
+ "repo_track_justification": "Git tracks every change to the source, with full history.",
32
+ "repo_interim_status": "Met",
33
+ "repo_interim_justification": "Interim changes between releases are visible as commits/branches/PRs in git.",
34
+ "repo_distributed_status": "Met",
35
+ "repo_distributed_justification": "Git is a distributed version control system.",
36
+ "version_unique_status": "Met",
37
+ "version_unique_justification": "Each release has a unique Semantic Version identifier maintained in pyproject.toml and src/clauster/__init__.py, which makes every build uniquely identifiable. Release tagging is automated via release-please (.release-please-manifest.json / release-please-config.json) and produces a vX.Y.Z git tag when a release is published.",
38
+ "version_semver_status": "Met",
39
+ "version_semver_justification": "The project uses Semantic Versioning (MAJOR.MINOR.PATCH).",
40
+ "version_tags_status": "Unmet",
41
+ "version_tags_justification": "No release has been cut yet, so no semantic-version git tags exist. Tagging is configured and automated via release-please and will produce vX.Y.Z tags once the first release is published.",
42
+ "release_notes_status": "N/A",
43
+ "release_notes_justification": "No release has been published, so there are no release notes to provide. Releases are published through the release-please workflow, which generates human-readable, grouped release notes (a CHANGELOG entry and a tagged GitHub Release) from the Conventional Commit history.",
44
+ "release_notes_vulns_status": "N/A",
45
+ "release_notes_vulns_justification": "No release has shipped a security fix and no publicly-known vulnerability has required disclosure. When a release fixes one, release-please records it in the generated release notes (Bug Fixes section).",
46
+ "report_process_status": "Met",
47
+ "report_process_justification": "Bugs are reported via GitHub Issues; security issues via SECURITY.md. https://github.com/schubydoo/clauster/issues",
48
+ "report_tracker_status": "Met",
49
+ "report_tracker_justification": "GitHub Issues serves as the bug tracker. https://github.com/schubydoo/clauster/issues",
50
+ "report_responses_status": "Met",
51
+ "report_responses_justification": "The project is new and has received no external bug reports yet (the only open issue is the auto-generated Renovate dependency dashboard). The maintainer monitors the GitHub issue tracker; there is no backlog of unanswered reports.",
52
+ "enhancement_responses_status": "Met",
53
+ "enhancement_responses_justification": "No enhancement requests have been received yet (the only open issue is the auto-generated Renovate dependency dashboard). The maintainer monitors the GitHub issue tracker and responds to requests as they arrive.",
54
+ "report_archive_status": "Met",
55
+ "report_archive_justification": "GitHub Issues provides a public, persistent archive of reports and responses. https://github.com/schubydoo/clauster/issues",
56
+ "vulnerability_report_process_status": "Met",
57
+ "vulnerability_report_process_justification": "SECURITY.md documents the vulnerability reporting process. https://github.com/schubydoo/clauster/blob/main/SECURITY.md",
58
+ "vulnerability_report_private_status": "Met",
59
+ "vulnerability_report_private_justification": "Vulnerabilities are reported privately via GitHub private security advisories. https://github.com/schubydoo/clauster/security/advisories/new",
60
+ "vulnerability_report_response_status": "Met",
61
+ "vulnerability_report_response_justification": "SECURITY.md commits to an initial response within a few days.",
62
+ "build_status": "Met",
63
+ "build_justification": "Built from source with a standard PEP 517 build (uv + hatchling); installable via pip/uv as a wheel/sdist.",
64
+ "build_common_tools_status": "Met",
65
+ "build_common_tools_justification": "Built with common, widely available FLOSS tools (uv, hatchling, CPython).",
66
+ "build_floss_tools_status": "Met",
67
+ "build_floss_tools_justification": "All build tooling is FLOSS.",
68
+ "test_status": "Met",
69
+ "test_justification": "A pytest test suite covers the codebase (400+ tests).",
70
+ "test_invocation_status": "Met",
71
+ "test_invocation_justification": "Running the tests is documented in CONTRIBUTING.md (`uv run pytest`). https://github.com/schubydoo/clauster/blob/main/CONTRIBUTING.md",
72
+ "test_most_status": "Met",
73
+ "test_most_justification": "The suite covers ~96% of statements, enforced by a CI coverage gate.",
74
+ "test_continuous_integration_status": "Met",
75
+ "test_continuous_integration_justification": "GitHub Actions runs the suite on every PR across a Linux/macOS/Windows x Python 3.11-3.14 matrix.",
76
+ "test_policy_status": "Met",
77
+ "test_policy_justification": "CONTRIBUTING requires tests for new functionality, backed by the merge-blocking 96% coverage gate.",
78
+ "tests_are_added_status": "Met",
79
+ "tests_are_added_justification": "New functionality is added with tests; the coverage gate blocks merges that would drop coverage.",
80
+ "tests_documented_added_status": "Met",
81
+ "tests_documented_added_justification": "The test-and-coverage policy is documented in CONTRIBUTING.md.",
82
+ "warnings_status": "Met",
83
+ "warnings_justification": "ruff (broad ruleset including bugbear and flake8-bandit) and pyright run in CI.",
84
+ "warnings_fixed_status": "Met",
85
+ "warnings_fixed_justification": "CI fails on any ruff or pyright finding; warnings are not tolerated on main.",
86
+ "warnings_strict_status": "Met",
87
+ "warnings_strict_justification": "Lint and type checks are treated as errors (CI-blocking).",
88
+ "know_secure_design_status": "Met",
89
+ "know_secure_design_justification": "The design applies least-privilege, fail-closed liveness, loopback-by-default networking, SSRF guards on clone, and per-project trust gating (documented in SECURITY.md).",
90
+ "know_common_errors_status": "Met",
91
+ "know_common_errors_justification": "The code guards against common errors (input validation, SSRF protection, no shell=True/argv lists, output redaction); ruff-S (bandit) and CodeQL enforce this in CI.",
92
+ "crypto_published_status": "Met",
93
+ "crypto_published_justification": "Cryptography is limited to argon2id password hashing (argon2-cffi) and Python's `secrets` module; both are standard, published mechanisms.",
94
+ "crypto_call_status": "Met",
95
+ "crypto_call_justification": "The project calls the argon2-cffi library and Python's stdlib `secrets`; it implements no home-grown cryptography.",
96
+ "crypto_floss_status": "Met",
97
+ "crypto_floss_justification": "argon2-cffi and the Python standard library are FLOSS.",
98
+ "crypto_keylength_status": "Met",
99
+ "crypto_keylength_justification": "argon2id is used with the library's secure default cost parameters; generated secrets/tokens are 256-bit (`secrets.token_bytes(32)`).",
100
+ "crypto_working_status": "Met",
101
+ "crypto_working_justification": "No broken or risky algorithms are used for security; argon2id for passwords, no MD5/SHA-1 for security purposes.",
102
+ "crypto_weaknesses_status": "Met",
103
+ "crypto_weaknesses_justification": "No known-weak cryptographic algorithms are used.",
104
+ "crypto_pfs_status": "N/A",
105
+ "crypto_pfs_justification": "Clauster performs no network key agreement itself; TLS (and forward secrecy) is provided by the operator's reverse proxy. It binds to loopback by default.",
106
+ "crypto_password_storage_status": "Met",
107
+ "crypto_password_storage_justification": "Login passwords are stored only as argon2id hashes (salted, memory-hard KDF) via argon2-cffi; plaintext is never stored. Verification is constant-time.",
108
+ "crypto_random_status": "Met",
109
+ "crypto_random_justification": "Cryptographically secure randomness uses Python's `secrets` module (token_bytes/token_hex).",
110
+ "delivery_mitm_status": "Met",
111
+ "delivery_mitm_justification": "The software is delivered over HTTPS (GitHub, PyPI, GHCR) and its dependencies are version/hash pinned, preventing MITM during retrieval.",
112
+ "delivery_unsigned_status": "Met",
113
+ "delivery_unsigned_justification": "Installation instructions use HTTPS package managers (pip/uv, docker pull); users are never directed to download unsigned code over HTTP.",
114
+ "vulnerabilities_fixed_60_days_status": "Met",
115
+ "vulnerabilities_fixed_60_days_justification": "Renovate and GitHub security alerts surface dependency vulnerabilities, which are patched promptly (well within 60 days).",
116
+ "vulnerabilities_critical_fixed_status": "Met",
117
+ "vulnerabilities_critical_fixed_justification": "No known unpatched vulnerabilities exist (OpenSSF Scorecard Vulnerabilities check scores 10/10).",
118
+ "no_leaked_credentials_status": "Met",
119
+ "no_leaked_credentials_justification": "No credentials are stored in the repository; gitleaks and GitHub secret scanning run in CI, sensitive config is gitignored, and session URLs/tokens are redacted in logs.",
120
+ "static_analysis_status": "Met",
121
+ "static_analysis_justification": "Static analysis runs in CI: ruff (including flake8-bandit `S` security rules), pyright, CodeQL, and Trivy.",
122
+ "static_analysis_common_vulnerabilities_status": "Met",
123
+ "static_analysis_common_vulnerabilities_justification": "CodeQL and ruff-S specifically target common vulnerability patterns.",
124
+ "static_analysis_fixed_status": "Met",
125
+ "static_analysis_fixed_justification": "Static-analysis findings are merge-blocking and fixed before merge.",
126
+ "static_analysis_often_status": "Met",
127
+ "static_analysis_often_justification": "Static analysis runs on every PR and push (CodeQL also on a weekly schedule).",
128
+ "dynamic_analysis_status": "Unmet",
129
+ "dynamic_analysis_justification": "No dynamic analysis or fuzzing is currently run; the project relies on static analysis plus a high-coverage test suite. Python is memory-safe.",
130
+ "dynamic_analysis_unsafe_status": "N/A",
131
+ "dynamic_analysis_unsafe_justification": "The project is written in Python, a memory-safe language; memory/behavior sanitizers for memory-unsafe languages do not apply.",
132
+ "dynamic_analysis_enable_assertions_status": "Met",
133
+ "dynamic_analysis_enable_assertions_justification": "The pytest suite exercises the code with assertions enabled throughout.",
134
+ "dynamic_analysis_fixed_status": "N/A",
135
+ "dynamic_analysis_fixed_justification": "No dynamic analysis is performed (see dynamic_analysis), so there are no such findings to fix; static analysis and tests cover this."
136
+ }
@@ -0,0 +1,17 @@
1
+ # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
2
+ #
3
+ # Docstrings: our gate is ruff's pydocstyle (D) rules, enforced on EVERY PR by the
4
+ # "ruff + pyright" CI job (.github/workflows/lint.yml, `on: pull_request`). It
5
+ # requires docstrings on all public modules/classes/functions (pep257 convention;
6
+ # D105/D107 and tests/** are intentionally exempt — see pyproject.toml).
7
+ #
8
+ # CodeRabbit's separate docstring-coverage pre-merge check computes a raw
9
+ # percentage that counts inner/nested helpers (preexec closures, signal handlers,
10
+ # test fixtures) which our gate deliberately exempts, so it reports a misleadingly
11
+ # low number against its 80% default. Defer to ruff D as the single source of
12
+ # truth and turn the redundant check off rather than run two disagreeing gates.
13
+ language: "en-US"
14
+ reviews:
15
+ pre_merge_checks:
16
+ docstrings:
17
+ mode: "off"
@@ -0,0 +1,29 @@
1
+ # Keep the build context (and final image) lean: only pyproject/uv.lock/README/src
2
+ # feed the build. Everything else is noise or must never ship.
3
+ .git
4
+ .github
5
+ .venv
6
+ **/__pycache__
7
+ **/*.py[cod]
8
+ .pytest_cache
9
+ .ruff_cache
10
+ .mypy_cache
11
+ htmlcov
12
+ .coverage*
13
+ tests
14
+ dist
15
+ build
16
+ *.spec
17
+ scripts
18
+
19
+ # Local config + internal notes (mirror .gitignore; never bake into an image)
20
+ clauster.yml
21
+ NOTES.md
22
+ HANDOFF.md
23
+ DEBUG_NOTES.md
24
+ PLAN_*.md
25
+ *_spec*.md
26
+ v4-panel-findings.md
27
+ .claude
28
+ .gitnexus
29
+ .logfire
@@ -0,0 +1,9 @@
1
+ # Code owners for Clauster — https://docs.github.com/articles/about-code-owners
2
+ #
3
+ # GitHub uses this to auto-request review from the owner on matching PRs.
4
+ # Branch protection does not (yet) *require* code-owner review — as a solo
5
+ # project there's no second reviewer to approve — but declaring ownership here
6
+ # is good hygiene and makes required code-owner review a one-line switch if the
7
+ # maintainer set grows.
8
+
9
+ * @schubydoo
@@ -0,0 +1,143 @@
1
+ # Probot Settings (https://github.com/repository-settings/app)
2
+ # Install the GitHub App on this repo to make settings declarative.
3
+ # Note: the App requires `Administration: Read & write` permission on the repo
4
+ # to apply branch protection rules.
5
+ #
6
+ # Borrowed from the schubydoo python+uv family (dump1090-exporter / dockerize2)
7
+ # and adapted for clauster. Two known gotchas are preserved verbatim below:
8
+ # - enforce_admins / restrictions MUST be `null`, never `false`/`[]` (#923)
9
+ # - every bot-applied label MUST be declared or Probot deletes it (#386)
10
+
11
+ repository:
12
+ name: clauster
13
+ description: >-
14
+ Your homelab's Claude Code launchpad. Self-hosted web UI that spawns &
15
+ manages remote-control bridges on a remote host from any browser or phone —
16
+ start/stop, spawn & permission modes, CLAUDE.md editor, git clone, live log
17
+ tail, cost tracking. Loopback by default; password/reverse-proxy auth. No
18
+ telemetry.
19
+ # Topics: GitHub slugs disallow spaces — a leading space after "comma+space"
20
+ # gets silently dropped. Folded scalar + bare commas => exactly `a,b,c`.
21
+ topics: >-
22
+ claude,claude-code,remote-control,self-hosted,homelab,fastapi,nas,dispatcher,python
23
+ homepage: https://github.com/schubydoo/clauster
24
+ has_issues: true
25
+ has_projects: false
26
+ has_wiki: false
27
+ has_downloads: true
28
+ default_branch: main
29
+ allow_squash_merge: true
30
+ allow_merge_commit: false
31
+ allow_rebase_merge: true
32
+ allow_auto_merge: true
33
+ delete_branch_on_merge: true
34
+ allow_update_branch: true
35
+ # Web-UI squash defaults. Both default to PR_TITLE / PR_BODY out of the box;
36
+ # we override:
37
+ # - squash_merge_commit_title: COMMIT_OR_PR_TITLE — use the single commit's
38
+ # subject when a PR has only one commit, fall back to PR title otherwise.
39
+ # Avoids the "(#NN)" suffix duplication.
40
+ # - squash_merge_commit_message: COMMIT_MESSAGES — concatenate the branch's
41
+ # commit messages into the squash body, preserving hand-crafted commit
42
+ # bodies (rationale + Co-Authored-By trailers) instead of the PR desc.
43
+ # merge_commit_* settings deliberately omitted — allow_merge_commit is false,
44
+ # so those settings are inert.
45
+ squash_merge_commit_title: COMMIT_OR_PR_TITLE
46
+ squash_merge_commit_message: COMMIT_MESSAGES
47
+
48
+ # Labels managed declaratively by Probot. Every label any of our bots applies
49
+ # MUST be listed here — Probot Settings has a long-running issue
50
+ # (repository-settings/app#386) where it deletes labels that aren't declared,
51
+ # and the family has watched it strip Renovate's `dependencies` and
52
+ # release-please's `autorelease: *` labels off PRs in real time. Standard
53
+ # GitHub-created labels (bug, enhancement, documentation, etc.) are left alone.
54
+ labels:
55
+ # ---- Renovate ----
56
+ - name: dependencies
57
+ color: ededed
58
+ description: Renovate-opened dependency update PRs (and the dashboard issue)
59
+ - name: security
60
+ color: d93f0b
61
+ description: Security-related — Renovate vulnerability alerts use this
62
+ - name: vendored-assets
63
+ color: ededed
64
+ description: Renovate heads-up to re-vendor self-hosted front-end assets (manual dist update)
65
+
66
+ # ---- Release Please ----
67
+ # release-please-action applies these to its release PRs / tagged release
68
+ # commits. Without them declared, Probot strips them mid-release-flow.
69
+ - name: "autorelease: pending"
70
+ color: ededed
71
+ description: Release Please — release PR is open, awaiting merge
72
+ - name: "autorelease: tagged"
73
+ color: 0e8a16
74
+ description: Release Please — release PR is merged and the tag has been pushed
75
+ - name: "autorelease: snapshot"
76
+ color: 5319e7
77
+ description: Release Please — snapshot/prerelease (rare)
78
+
79
+ branches:
80
+ - name: main
81
+ protection:
82
+ required_pull_request_reviews:
83
+ # Solo maintainer: 0 required approvals, but stale reviews are dismissed
84
+ # and conversations must resolve. Raise this once there are co-maintainers.
85
+ required_approving_review_count: 0
86
+ dismiss_stale_reviews: true
87
+ require_code_owner_reviews: false
88
+ required_status_checks:
89
+ # Context names must match the check-run names GitHub records exactly —
90
+ # that's each job's `name:` field, NOT "workflow / job". A check must
91
+ # also have run on the repo at least once before it can be required.
92
+ #
93
+ # GOTCHA (observed on clauster's first push): when this file ships in a
94
+ # repo's FIRST push, none of these contexts have reported yet, and the
95
+ # Settings App's branch-protection PUT is rejected wholesale — labels /
96
+ # description / merge settings still apply (different API calls), but
97
+ # protection silently does NOT. Fix: once every listed check has run
98
+ # once, re-push this file (a no-op edit is enough) to re-apply.
99
+ #
100
+ # Aggregator pattern (mirrors the family):
101
+ # - `ci required checks passed` — ci.yml aggregator (needs: tests, build)
102
+ # - `security required checks passed` — security.yml aggregator
103
+ # (needs: changes, codeql, gitleaks,
104
+ # trivy-fs, trivy-image, zizmor,
105
+ # dependency-review)
106
+ # - `conventional PR title` — pr-title.yml (gates the squash subject
107
+ # that Release Please parses)
108
+ # - `lint` — lint.yml (ruff + pyright + the
109
+ # Markdown/YAML doc-lint; we gate it,
110
+ # the family leaves lint non-required).
111
+ # Direct job name, so renaming that job
112
+ # means updating this list too.
113
+ #
114
+ # `OSSF Scorecard` is intentionally NOT required: scorecard.yml only
115
+ # triggers on push + schedule + branch_protection_rule, so requiring it
116
+ # would leave every PR perpetually pending.
117
+ strict: true
118
+ contexts:
119
+ - "ci required checks passed"
120
+ - "security required checks passed"
121
+ - "conventional PR title"
122
+ - "lint"
123
+ # `true` enforces branch protection for admins too — OpenSSF Scorecard's
124
+ # Branch-Protection check rewards this. NB the Settings App rejects `false`
125
+ # here silently (repository-settings/app#923): use `null` to disable,
126
+ # `true` to enforce. Trade-off: this removes the admin-merge bypass we used
127
+ # for the job-rename in #66, so a future required-context change can't be
128
+ # force-merged the same way — revert to `null` if you need that escape hatch.
129
+ #
130
+ # ⚠️ CONFIRMED (A/B test, 2026-06-02): the Settings App does NOT apply this
131
+ # field on an already-protected branch. It reconciles other sections on push
132
+ # (security log: settings[bot] `repo.change_merge_setting`, probot/14.3.2) but
133
+ # leaves enforce_admins untouched — #70's null→true never took. Set it directly:
134
+ # gh api --method POST repos/schubydoo/clauster/branches/main/protection/enforce_admins
135
+ # (DELETE to disable) and re-GET to verify. Keep this `true` so the desired
136
+ # state is still declared, but don't trust the App to apply it.
137
+ enforce_admins: true
138
+ required_linear_history: true
139
+ allow_force_pushes: false
140
+ allow_deletions: false
141
+ required_conversation_resolution: true
142
+ # Same `null`-vs-empty-array gotcha; explicit `null` for safety.
143
+ restrictions: null
@@ -0,0 +1,167 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ # --------------------------------------------------------------------
17
+ # Unit tests across the supported Python versions (Linux only — clauster
18
+ # targets a Linux/NAS host; psutil/proc + bridge management aren't
19
+ # exercised on macOS/Windows). The 96% coverage gate lives in
20
+ # pyproject [tool.pytest.ini_options] addopts (--cov-fail-under=96), so
21
+ # a bare `pytest` enforces it on every cell.
22
+ # --------------------------------------------------------------------
23
+ tests:
24
+ name: tests (${{ matrix.os }}, ${{ matrix.python-version }})
25
+ runs-on: ${{ matrix.os }}
26
+ timeout-minutes: 15
27
+ # Ubuntu, macOS, and Windows are all required (spec target: tri-platform).
28
+ # macOS + Windows were brought green via a .cmd entrypoint for the test stubs
29
+ # plus cross-platform guards; they now block merges like Linux, so a future
30
+ # regression on any platform can't silently rot the way these 29 once did.
31
+ strategy:
32
+ fail-fast: false
33
+ # Spec target is Ubuntu + macOS + Windows (conductor_spec_v3 §"Ubuntu,
34
+ # macOS, Windows" + 4-runner PyInstaller matrix; psutil was chosen for
35
+ # cross-platform liveness). Full Python sweep on every OS (3 × 4 = 12
36
+ # cells) so a version-specific portability bug can't hide on an untested
37
+ # OS×version combo. Public repo → Actions minutes are free; cells run in
38
+ # parallel. Only Linux enforces the coverage gate (see the pytest step).
39
+ matrix:
40
+ os: [ubuntu-latest, macos-latest, windows-latest]
41
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
42
+ steps:
43
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
44
+ with:
45
+ persist-credentials: false
46
+ - name: Set up uv
47
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
48
+ with:
49
+ enable-cache: true
50
+ # Matrix values flow through env vars (not inlined into `run:`) so zizmor's
51
+ # template-injection analysis doesn't flag the shell blocks. The matrix is
52
+ # hard-coded above, not derived from any PR-controlled input. `shell: bash`
53
+ # keeps one script across all three runners (Git Bash ships on Windows).
54
+ - name: Set up Python ${{ matrix.python-version }}
55
+ shell: bash
56
+ env:
57
+ PYTHON_VERSION: ${{ matrix.python-version }}
58
+ run: uv python install "$PYTHON_VERSION"
59
+ - name: Install
60
+ shell: bash
61
+ env:
62
+ PYTHON_VERSION: ${{ matrix.python-version }}
63
+ run: uv sync --extra dev --python "$PYTHON_VERSION"
64
+ # Linux enforces the 96% coverage gate (pyproject addopts). macOS + Windows
65
+ # still REPORT coverage (term-missing, for visibility into platform-specific
66
+ # gaps) but don't gate on it — `--cov-fail-under=0` overrides the addopts
67
+ # threshold — since branches are inherently platform-specific (Linux jiffies
68
+ # procStart, Windows O_BINARY/WNOHANG guards) and no single OS reaches 96%.
69
+ - name: pytest
70
+ shell: bash
71
+ env:
72
+ PYTHON_VERSION: ${{ matrix.python-version }}
73
+ RUNNER_OS: ${{ runner.os }}
74
+ run: |
75
+ if [ "$RUNNER_OS" = "Linux" ]; then
76
+ uv run --python "$PYTHON_VERSION" pytest -q \
77
+ --cov-report=xml --junitxml=junit.xml -o junit_family=legacy
78
+ else
79
+ uv run --python "$PYTHON_VERSION" pytest -q --cov-fail-under=0
80
+ fi
81
+ # Upload coverage + test results to Codecov from a single Linux cell (the
82
+ # gate platform). Linux is where the 96% number is measured; macOS/Windows
83
+ # coverage is platform-partial by design (see the pytest step), so uploading
84
+ # them would only muddy the trend. Token-based upload (CODECOV_TOKEN secret)
85
+ # → no `id-token` permission needed. Both uploads are non-blocking: Codecov
86
+ # isn't a required check and upload flakiness must never fail CI
87
+ # (fail_ci_if_error: false). Both use `!cancelled()` so a FAILED pytest run
88
+ # still uploads — pytest writes coverage.xml/junit.xml before the gate check,
89
+ # so a coverage drop shows up in the Codecov trend precisely when it matters,
90
+ # and failed tests still feed Codecov's flaky-test analytics. Test results go
91
+ # through codecov-action with `report_type: test_results` (the standalone
92
+ # test-results-action is deprecated), so we pin a single action SHA.
93
+ - name: Upload coverage to Codecov
94
+ if: ${{ !cancelled() && runner.os == 'Linux' && matrix.python-version == '3.13' }}
95
+ uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
96
+ with:
97
+ token: ${{ secrets.CODECOV_TOKEN }}
98
+ files: coverage.xml
99
+ fail_ci_if_error: false
100
+ - name: Upload test results to Codecov
101
+ if: ${{ !cancelled() && runner.os == 'Linux' && matrix.python-version == '3.13' }}
102
+ uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
103
+ with:
104
+ token: ${{ secrets.CODECOV_TOKEN }}
105
+ files: junit.xml
106
+ report_type: test_results
107
+ fail_ci_if_error: false
108
+
109
+ # --------------------------------------------------------------------
110
+ # Build sdist + wheel. Cheap; runs on every push and PR so we always
111
+ # know the package is shippable.
112
+ # --------------------------------------------------------------------
113
+ build:
114
+ name: build sdist + wheel
115
+ runs-on: ubuntu-latest
116
+ timeout-minutes: 5
117
+ steps:
118
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
119
+ with:
120
+ persist-credentials: false
121
+ - name: Set up uv
122
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
123
+ with:
124
+ enable-cache: true
125
+ - name: Set up Python 3.13
126
+ run: uv python install 3.13
127
+ - name: Build
128
+ run: uv build
129
+ - name: Upload dist artefacts
130
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
131
+ with:
132
+ name: dist
133
+ path: dist/
134
+ retention-days: 7
135
+ if-no-files-found: error
136
+
137
+ # --------------------------------------------------------------------
138
+ # Required-checks aggregator. ONE check for branch protection to gate
139
+ # on, regardless of how many underlying jobs exist. `if: always()` so it
140
+ # runs even when a need failed; jq fails the aggregator iff any listed
141
+ # job is not success-or-skipped. Add a new ci.yml job by appending to
142
+ # `needs:` — branch protection lists only this one name.
143
+ # --------------------------------------------------------------------
144
+ ci-required-checks-passed:
145
+ name: ci required checks passed
146
+ if: always()
147
+ needs:
148
+ - tests
149
+ - build
150
+ runs-on: ubuntu-latest
151
+ timeout-minutes: 2
152
+ steps:
153
+ - name: Check required jobs passed
154
+ env:
155
+ NEEDS_JSON: ${{ toJSON(needs) }}
156
+ run: |
157
+ failing=$(echo "$NEEDS_JSON" | jq -r '
158
+ to_entries[]
159
+ | select(.value.result != "success" and .value.result != "skipped")
160
+ | "\(.key): \(.value.result)"
161
+ ')
162
+ if [ -n "$failing" ]; then
163
+ echo "Failed or cancelled jobs:"
164
+ echo "$failing"
165
+ exit 1
166
+ fi
167
+ echo "All required jobs passed (or were intentionally skipped)."
@@ -0,0 +1,55 @@
1
+ name: Lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: lint-${{ github.workflow }}-${{ github.head_ref || github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ lint:
17
+ # This job name is a REQUIRED status check — it must match the context list
18
+ # in .github/settings.yml exactly (renaming means updating that list too).
19
+ # Runs ruff (check + format), pyright, and the Markdown + YAML doc-lint
20
+ # (scripts/lint-docs.sh).
21
+ name: lint
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 5
24
+ steps:
25
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
26
+ with:
27
+ persist-credentials: false
28
+ - name: Set up uv
29
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
30
+ with:
31
+ enable-cache: true
32
+ - name: Set up Python 3.13
33
+ run: uv python install 3.13
34
+ - name: Install
35
+ run: uv sync --extra dev --python 3.13
36
+ - name: ruff check
37
+ run: uv run ruff check .
38
+ - name: ruff format --check
39
+ run: uv run ruff format --check .
40
+ - name: pyright
41
+ run: uv run pyright src/clauster
42
+ # Node for markdownlint-cli2: pin the runtime (ubuntu-latest's bundled Node
43
+ # drifts across image refreshes) and install the version-pinned, integrity-
44
+ # checked package from the lockfile via `npm ci`. yamllint comes from
45
+ # `uv sync --extra dev` above.
46
+ - name: Set up Node 20
47
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
48
+ with:
49
+ node-version: "20"
50
+ cache: npm
51
+ - name: Install Node deps
52
+ run: npm ci
53
+ # Markdown + YAML lint — the same script developers run locally.
54
+ - name: markdown + yaml lint
55
+ run: bash scripts/lint-docs.sh
@@ -0,0 +1,41 @@
1
+ name: PR Title
2
+
3
+ # Validate that the pull-request title follows Conventional Commits. Because the
4
+ # repo squash-merges, the PR title becomes the squashed commit subject — which
5
+ # release-please parses to decide version bumps and CHANGELOG sections.
6
+
7
+ on:
8
+ pull_request:
9
+ types: [opened, edited, reopened, synchronize]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: pr-title-${{ github.event.pull_request.number }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ validate:
20
+ name: conventional PR title
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 5
23
+ permissions:
24
+ pull-requests: read
25
+ steps:
26
+ - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
27
+ env:
28
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29
+ with:
30
+ types: |
31
+ feat
32
+ fix
33
+ perf
34
+ revert
35
+ docs
36
+ chore
37
+ ci
38
+ build
39
+ test
40
+ refactor
41
+ style