@@ -7,6 +7,8 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
77MANIFEST=" $ROOT /scripts/payload-manifest.json"
88PAYLOAD_DIR=" $ROOT /skills/python-project-workflow"
99CI_MODE=false
10+ MANIFEST_FILES=()
11+ MANIFEST_SCRIPTS=()
1012
1113[ " ${1:- } " = " --ci" ] && CI_MODE=true
1214
@@ -17,6 +19,42 @@ DRIFT=false
1719CHANGED=false
1820SYNC_ERROR=false
1921
22+ manifest_entries () {
23+ local key=" $1 "
24+
25+ python3 - " $MANIFEST " " $key " << 'PY '
26+ import json
27+ import sys
28+
29+ path, key = sys.argv[1], sys.argv[2]
30+ with open(path, encoding="utf-8") as fh:
31+ data = json.load(fh)
32+ for item in data.get(key, []):
33+ print(item)
34+ PY
35+ }
36+
37+ manifest_scalar () {
38+ local key=" $1 "
39+
40+ python3 - " $MANIFEST " " $key " << 'PY '
41+ import json
42+ import sys
43+
44+ path, key = sys.argv[1], sys.argv[2]
45+ with open(path, encoding="utf-8") as fh:
46+ data = json.load(fh)
47+ value = data.get(key, "")
48+ sys.stdout.write(value if isinstance(value, str) else "")
49+ PY
50+ }
51+
52+ # Portable newline-delimited read (compatible with bash 3.2 on macOS).
53+ # The ;echo guarantees at least one line so arrays are never empty under set -u.
54+ while IFS= read -r item; do MANIFEST_FILES+=(" $item " ); done < <( manifest_entries files; echo)
55+ while IFS= read -r item; do MANIFEST_SCRIPTS+=(" $item " ); done < <( manifest_entries scripts; echo)
56+ REF_MODE=" $( manifest_scalar references) "
57+
2058mode_matches () {
2159 local target=" $1 "
2260 local mode=" $2 "
@@ -58,19 +96,20 @@ sync_file() {
5896
5997is_covered () {
6098 local rel=" $1 "
99+ local f s
61100 # Authored-in-place files (written directly in payload, not copied)
62101 case " $rel " in
63102 SKILL.md) return 0 ;;
64103 esac
65- 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
104+ for f in " ${MANIFEST_FILES[@]} " ; do
105+ [ -z " $f " ] && continue
66106 [ " $rel " = " $f " ] && return 0
67107 done
68- 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
108+ for s in " ${MANIFEST_SCRIPTS[@]} " ; do
109+ [ -z " $s " ] && continue
69110 [ " $rel " = " scripts/$s " ] && return 0
70111 done
71- local ref_mode
72- 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)
73- if [ " $ref_mode " = " *" ]; then
112+ if [ " $REF_MODE " = " *" ]; then
74113 case " $rel " in
75114 references/* ) [ -f " $ROOT /$rel " ] && return 0 ;;
76115 esac
@@ -79,14 +118,16 @@ is_covered() {
79118}
80119
81120# Files
82- 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
121+ for f in " ${MANIFEST_FILES[@]} " ; do
122+ [ -z " $f " ] && continue
83123 source=" $ROOT /$f "
84124 target=" $PAYLOAD_DIR /$f "
85125 sync_file " $source " " $target " " $f "
86126done
87127
88128# Scripts
89- 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
129+ for s in " ${MANIFEST_SCRIPTS[@]} " ; do
130+ [ -z " $s " ] && continue
90131 source=" $ROOT /scripts/$s "
91132 target=" $PAYLOAD_DIR /scripts/$s "
92133 mode=644
@@ -95,8 +136,7 @@ for s in $(python3 -c "import json; d=json.load(open('$MANIFEST')); print('\n'.j
95136done
96137
97138# References (mirror)
98- 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)
99- if [ " $ref_mode " = " *" ]; then
139+ if [ " $REF_MODE " = " *" ]; then
100140 for source in " $ROOT /references/" * .md; do
101141 if [ ! -f " $source " ]; then
102142 echo " MISSING source: references/*.md"
0 commit comments