bigpowers 2.75.1 → 2.76.0
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 +20 -0
- package/bin/bigspec +35 -1
- package/kernel/src/publish-to-wiki.sh +131 -6
- package/package.json +1 -1
- package/scripts/audit-compliance.sh +2 -3
- package/scripts/build-receipts.sh +2 -2
- package/scripts/build-skill-graph.sh +1 -1
- package/scripts/check-spec-drift.sh +2 -2
- package/scripts/check-spec-version-gap.sh +13 -13
- package/scripts/compile-rule-matrix.sh +2 -2
- package/scripts/decompose-conventions.sh +2 -2
- package/scripts/generate-adr-wiki.sh +3 -2
- package/scripts/generate-agent-guide.sh +2 -2
- package/scripts/generate-epics-wiki.sh +3 -2
- package/scripts/hooks/token-mgmt-pre-tool-use.sh +5 -5
- package/scripts/land-branch.sh +11 -11
- package/scripts/lib/audit-compliance-report.sh +1 -0
- package/scripts/lib/emit-okf-metrics.sh +1 -0
- package/scripts/lib/golden-suite-report.sh +1 -0
- package/scripts/lib/golden-suite-run.sh +5 -1
- package/scripts/lib/migrate-version-transforms.sh +3 -4
- package/scripts/lib/record-cycle-time-lib.sh +31 -0
- package/scripts/lib/sync-post.sh +6 -1
- package/scripts/lib/test-assertions.sh +11 -0
- package/scripts/lib/validate-okf-kinds.sh +7 -4
- package/scripts/record-cycle-time.sh +4 -28
- package/scripts/sync-bugs-registry.sh +1 -0
- package/scripts/sync-skills.sh +3 -3
- package/scripts/test-adapters.sh +13 -13
- package/scripts/test-trace-matrix-size.sh +4 -4
- package/scripts/test-trace-strict.sh +8 -8
- package/scripts/validate-doctrine.sh +18 -18
- package/scripts/validate-skill-catalog.sh +10 -10
- package/scripts/verify-install.sh +39 -37
- package/tests/test-epic-status-drift.sh +53 -0
- package/tests/test-okf-generators.sh +57 -0
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# story: e44s03
|
|
3
3
|
if [ -n "${MIGRATE_VERSION_TRANSFORMS_LOADED:-}" ]; then return 0; fi
|
|
4
4
|
MIGRATE_VERSION_TRANSFORMS_LOADED=1
|
|
5
|
-
yaml_get() { migrate_yaml_get "$@"; }
|
|
6
5
|
apply_transform() {
|
|
7
6
|
local mig_id="$1" action="$2" tjson="$3"
|
|
8
7
|
|
|
@@ -107,7 +106,7 @@ with open(tgt, 'w') as f:
|
|
|
107
106
|
val="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('value',''))")"
|
|
108
107
|
if_missing="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('if_missing','false'))" 2>/dev/null)" || if_missing="false"
|
|
109
108
|
if [ "$if_missing" = "true" ] || [ "$if_missing" = "True" ]; then
|
|
110
|
-
if
|
|
109
|
+
if migrate_yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
|
|
111
110
|
log_skip "$action: $file $key already set (if_missing=true) — idempotent"
|
|
112
111
|
SKIPPED+=("$action:$key")
|
|
113
112
|
return
|
|
@@ -122,7 +121,7 @@ with open(tgt, 'w') as f:
|
|
|
122
121
|
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
123
122
|
old_key="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('old_key',''))")"
|
|
124
123
|
new_key="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('new_key',''))")"
|
|
125
|
-
old_val="$(
|
|
124
|
+
old_val="$(migrate_yaml_get "$REPO_ROOT/$file" "$old_key" 2>/dev/null)" || true
|
|
126
125
|
if [ -z "${old_val:-}" ]; then
|
|
127
126
|
log_skip "$action: key '$old_key' not found in $file"
|
|
128
127
|
SKIPPED+=("$action:$old_key"); return
|
|
@@ -148,7 +147,7 @@ with open('$REPO_ROOT/$file', 'w') as f:
|
|
|
148
147
|
local file key
|
|
149
148
|
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
150
149
|
key="$(echo "$tjson" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('path') or d.get('key',''))")"
|
|
151
|
-
if !
|
|
150
|
+
if ! migrate_yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
|
|
152
151
|
log_skip "$action: key '$key' not found in $file — idempotent"
|
|
153
152
|
SKIPPED+=("$action:$key"); return
|
|
154
153
|
fi
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# record-cycle-time-lib.sh — shared helpers for record-cycle-time.sh
|
|
3
|
+
|
|
4
|
+
record_cycle_die() { echo "record-cycle-time: $*" >&2; exit 1; }
|
|
5
|
+
|
|
6
|
+
record_cycle_usage() {
|
|
7
|
+
cat <<'EOF'
|
|
8
|
+
Usage: scripts/record-cycle-time.sh <command> [flags]
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
report Compute per-story effort partition + additivity self-check.
|
|
12
|
+
Flags: --range <git-range> --story-key <trailer-key>
|
|
13
|
+
append Recompute partition and append one row for a story.\n Flags: --story <id> --bcps <n> [--bcp-plus <n>] [--range <git-range>]\n [--merged-at <epoch>] [--file <path>] [--story-key <trailer-key>]
|
|
14
|
+
self-test Run report, then exit 0 on additivity PASS; exit 1 on FAIL.
|
|
15
|
+
Flags: --range <git-range> --story-key <trailer-key>
|
|
16
|
+
help Show this message.
|
|
17
|
+
|
|
18
|
+
Global flags:
|
|
19
|
+
--help Show this message (works with any subcommand).
|
|
20
|
+
|
|
21
|
+
Description:
|
|
22
|
+
Computes git-derived, additive effort from commit history.
|
|
23
|
+
Two metrics, separated: effort_hours (ADDITIVE, Σ = total) and
|
|
24
|
+
lead_time_minutes (calendar latency, median-aggregate, NEVER sum).
|
|
25
|
+
|
|
26
|
+
Uses the git-hours model: commits sorted by author-date across the
|
|
27
|
+
whole range; gaps < 120 min counted as effort; gaps >= 120 min
|
|
28
|
+
start a new session with a 120-min pad.
|
|
29
|
+
EOF
|
|
30
|
+
exit 0
|
|
31
|
+
}
|
package/scripts/lib/sync-post.sh
CHANGED
|
@@ -8,6 +8,11 @@ if [ -n "${SYNC_POST_LOADED:-}" ]; then
|
|
|
8
8
|
fi
|
|
9
9
|
SYNC_POST_LOADED=1
|
|
10
10
|
|
|
11
|
+
sync_post_gemini_version_mismatch() {
|
|
12
|
+
local ext_ver="$1" pkg_ver="$2"
|
|
13
|
+
[[ -n "$ext_ver" && -n "$pkg_ver" && "$ext_ver" != "$pkg_ver" ]]
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
sync_post_run() {
|
|
12
17
|
local skill_count="${1:-0}"
|
|
13
18
|
local opencode_count="${2:-0}"
|
|
@@ -116,7 +121,7 @@ sync_post_regression_guards() {
|
|
|
116
121
|
if [[ -f "$manifest" ]]; then
|
|
117
122
|
ext_ver=$(jq -r '.version // empty' "$manifest")
|
|
118
123
|
pkg_ver=$(jq -r '.version // empty' "$REPO_ROOT/package.json")
|
|
119
|
-
if
|
|
124
|
+
if sync_post_gemini_version_mismatch "$ext_ver" "$pkg_ver"; then
|
|
120
125
|
echo "sync-skills: FAIL — gemini-extension.json version ($ext_ver) != package.json ($pkg_ver)" >&2
|
|
121
126
|
exit 1
|
|
122
127
|
fi
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-assertions.sh — shared pass/fail/cleanup helpers for verification scripts
|
|
3
|
+
|
|
4
|
+
if [[ -n "${TEST_ASSERTIONS_LOADED:-}" ]]; then return 0; fi
|
|
5
|
+
TEST_ASSERTIONS_LOADED=1
|
|
6
|
+
|
|
7
|
+
ta_cleanup() { [[ -n "${TA_TMPDIR:-}" && -d "$TA_TMPDIR" ]] && rm -rf "$TA_TMPDIR"; }
|
|
8
|
+
|
|
9
|
+
ta_pass() { echo " PASS $1"; TA_PASS=$((TA_PASS + 1)); }
|
|
10
|
+
|
|
11
|
+
ta_fail() { echo " FAIL $1"; TA_FAIL=$((TA_FAIL + 1)); }
|
|
@@ -59,9 +59,12 @@ validate_story_metrics() {
|
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
61
|
commit_range="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
|
|
62
|
-
if
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if [ "$source" != "estimated" ]; then
|
|
63
|
+
if ! git -C "$ROOT" log --oneline -1 "$commit_range" >/dev/null 2>&1; then
|
|
64
|
+
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
|
|
65
|
+
EXIT_CODE=1
|
|
66
|
+
return
|
|
67
|
+
fi
|
|
65
68
|
fi
|
|
66
69
|
|
|
67
70
|
eff="$(echo "$fm" | $PYTHON -c "
|
|
@@ -265,7 +268,7 @@ okf_scan_dir() {
|
|
|
265
268
|
validate_bundle "$f"
|
|
266
269
|
count=$((count + 1))
|
|
267
270
|
fi
|
|
268
|
-
done < <(find "$dir" -maxdepth 1 -name '*.md' -print0 2>/dev/null || true)
|
|
271
|
+
done < <(find "$dir" -maxdepth 1 \( -name '*.okf.md' -o -name 'registry.okf.md' \) -print0 2>/dev/null || true)
|
|
269
272
|
if [ "$count" -eq 0 ]; then
|
|
270
273
|
printf "${YELLOW}SKIP${NC} no OKF bundles found in %s\n" "$dir"
|
|
271
274
|
fi
|
|
@@ -49,35 +49,10 @@ PAD_SECONDS=$(( 120 * 60 ))
|
|
|
49
49
|
STORY_KEY="Story"
|
|
50
50
|
# git-hours engine — partition + whole_range_hours (additivity oracle)
|
|
51
51
|
true && source "$(dirname "${BASH_SOURCE[0]}")/lib/git-hours.sh"
|
|
52
|
+
true && source "$(dirname "${BASH_SOURCE[0]}")/lib/record-cycle-time-lib.sh"
|
|
52
53
|
|
|
53
|
-
die() {
|
|
54
|
-
|
|
55
|
-
usage_cycle() {
|
|
56
|
-
cat <<'EOF'
|
|
57
|
-
Usage: scripts/record-cycle-time.sh <command> [flags]
|
|
58
|
-
|
|
59
|
-
Commands:
|
|
60
|
-
report Compute per-story effort partition + additivity self-check.
|
|
61
|
-
Flags: --range <git-range> --story-key <trailer-key>
|
|
62
|
-
append Recompute partition and append one row for a story.\n Flags: --story <id> --bcps <n> [--bcp-plus <n>] [--range <git-range>]\n [--merged-at <epoch>] [--file <path>] [--story-key <trailer-key>]
|
|
63
|
-
self-test Run report, then exit 0 on additivity PASS; exit 1 on FAIL.
|
|
64
|
-
Flags: --range <git-range> --story-key <trailer-key>
|
|
65
|
-
help Show this message.
|
|
66
|
-
|
|
67
|
-
Global flags:
|
|
68
|
-
--help Show this message (works with any subcommand).
|
|
69
|
-
|
|
70
|
-
Description:
|
|
71
|
-
Computes git-derived, additive effort from commit history.
|
|
72
|
-
Two metrics, separated: effort_hours (ADDITIVE, Σ = total) and
|
|
73
|
-
lead_time_minutes (calendar latency, median-aggregate, NEVER sum).
|
|
74
|
-
|
|
75
|
-
Uses the git-hours model: commits sorted by author-date across the
|
|
76
|
-
whole range; gaps < 120 min counted as effort; gaps >= 120 min
|
|
77
|
-
start a new session with a 120-min pad.
|
|
78
|
-
EOF
|
|
79
|
-
exit 0
|
|
80
|
-
}
|
|
54
|
+
die() { record_cycle_die "$@"; }
|
|
55
|
+
usage_cycle() { record_cycle_usage; }
|
|
81
56
|
|
|
82
57
|
command -v git >/dev/null 2>&1 || die "git not found"
|
|
83
58
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || die "not inside a git repo"
|
|
@@ -212,6 +187,7 @@ cmd_append() {
|
|
|
212
187
|
printf -- '---\n'
|
|
213
188
|
printf -- 'okf_kind: story-metrics\n'
|
|
214
189
|
printf -- 'okf_version: "0.1"\n'
|
|
190
|
+
printf -- 'type: StoryMetrics\n'
|
|
215
191
|
printf -- 'id: %s\n' "$story"
|
|
216
192
|
printf -- 'epic: %s\n' "${story%%s*}" # e40s03 → e40
|
|
217
193
|
printf -- 'bcps: %s\n' "$bcps"
|
package/scripts/sync-skills.sh
CHANGED
|
@@ -43,9 +43,9 @@ for arg in "$@"; do
|
|
|
43
43
|
done
|
|
44
44
|
|
|
45
45
|
_LIB_DIR="$(dirname "${BASH_SOURCE[0]}")/lib"
|
|
46
|
-
source "$
|
|
47
|
-
source "$
|
|
48
|
-
source "$
|
|
46
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
47
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/sync-render.sh"
|
|
48
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/sync-post.sh"
|
|
49
49
|
resolve_repo_root
|
|
50
50
|
|
|
51
51
|
CURSOR_RULES="$REPO_ROOT/.cursor/rules"
|
package/scripts/test-adapters.sh
CHANGED
|
@@ -22,12 +22,12 @@ bash "$SCRIPT_DIR/validate-targets-yaml.sh" >/dev/null
|
|
|
22
22
|
PASS=0
|
|
23
23
|
FAIL=0
|
|
24
24
|
TMPDIR=""
|
|
25
|
+
TA_PASS=0
|
|
26
|
+
TA_FAIL=0
|
|
27
|
+
TA_TMPDIR=""
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
trap
|
|
28
|
-
|
|
29
|
-
pass() { echo " PASS $1"; PASS=$((PASS + 1)); }
|
|
30
|
-
fail() { echo " FAIL $1"; FAIL=$((FAIL + 1)); }
|
|
29
|
+
source "$SCRIPT_DIR/lib/test-assertions.sh"
|
|
30
|
+
trap ta_cleanup EXIT
|
|
31
31
|
|
|
32
32
|
test_adapter() {
|
|
33
33
|
local id="$1"
|
|
@@ -38,11 +38,11 @@ test_adapter() {
|
|
|
38
38
|
return 0
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
|
-
[[ -f "$adapter_file" ]] || {
|
|
42
|
-
|
|
41
|
+
[[ -f "$adapter_file" ]] || { ta_fail "$id: adapter missing"; return; }
|
|
42
|
+
ta_pass "$id: exists"
|
|
43
43
|
|
|
44
44
|
# shellcheck source=/dev/null
|
|
45
|
-
source "$adapter_file" 2>/dev/null &&
|
|
45
|
+
source "$adapter_file" 2>/dev/null && ta_pass "$id: sourceable" || { ta_fail "$id: source error"; return; }
|
|
46
46
|
|
|
47
47
|
local has_skill has_context mode
|
|
48
48
|
has_skill=$(yq -r ".targets[] | select(.id==\"$id\") | .skill != null" "$TARGETS_FILE")
|
|
@@ -50,11 +50,11 @@ test_adapter() {
|
|
|
50
50
|
mode=$(yq -r ".targets[] | select(.id==\"$id\") | .context.mode // \"\"" "$TARGETS_FILE")
|
|
51
51
|
|
|
52
52
|
if [[ "$has_skill" == "true" ]]; then
|
|
53
|
-
declare -f render_skill >/dev/null 2>&1 &&
|
|
53
|
+
declare -f render_skill >/dev/null 2>&1 && ta_pass "$id: render_skill defined" || ta_fail "$id: render_skill missing"
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
56
|
if [[ "$has_context" == "true" && "$mode" != "native" ]]; then
|
|
57
|
-
declare -f wire_context >/dev/null 2>&1 &&
|
|
57
|
+
declare -f wire_context >/dev/null 2>&1 && ta_pass "$id: wire_context defined" || ta_fail "$id: wire_context missing"
|
|
58
58
|
|
|
59
59
|
TMPDIR=$(mktemp -d)
|
|
60
60
|
cp "$SCRIPT_DIR/../docs/templates/AGENTS.md" "$TMPDIR/AGENTS.md" 2>/dev/null || echo "# test" > "$TMPDIR/AGENTS.md"
|
|
@@ -63,7 +63,7 @@ test_adapter() {
|
|
|
63
63
|
# shellcheck source=/dev/null
|
|
64
64
|
source "$adapter_file"
|
|
65
65
|
wire_context 2>/dev/null || true
|
|
66
|
-
wire_context 2>/dev/null &&
|
|
66
|
+
wire_context 2>/dev/null && ta_pass "$id: idempotent wire_context" || ta_fail "$id: wire_context failed"
|
|
67
67
|
)
|
|
68
68
|
fi
|
|
69
69
|
}
|
|
@@ -76,5 +76,5 @@ else
|
|
|
76
76
|
done < <(yq -r '.targets[] | (.skill.adapter // .context.adapter)' "$TARGETS_FILE" | sort -u)
|
|
77
77
|
fi
|
|
78
78
|
|
|
79
|
-
echo "test-adapters: $
|
|
80
|
-
[[ "$
|
|
79
|
+
echo "test-adapters: $TA_PASS passed, $TA_FAIL failed"
|
|
80
|
+
[[ "$TA_FAIL" -eq 0 ]] || exit 1
|
|
@@ -16,17 +16,17 @@ NC='\033[0m'
|
|
|
16
16
|
CAP=300
|
|
17
17
|
FAILURES=0
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
trace_size_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
20
|
+
trace_size_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
21
21
|
|
|
22
22
|
check_lines() {
|
|
23
23
|
local file="$1"
|
|
24
24
|
local lines
|
|
25
25
|
lines=$(wc -l < "$REPO_ROOT/$file" | tr -d ' ')
|
|
26
26
|
if [[ "$lines" -le "$CAP" ]]; then
|
|
27
|
-
|
|
27
|
+
trace_size_pass "$file: $lines lines (≤ $CAP)"
|
|
28
28
|
else
|
|
29
|
-
|
|
29
|
+
trace_size_fail "$file: $lines lines (exceeds $CAP)"
|
|
30
30
|
fi
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -19,8 +19,8 @@ NC='\033[0m'
|
|
|
19
19
|
TRACE_ENGINE="$REPO_ROOT/scripts/lib/trace-stories.py"
|
|
20
20
|
FAILURES=0
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
trace_strict_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
23
|
+
trace_strict_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
24
24
|
|
|
25
25
|
run_strict() {
|
|
26
26
|
local root="$1"
|
|
@@ -88,9 +88,9 @@ bash "$REPO_ROOT/scripts/trace-stories.sh" --strict --json >/dev/null 2>&1
|
|
|
88
88
|
repo_exit=$?
|
|
89
89
|
set -e
|
|
90
90
|
if [[ $repo_exit -eq 0 ]]; then
|
|
91
|
-
|
|
91
|
+
trace_strict_pass "trace-stories.sh --strict exits 0 on repo"
|
|
92
92
|
else
|
|
93
|
-
|
|
93
|
+
trace_strict_fail "trace-stories.sh --strict exits $repo_exit on repo (expected 0)"
|
|
94
94
|
fi
|
|
95
95
|
|
|
96
96
|
echo ""
|
|
@@ -103,9 +103,9 @@ todo_output=$(run_strict "$FIXTURE_ROOT")
|
|
|
103
103
|
todo_exit=$?
|
|
104
104
|
set -e
|
|
105
105
|
if [[ $todo_exit -eq 0 ]]; then
|
|
106
|
-
|
|
106
|
+
trace_strict_pass "strict passes when P0 stories are todo with 0 links"
|
|
107
107
|
else
|
|
108
|
-
|
|
108
|
+
trace_strict_fail "strict exits $todo_exit on all-todo fixture (expected 0)"
|
|
109
109
|
echo "$todo_output"
|
|
110
110
|
fi
|
|
111
111
|
|
|
@@ -117,9 +117,9 @@ done_output=$(run_strict "$FIXTURE_ROOT")
|
|
|
117
117
|
done_exit=$?
|
|
118
118
|
set -e
|
|
119
119
|
if [[ $done_exit -eq 2 ]] && grep -q "P0 stories with 0% coverage" <<<"$done_output"; then
|
|
120
|
-
|
|
120
|
+
trace_strict_pass "strict fails when a done P0 story has 0 links"
|
|
121
121
|
else
|
|
122
|
-
|
|
122
|
+
trace_strict_fail "strict exits $done_exit on done-dark fixture (expected 2 with P0 message)"
|
|
123
123
|
echo "$done_output"
|
|
124
124
|
fi
|
|
125
125
|
|
|
@@ -9,12 +9,12 @@ cd "$REPO_ROOT"
|
|
|
9
9
|
|
|
10
10
|
ERRORS=0
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
doctrine_fail() {
|
|
13
13
|
echo "FAIL: $*" >&2
|
|
14
14
|
ERRORS=$((ERRORS + 1))
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
doctrine_pass() {
|
|
18
18
|
echo "ok : $*"
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -33,10 +33,10 @@ done < <(grep -oE '`[a-z][a-z-]+`' docs/references/model-profiles.md \
|
|
|
33
33
|
| sort -u)
|
|
34
34
|
|
|
35
35
|
if [[ ${#DEAD[@]} -eq 0 ]]; then
|
|
36
|
-
|
|
36
|
+
doctrine_pass "all skill names in model-profiles.md resolve to real directories"
|
|
37
37
|
else
|
|
38
38
|
for name in "${DEAD[@]}"; do
|
|
39
|
-
|
|
39
|
+
doctrine_fail "dead skill name in model-profiles.md: '$name' has no matching directory"
|
|
40
40
|
done
|
|
41
41
|
fi
|
|
42
42
|
|
|
@@ -49,9 +49,9 @@ LEGACY_HITS=$(grep -rE "PLAN\.md|STATE\.md|PROJECT\.md|CONTEXT\.md|SUMMARY\.md|V
|
|
|
49
49
|
| grep -v "specs/archive" \
|
|
50
50
|
| wc -l | tr -d ' ') || true
|
|
51
51
|
if [[ "$LEGACY_HITS" -eq 0 ]]; then
|
|
52
|
-
|
|
52
|
+
doctrine_pass "no legacy MD artifact names in live docs/ (docs/archive/ exempt)"
|
|
53
53
|
else
|
|
54
|
-
|
|
54
|
+
doctrine_fail "legacy MD artifact names found in docs/ ($LEGACY_HITS occurrences)"
|
|
55
55
|
grep -rE "PLAN\.md|STATE\.md|PROJECT\.md|CONTEXT\.md|SUMMARY\.md|VERIFICATION\.md|RESEARCH\.md|RELEASE-PLAN\.md" \
|
|
56
56
|
docs/ 2>/dev/null \
|
|
57
57
|
| grep -v "docs/archive/" \
|
|
@@ -68,9 +68,9 @@ fi
|
|
|
68
68
|
STALE_COUNT_HITS=$(grep -rE "expect [0-9]+" docs/ 2>/dev/null \
|
|
69
69
|
| grep -v "expect $LIVE_COUNT" | wc -l | tr -d ' ') || true
|
|
70
70
|
if [[ "$STALE_COUNT_HITS" -eq 0 ]]; then
|
|
71
|
-
|
|
71
|
+
doctrine_pass "skill count annotations match live count ($LIVE_COUNT)"
|
|
72
72
|
else
|
|
73
|
-
|
|
73
|
+
doctrine_fail "stale skill count annotation in docs/ (live=$LIVE_COUNT, $STALE_COUNT_HITS mismatches)"
|
|
74
74
|
grep -rE "expect [0-9]+" docs/ 2>/dev/null | grep -v "expect $LIVE_COUNT" >&2
|
|
75
75
|
fi
|
|
76
76
|
|
|
@@ -86,9 +86,9 @@ LEGACY_SPECS=$(grep -rE "specs/(requirements|plans|audit)\b" \
|
|
|
86
86
|
| grep -v "CHANGELOG\.md\|specs/verifications/reports/\|PLAN-evolve-structure\.md" \
|
|
87
87
|
| wc -l | tr -d ' ') || true
|
|
88
88
|
if [[ "$LEGACY_SPECS" -eq 0 ]]; then
|
|
89
|
-
|
|
89
|
+
doctrine_pass "no legacy specs/ subpaths (requirements/, plans/, audit/)"
|
|
90
90
|
else
|
|
91
|
-
|
|
91
|
+
doctrine_fail "legacy specs/ subpaths found ($LEGACY_SPECS occurrences)"
|
|
92
92
|
grep -rE "specs/(requirements|plans|audit)\b" \
|
|
93
93
|
--include="*.md" --include="*.sh" --include="*.yaml" --include="*.yml" --include="*.json" . \
|
|
94
94
|
2>/dev/null \
|
|
@@ -103,9 +103,9 @@ BCP_SLOP=$(grep -ri "build commit point" . 2>/dev/null \
|
|
|
103
103
|
| grep -v "specs/archive\|\.git\|node_modules\|\.gemini\|\.cursor\|validate-doctrine\.sh" \
|
|
104
104
|
| wc -l | tr -d ' ') || true
|
|
105
105
|
if [[ "$BCP_SLOP" -eq 0 ]]; then
|
|
106
|
-
|
|
106
|
+
doctrine_pass "no 'Build Commit Point' slop — BCP = Business Complexity Points"
|
|
107
107
|
else
|
|
108
|
-
|
|
108
|
+
doctrine_fail "'Build Commit Point' found ($BCP_SLOP hits) — canonical term is 'Business Complexity Points'; see docs/references/bcp.md"
|
|
109
109
|
grep -ri "build commit point" . 2>/dev/null \
|
|
110
110
|
| grep -v "specs/archive\|\.git\|node_modules\|\.gemini\|\.cursor\|validate-doctrine\.sh" | head -5 >&2
|
|
111
111
|
fi
|
|
@@ -113,9 +113,9 @@ fi
|
|
|
113
113
|
# ── Epic 4: SKILL.md size cap ─────────────────────────────────────────────────
|
|
114
114
|
echo "--- [Epic 4] SKILL.md size cap ---"
|
|
115
115
|
if bash "$REPO_ROOT/scripts/check-skill-size.sh" >/dev/null 2>&1; then
|
|
116
|
-
|
|
116
|
+
doctrine_pass "all SKILL.md files within tiered size cap (150 critical-path / 120 utility)"
|
|
117
117
|
else
|
|
118
|
-
|
|
118
|
+
doctrine_fail "a SKILL.md exceeds its size cap — run: bash scripts/check-skill-size.sh"
|
|
119
119
|
bash "$REPO_ROOT/scripts/check-skill-size.sh" 2>&1 | grep "^FAIL:" >&2 || true
|
|
120
120
|
fi
|
|
121
121
|
|
|
@@ -130,19 +130,19 @@ for s in "${CRITICAL_HANDOFF[@]}"; do
|
|
|
130
130
|
fi
|
|
131
131
|
done
|
|
132
132
|
if [[ ${#MISSING_HANDOFF[@]} -eq 0 ]]; then
|
|
133
|
-
|
|
133
|
+
doctrine_pass "all critical-path skills document a handoff target"
|
|
134
134
|
else
|
|
135
135
|
for s in "${MISSING_HANDOFF[@]}"; do
|
|
136
|
-
|
|
136
|
+
doctrine_fail "critical-path skill '$s' has no documented handoff/next_skill"
|
|
137
137
|
done
|
|
138
138
|
fi
|
|
139
139
|
|
|
140
140
|
# ── CI: trace-stories wrapper smoke (REPO_ROOT must be set under set -u) ───────
|
|
141
141
|
echo "--- [CI] trace-stories wrapper smoke ---"
|
|
142
142
|
if bash "$REPO_ROOT/scripts/trace-stories.sh" --help >/dev/null 2>&1; then
|
|
143
|
-
|
|
143
|
+
doctrine_pass "trace-stories.sh --help exits 0 (REPO_ROOT initialized)"
|
|
144
144
|
else
|
|
145
|
-
|
|
145
|
+
doctrine_fail "trace-stories.sh --help failed — REPO_ROOT likely unset; run: bash scripts/trace-stories.sh --help"
|
|
146
146
|
fi
|
|
147
147
|
|
|
148
148
|
# ── Summary ────────────────────────────────────────────────────────────────────
|
|
@@ -20,7 +20,7 @@ WARNINGS=0
|
|
|
20
20
|
# Documented naming exceptions (craft-skill REFERENCE.md)
|
|
21
21
|
VERB_NOUN_EXCEPTIONS="grill-me grill-with-docs context7-mcp using-bigpowers deploy"
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
catalog_usage() {
|
|
24
24
|
echo "Usage: bash scripts/validate-skill-catalog.sh [--archive] [--strict] [--skill <name>]"
|
|
25
25
|
exit 2
|
|
26
26
|
}
|
|
@@ -31,15 +31,15 @@ while [[ $# -gt 0 ]]; do
|
|
|
31
31
|
--strict) STRICT=true; shift ;;
|
|
32
32
|
--skill) TARGET_SKILL="${2:-}"; shift 2 ;;
|
|
33
33
|
--skill=*) TARGET_SKILL="${1#*=}"; shift ;;
|
|
34
|
-
-h|--help)
|
|
35
|
-
*) echo "Unknown flag: $1" >&2;
|
|
34
|
+
-h|--help) catalog_usage ;;
|
|
35
|
+
*) echo "Unknown flag: $1" >&2; catalog_usage ;;
|
|
36
36
|
esac
|
|
37
37
|
done
|
|
38
38
|
|
|
39
39
|
report_catalog_violation() { echo "FAIL: $1" >&2; VIOLATIONS=$((VIOLATIONS + 1)); }
|
|
40
40
|
report_catalog_warning() { echo "WARN: $1" >&2; WARNINGS=$((WARNINGS + 1)); }
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
catalog_validate_verb_noun() {
|
|
43
43
|
local name="$1"
|
|
44
44
|
if echo " $VERB_NOUN_EXCEPTIONS " | grep -q " $name "; then
|
|
45
45
|
return 0
|
|
@@ -55,12 +55,12 @@ check_verb_noun() {
|
|
|
55
55
|
fi
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
catalog_validate_skill() {
|
|
59
59
|
local skill_md="$1"
|
|
60
60
|
local name
|
|
61
61
|
name=$(basename "$(dirname "$skill_md")")
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
catalog_validate_verb_noun "$name" || true
|
|
64
64
|
|
|
65
65
|
if ! grep -q 'HARD GATE' "$skill_md" 2>/dev/null; then
|
|
66
66
|
if $STRICT; then report_catalog_violation "$name: missing HARD GATE block"; else report_catalog_warning "$name: missing HARD GATE block"; fi
|
|
@@ -68,7 +68,7 @@ check_skill() {
|
|
|
68
68
|
|
|
69
69
|
local desc
|
|
70
70
|
desc=$(awk 'BEGIN{f=0} /^---$/{f++; next} f==1{print} f==2{exit}' "$skill_md" \
|
|
71
|
-
| grep -E '^description:' | head -1 | sed 's/^description: *//; s
|
|
71
|
+
| grep -E '^description:' | head -1 | sed 's/^description: *//; s/^\x3e //' | tr -d '"' || true)
|
|
72
72
|
if [[ -n "$desc" && ${#desc} -gt 1024 ]]; then
|
|
73
73
|
report_catalog_violation "$name: description ${#desc} chars (max 1024)"
|
|
74
74
|
fi
|
|
@@ -80,7 +80,7 @@ check_skill() {
|
|
|
80
80
|
fi
|
|
81
81
|
|
|
82
82
|
local cmd
|
|
83
|
-
cmd=$(grep
|
|
83
|
+
cmd=$(grep 'verify:' "$skill_md" 2>/dev/null | tail -1 | sed 's/^\x3e //; s/^→ verify: *//' || true)
|
|
84
84
|
if [[ -z "$cmd" ]]; then
|
|
85
85
|
if $STRICT; then report_catalog_violation "$name: missing → verify: command"; else report_catalog_warning "$name: missing → verify: command"; fi
|
|
86
86
|
fi
|
|
@@ -90,11 +90,11 @@ echo "Validating skill catalog under $SKILLS_ROOT ..."
|
|
|
90
90
|
if [[ -n "$TARGET_SKILL" ]]; then
|
|
91
91
|
skill_md="$SKILLS_ROOT/$TARGET_SKILL/SKILL.md"
|
|
92
92
|
[[ -f "$skill_md" ]] || { echo "FAIL: $skill_md not found" >&2; exit 1; }
|
|
93
|
-
|
|
93
|
+
catalog_validate_skill "$skill_md"
|
|
94
94
|
else
|
|
95
95
|
for skill_md in "$SKILLS_ROOT"/*/SKILL.md; do
|
|
96
96
|
[[ -f "$skill_md" ]] || continue
|
|
97
|
-
|
|
97
|
+
catalog_validate_skill "$skill_md"
|
|
98
98
|
done
|
|
99
99
|
fi
|
|
100
100
|
|