bigpowers 2.58.1 → 2.58.3
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.
- package/.pi/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/CONVENTIONS.md +12 -0
- package/package.json +1 -1
- package/scripts/audit-compliance.sh +3 -50
- package/scripts/check-blind-spots.sh +9 -307
- package/scripts/generate-skill-index.sh +1 -0
- package/scripts/land-branch.sh +2 -2
- package/scripts/lib/blind-spots.py +234 -0
- package/scripts/lib/git-hours.sh +50 -0
- package/scripts/lib/trace-stories.py +362 -0
- package/scripts/lib/waiver-utils.sh +39 -0
- package/scripts/record-cycle-time.sh +10 -52
- package/scripts/trace-stories.sh +8 -552
- package/scripts/validate-okf.sh +204 -119
- package/scripts/wait-for-ci.sh +3 -3
package/scripts/validate-okf.sh
CHANGED
|
@@ -1,59 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# validate-okf.sh — provenance gate for OKF
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# 1. The generator ran (generator field matches scripts/record-cycle-time.sh).
|
|
7
|
-
# 2. commit_range resolves to real commits in the repo.
|
|
8
|
-
# 3. Source enum is valid (measured|estimated|backfilled).
|
|
9
|
-
# 4. Required keys are present with correct aggregation tags.
|
|
10
|
-
#
|
|
11
|
-
# This script gates on PROVENANCE, NEVER on a specific metric value.
|
|
12
|
-
# Gate on freshness (did the right generator run?) not on outcomes.
|
|
13
|
-
#
|
|
14
|
-
# Usage:
|
|
15
|
-
# scripts/validate-okf.sh [--dir <path>] [--help]
|
|
16
|
-
# scripts/validate-okf.sh --bundle <file> # validate a single bundle
|
|
17
|
-
#
|
|
2
|
+
# story: e38s09
|
|
3
|
+
# validate-okf.sh — kind-aware, fail-closed provenance gate for OKF bundles.
|
|
4
|
+
# Validates story-metrics, spec-migration, and migration-registry bundles.
|
|
5
|
+
# Multi-dir default: specs/metrics/ + specs/migrations/.
|
|
18
6
|
set -euo pipefail
|
|
19
7
|
|
|
20
8
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "validate-okf: not inside a git repo"; exit 1; }
|
|
21
|
-
METRICS_DIR="$ROOT/specs/metrics"
|
|
22
9
|
EXIT_CODE=0
|
|
23
|
-
REQUIRED_KEYS="id epic bcps commit_range source generated_at generator"
|
|
24
|
-
VALID_SOURCES="measured estimated backfilled"
|
|
25
10
|
|
|
26
11
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'
|
|
27
12
|
|
|
28
|
-
|
|
13
|
+
usage_okf() {
|
|
29
14
|
cat <<'EOF'
|
|
30
15
|
Usage: scripts/validate-okf.sh [flags]
|
|
31
16
|
|
|
32
17
|
Flags:
|
|
33
|
-
--dir <path> Validate all OKF bundles in directory
|
|
18
|
+
--dir <path> Validate all OKF bundles in directory
|
|
34
19
|
--bundle <file> Validate a single OKF bundle
|
|
35
20
|
--help Show this message
|
|
36
21
|
|
|
37
22
|
Description:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
23
|
+
Kind-aware provenance gate for OKF bundles. Without flags, validates all
|
|
24
|
+
bundles in specs/metrics/ AND specs/migrations/. NEVER gates on a specific
|
|
25
|
+
metric value — only on provenance and schema conformance.
|
|
26
|
+
|
|
27
|
+
OKF kinds validated:
|
|
28
|
+
story-metrics — effort/lead-time provenance (e40)
|
|
29
|
+
spec-migration — migration bundle schema (e44)
|
|
30
|
+
migration-registry — registry index integrity (e44)
|
|
46
31
|
EOF
|
|
47
32
|
exit 0
|
|
48
33
|
}
|
|
49
34
|
|
|
50
|
-
#
|
|
51
|
-
|
|
35
|
+
# ---- Helpers ------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# True if value is empty, null JSON literal, or Python None string.
|
|
38
|
+
val_nullish() { [ -z "$1" ] || [ "$1" = "null" ] || [ "$1" = "None" ]; }
|
|
39
|
+
|
|
40
|
+
# True if value is nullish OR the JSON empty array literal.
|
|
41
|
+
val_nullish_or_empty() { val_nullish "$1" || [ "$1" = "[]" ]; }
|
|
42
|
+
|
|
43
|
+
# ---- YAML frontmatter parser (datetime-safe) ---------------------------
|
|
52
44
|
parse_frontmatter() {
|
|
53
45
|
local file="$1"
|
|
54
|
-
# Extract YAML frontmatter between --- markers
|
|
55
46
|
python3 -c "
|
|
56
|
-
import yaml, sys
|
|
47
|
+
import yaml, sys, json
|
|
48
|
+
|
|
49
|
+
class SafeEncoder(json.JSONEncoder):
|
|
50
|
+
def default(self, obj):
|
|
51
|
+
try:
|
|
52
|
+
return super().default(obj)
|
|
53
|
+
except TypeError:
|
|
54
|
+
return str(obj)
|
|
55
|
+
|
|
57
56
|
content = open('$file').read()
|
|
58
57
|
parts = content.split('---')
|
|
59
58
|
if len(parts) < 3:
|
|
@@ -62,137 +61,223 @@ try:
|
|
|
62
61
|
fm = yaml.safe_load(parts[1])
|
|
63
62
|
if fm is None:
|
|
64
63
|
sys.exit(1)
|
|
65
|
-
|
|
66
|
-
import json
|
|
67
|
-
print(json.dumps(fm))
|
|
64
|
+
print(json.dumps(fm, cls=SafeEncoder))
|
|
68
65
|
except Exception as e:
|
|
69
|
-
print(f'
|
|
66
|
+
print(f'YAML parse error: {e}', file=sys.stderr)
|
|
70
67
|
sys.exit(1)
|
|
71
68
|
" 2>/dev/null
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
local file="$1"
|
|
76
|
-
local bundle_name; bundle_name="$(basename "$file")"
|
|
77
|
-
|
|
78
|
-
# Parse frontmatter
|
|
79
|
-
local fm
|
|
80
|
-
fm="$(parse_frontmatter "$file")" || {
|
|
81
|
-
printf "${RED}FAIL${NC} %s: cannot parse YAML frontmatter\n" "$bundle_name"
|
|
82
|
-
EXIT_CODE=1
|
|
83
|
-
return
|
|
84
|
-
}
|
|
71
|
+
# ---- Kind-specific validators ------------------------------------------
|
|
85
72
|
|
|
86
|
-
|
|
87
|
-
local
|
|
88
|
-
|
|
89
|
-
if [ "$kind" != "story-metrics" ]; then
|
|
90
|
-
printf "${YELLOW}SKIP${NC} %s: not a story-metrics bundle (kind=%s)\n" "$bundle_name" "${kind:-none}"
|
|
91
|
-
return
|
|
92
|
-
fi
|
|
73
|
+
validate_story_metrics() {
|
|
74
|
+
local file="$1" fm="$2" name="$3"
|
|
75
|
+
local errs=0
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
for key in $REQUIRED_KEYS; do
|
|
77
|
+
for key in id epic bcps commit_range source generated_at generator; do
|
|
96
78
|
local val
|
|
97
79
|
val="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
98
|
-
if
|
|
99
|
-
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$
|
|
100
|
-
|
|
101
|
-
return
|
|
80
|
+
if val_nullish "$val"; then
|
|
81
|
+
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
82
|
+
errs=$((errs + 1))
|
|
102
83
|
fi
|
|
103
84
|
done
|
|
85
|
+
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
104
86
|
|
|
105
|
-
#
|
|
87
|
+
# Source enum
|
|
106
88
|
local source
|
|
107
|
-
source="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d
|
|
108
|
-
if ! echo "
|
|
109
|
-
printf "${RED}FAIL${NC} %s: invalid source '%s' — must be
|
|
110
|
-
EXIT_CODE=1
|
|
111
|
-
return
|
|
89
|
+
source="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('source',''))" 2>/dev/null)"
|
|
90
|
+
if ! echo "measured estimated backfilled" | grep -qw "$source"; then
|
|
91
|
+
printf "${RED}FAIL${NC} %s: invalid source '%s' — must be measured|estimated|backfilled\n" "$name" "$source"
|
|
92
|
+
EXIT_CODE=1; return
|
|
112
93
|
fi
|
|
113
94
|
|
|
114
|
-
#
|
|
95
|
+
# Generator
|
|
115
96
|
local generator
|
|
116
|
-
generator="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d
|
|
97
|
+
generator="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('generator',''))" 2>/dev/null)"
|
|
117
98
|
if [ "$generator" != "scripts/record-cycle-time.sh" ]; then
|
|
118
|
-
printf "${RED}FAIL${NC} %s: wrong generator '%s' — expected scripts/record-cycle-time.sh\n" "$
|
|
119
|
-
EXIT_CODE=1
|
|
120
|
-
return
|
|
99
|
+
printf "${RED}FAIL${NC} %s: wrong generator '%s' — expected scripts/record-cycle-time.sh\n" "$name" "$generator"
|
|
100
|
+
EXIT_CODE=1; return
|
|
121
101
|
fi
|
|
122
102
|
|
|
123
|
-
#
|
|
103
|
+
# commit_range resolves
|
|
124
104
|
local commit_range
|
|
125
|
-
commit_range="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d
|
|
105
|
+
commit_range="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
|
|
126
106
|
if ! git -C "$ROOT" log --oneline -1 "$commit_range" >/dev/null 2>&1; then
|
|
127
|
-
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$
|
|
128
|
-
EXIT_CODE=1
|
|
129
|
-
return
|
|
107
|
+
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
|
|
108
|
+
EXIT_CODE=1; return
|
|
130
109
|
fi
|
|
131
110
|
|
|
132
|
-
#
|
|
133
|
-
local
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
111
|
+
# effort.effort_hours present and non-negative
|
|
112
|
+
local eff
|
|
113
|
+
eff="$(echo "$fm" | python3 -c "
|
|
114
|
+
import json,sys
|
|
115
|
+
d=json.load(sys.stdin)
|
|
116
|
+
e=d.get('effort',{}).get('effort_hours')
|
|
117
|
+
if e is None: sys.exit(1)
|
|
118
|
+
if e < 0: sys.exit(2)
|
|
119
|
+
print(e)
|
|
120
|
+
" 2>/dev/null)" || {
|
|
121
|
+
local rc=$?
|
|
122
|
+
if [ "$rc" -eq 1 ]; then
|
|
123
|
+
printf "${RED}FAIL${NC} %s: effort.effort_hours missing or null\n" "$name"
|
|
124
|
+
else
|
|
125
|
+
printf "${RED}FAIL${NC} %s: effort_hours out of bounds\n" "$name"
|
|
126
|
+
fi
|
|
127
|
+
EXIT_CODE=1; return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
printf "${GREEN}PASS${NC} %s (source=%s, generator=%s, commit_range resolves)\n" "$name" "$source" "$generator"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
validate_spec_migration() {
|
|
134
|
+
local file="$1" fm="$2" name="$3"
|
|
135
|
+
local errs=0
|
|
136
|
+
|
|
137
|
+
for key in id title since_version order actions_needed; do
|
|
138
|
+
local val
|
|
139
|
+
val="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
140
|
+
if val_nullish_or_empty "$val"; then
|
|
141
|
+
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
142
|
+
errs=$((errs + 1))
|
|
143
|
+
fi
|
|
144
|
+
done
|
|
145
|
+
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
146
|
+
|
|
147
|
+
# fingerprint.any has at least one check
|
|
148
|
+
local fp_count
|
|
149
|
+
fp_count="$(echo "$fm" | python3 -c "
|
|
150
|
+
import json,sys
|
|
151
|
+
d=json.load(sys.stdin)
|
|
152
|
+
fp=d.get('fingerprint',{}).get('any',[])
|
|
153
|
+
print(len(fp))
|
|
154
|
+
" 2>/dev/null)"
|
|
155
|
+
if [ "${fp_count:-0}" -eq 0 ]; then
|
|
156
|
+
printf "${RED}FAIL${NC} %s: fingerprint.any is empty — no detection criteria\n" "$name"
|
|
157
|
+
EXIT_CODE=1; return
|
|
139
158
|
fi
|
|
140
159
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
import json, sys
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
printf "${GREEN}PASS${NC} %s (actions=%s, fingerprint has %s check(s))\n" \
|
|
161
|
+
"$name" \
|
|
162
|
+
"$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('actions_needed',[])))" 2>/dev/null)" \
|
|
163
|
+
"$fp_count"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
validate_migration_registry() {
|
|
167
|
+
local file="$1" fm="$2" name="$3"
|
|
168
|
+
|
|
169
|
+
local count
|
|
170
|
+
count="$(echo "$fm" | python3 -c "
|
|
171
|
+
import json,sys
|
|
172
|
+
d=json.load(sys.stdin)
|
|
173
|
+
migrations=d.get('migrations',[])
|
|
174
|
+
print(len(migrations))
|
|
175
|
+
" 2>/dev/null)"
|
|
176
|
+
if [ "${count:-0}" -eq 0 ]; then
|
|
177
|
+
printf "${RED}FAIL${NC} %s: migrations list is empty\n" "$name"
|
|
178
|
+
EXIT_CODE=1; return
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Check each migration has id + file + status
|
|
182
|
+
local bad
|
|
183
|
+
bad="$(echo "$fm" | python3 -c "
|
|
184
|
+
import json,sys
|
|
185
|
+
d=json.load(sys.stdin)
|
|
186
|
+
for m in d.get('migrations',[]):
|
|
187
|
+
if not all(k in m for k in ('id','file','status')):
|
|
188
|
+
print(m.get('id','?')); sys.exit(1)
|
|
189
|
+
" 2>/dev/null)" || {
|
|
190
|
+
printf "${RED}FAIL${NC} %s: migration '%s' missing id/file/status\n" "$name" "${bad:-?}"
|
|
191
|
+
EXIT_CODE=1; return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
printf "${GREEN}PASS${NC} %s (%s migrations indexed)\n" "$name" "$count"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# ---- Main validation dispatcher ----------------------------------------
|
|
198
|
+
validate_bundle() {
|
|
199
|
+
local file="$1"
|
|
200
|
+
local name; name="$(basename "$file")"
|
|
201
|
+
|
|
202
|
+
local fm
|
|
203
|
+
fm="$(parse_frontmatter "$file")" || {
|
|
204
|
+
printf "${RED}FAIL${NC} %s: cannot parse YAML frontmatter\n" "$name"
|
|
150
205
|
EXIT_CODE=1
|
|
151
206
|
return
|
|
152
207
|
}
|
|
153
208
|
|
|
154
|
-
|
|
209
|
+
local kind
|
|
210
|
+
kind="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('okf_kind',''))" 2>/dev/null)"
|
|
211
|
+
|
|
212
|
+
case "$kind" in
|
|
213
|
+
story-metrics) validate_story_metrics "$file" "$fm" "$name" ;;
|
|
214
|
+
spec-migration) validate_spec_migration "$file" "$fm" "$name" ;;
|
|
215
|
+
migration-registry) validate_migration_registry "$file" "$fm" "$name" ;;
|
|
216
|
+
"")
|
|
217
|
+
printf "${YELLOW}SKIP${NC} %s: no okf_kind — not an OKF bundle\n" "$name"
|
|
218
|
+
;;
|
|
219
|
+
*)
|
|
220
|
+
printf "${YELLOW}SKIP${NC} %s: unknown okf_kind '%s'\n" "$name" "$kind"
|
|
221
|
+
;;
|
|
222
|
+
esac
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# ---- Scan directory ----------------------------------------------------
|
|
226
|
+
scan_dir() {
|
|
227
|
+
local dir="$1"
|
|
228
|
+
if [ ! -d "$dir" ]; then
|
|
229
|
+
printf "${RED}FAIL${NC} directory not found: %s\n" "$dir"
|
|
230
|
+
EXIT_CODE=1
|
|
231
|
+
return
|
|
232
|
+
fi
|
|
233
|
+
local count=0
|
|
234
|
+
while IFS= read -r -d '' f; do
|
|
235
|
+
if head -1 "$f" 2>/dev/null | grep -q '^---$'; then
|
|
236
|
+
validate_bundle "$f"
|
|
237
|
+
count=$((count + 1))
|
|
238
|
+
fi
|
|
239
|
+
done < <(find "$dir" -maxdepth 1 -name '*.md' -print0 2>/dev/null || true)
|
|
240
|
+
if [ "$count" -eq 0 ]; then
|
|
241
|
+
printf "${YELLOW}SKIP${NC} no OKF bundles found in %s\n" "$dir"
|
|
242
|
+
fi
|
|
155
243
|
}
|
|
156
244
|
|
|
157
245
|
# ---- main ----------------------------------------------------------------
|
|
158
|
-
|
|
246
|
+
DIRS=()
|
|
159
247
|
BUNDLE=""
|
|
160
248
|
|
|
161
|
-
[ $# -
|
|
249
|
+
if [ $# -eq 0 ]; then
|
|
250
|
+
DIRS=("$ROOT/specs/metrics" "$ROOT/specs/migrations")
|
|
251
|
+
fi
|
|
162
252
|
|
|
163
253
|
while [ $# -gt 0 ]; do
|
|
164
254
|
case "$1" in
|
|
165
|
-
--dir)
|
|
166
|
-
--bundle)
|
|
167
|
-
--help|-h)
|
|
168
|
-
*)
|
|
255
|
+
--dir) DIRS+=("$2"); shift 2 ;;
|
|
256
|
+
--bundle) BUNDLE="$2"; shift 2 ;;
|
|
257
|
+
--help|-h) usage_okf ;;
|
|
258
|
+
*)
|
|
259
|
+
echo "validate-okf: unknown flag: $1" >&2
|
|
260
|
+
echo "Run 'scripts/validate-okf.sh --help' for help." >&2
|
|
261
|
+
exit 2
|
|
262
|
+
;;
|
|
169
263
|
esac
|
|
170
264
|
done
|
|
171
265
|
|
|
172
266
|
echo "validate-okf: scanning for OKF bundles..."
|
|
173
267
|
|
|
174
268
|
if [ -n "$BUNDLE" ]; then
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
printf "${YELLOW}SKIP${NC} directory not found: %s\n" "$DIR"
|
|
179
|
-
exit 0
|
|
180
|
-
fi
|
|
181
|
-
count=0
|
|
182
|
-
while IFS= read -r -d '' f; do
|
|
183
|
-
# Only process .md files with YAML frontmatter
|
|
184
|
-
if head -1 "$f" 2>/dev/null | grep -q '^---$'; then
|
|
185
|
-
validate_bundle "$f"
|
|
186
|
-
count=$((count + 1))
|
|
187
|
-
fi
|
|
188
|
-
done < <(find "$DIR" -maxdepth 1 -name '*.md' -print0 2>/dev/null || true)
|
|
189
|
-
if [ "$count" -eq 0 ]; then
|
|
190
|
-
printf "${YELLOW}SKIP${NC} no OKF bundles found in %s\n" "$DIR"
|
|
191
|
-
else
|
|
192
|
-
echo "validate-okf: scanned $count bundle(s)"
|
|
269
|
+
if [ ! -f "$BUNDLE" ]; then
|
|
270
|
+
printf "${RED}FAIL${NC} bundle not found: %s\n" "$BUNDLE"
|
|
271
|
+
exit 1
|
|
193
272
|
fi
|
|
273
|
+
validate_bundle "$BUNDLE"
|
|
274
|
+
elif [ ${#DIRS[@]} -gt 0 ]; then
|
|
275
|
+
for d in "${DIRS[@]}"; do
|
|
276
|
+
scan_dir "$d"
|
|
277
|
+
done
|
|
194
278
|
else
|
|
195
|
-
echo "validate-okf: nothing to validate" >&2
|
|
279
|
+
echo "validate-okf: nothing to validate (no --dir or --bundle)" >&2
|
|
280
|
+
exit 1
|
|
196
281
|
fi
|
|
197
282
|
|
|
198
283
|
exit "$EXIT_CODE"
|
package/scripts/wait-for-ci.sh
CHANGED
|
@@ -18,7 +18,7 @@ BRANCH=$(git branch --show-current 2>/dev/null || echo "main")
|
|
|
18
18
|
COMMIT=""
|
|
19
19
|
OUTPUT_JSON=false
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
usage_ci() {
|
|
22
22
|
sed -n '2,12p' "$0" | sed 's/^# //'
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -29,8 +29,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
29
29
|
--branch) BRANCH="$2"; shift 2 ;;
|
|
30
30
|
--commit) COMMIT="$2"; shift 2 ;;
|
|
31
31
|
--json) OUTPUT_JSON=true; shift ;;
|
|
32
|
-
--help)
|
|
33
|
-
*) echo "Unknown flag: $1";
|
|
32
|
+
--help) usage_ci; exit 0 ;;
|
|
33
|
+
*) echo "Unknown flag: $1"; usage_ci; exit 2 ;;
|
|
34
34
|
esac
|
|
35
35
|
done
|
|
36
36
|
|