bigpowers 2.58.1 → 2.58.2
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 +7 -0
- package/CONVENTIONS.md +12 -0
- package/package.json +1 -1
- package/scripts/generate-skill-index.sh +1 -0
- package/scripts/validate-okf.sh +209 -106
package/.pi/package.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [2.58.2](https://github.com/danielvm-git/bigpowers/compare/v2.58.1...v2.58.2) (2026-07-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **gates:** restore lost gap-closure fixes — e13s02 tag, validate-okf.sh, waivers, version ([3ddc000](https://github.com/danielvm-git/bigpowers/commit/3ddc0003288f65a5a7c3f681588d6281a6f83a05))
|
|
7
|
+
|
|
1
8
|
## [2.58.1](https://github.com/danielvm-git/bigpowers/compare/v2.58.0...v2.58.1) (2026-07-03)
|
|
2
9
|
|
|
3
10
|
|
package/CONVENTIONS.md
CHANGED
|
@@ -231,3 +231,15 @@ name must return < 5 results across the repo.
|
|
|
231
231
|
| `grill-with-docs` | three-word verb-prep-noun | Variant of `grill-me`; the `-with-docs` suffix is the distinguishing feature — collapsing it would hide the doc-grounded contract |
|
|
232
232
|
|
|
233
233
|
Any new exception requires an entry in this table before the skill is published.
|
|
234
|
+
|
|
235
|
+
## File-Size Exceptions
|
|
236
|
+
|
|
237
|
+
The 300-line file cap exists to ensure files fit in a single agent context
|
|
238
|
+
window. The following files are documented exceptions:
|
|
239
|
+
|
|
240
|
+
| File | Lines | Rationale | Review date |
|
|
241
|
+
|------|-------|-----------|-------------|
|
|
242
|
+
| `scripts/trace-stories.sh` | 613 | Parse-match-report phases are tightly coupled; splitting introduces grep-fragile inter-script state and breaks `set -e` error propagation. Tracked as P3 technical debt (BUG-2026-07-03-trace-stories-613-line). | 2026-10-01 |
|
|
243
|
+
| `scripts/audit-compliance.sh` | ~280 | Gherkin harness with embedded judge logic and waiver subsystem; splitting the judge loop from the reporting layer would require fragile state-passing. Approaching the 300-line cap; monitor. | 2026-10-01 |
|
|
244
|
+
|
|
245
|
+
Any new exception requires an entry in this table with rationale and a review date.
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# story: e13s02
|
|
2
3
|
# generate-skill-index.sh — Auto-generate SKILL-INDEX.md from skills-lock.json + SKILL.md frontmatter
|
|
3
4
|
# This file is called by sync-skills.sh. Output is SKILL-INDEX.md at repo root.
|
|
4
5
|
# DO NOT edit SKILL-INDEX.md manually — it is auto-generated.
|
package/scripts/validate-okf.sh
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# story: e38s09
|
|
2
3
|
#
|
|
3
|
-
# validate-okf.sh — provenance gate for OKF
|
|
4
|
+
# validate-okf.sh — provenance gate for OKF bundles (e40/e31/e44 style).
|
|
4
5
|
#
|
|
5
|
-
#
|
|
6
|
+
# Kind-aware: validates story-metrics, spec-migration, and migration-registry
|
|
7
|
+
# bundles against their respective schemas. Fail-closed: unknown flags exit 2,
|
|
8
|
+
# missing directories exit 1. Multi-dir default scans specs/metrics/ AND
|
|
9
|
+
# specs/migrations/.
|
|
10
|
+
#
|
|
11
|
+
# A story-metrics bundle is valid iff:
|
|
6
12
|
# 1. The generator ran (generator field matches scripts/record-cycle-time.sh).
|
|
7
13
|
# 2. commit_range resolves to real commits in the repo.
|
|
8
14
|
# 3. Source enum is valid (measured|estimated|backfilled).
|
|
9
|
-
# 4. Required keys are present with
|
|
15
|
+
# 4. Required keys are present with non-null values.
|
|
16
|
+
#
|
|
17
|
+
# A spec-migration bundle is valid iff:
|
|
18
|
+
# 1. id, title, since_version, order, actions_needed are present.
|
|
19
|
+
# 2. fingerprint.any has at least one file/exists check.
|
|
20
|
+
#
|
|
21
|
+
# A migration-registry bundle is valid iff:
|
|
22
|
+
# 1. migrations list has at least one entry with id + file + status.
|
|
10
23
|
#
|
|
11
24
|
# This script gates on PROVENANCE, NEVER on a specific metric value.
|
|
12
|
-
# Gate on freshness (did the right generator run?) not on outcomes.
|
|
13
25
|
#
|
|
14
26
|
# Usage:
|
|
15
|
-
# scripts/validate-okf.sh [--dir <path>] [--help]
|
|
16
|
-
# scripts/validate-okf.sh --bundle <file> # validate a single bundle
|
|
27
|
+
# scripts/validate-okf.sh [--dir <path>] [--bundle <file>] [--help]
|
|
17
28
|
#
|
|
18
29
|
set -euo pipefail
|
|
19
30
|
|
|
20
31
|
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
32
|
EXIT_CODE=0
|
|
23
|
-
REQUIRED_KEYS="id epic bcps commit_range source generated_at generator"
|
|
24
|
-
VALID_SOURCES="measured estimated backfilled"
|
|
25
33
|
|
|
26
34
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'
|
|
27
35
|
|
|
@@ -30,30 +38,36 @@ usage() {
|
|
|
30
38
|
Usage: scripts/validate-okf.sh [flags]
|
|
31
39
|
|
|
32
40
|
Flags:
|
|
33
|
-
--dir <path> Validate all OKF bundles in directory
|
|
41
|
+
--dir <path> Validate all OKF bundles in directory
|
|
34
42
|
--bundle <file> Validate a single OKF bundle
|
|
35
43
|
--help Show this message
|
|
36
44
|
|
|
37
45
|
Description:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
Kind-aware provenance gate for OKF bundles. Without flags, validates all
|
|
47
|
+
bundles in specs/metrics/ AND specs/migrations/. NEVER gates on a specific
|
|
48
|
+
metric value — only on provenance and schema conformance.
|
|
49
|
+
|
|
50
|
+
OKF kinds validated:
|
|
51
|
+
story-metrics — effort/lead-time provenance (e40)
|
|
52
|
+
spec-migration — migration bundle schema (e44)
|
|
53
|
+
migration-registry — registry index integrity (e44)
|
|
46
54
|
EOF
|
|
47
55
|
exit 0
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
#
|
|
51
|
-
# Returns the frontmatter as JSON via Python (which handles YAML safely).
|
|
58
|
+
# ---- YAML frontmatter parser (datetime-safe) ---------------------------
|
|
52
59
|
parse_frontmatter() {
|
|
53
60
|
local file="$1"
|
|
54
|
-
# Extract YAML frontmatter between --- markers
|
|
55
61
|
python3 -c "
|
|
56
|
-
import yaml, sys
|
|
62
|
+
import yaml, sys, json
|
|
63
|
+
|
|
64
|
+
class SafeEncoder(json.JSONEncoder):
|
|
65
|
+
def default(self, obj):
|
|
66
|
+
try:
|
|
67
|
+
return super().default(obj)
|
|
68
|
+
except TypeError:
|
|
69
|
+
return str(obj)
|
|
70
|
+
|
|
57
71
|
content = open('$file').read()
|
|
58
72
|
parts = content.split('---')
|
|
59
73
|
if len(parts) < 3:
|
|
@@ -62,137 +76,226 @@ try:
|
|
|
62
76
|
fm = yaml.safe_load(parts[1])
|
|
63
77
|
if fm is None:
|
|
64
78
|
sys.exit(1)
|
|
65
|
-
|
|
66
|
-
import json
|
|
67
|
-
print(json.dumps(fm))
|
|
79
|
+
print(json.dumps(fm, cls=SafeEncoder))
|
|
68
80
|
except Exception as e:
|
|
69
|
-
print(f'
|
|
81
|
+
print(f'YAML parse error: {e}', file=sys.stderr)
|
|
70
82
|
sys.exit(1)
|
|
71
83
|
" 2>/dev/null
|
|
72
84
|
}
|
|
73
85
|
|
|
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
|
-
}
|
|
86
|
+
# ---- Kind-specific validators ------------------------------------------
|
|
85
87
|
|
|
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
|
|
88
|
+
validate_story_metrics() {
|
|
89
|
+
local file="$1" fm="$2" name="$3"
|
|
90
|
+
local errs=0
|
|
93
91
|
|
|
94
|
-
#
|
|
95
|
-
for key in
|
|
92
|
+
# Required keys
|
|
93
|
+
for key in id epic bcps commit_range source generated_at generator; do
|
|
96
94
|
local val
|
|
97
95
|
val="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
98
96
|
if [ -z "$val" ] || [ "$val" = "null" ] || [ "$val" = "None" ]; then
|
|
99
|
-
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$
|
|
100
|
-
|
|
101
|
-
return
|
|
97
|
+
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
98
|
+
errs=$((errs + 1))
|
|
102
99
|
fi
|
|
103
100
|
done
|
|
101
|
+
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
104
102
|
|
|
105
|
-
#
|
|
103
|
+
# Source enum
|
|
106
104
|
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
|
|
105
|
+
source="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('source',''))" 2>/dev/null)"
|
|
106
|
+
if ! echo "measured estimated backfilled" | grep -qw "$source"; then
|
|
107
|
+
printf "${RED}FAIL${NC} %s: invalid source '%s' — must be measured|estimated|backfilled\n" "$name" "$source"
|
|
108
|
+
EXIT_CODE=1; return
|
|
112
109
|
fi
|
|
113
110
|
|
|
114
|
-
#
|
|
111
|
+
# Generator
|
|
115
112
|
local generator
|
|
116
|
-
generator="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d
|
|
113
|
+
generator="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('generator',''))" 2>/dev/null)"
|
|
117
114
|
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
|
|
115
|
+
printf "${RED}FAIL${NC} %s: wrong generator '%s' — expected scripts/record-cycle-time.sh\n" "$name" "$generator"
|
|
116
|
+
EXIT_CODE=1; return
|
|
121
117
|
fi
|
|
122
118
|
|
|
123
|
-
#
|
|
119
|
+
# commit_range resolves
|
|
124
120
|
local commit_range
|
|
125
|
-
commit_range="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d
|
|
121
|
+
commit_range="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
|
|
126
122
|
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
|
|
123
|
+
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
|
|
124
|
+
EXIT_CODE=1; return
|
|
130
125
|
fi
|
|
131
126
|
|
|
132
|
-
#
|
|
133
|
-
local
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
# effort.effort_hours present and non-negative
|
|
128
|
+
local eff
|
|
129
|
+
eff="$(echo "$fm" | python3 -c "
|
|
130
|
+
import json,sys
|
|
131
|
+
d=json.load(sys.stdin)
|
|
132
|
+
e=d.get('effort',{}).get('effort_hours')
|
|
133
|
+
if e is None: sys.exit(1)
|
|
134
|
+
if e < 0: sys.exit(2)
|
|
135
|
+
print(e)
|
|
136
|
+
" 2>/dev/null)" || {
|
|
137
|
+
local rc=$?
|
|
138
|
+
if [ "$rc" -eq 1 ]; then
|
|
139
|
+
printf "${RED}FAIL${NC} %s: effort.effort_hours missing or null\n" "$name"
|
|
140
|
+
else
|
|
141
|
+
printf "${RED}FAIL${NC} %s: effort_hours out of bounds\n" "$name"
|
|
142
|
+
fi
|
|
143
|
+
EXIT_CODE=1; return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
printf "${GREEN}PASS${NC} %s (source=%s, generator=%s, commit_range resolves)\n" "$name" "$source" "$generator"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
validate_spec_migration() {
|
|
150
|
+
local file="$1" fm="$2" name="$3"
|
|
151
|
+
local errs=0
|
|
152
|
+
|
|
153
|
+
# Required keys
|
|
154
|
+
for key in id title since_version order actions_needed; do
|
|
155
|
+
local val
|
|
156
|
+
val="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
157
|
+
if [ -z "$val" ] || [ "$val" = "null" ] || [ "$val" = "None" ] || [ "$val" = "[]" ]; then
|
|
158
|
+
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
159
|
+
errs=$((errs + 1))
|
|
160
|
+
fi
|
|
161
|
+
done
|
|
162
|
+
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
163
|
+
|
|
164
|
+
# fingerprint.any has at least one check
|
|
165
|
+
local fp_count
|
|
166
|
+
fp_count="$(echo "$fm" | python3 -c "
|
|
167
|
+
import json,sys
|
|
168
|
+
d=json.load(sys.stdin)
|
|
169
|
+
fp=d.get('fingerprint',{}).get('any',[])
|
|
170
|
+
print(len(fp))
|
|
171
|
+
" 2>/dev/null)"
|
|
172
|
+
if [ "${fp_count:-0}" -eq 0 ]; then
|
|
173
|
+
printf "${RED}FAIL${NC} %s: fingerprint.any is empty — no detection criteria\n" "$name"
|
|
174
|
+
EXIT_CODE=1; return
|
|
139
175
|
fi
|
|
140
176
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
import json, sys
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
printf "${GREEN}PASS${NC} %s (actions=%s, fingerprint has %s check(s))\n" \
|
|
178
|
+
"$name" \
|
|
179
|
+
"$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('actions_needed',[])))" 2>/dev/null)" \
|
|
180
|
+
"$fp_count"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
validate_migration_registry() {
|
|
184
|
+
local file="$1" fm="$2" name="$3"
|
|
185
|
+
|
|
186
|
+
local count
|
|
187
|
+
count="$(echo "$fm" | python3 -c "
|
|
188
|
+
import json,sys
|
|
189
|
+
d=json.load(sys.stdin)
|
|
190
|
+
migrations=d.get('migrations',[])
|
|
191
|
+
print(len(migrations))
|
|
192
|
+
" 2>/dev/null)"
|
|
193
|
+
if [ "${count:-0}" -eq 0 ]; then
|
|
194
|
+
printf "${RED}FAIL${NC} %s: migrations list is empty\n" "$name"
|
|
195
|
+
EXIT_CODE=1; return
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Check each migration has id + file + status
|
|
199
|
+
local bad
|
|
200
|
+
bad="$(echo "$fm" | python3 -c "
|
|
201
|
+
import json,sys
|
|
202
|
+
d=json.load(sys.stdin)
|
|
203
|
+
for m in d.get('migrations',[]):
|
|
204
|
+
if not all(k in m for k in ('id','file','status')):
|
|
205
|
+
print(m.get('id','?')); sys.exit(1)
|
|
206
|
+
" 2>/dev/null)" || {
|
|
207
|
+
printf "${RED}FAIL${NC} %s: migration '%s' missing id/file/status\n" "$name" "${bad:-?}"
|
|
208
|
+
EXIT_CODE=1; return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
printf "${GREEN}PASS${NC} %s (%s migrations indexed)\n" "$name" "$count"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# ---- Main validation dispatcher ----------------------------------------
|
|
215
|
+
validate_bundle() {
|
|
216
|
+
local file="$1"
|
|
217
|
+
local name; name="$(basename "$file")"
|
|
218
|
+
|
|
219
|
+
local fm
|
|
220
|
+
fm="$(parse_frontmatter "$file")" || {
|
|
221
|
+
printf "${RED}FAIL${NC} %s: cannot parse YAML frontmatter\n" "$name"
|
|
150
222
|
EXIT_CODE=1
|
|
151
223
|
return
|
|
152
224
|
}
|
|
153
225
|
|
|
154
|
-
|
|
226
|
+
local kind
|
|
227
|
+
kind="$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('okf_kind',''))" 2>/dev/null)"
|
|
228
|
+
|
|
229
|
+
case "$kind" in
|
|
230
|
+
story-metrics) validate_story_metrics "$file" "$fm" "$name" ;;
|
|
231
|
+
spec-migration) validate_spec_migration "$file" "$fm" "$name" ;;
|
|
232
|
+
migration-registry) validate_migration_registry "$file" "$fm" "$name" ;;
|
|
233
|
+
"")
|
|
234
|
+
printf "${YELLOW}SKIP${NC} %s: no okf_kind — not an OKF bundle\n" "$name"
|
|
235
|
+
;;
|
|
236
|
+
*)
|
|
237
|
+
printf "${YELLOW}SKIP${NC} %s: unknown okf_kind '%s'\n" "$name" "$kind"
|
|
238
|
+
;;
|
|
239
|
+
esac
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# ---- Scan directory ----------------------------------------------------
|
|
243
|
+
scan_dir() {
|
|
244
|
+
local dir="$1"
|
|
245
|
+
if [ ! -d "$dir" ]; then
|
|
246
|
+
printf "${RED}FAIL${NC} directory not found: %s\n" "$dir"
|
|
247
|
+
EXIT_CODE=1
|
|
248
|
+
return
|
|
249
|
+
fi
|
|
250
|
+
local count=0
|
|
251
|
+
while IFS= read -r -d '' f; do
|
|
252
|
+
if head -1 "$f" 2>/dev/null | grep -q '^---$'; then
|
|
253
|
+
validate_bundle "$f"
|
|
254
|
+
count=$((count + 1))
|
|
255
|
+
fi
|
|
256
|
+
done < <(find "$dir" -maxdepth 1 -name '*.md' -print0 2>/dev/null || true)
|
|
257
|
+
if [ "$count" -eq 0 ]; then
|
|
258
|
+
printf "${YELLOW}SKIP${NC} no OKF bundles found in %s\n" "$dir"
|
|
259
|
+
fi
|
|
155
260
|
}
|
|
156
261
|
|
|
157
262
|
# ---- main ----------------------------------------------------------------
|
|
158
|
-
|
|
263
|
+
DIRS=()
|
|
159
264
|
BUNDLE=""
|
|
265
|
+
DEFAULT_DIRS=("$ROOT/specs/metrics" "$ROOT/specs/migrations")
|
|
160
266
|
|
|
161
|
-
[ $# -
|
|
267
|
+
if [ $# -eq 0 ]; then
|
|
268
|
+
DIRS=("$ROOT/specs/metrics" "$ROOT/specs/migrations")
|
|
269
|
+
fi
|
|
162
270
|
|
|
163
271
|
while [ $# -gt 0 ]; do
|
|
164
272
|
case "$1" in
|
|
165
|
-
--dir)
|
|
166
|
-
--bundle)
|
|
273
|
+
--dir) DIRS+=("$2"); shift 2 ;;
|
|
274
|
+
--bundle) BUNDLE="$2"; shift 2 ;;
|
|
167
275
|
--help|-h) usage ;;
|
|
168
|
-
*)
|
|
276
|
+
*)
|
|
277
|
+
echo "validate-okf: unknown flag: $1" >&2
|
|
278
|
+
echo "Run 'scripts/validate-okf.sh --help' for usage." >&2
|
|
279
|
+
exit 2
|
|
280
|
+
;;
|
|
169
281
|
esac
|
|
170
282
|
done
|
|
171
283
|
|
|
172
284
|
echo "validate-okf: scanning for OKF bundles..."
|
|
173
285
|
|
|
174
286
|
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)"
|
|
287
|
+
if [ ! -f "$BUNDLE" ]; then
|
|
288
|
+
printf "${RED}FAIL${NC} bundle not found: %s\n" "$BUNDLE"
|
|
289
|
+
exit 1
|
|
193
290
|
fi
|
|
291
|
+
validate_bundle "$BUNDLE"
|
|
292
|
+
elif [ ${#DIRS[@]} -gt 0 ]; then
|
|
293
|
+
for d in "${DIRS[@]}"; do
|
|
294
|
+
scan_dir "$d"
|
|
295
|
+
done
|
|
194
296
|
else
|
|
195
|
-
echo "validate-okf: nothing to validate" >&2
|
|
297
|
+
echo "validate-okf: nothing to validate (no --dir or --bundle)" >&2
|
|
298
|
+
exit 1
|
|
196
299
|
fi
|
|
197
300
|
|
|
198
301
|
exit "$EXIT_CODE"
|