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.
Files changed (37) hide show
  1. package/.pi/package.json +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/bin/bigspec +35 -1
  4. package/kernel/src/publish-to-wiki.sh +131 -6
  5. package/package.json +1 -1
  6. package/scripts/audit-compliance.sh +2 -3
  7. package/scripts/build-receipts.sh +2 -2
  8. package/scripts/build-skill-graph.sh +1 -1
  9. package/scripts/check-spec-drift.sh +2 -2
  10. package/scripts/check-spec-version-gap.sh +13 -13
  11. package/scripts/compile-rule-matrix.sh +2 -2
  12. package/scripts/decompose-conventions.sh +2 -2
  13. package/scripts/generate-adr-wiki.sh +3 -2
  14. package/scripts/generate-agent-guide.sh +2 -2
  15. package/scripts/generate-epics-wiki.sh +3 -2
  16. package/scripts/hooks/token-mgmt-pre-tool-use.sh +5 -5
  17. package/scripts/land-branch.sh +11 -11
  18. package/scripts/lib/audit-compliance-report.sh +1 -0
  19. package/scripts/lib/emit-okf-metrics.sh +1 -0
  20. package/scripts/lib/golden-suite-report.sh +1 -0
  21. package/scripts/lib/golden-suite-run.sh +5 -1
  22. package/scripts/lib/migrate-version-transforms.sh +3 -4
  23. package/scripts/lib/record-cycle-time-lib.sh +31 -0
  24. package/scripts/lib/sync-post.sh +6 -1
  25. package/scripts/lib/test-assertions.sh +11 -0
  26. package/scripts/lib/validate-okf-kinds.sh +7 -4
  27. package/scripts/record-cycle-time.sh +4 -28
  28. package/scripts/sync-bugs-registry.sh +1 -0
  29. package/scripts/sync-skills.sh +3 -3
  30. package/scripts/test-adapters.sh +13 -13
  31. package/scripts/test-trace-matrix-size.sh +4 -4
  32. package/scripts/test-trace-strict.sh +8 -8
  33. package/scripts/validate-doctrine.sh +18 -18
  34. package/scripts/validate-skill-catalog.sh +10 -10
  35. package/scripts/verify-install.sh +39 -37
  36. package/tests/test-epic-status-drift.sh +53 -0
  37. 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 yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
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="$(yaml_get "$REPO_ROOT/$file" "$old_key" 2>/dev/null)" || true
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 ! yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
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
+ }
@@ -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 [[ -n "$ext_ver" && -n "$pkg_ver" && "$ext_ver" != "$pkg_ver" ]]; then
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 ! git -C "$ROOT" log --oneline -1 "$commit_range" >/dev/null 2>&1; then
63
- printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
64
- EXIT_CODE=1; return
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() { echo "record-cycle-time: $*" >&2; exit 1; }
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"
@@ -77,6 +77,7 @@ for e in entries:
77
77
  bundle = f"""---
78
78
  okf_kind: concept
79
79
  okf_version: "0.1"
80
+ type: Bug
80
81
  id: "{bug_id}"
81
82
  title: "{title_escaped}"
82
83
  category: bug
@@ -43,9 +43,9 @@ for arg in "$@"; do
43
43
  done
44
44
 
45
45
  _LIB_DIR="$(dirname "${BASH_SOURCE[0]}")/lib"
46
- source "$_LIB_DIR/skill-common.sh"
47
- source "$_LIB_DIR/sync-render.sh"
48
- source "$_LIB_DIR/sync-post.sh"
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"
@@ -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
- cleanup() { [[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"; }
27
- trap cleanup EXIT
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" ]] || { fail "$id: adapter missing"; return; }
42
- pass "$id: exists"
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 && pass "$id: sourceable" || { fail "$id: source error"; return; }
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 && pass "$id: render_skill defined" || fail "$id: render_skill missing"
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 && pass "$id: wire_context defined" || fail "$id: wire_context missing"
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 && pass "$id: idempotent wire_context" || fail "$id: wire_context failed"
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: $PASS passed, $FAIL failed"
80
- [[ "$FAIL" -eq 0 ]] || exit 1
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
- pass() { echo -e "${GREEN}PASS${NC} $1"; }
20
- fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
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
- pass "$file: $lines lines (≤ $CAP)"
27
+ trace_size_pass "$file: $lines lines (≤ $CAP)"
28
28
  else
29
- fail "$file: $lines lines (exceeds $CAP)"
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
- pass() { echo -e "${GREEN}PASS${NC} $1"; }
23
- fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
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
- pass "trace-stories.sh --strict exits 0 on repo"
91
+ trace_strict_pass "trace-stories.sh --strict exits 0 on repo"
92
92
  else
93
- fail "trace-stories.sh --strict exits $repo_exit on repo (expected 0)"
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
- pass "strict passes when P0 stories are todo with 0 links"
106
+ trace_strict_pass "strict passes when P0 stories are todo with 0 links"
107
107
  else
108
- fail "strict exits $todo_exit on all-todo fixture (expected 0)"
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
- pass "strict fails when a done P0 story has 0 links"
120
+ trace_strict_pass "strict fails when a done P0 story has 0 links"
121
121
  else
122
- fail "strict exits $done_exit on done-dark fixture (expected 2 with P0 message)"
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
- fail() {
12
+ doctrine_fail() {
13
13
  echo "FAIL: $*" >&2
14
14
  ERRORS=$((ERRORS + 1))
15
15
  }
16
16
 
17
- pass() {
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
- pass "all skill names in model-profiles.md resolve to real directories"
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
- fail "dead skill name in model-profiles.md: '$name' has no matching directory"
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
- pass "no legacy MD artifact names in live docs/ (docs/archive/ exempt)"
52
+ doctrine_pass "no legacy MD artifact names in live docs/ (docs/archive/ exempt)"
53
53
  else
54
- fail "legacy MD artifact names found in docs/ ($LEGACY_HITS occurrences)"
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
- pass "skill count annotations match live count ($LIVE_COUNT)"
71
+ doctrine_pass "skill count annotations match live count ($LIVE_COUNT)"
72
72
  else
73
- fail "stale skill count annotation in docs/ (live=$LIVE_COUNT, $STALE_COUNT_HITS mismatches)"
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
- pass "no legacy specs/ subpaths (requirements/, plans/, audit/)"
89
+ doctrine_pass "no legacy specs/ subpaths (requirements/, plans/, audit/)"
90
90
  else
91
- fail "legacy specs/ subpaths found ($LEGACY_SPECS occurrences)"
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
- pass "no 'Build Commit Point' slop — BCP = Business Complexity Points"
106
+ doctrine_pass "no 'Build Commit Point' slop — BCP = Business Complexity Points"
107
107
  else
108
- fail "'Build Commit Point' found ($BCP_SLOP hits) — canonical term is 'Business Complexity Points'; see docs/references/bcp.md"
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
- pass "all SKILL.md files within tiered size cap (150 critical-path / 120 utility)"
116
+ doctrine_pass "all SKILL.md files within tiered size cap (150 critical-path / 120 utility)"
117
117
  else
118
- fail "a SKILL.md exceeds its size cap — run: bash scripts/check-skill-size.sh"
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
- pass "all critical-path skills document a handoff target"
133
+ doctrine_pass "all critical-path skills document a handoff target"
134
134
  else
135
135
  for s in "${MISSING_HANDOFF[@]}"; do
136
- fail "critical-path skill '$s' has no documented handoff/next_skill"
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
- pass "trace-stories.sh --help exits 0 (REPO_ROOT initialized)"
143
+ doctrine_pass "trace-stories.sh --help exits 0 (REPO_ROOT initialized)"
144
144
  else
145
- fail "trace-stories.sh --help failed — REPO_ROOT likely unset; run: bash scripts/trace-stories.sh --help"
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
- usage() {
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) usage ;;
35
- *) echo "Unknown flag: $1" >&2; usage ;;
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
- check_verb_noun() {
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
- check_skill() {
58
+ catalog_validate_skill() {
59
59
  local skill_md="$1"
60
60
  local name
61
61
  name=$(basename "$(dirname "$skill_md")")
62
62
 
63
- check_verb_noun "$name" || true
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/^> //' | tr -d '"' || true)
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 -E '^(> )?→ verify:' "$skill_md" 2>/dev/null | tail -1 | sed 's/^> // ; s/^→ verify: *//' || true)
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
- check_skill "$skill_md"
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
- check_skill "$skill_md"
97
+ catalog_validate_skill "$skill_md"
98
98
  done
99
99
  fi
100
100