Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adept",
"version": "4.7.0",
"version": "4.7.1",
"description": "Development-workflow skills: design, TDD, adversarial review, shipping, and campaign orchestration for Claude Code and Codex.",
"author": {
"name": "David Christensen"
Expand Down
12 changes: 12 additions & 0 deletions scripts/check-public-safety-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ if "$CHECKER" "$SCRATCH/repo" >"$SCRATCH/output" 2>&1; then
fi
rm -f "$SCRATCH/repo/linux.txt"

# Windows profile paths can appear in a publication composed on any host.
for private_path in 'C:\Us''ers\example-user\project' 'd:/us''ers/example-user/project'; do
printf '%s\n' "$private_path" >"$SCRATCH/repo/windows.txt"
status=0
"$CHECKER" "$SCRATCH/repo/windows.txt" >"$SCRATCH/output" 2>&1 || status=$?
if [ "$status" -ne 1 ]; then
printf 'public-safety-test: Windows profile leak must return 1, got %s\n' "$status" >&2
exit 1
fi
done
rm -f "$SCRATCH/repo/windows.txt"

# /home/runner and /home/linuxbrew are published constants of GitHub's runner
# images, not anyone's home directory. The ubuntu image documents the shellenv
# line below as the way to reach Homebrew, so a workflow that carries it must
Expand Down
370 changes: 4 additions & 366 deletions scripts/check-public-safety.sh

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions skills/quest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,17 @@ specifies into a `mktemp` file beside the ledger — no headings; each destinati
its own — atomically rename it only after the write, reject carriage return, NUL, and
outer annotation markers, and keep the temporary and installed payload in mode 0600.
A run with nothing to carry creates no payload file and skips every payload step below.

The publication helper and its `scripts/check-public-safety` scanner ship together in this
skill. Resolve both from the installed skill directory; never search the target repository for
Adept maintenance scripts. The scanner requires `rg` and `jq`; the helper checks these alongside
its other commands before composing content. Missing commands name an installation/PATH remedy.
A missing bundled scanner requires repairing the installed skill, not substituting a repository gate.
The scanner checks generic private paths (including Windows profile paths), private addresses,
and credential patterns. It is a backstop to the public-safety review, not exhaustive PII detection.
Unsafe content and scan failures both stop publication and retain evidence, with distinct messages
that do not echo matched content.

Before any PR-body write, invoke the helper in validation-only mode with the exact publication
arguments:

Expand Down
362 changes: 362 additions & 0 deletions skills/quest/scripts/check-public-safety

Large diffs are not rendered by default.

22 changes: 15 additions & 7 deletions skills/quest/scripts/publish-forge-review
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ MAX_BODY_BYTES=32768
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[ "$SCRIPT_DIR" = "${BASH_SOURCE[0]}" ] && SCRIPT_DIR=.
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
body=''
disposer=''
repo=''
Expand Down Expand Up @@ -46,17 +45,20 @@ fail() {

preflight() {
local command
for command in gh jq mktemp awk git iconv od grep sed dirname tail uname cat chmod stat wc rm; do
command -v "$command" >/dev/null 2>&1 || fail "required command is unavailable"
for command in gh jq rg mktemp awk git iconv od grep sed dirname tail uname cat chmod stat wc rm; do
command -v "$command" >/dev/null 2>&1 ||
fail "required command is unavailable: $command; install it or expose it in PATH"
done
platform=$(uname -s)
case $platform in
Darwin) disposer=trash ;;
Linux) disposer=gio ;;
*) fail 'no supported recoverable-delete command on this platform' ;;
esac
command -v "$disposer" >/dev/null 2>&1 || fail 'recoverable-delete command is unavailable'
[ -x "$ROOT/scripts/check-public-safety.sh" ] || fail 'public-safety check is unavailable'
command -v "$disposer" >/dev/null 2>&1 ||
fail "recoverable-delete command is unavailable: $disposer; install it or expose it in PATH"
[ -x "$SCRIPT_DIR/check-public-safety" ] ||
fail 'bundled public-safety check is unavailable; repair the installed quest skill'
}

validate_arguments() {
Expand Down Expand Up @@ -225,8 +227,14 @@ compose_body() {
fi
printf '%s\n' "$OUTER_SENTINEL" >>"$body" || fail 'cannot compose publication body'
check_size 'publication body' "$body" "$MAX_BODY_BYTES"
"$ROOT/scripts/check-public-safety.sh" "$body" >/dev/null 2>&1 ||
fail 'publication body did not pass public-safety validation'
local scan_status=0
# Scanner diagnostics contain matched private content; expose only the outcome.
"$SCRIPT_DIR/check-public-safety" "$body" >/dev/null 2>&1 || scan_status=$?
case $scan_status in
0) ;;
1) fail 'publication body contains unsafe content; redact the retained body and source inputs' ;;
*) fail "public-safety scan could not complete (exit $scan_status); check scanner dependencies and input access" ;;
esac
}

post_comment() {
Expand Down
39 changes: 38 additions & 1 deletion tests/fixtures/quest/publish-forge-review-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. "$SCRIPT_DIR/../../../scripts/test-fixture-helpers.sh"

clear_git_env
SCRIPT="$SCRIPT_DIR/../../../skills/quest/scripts/publish-forge-review"
SKILL_SOURCE="$SCRIPT_DIR/../../../skills/quest"
ORIGINAL_PATH=$PATH
SYSTEM_CAT=$(command -v cat)
SYSTEM_TAIL=$(command -v tail)
Expand All @@ -16,6 +16,9 @@ SYSTEM_UNAME=$(command -v uname)
passed=0
failed=0
fixture_init publish-forge-review-test
# Run every publication case from the installed skill alone.
cp -R "$SKILL_SOURCE" "$SCRATCH/quest"
SCRIPT="$SCRATCH/quest/scripts/publish-forge-review"

ok() {
passed=$((passed + 1))
Expand Down Expand Up @@ -351,6 +354,10 @@ case_public_safety_stops_publication() {
if [ "$STATUS" -eq 0 ] || ! assert_no_post "$name" || ! assert_retained "$name"; then
return
fi
if ! grep -q 'unsafe content' "$REPO/error"; then
fail "$name" 'unsafe content was not distinguished from a scanner fault'
return
fi
new_case
mkdir -p "$REPO/scan-fault"
printf '%s\n' '#!/usr/bin/env bash' 'exit 2' >"$REPO/scan-fault/rg"
Expand All @@ -359,6 +366,35 @@ case_public_safety_stops_publication() {
if [ "$STATUS" -eq 0 ] || ! assert_no_post "$name" || ! assert_retained "$name"; then
return
fi
if ! grep -q 'scan could not complete (exit 2)' "$REPO/error"; then
fail "$name" 'scanner fault was reported as unsafe content'
return
fi
ok "$name"
}

case_missing_scan_dependencies() {
local name='PFR missing scan dependencies fail before composition' missing tool
for missing in rg jq; do
new_case
mkdir "$REPO/bin"
for tool in bash gh jq rg mktemp awk git iconv od grep sed dirname tail uname cat chmod stat wc rm; do
[ "$tool" != "$missing" ] || continue
ln -s "$(command -v "$tool")" "$REPO/bin/$tool"
done
cp "$LEDGER" "$REPO/ledger-before"
run_preflight required "$REVIEW" /usr/bin/env PATH="$FAKES:$REPO/bin"
if [ "$STATUS" -eq 0 ] || [ ! -f "$REVIEW" ] || [ ! -f "$SUMMARY" ] ||
! cmp -s "$LEDGER" "$REPO/ledger-before" || body_file >/dev/null; then
fail "$name" 'missing dependency did not stop before composition with sources retained'
return
fi
assert_no_post "$name" || return
if ! grep -q "required command is unavailable: $missing" "$REPO/error"; then
fail "$name" 'missing tool was not named'
return
fi
done
ok "$name"
}

Expand Down Expand Up @@ -985,6 +1021,7 @@ case_argument_arity_is_bounded() {
printf 'publish-forge-review\n\n'
case_required_safe_review
case_public_safety_stops_publication
case_missing_scan_dependencies
case_compose_source_failure_stops_publication
case_publication_modes
case_comment_failures_never_retry
Expand Down