From 8d564d8d9f55ecbe24b6bc030a724a3e428816dc Mon Sep 17 00:00:00 2001 From: CodeSigils Date: Thu, 16 Jul 2026 12:15:46 +0300 Subject: [PATCH 1/5] fix: harden ci and sync workflow --- .github/workflows/ci.yml | 2 ++ scripts/sync-payload.sh | 50 ++++++++++++++++++++++++++++++++-------- scripts/validate-ci.py | 2 ++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9215132..7e36c6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: paths: - ".github/workflows/ci.yml" - ".github/scripts/**" + - ".gitignore" - "README.md" - "CITATION.cff" - "SECURITY.md" @@ -17,6 +18,7 @@ on: paths: - ".github/workflows/ci.yml" - ".github/scripts/**" + - ".gitignore" - "README.md" - "CITATION.cff" - "SECURITY.md" diff --git a/scripts/sync-payload.sh b/scripts/sync-payload.sh index 26c4ff8..666ab21 100644 --- a/scripts/sync-payload.sh +++ b/scripts/sync-payload.sh @@ -7,6 +7,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" MANIFEST="$ROOT/scripts/payload-manifest.json" PAYLOAD_DIR="$ROOT/skills/python-project-workflow" CI_MODE=false +declare -a MANIFEST_FILES MANIFEST_SCRIPTS [ "${1:-}" = "--ci" ] && CI_MODE=true @@ -17,6 +18,40 @@ DRIFT=false CHANGED=false SYNC_ERROR=false +manifest_entries() { + local key="$1" + + python3 - "$MANIFEST" "$key" <<'PY' +import json +import sys + +path, key = sys.argv[1], sys.argv[2] +with open(path, encoding="utf-8") as fh: + data = json.load(fh) +for item in data.get(key, []): + sys.stdout.write(f"{item}\0") +PY +} + +manifest_scalar() { + local key="$1" + + python3 - "$MANIFEST" "$key" <<'PY' +import json +import sys + +path, key = sys.argv[1], sys.argv[2] +with open(path, encoding="utf-8") as fh: + data = json.load(fh) +value = data.get(key, "") +sys.stdout.write(value if isinstance(value, str) else "") +PY +} + +mapfile -d '' -t MANIFEST_FILES < <(manifest_entries files) +mapfile -d '' -t MANIFEST_SCRIPTS < <(manifest_entries scripts) +REF_MODE="$(manifest_scalar references)" + mode_matches() { local target="$1" local mode="$2" @@ -62,15 +97,13 @@ is_covered() { case "$rel" in SKILL.md) return 0 ;; esac - for f in $(python3 -c "import json; d=json.load(open('$MANIFEST')); print('\n'.join(str(x) for x in d.get('files',[])))" 2>/dev/null); do + for f in "${MANIFEST_FILES[@]}"; do [ "$rel" = "$f" ] && return 0 done - for s in $(python3 -c "import json; d=json.load(open('$MANIFEST')); print('\n'.join(str(x) for x in d.get('scripts',[])))" 2>/dev/null); do + for s in "${MANIFEST_SCRIPTS[@]}"; do [ "$rel" = "scripts/$s" ] && return 0 done - local ref_mode - ref_mode=$(python3 -c "import json; d=json.load(open('$MANIFEST')); r=d.get('references',''); print(r if isinstance(r,str) else '')" 2>/dev/null) - if [ "$ref_mode" = "*" ]; then + if [ "$REF_MODE" = "*" ]; then case "$rel" in references/*) [ -f "$ROOT/$rel" ] && return 0 ;; esac @@ -79,14 +112,14 @@ is_covered() { } # Files -for f in $(python3 -c "import json; d=json.load(open('$MANIFEST')); print('\n'.join(str(x) for x in d.get('files',[])))" 2>/dev/null); do +for f in "${MANIFEST_FILES[@]}"; do source="$ROOT/$f" target="$PAYLOAD_DIR/$f" sync_file "$source" "$target" "$f" done # Scripts -for s in $(python3 -c "import json; d=json.load(open('$MANIFEST')); print('\n'.join(str(x) for x in d.get('scripts',[])))" 2>/dev/null); do +for s in "${MANIFEST_SCRIPTS[@]}"; do source="$ROOT/scripts/$s" target="$PAYLOAD_DIR/scripts/$s" mode=644 @@ -95,8 +128,7 @@ for s in $(python3 -c "import json; d=json.load(open('$MANIFEST')); print('\n'.j done # References (mirror) -ref_mode=$(python3 -c "import json; d=json.load(open('$MANIFEST')); r=d.get('references',''); print(r if isinstance(r,str) else '')" 2>/dev/null) -if [ "$ref_mode" = "*" ]; then +if [ "$REF_MODE" = "*" ]; then for source in "$ROOT/references/"*.md; do if [ ! -f "$source" ]; then echo " MISSING source: references/*.md" diff --git a/scripts/validate-ci.py b/scripts/validate-ci.py index b2c8c4f..5751d4e 100644 --- a/scripts/validate-ci.py +++ b/scripts/validate-ci.py @@ -29,6 +29,8 @@ def main() -> int: if validate is None: errors.append("ci.yml: missing validate job") else: + if '".gitignore"' not in workflow: + errors.append("ci.yml: validation workflow must trigger on .gitignore changes") if "python3 scripts/verify-urls.py" in validate: errors.append( "ci.yml: live URL checks must not run in the validation matrix" From 4168b77aad0765efb09a814b090f5fffe9d41aa5 Mon Sep 17 00:00:00 2001 From: CodeSigils Date: Thu, 16 Jul 2026 12:30:10 +0300 Subject: [PATCH 2/5] chore: remove stale gitignore entry for renamed skill workspace --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index eab8269..7e439a4 100644 --- a/.gitignore +++ b/.gitignore @@ -49,10 +49,6 @@ ENV/ .DS_Store .vscode/ -# Local transcript benchmark workspace -python-best-practices-workspace/ -# ^ this ignore entry is stale (skill was renamed). Kept to not break existing clones. - # Local agent/tool state .omo/ .open-mem/ From 8a8e5de92d340093947e7639b8206648d4c04389 Mon Sep 17 00:00:00 2001 From: CodeSigils Date: Thu, 16 Jul 2026 12:32:54 +0300 Subject: [PATCH 3/5] fix: replace mapfile with portable read loop for macOS bash 3.2 compat --- scripts/sync-payload.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/sync-payload.sh b/scripts/sync-payload.sh index 666ab21..2492153 100644 --- a/scripts/sync-payload.sh +++ b/scripts/sync-payload.sh @@ -48,8 +48,9 @@ sys.stdout.write(value if isinstance(value, str) else "") PY } -mapfile -d '' -t MANIFEST_FILES < <(manifest_entries files) -mapfile -d '' -t MANIFEST_SCRIPTS < <(manifest_entries scripts) +# Portable null-delimited read (compatible with bash 3.2 on macOS) +while IFS= read -r -d '' item; do MANIFEST_FILES+=("$item"); done < <(manifest_entries files) +while IFS= read -r -d '' item; do MANIFEST_SCRIPTS+=("$item"); done < <(manifest_entries scripts) REF_MODE="$(manifest_scalar references)" mode_matches() { From 54995b4dfa30cc82eedc356ddde5f0c2b5f9a47a Mon Sep 17 00:00:00 2001 From: CodeSigils Date: Thu, 16 Jul 2026 12:36:10 +0300 Subject: [PATCH 4/5] fix: use newline-delimited read instead of NUL for bash 3.2 compat --- scripts/sync-payload.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/sync-payload.sh b/scripts/sync-payload.sh index 2492153..1e4753a 100644 --- a/scripts/sync-payload.sh +++ b/scripts/sync-payload.sh @@ -29,7 +29,7 @@ path, key = sys.argv[1], sys.argv[2] with open(path, encoding="utf-8") as fh: data = json.load(fh) for item in data.get(key, []): - sys.stdout.write(f"{item}\0") + print(item) PY } @@ -48,9 +48,9 @@ sys.stdout.write(value if isinstance(value, str) else "") PY } -# Portable null-delimited read (compatible with bash 3.2 on macOS) -while IFS= read -r -d '' item; do MANIFEST_FILES+=("$item"); done < <(manifest_entries files) -while IFS= read -r -d '' item; do MANIFEST_SCRIPTS+=("$item"); done < <(manifest_entries scripts) +# Portable newline-delimited read (compatible with bash 3.2 on macOS) +while IFS= read -r item; do MANIFEST_FILES+=("$item"); done < <(manifest_entries files) +while IFS= read -r item; do MANIFEST_SCRIPTS+=("$item"); done < <(manifest_entries scripts) REF_MODE="$(manifest_scalar references)" mode_matches() { From a15da669c03c51babfb4d738c090091b4c08daf3 Mon Sep 17 00:00:00 2001 From: CodeSigils Date: Thu, 16 Jul 2026 12:40:01 +0300 Subject: [PATCH 5/5] fix: guard empty arrays in bash 3.2 set -u and declare local loop vars --- scripts/sync-payload.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/sync-payload.sh b/scripts/sync-payload.sh index 1e4753a..84007ff 100644 --- a/scripts/sync-payload.sh +++ b/scripts/sync-payload.sh @@ -7,7 +7,8 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" MANIFEST="$ROOT/scripts/payload-manifest.json" PAYLOAD_DIR="$ROOT/skills/python-project-workflow" CI_MODE=false -declare -a MANIFEST_FILES MANIFEST_SCRIPTS +MANIFEST_FILES=() +MANIFEST_SCRIPTS=() [ "${1:-}" = "--ci" ] && CI_MODE=true @@ -48,9 +49,10 @@ sys.stdout.write(value if isinstance(value, str) else "") PY } -# Portable newline-delimited read (compatible with bash 3.2 on macOS) -while IFS= read -r item; do MANIFEST_FILES+=("$item"); done < <(manifest_entries files) -while IFS= read -r item; do MANIFEST_SCRIPTS+=("$item"); done < <(manifest_entries scripts) +# Portable newline-delimited read (compatible with bash 3.2 on macOS). +# The ;echo guarantees at least one line so arrays are never empty under set -u. +while IFS= read -r item; do MANIFEST_FILES+=("$item"); done < <(manifest_entries files; echo) +while IFS= read -r item; do MANIFEST_SCRIPTS+=("$item"); done < <(manifest_entries scripts; echo) REF_MODE="$(manifest_scalar references)" mode_matches() { @@ -94,14 +96,17 @@ sync_file() { is_covered() { local rel="$1" + local f s # Authored-in-place files (written directly in payload, not copied) case "$rel" in SKILL.md) return 0 ;; esac for f in "${MANIFEST_FILES[@]}"; do + [ -z "$f" ] && continue [ "$rel" = "$f" ] && return 0 done for s in "${MANIFEST_SCRIPTS[@]}"; do + [ -z "$s" ] && continue [ "$rel" = "scripts/$s" ] && return 0 done if [ "$REF_MODE" = "*" ]; then @@ -114,6 +119,7 @@ is_covered() { # Files for f in "${MANIFEST_FILES[@]}"; do + [ -z "$f" ] && continue source="$ROOT/$f" target="$PAYLOAD_DIR/$f" sync_file "$source" "$target" "$f" @@ -121,6 +127,7 @@ done # Scripts for s in "${MANIFEST_SCRIPTS[@]}"; do + [ -z "$s" ] && continue source="$ROOT/scripts/$s" target="$PAYLOAD_DIR/scripts/$s" mode=644