Skip to content

Commit 7a294ca

Browse files
aksOpsclaude
andauthored
chore(bestpractices): uplift to Met across the passing tier (#18)
Author the missing documentation and infrastructure so .bestpractices.json reaches 64 Met / 3 N/A / 0 Unmet (was 58 / 6 / 3) without aspirational answers. Justifications carry a URL only for the eight criteria where the upstream criteria.yml sets met_url_required (contribution, contribution_requirements, license_location, release_notes, report_process, report_archive, vulnerability_report_process, vulnerability_report_private); every other justification is prose only. New documentation: - CONTRIBUTING.md — bug-report process, PR conventions, coding standards table, static + dynamic analysis gates, license note. - SECURITY.md — supported-version matrix, vulnerability reporting channels (GitHub private advisory + email fallback), what to include, response-time commitments, scope, security architecture reference, credit policy. Release notes uplift: - .github/workflows/release.yml now parses Conventional Commit prefixes (feat/fix/perf/refactor/docs/test/ci/build/chore) and groups them into human-readable categorised sections, replacing the raw `git log` dump. Anything that doesn't match a known prefix lands in an 'Other' section so nothing is silently dropped. Verified locally against the v0.1.10..v0.1.15 range. Dynamic analysis: - ci.yml + release.yml: `go test` now runs with `-race`, Go's runtime data-race detector. The race detector immediately surfaced a real test-side race in cmd/logs_extra_test.go: TestTailLog_DrainsThenExitsOnContextCancel's goroutine still had withFlags' deferred restore in flight when the test returned, racing the next test's withFlags read. Fixed by gating the test exit on a sync.WaitGroup so the goroutine fully completes before the test function returns. Full -race suite passes (918 tests, 27 packages, ~0 races). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 93d6c7f commit 7a294ca

6 files changed

Lines changed: 284 additions & 42 deletions

File tree

.bestpractices.json

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"_comment": "OpenSSF Best Practices answers for the 'passing' tier. The bestpractices.dev BadgeApp reads this file from the repo root (per docs/bestpractices-json.md upstream) when the project is registered there, and uses each <criterion>_status / <criterion>_justification pair as the proposed answer. To trigger re-ingestion after edits, the maintainer opens the project's edit page on bestpractices.dev and clicks 'Save (and continue) 🤖'. Status '?' means 'unknown' and is ignored — safe placeholder. .github/workflows/bestpractices.yml lints this file on every push to main so it stays parseable and on-schema.",
2+
"_comment": "OpenSSF Best Practices answers for the 'passing' tier. The bestpractices.dev BadgeApp reads this file from the repo root (per docs/bestpractices-json.md upstream) when the project is registered there, and uses each <criterion>_status / <criterion>_justification pair as the proposed answer. To trigger re-ingestion after edits, the maintainer opens the project's edit page on bestpractices.dev and clicks 'Save (and continue) 🤖'. Status '?' means 'unknown' and is ignored — safe placeholder. .github/workflows/bestpractices.yml lints this file on every push to main so it stays parseable and on-schema. Per the upstream criteria, only the eight criteria with met_url_required=true (contribution, contribution_requirements, license_location, release_notes, report_process, report_archive, vulnerability_report_process, vulnerability_report_private) carry a bare URL in their justification; every other justification is prose only.",
33

44
"name": "ctm",
55
"description": "Claude Tmux Manager — survive SSH drops, reattach from your phone.",
@@ -11,25 +11,25 @@
1111
"description_good_justification": "README opens with: 'Claude Tmux Manager — survive SSH drops, reattach from your phone.'",
1212

1313
"interact_status": "Met",
14-
"interact_justification": "https://github.com/RandomCodeSpace/ctm/issues — GitHub Issues + Pull Requests are enabled.",
14+
"interact_justification": "GitHub Issues + Pull Requests are enabled.",
1515

16-
"contribution_status": "Unmet",
17-
"contribution_justification": "CONTRIBUTING.md not yet authored. Tracked as follow-up; PRs are accepted via the standard GitHub flow in the meantime.",
16+
"contribution_status": "Met",
17+
"contribution_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/CONTRIBUTING.md",
1818

19-
"contribution_requirements_status": "Unmet",
20-
"contribution_requirements_justification": "Will be documented in CONTRIBUTING.md once added.",
19+
"contribution_requirements_status": "Met",
20+
"contribution_requirements_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/CONTRIBUTING.md#coding-standards",
2121

2222
"floss_license_status": "Met",
23-
"floss_license_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/LICENSE — MIT License.",
23+
"floss_license_justification": "MIT License.",
2424

2525
"floss_license_osi_status": "Met",
26-
"floss_license_osi_justification": "MIT is OSI-approved (https://opensource.org/license/mit).",
26+
"floss_license_osi_justification": "MIT is OSI-approved.",
2727

2828
"license_location_status": "Met",
29-
"license_location_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/LICENSE — LICENSE file at repository root.",
29+
"license_location_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/LICENSE",
3030

3131
"documentation_basics_status": "Met",
32-
"documentation_basics_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/README.md documents installation, configuration, and primary commands.",
32+
"documentation_basics_justification": "README documents installation, configuration, and primary commands.",
3333

3434
"documentation_interface_status": "Met",
3535
"documentation_interface_justification": "README has a Commands section listing every external interface (yolo, safe, attach, kill, list, ctm serve, etc.).",
@@ -38,7 +38,7 @@
3838
"sites_https_justification": "All project URLs are GitHub-hosted and use HTTPS.",
3939

4040
"discussion_status": "Met",
41-
"discussion_justification": "https://github.com/RandomCodeSpace/ctm/issues — GitHub Issues serve as the discussion forum.",
41+
"discussion_justification": "GitHub Issues serve as the discussion forum.",
4242

4343
"english_status": "Met",
4444
"english_justification": "All documentation and source comments are in English.",
@@ -59,25 +59,25 @@
5959
"repo_distributed_justification": "git is a distributed VCS.",
6060

6161
"version_unique_status": "Met",
62-
"version_unique_justification": "https://github.com/RandomCodeSpace/ctm/tags — each release is tagged with a unique semver tag.",
62+
"version_unique_justification": "Each release is tagged with a unique semver tag.",
6363

6464
"version_semver_status": "Met",
6565
"version_semver_justification": "Tags follow vMAJOR.MINOR.PATCH.",
6666

6767
"version_tags_status": "Met",
68-
"version_tags_justification": "https://github.com/RandomCodeSpace/ctm/releases — releases are git-tagged.",
68+
"version_tags_justification": "Releases are git-tagged.",
6969

7070
"release_notes_status": "Met",
71-
"release_notes_justification": "Each GitHub Release includes auto-generated notes summarising changes since the previous tag.",
71+
"release_notes_justification": "https://github.com/RandomCodeSpace/ctm/releases",
7272

7373
"release_notes_vulns_status": "N/A",
7474
"release_notes_vulns_justification": "No publicly disclosed vulnerabilities to date.",
7575

7676
"report_process_status": "Met",
77-
"report_process_justification": "Bug reports go through GitHub Issues; the README links to the Issues tab.",
77+
"report_process_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/CONTRIBUTING.md#reporting-bugs-or-asking-questions",
7878

7979
"report_tracker_status": "Met",
80-
"report_tracker_justification": "https://github.com/RandomCodeSpace/ctm/issues — GitHub Issues.",
80+
"report_tracker_justification": "GitHub Issues.",
8181

8282
"report_responses_status": "Met",
8383
"report_responses_justification": "Issues are triaged by the maintainer on a best-effort basis.",
@@ -86,16 +86,16 @@
8686
"enhancement_responses_justification": "Feature requests via Issues receive a response (accept / defer / decline) on a best-effort basis.",
8787

8888
"report_archive_status": "Met",
89-
"report_archive_justification": "GitHub Issues retains a full archive of reports and responses.",
89+
"report_archive_justification": "https://github.com/RandomCodeSpace/ctm/issues?q=is%3Aissue",
9090

91-
"vulnerability_report_process_status": "Unmet",
92-
"vulnerability_report_process_justification": "SECURITY.md not yet authored. Tracked as follow-up; for now, security reports can be filed as a private security advisory on GitHub.",
91+
"vulnerability_report_process_status": "Met",
92+
"vulnerability_report_process_justification": "https://github.com/RandomCodeSpace/ctm/blob/main/SECURITY.md",
9393

9494
"vulnerability_report_private_status": "Met",
95-
"vulnerability_report_private_justification": "https://github.com/RandomCodeSpace/ctm/security/advisories/new — GitHub's private security advisories are enabled.",
95+
"vulnerability_report_private_justification": "https://github.com/RandomCodeSpace/ctm/security/advisories/new",
9696

9797
"vulnerability_report_response_status": "Met",
98-
"vulnerability_report_response_justification": "Maintainer commits to acknowledging vulnerability reports within 14 days; window will be formalised in SECURITY.md.",
98+
"vulnerability_report_response_justification": "Formal response targets in SECURITY.md: acknowledge within 14 days, initial assessment within 30 days, fix High/Critical within 60 days, default 90-day disclosure window.",
9999

100100
"build_status": "Met",
101101
"build_justification": "Standard `go build -tags sqlite_fts5` builds the binary; `pnpm build` builds the embedded UI.",
@@ -113,10 +113,10 @@
113113
"test_invocation_justification": "README documents `go test -tags sqlite_fts5 ./...` and `pnpm exec vitest run`.",
114114

115115
"test_most_status": "Met",
116-
"test_most_justification": "https://sonarcloud.io/summary/overall?id=RandomCodeSpace_ctm — 85.2% line coverage.",
116+
"test_most_justification": "85.2% line coverage measured by SonarCloud across Go + TypeScript.",
117117

118118
"test_continuous_integration_status": "Met",
119-
"test_continuous_integration_justification": "https://github.com/RandomCodeSpace/ctm/actions — GitHub Actions runs Go build/test, UI typecheck/test, SonarCloud, CodeQL, and Scorecard on every push and PR.",
119+
"test_continuous_integration_justification": "GitHub Actions runs Go build/test, UI typecheck/test, SonarCloud, CodeQL, and Scorecard on every push and PR.",
120120

121121
"test_policy_status": "Met",
122122
"test_policy_justification": "New features must ship with tests; SonarCloud's new-code coverage gate fails PRs that drop coverage below threshold.",
@@ -125,7 +125,7 @@
125125
"tests_are_added_justification": "PRs adding functionality include unit and/or integration tests; enforced by the new-code coverage gate.",
126126

127127
"tests_documented_added_status": "Met",
128-
"tests_documented_added_justification": "test_policy is enforced in PR review and by the coverage gate; recent PRs (#11–#14) demonstrate the practice.",
128+
"tests_documented_added_justification": "test_policy is enforced in PR review and by the coverage gate.",
129129

130130
"warnings_status": "Met",
131131
"warnings_justification": "go vet, gopls language-server checks, ESLint with strict TypeScript rules, and SonarCloud all run on every push.",
@@ -161,7 +161,7 @@
161161
"crypto_weaknesses_justification": "No use of MD5, SHA1 (for integrity), DES, RC4, or ECB mode anywhere in the codebase.",
162162

163163
"crypto_pfs_status": "N/A",
164-
"crypto_pfs_justification": "ctm binds 127.0.0.1 only; TLS termination is the operator's reverse-proxy responsibility (the README documents the dev.randomcodespace.dev fronting setup).",
164+
"crypto_pfs_justification": "ctm binds 127.0.0.1 only; TLS termination is the operator's reverse-proxy responsibility.",
165165

166166
"crypto_password_storage_status": "Met",
167167
"crypto_password_storage_justification": "Passwords stored as argon2id hashes (V27 single-user auth); never logged or persisted in plaintext.",
@@ -173,7 +173,7 @@
173173
"delivery_mitm_justification": "Releases delivered via HTTPS (GitHub Releases) with TLS-protected git fetch.",
174174

175175
"delivery_unsigned_status": "Met",
176-
"delivery_unsigned_justification": "https://github.com/RandomCodeSpace/ctm/releases — release artifacts include SHA256 checksums via the release.yml workflow.",
176+
"delivery_unsigned_justification": "Release artifacts include SHA256 checksums via the release.yml workflow.",
177177

178178
"vulnerabilities_fixed_60_days_status": "Met",
179179
"vulnerabilities_fixed_60_days_justification": "No publicly disclosed vulnerabilities to date; commitment is to address any future critical reports within 60 days.",
@@ -185,26 +185,26 @@
185185
"no_leaked_credentials_justification": "SonarCloud's secret-detection rules + GitHub's secret scanning run on every push; no credentials in commit history.",
186186

187187
"static_analysis_status": "Met",
188-
"static_analysis_justification": "https://sonarcloud.io/summary/overall?id=RandomCodeSpace_ctm — SonarCloud (Go + TypeScript) and CodeQL (security) run on every push and PR.",
188+
"static_analysis_justification": "SonarCloud (Go + TypeScript) and CodeQL (security) run on every push and PR.",
189189

190190
"static_analysis_common_vulnerabilities_status": "Met",
191191
"static_analysis_common_vulnerabilities_justification": "CodeQL covers OWASP Top-10 vulnerability families; SonarCloud's security profile covers CWE Top-25.",
192192

193193
"static_analysis_fixed_status": "Met",
194-
"static_analysis_fixed_justification": "Findings are either fixed in code or explicitly Accepted with a documented justification (see .github/workflows/sonar-bulk-accept.yml).",
194+
"static_analysis_fixed_justification": "Findings are either fixed in code or explicitly Accepted with a documented justification.",
195195

196196
"static_analysis_often_status": "Met",
197197
"static_analysis_often_justification": "Static analysis runs on every push and PR — well exceeding the 'before each release' bar.",
198198

199-
"dynamic_analysis_status": "N/A",
200-
"dynamic_analysis_justification": "ctm is a CLI / HTTP daemon; integration tests exercise the live HTTP surface, which is the realistic dynamic-analysis bar for this class of software.",
199+
"dynamic_analysis_status": "Met",
200+
"dynamic_analysis_justification": "`go test -race ./...` runs Go's runtime data-race detector on every PR and on every release. The race detector instruments memory accesses across goroutines and panics on a detected race; for a goroutine-heavy HTTP+tmux daemon this is the realistic dynamic-analysis tool.",
201201

202202
"dynamic_analysis_unsafe_status": "N/A",
203-
"dynamic_analysis_unsafe_justification": "Go is memory-safe (no manual memory management, bounds-checked slices). The few unsafe.Pointer uses are inside the vendored sqlite3 driver.",
203+
"dynamic_analysis_unsafe_justification": "Go is memory-safe (no manual memory management; bounds-checked slices; nil-checked pointer dereferences). Race-detector coverage above provides the meaningful dynamic-safety check.",
204204

205-
"dynamic_analysis_enable_assertions_status": "N/A",
206-
"dynamic_analysis_enable_assertions_justification": "Go has no compile-time assertion mechanism; runtime panics are used for invariant violations.",
205+
"dynamic_analysis_enable_assertions_status": "Met",
206+
"dynamic_analysis_enable_assertions_justification": "Go's runtime always enables bounds checking, nil-pointer panics, and `go test -race` adds happens-before assertions across goroutines. Test builds run with the race detector enabled; production builds inherit the language-level checks but omit the race detector.",
207207

208-
"dynamic_analysis_fixed_status": "N/A",
209-
"dynamic_analysis_fixed_justification": "Same as dynamic_analysis."
208+
"dynamic_analysis_fixed_status": "Met",
209+
"dynamic_analysis_fixed_justification": "Any race-detector finding fails CI and must be fixed before merge."
210210
}

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,13 @@ jobs:
6464
run: go build -tags sqlite_fts5 ./...
6565

6666
- name: Go test
67-
run: go test -tags sqlite_fts5 ./...
67+
# -race: Go's runtime data-race detector. Instruments memory
68+
# accesses across goroutines and panics on a detected race —
69+
# the realistic dynamic-analysis tool for a pure-Go HTTP +
70+
# tmux-shelling-out daemon. Satisfies the OpenSSF Best
71+
# Practices `dynamic_analysis` and
72+
# `dynamic_analysis_enable_assertions` criteria.
73+
run: go test -tags sqlite_fts5 -race ./...
6874

6975
- name: UI typecheck
7076
run: pnpm -C ui exec tsc --noEmit

.github/workflows/release.yml

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ jobs:
7373
# `CREATE VIRTUAL TABLE … USING fts5(…)` — without the tag,
7474
# OpenCostStore() fails at runtime with "no such module: fts5"
7575
# and ~25 store/serve tests blow up.
76-
run: go test -tags sqlite_fts5 ./...
76+
# -race: Go's data-race detector. Gates the release on a clean
77+
# dynamic-analysis pass per OpenSSF Best Practices
78+
# `dynamic_analysis` / `dynamic_analysis_enable_assertions`.
79+
run: go test -tags sqlite_fts5 -race ./...
7780

7881
- name: Determine bump level
7982
id: level
@@ -118,6 +121,32 @@ jobs:
118121
VERSION: ${{ steps.version.outputs.next }}
119122
PREV: ${{ steps.version.outputs.latest }}
120123
run: |
124+
# Build human-readable, categorised release notes by
125+
# parsing Conventional Commit prefixes (feat:, fix:, etc.)
126+
# rather than dumping raw `git log` output. This satisfies
127+
# the OpenSSF Best Practices `release_notes` criterion,
128+
# which requires a human-readable summary that is NOT the
129+
# raw output of a version control log.
130+
range_args=()
131+
if [ "$PREV" != "v0.0.0" ]; then
132+
range_args+=("$PREV"..HEAD)
133+
fi
134+
git log --no-merges --pretty=format:'%s|%h' "${range_args[@]}" > /tmp/commits.tsv
135+
136+
group_by_prefix() {
137+
local heading="$1" prefix_re="$2"
138+
local body
139+
body=$(grep -E "^${prefix_re}(\\([^)]+\\))?(!)?:" /tmp/commits.tsv \
140+
| sed -E 's/^[a-z]+(\([^)]+\))?(!)?:[[:space:]]*(.*)\|([0-9a-f]+)$/- \3 (\4)/' \
141+
|| true)
142+
if [ -n "$body" ]; then
143+
echo "### ${heading}"
144+
echo
145+
echo "$body"
146+
echo
147+
fi
148+
}
149+
121150
{
122151
echo "## Install"
123152
echo
@@ -144,12 +173,26 @@ jobs:
144173
echo
145174
echo "The \`ctm-${VERSION}-src.tar.gz\` archive is a vendored source tree (\`go mod vendor\` populated) that builds offline with \`go build .\`."
146175
echo
147-
echo "## Changes"
176+
echo "## What's changed"
148177
echo
149-
if [ "$PREV" = "v0.0.0" ]; then
150-
git log --pretty=format:'- %s (%h)' | head -n 200
151-
else
152-
git log --pretty=format:'- %s (%h)' "$PREV"..HEAD
178+
group_by_prefix "Features" "feat"
179+
group_by_prefix "Bug fixes" "fix"
180+
group_by_prefix "Performance" "perf"
181+
group_by_prefix "Refactoring" "refactor"
182+
group_by_prefix "Documentation" "docs"
183+
group_by_prefix "Tests" "test"
184+
group_by_prefix "CI / build" "(ci|build)"
185+
group_by_prefix "Chores" "chore"
186+
187+
# Anything that doesn't follow Conventional Commits ends
188+
# up here so nothing gets dropped silently.
189+
other=$(grep -vE '^[a-z]+(\([^)]+\))?(!)?:' /tmp/commits.tsv \
190+
| sed -E 's/^(.*)\|([0-9a-f]+)$/- \1 (\2)/' || true)
191+
if [ -n "$other" ]; then
192+
echo "### Other"
193+
echo
194+
echo "$other"
195+
echo
153196
fi
154197
} > release-notes.md
155198
echo "Generated notes:"

0 commit comments

Comments
 (0)