bigpowers 2.75.2 → 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 (34) hide show
  1. package/.pi/package.json +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/kernel/src/publish-to-wiki.sh +13 -3
  4. package/package.json +1 -1
  5. package/scripts/audit-compliance.sh +2 -3
  6. package/scripts/build-receipts.sh +2 -2
  7. package/scripts/build-skill-graph.sh +1 -1
  8. package/scripts/check-spec-drift.sh +2 -2
  9. package/scripts/check-spec-version-gap.sh +13 -13
  10. package/scripts/compile-rule-matrix.sh +2 -2
  11. package/scripts/decompose-conventions.sh +2 -2
  12. package/scripts/generate-adr-wiki.sh +2 -2
  13. package/scripts/generate-agent-guide.sh +2 -2
  14. package/scripts/generate-epics-wiki.sh +2 -2
  15. package/scripts/hooks/token-mgmt-pre-tool-use.sh +5 -5
  16. package/scripts/land-branch.sh +11 -11
  17. package/scripts/lib/audit-compliance-report.sh +1 -0
  18. package/scripts/lib/emit-okf-metrics.sh +1 -0
  19. package/scripts/lib/golden-suite-report.sh +1 -0
  20. package/scripts/lib/golden-suite-run.sh +5 -1
  21. package/scripts/lib/migrate-version-transforms.sh +3 -4
  22. package/scripts/lib/record-cycle-time-lib.sh +31 -0
  23. package/scripts/lib/sync-post.sh +6 -1
  24. package/scripts/lib/test-assertions.sh +11 -0
  25. package/scripts/lib/validate-okf-kinds.sh +6 -3
  26. package/scripts/record-cycle-time.sh +4 -28
  27. package/scripts/sync-skills.sh +3 -3
  28. package/scripts/test-adapters.sh +13 -13
  29. package/scripts/test-trace-matrix-size.sh +4 -4
  30. package/scripts/test-trace-strict.sh +8 -8
  31. package/scripts/validate-doctrine.sh +18 -18
  32. package/scripts/validate-skill-catalog.sh +10 -10
  33. package/scripts/verify-install.sh +39 -37
  34. package/tests/test-okf-generators.sh +5 -0
package/.pi/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.75.2",
3
+ "version": "2.76.0",
4
4
  "description": "77 skills — 73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "pi-package"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [2.76.0](https://github.com/danielvm-git/bigpowers/compare/v2.75.2...v2.76.0) (2026-07-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **ci:** add OKF type fields and gate wiki publish on WIKI_PAT ([9628122](https://github.com/danielvm-git/bigpowers/commit/96281227e91ebed19300daed8dd05d51d6021c9e))
7
+ * **ci:** skip estimated commit_range check and wiki publish without PAT ([bf079ad](https://github.com/danielvm-git/bigpowers/commit/bf079ad384c4363980fe30383406d37629718b89))
8
+
9
+
10
+ ### Features
11
+
12
+ * **e37:** Reach — Close release: AGENTS.md spine, targets.yaml registry, OSS waves A-E; fix 6 compliance bugs ([5a7f407](https://github.com/danielvm-git/bigpowers/commit/5a7f407008818cec518969a2b46e5ae28ee130bb))
13
+
1
14
  ## [2.75.2](https://github.com/danielvm-git/bigpowers/compare/v2.75.1...v2.75.2) (2026-07-07)
2
15
 
3
16
 
@@ -83,8 +83,13 @@ push_to_wiki() {
83
83
  local work_dir="${WIKI_WORK_DIR:-}"
84
84
 
85
85
  if [[ -z "$wiki_repo" ]]; then
86
- echo "publish-to-wiki: WIKI_REPO_URL is not set — cannot push" >&2
87
- return 1
86
+ echo "publish-to-wiki: WIKI_REPO_URL is not set — skipping push" >&2
87
+ return 0
88
+ fi
89
+
90
+ local clone_url="$wiki_repo"
91
+ if [[ -n "${WIKI_PAT:-}" && "$wiki_repo" == https://* ]]; then
92
+ clone_url="${wiki_repo/https:\/\//https://x-access-token:${WIKI_PAT}@}"
88
93
  fi
89
94
 
90
95
  if [[ -z "$work_dir" ]]; then
@@ -95,7 +100,7 @@ push_to_wiki() {
95
100
  if [[ -d "$work_dir/.git" ]]; then
96
101
  git -C "$work_dir" pull --rebase origin master 2>/dev/null || git -C "$work_dir" pull --rebase origin main
97
102
  else
98
- git clone "$wiki_repo" "$work_dir"
103
+ git clone "$clone_url" "$work_dir"
99
104
  fi
100
105
 
101
106
  rsync -a --delete "$staged_root/" "$work_dir/"
@@ -137,4 +142,9 @@ if [[ "$DRY_RUN" -eq 1 ]]; then
137
142
  exit 0
138
143
  fi
139
144
 
145
+ if [[ -z "${WIKI_PAT:-}" ]]; then
146
+ echo "publish-to-wiki: WIKI_PAT not set — skipping wiki push"
147
+ exit 0
148
+ fi
149
+
140
150
  push_to_wiki "$STAGING"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.75.2",
3
+ "version": "2.76.0",
4
4
  "description": "73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # audit-compliance.sh — Agentic Gherkin Compliance Harness (LLM-Judge Upgrade)
3
3
 
4
- _LIB="$(dirname "${BASH_SOURCE[0]}")/lib"
5
- source "$_LIB/audit-compliance-runner.sh"
6
- source "$_LIB/audit-compliance-report.sh"
4
+ source "$(dirname "${BASH_SOURCE[0]}")/lib/audit-compliance-runner.sh"
5
+ source "$(dirname "${BASH_SOURCE[0]}")/lib/audit-compliance-report.sh"
7
6
 
8
7
  audit_show_help() {
9
8
  cat <<EOF
@@ -6,7 +6,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
6
6
  ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "build-receipts: not inside a git repo" >&2; exit 1; }
7
7
  OUTFILE="$ROOT/specs/receipts.json"
8
8
  TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
9
- emit_json() {
9
+ receipts_emit_json() {
10
10
  $PYTHON -c "
11
11
  import json, sys, os, subprocess, glob as gb
12
12
  sections = {}
@@ -103,6 +103,6 @@ for key, sec in sections.items(): print(f' {key}: source={sec.get(\"source\",\"
103
103
  "
104
104
  }
105
105
  echo "build-receipts: aggregating quality evidence..."
106
- emit_json
106
+ receipts_emit_json
107
107
  echo "build-receipts: done — $(date -u +%H:%M:%SZ)"
108
108
  exit 0
@@ -12,7 +12,7 @@ GRAPH_JSONL="$REPO_ROOT/bigpowers-mcp/graph.jsonl"
12
12
  SKILL_GRAPH_JSON="$REPO_ROOT/specs/skill-graph.json"
13
13
  SKILLS_WIKI="$REPO_ROOT/specs/skills-wiki/skills"
14
14
 
15
- show_help() {
15
+ skill_graph_show_help() {
16
16
  cat <<EOF
17
17
  Usage: $(basename "$0") [OPTION]
18
18
 
@@ -10,7 +10,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
10
10
  TRACE_MATRIX="$REPO_ROOT/specs/traceability-matrix.json"
11
11
  DRIFT_REPORT="$REPO_ROOT/specs/drift-report.json"
12
12
 
13
- show_help() {
13
+ drift_show_help() {
14
14
  cat <<EOF
15
15
  Usage: $(basename "$0") [OPTION]
16
16
 
@@ -23,7 +23,7 @@ EOF
23
23
  }
24
24
 
25
25
  if [ "$#" -eq 1 ] && [ "$1" = "--help" ]; then
26
- show_help
26
+ drift_show_help
27
27
  exit 0
28
28
  fi
29
29
 
@@ -5,7 +5,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
5
5
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
6
  BP_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
7
7
 
8
- usage() {
8
+ gap_usage() {
9
9
  cat <<'EOF'
10
10
  Usage: check-spec-version-gap.sh [--help] [--project <dir>] [--json]
11
11
  Exit codes: 0=no gap, 1=gap, 2=no specs, 3=blocked, 4=error
@@ -18,7 +18,7 @@ JSON_STDOUT=false
18
18
  SKIP_BLOCK=false
19
19
  while [[ $# -gt 0 ]]; do
20
20
  case "$1" in
21
- --help|-h) usage ;;
21
+ --help|-h) gap_usage ;;
22
22
  --project) PROJECT_DIR="${2:?}"; shift 2 ;;
23
23
  --json) JSON_STDOUT=true; shift ;;
24
24
  --skip-block) SKIP_BLOCK=true; shift ;;
@@ -29,7 +29,7 @@ done
29
29
  PROJECT_DIR="$(cd "$PROJECT_DIR" 2>/dev/null && pwd)" || { echo "bad dir: $PROJECT_DIR" >&2; exit 4; }
30
30
  SPECS="$PROJECT_DIR/specs"
31
31
 
32
- emit_json() {
32
+ gap_emit_json() {
33
33
  if $JSON_STDOUT; then echo "$1"; else echo "$1" >&2; fi
34
34
  }
35
35
 
@@ -37,7 +37,7 @@ get_installed_version() {
37
37
  $PYTHON -c "import json; d=json.load(open('$BP_ROOT/package.json')); print(d.get('version','unknown'))" 2>/dev/null || echo "unknown"
38
38
  }
39
39
 
40
- yaml_get() {
40
+ gap_yaml_get() {
41
41
  [[ ! -f "$1" ]] && { echo ""; return; }
42
42
  $PYTHON -c "
43
43
  import sys, yaml
@@ -116,29 +116,29 @@ check_active_work() {
116
116
  branch=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "")
117
117
  if on_feature_branch "$branch"; then echo "true"; return; fi
118
118
  if has_dirty_specs; then echo "true"; return; fi
119
- af=$(yaml_get "$SPECS/state.yaml" "active_flow")
119
+ af=$(gap_yaml_get "$SPECS/state.yaml" "active_flow")
120
120
  if is_blocking_active_flow "$af"; then echo "true"; return; fi
121
121
  echo "false"
122
122
  }
123
123
 
124
124
  # Main
125
- [[ ! -d "$SPECS" ]] && { emit_json '{"gap":false,"reason":"NO_SPECS"}'; exit 2; }
125
+ [[ ! -d "$SPECS" ]] && { gap_emit_json '{"gap":false,"reason":"NO_SPECS"}'; exit 2; }
126
126
  IV=$(get_installed_version)
127
127
  if [ "$SKIP_BLOCK" != "true" ] && [[ "$(check_active_work)" == "true" ]]; then
128
- emit_json '{"gap":true,"blocked":true,"reason":"ACTIVE_WORK","installed_version":"'"$IV"'"}'
128
+ gap_emit_json '{"gap":true,"blocked":true,"reason":"ACTIVE_WORK","installed_version":"'"$IV"'"}'
129
129
  exit 3
130
130
  fi
131
131
 
132
- SV=$(yaml_get "$SPECS/state.yaml" "bigpowers_version")
132
+ SV=$(gap_yaml_get "$SPECS/state.yaml" "bigpowers_version")
133
133
 
134
134
  if yaml_value_set "$SV"; then
135
135
  if [[ "$SV" == "$IV" ]]; then
136
- emit_json '{"gap":false,"stamp":true,"detected_version":"'"$SV"'","installed_version":"'"$IV"'","detection_method":"stamp","confidence":"high"}'
136
+ gap_emit_json '{"gap":false,"stamp":true,"detected_version":"'"$SV"'","installed_version":"'"$IV"'","detection_method":"stamp","confidence":"high"}'
137
137
  exit 0
138
138
  fi
139
139
  APP=$(find_migrations "$SV")
140
140
  CNT=$(echo "$APP" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
141
- emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); print(json.dumps({'gap':True,'stamp':True,'detected_version':'$SV','detection_method':'stamp','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[m['id'] for m in a],'confidence':'high','active_work_blocked':False}))")"
141
+ gap_emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); print(json.dumps({'gap':True,'stamp':True,'detected_version':'$SV','detection_method':'stamp','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[m['id'] for m in a],'confidence':'high','active_work_blocked':False}))")"
142
142
  exit 1
143
143
  fi
144
144
 
@@ -150,7 +150,7 @@ for f in release-plan.yaml execution-status.yaml product/SCOPE_LATEST.yaml metri
150
150
  [[ -f "$SPECS/$f" ]] && { MARKERS+=("$f"); [[ -z "$DV" ]] && DV="2.0.0"; }
151
151
  done
152
152
  if [[ -f "$SPECS/state.yaml" ]]; then
153
- ec=$(yaml_get "$SPECS/state.yaml" "epic_cycle.current_step")
153
+ ec=$(gap_yaml_get "$SPECS/state.yaml" "epic_cycle.current_step")
154
154
  if yaml_value_set "$ec"; then
155
155
  MARKERS+=("epic_cycle"); [[ "$DV" == "2.0.0" ]] && DV="2.20.0"
156
156
  fi
@@ -161,12 +161,12 @@ fi
161
161
  [[ -z "$DV" ]] && { DV="unknown"; CONF="low"; }
162
162
 
163
163
  if [[ "$DV" != "unknown" ]] && ! version_lt "$DV" "$IV"; then
164
- [[ "$DV" == "$IV" ]] && { emit_json '{"gap":false,"stamp":false,"detected_version":"'"$DV"'","installed_version":"'"$IV"'","detection_method":"fingerprint","confidence":"'"$CONF"'"}'; exit 0; }
164
+ [[ "$DV" == "$IV" ]] && { gap_emit_json '{"gap":false,"stamp":false,"detected_version":"'"$DV"'","installed_version":"'"$IV"'","detection_method":"fingerprint","confidence":"'"$CONF"'"}'; exit 0; }
165
165
  fi
166
166
 
167
167
  APP=$(find_migrations "$DV")
168
168
  CNT=$(echo "$APP" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
169
169
  MJ=$(printf '%s\n' "${MARKERS[@]}" | $PYTHON -c "import json,sys; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))" 2>/dev/null || echo "[]")
170
170
 
171
- emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); m=json.loads('''$MJ'''); print(json.dumps({'gap':True,'stamp':False,'detected_version':'$DV','detection_method':'fingerprint','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[x['id'] for x in a],'confidence':'$CONF','active_work_blocked':False,'matched_markers':m}))")"
171
+ gap_emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); m=json.loads('''$MJ'''); print(json.dumps({'gap':True,'stamp':False,'detected_version':'$DV','detection_method':'fingerprint','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[x['id'] for x in a],'confidence':'$CONF','active_work_blocked':False,'matched_markers':m}))")"
172
172
  exit 1
@@ -10,7 +10,7 @@ CONVENTIONS="$REPO_ROOT/CONVENTIONS.md"
10
10
  OUTPUT="$REPO_ROOT/specs/rule-matrix.json"
11
11
  MATRIX_VERSION="1.0.0"
12
12
 
13
- show_help() {
13
+ matrix_show_help() {
14
14
  cat <<EOF
15
15
  Usage: $(basename "$0") [OPTION]
16
16
 
@@ -26,7 +26,7 @@ EOF
26
26
  STDOUT_ONLY=0
27
27
  if [ "$#" -eq 1 ]; then
28
28
  case "$1" in
29
- --help) show_help; exit 0 ;;
29
+ --help) matrix_show_help; exit 0 ;;
30
30
  --stdout) STDOUT_ONLY=1 ;;
31
31
  *) echo "[ERROR] Unknown option: $1" >&2; exit 1 ;;
32
32
  esac
@@ -11,7 +11,7 @@ CONVENTIONS_WIKI="$REPO_ROOT/specs/conventions-wiki"
11
11
  CONVENTIONS_INDEX="$CONVENTIONS_WIKI/index.md"
12
12
  SKILL_GRAPH="$REPO_ROOT/specs/skill-graph.json"
13
13
 
14
- show_help() {
14
+ decompose_show_help() {
15
15
  cat <<EOF
16
16
  Usage: $(basename "$0") [OPTION]
17
17
 
@@ -24,7 +24,7 @@ EOF
24
24
  }
25
25
 
26
26
  if [ "$#" -eq 1 ] && [ "$1" = "--help" ]; then
27
- show_help
27
+ decompose_show_help
28
28
  exit 0
29
29
  fi
30
30
 
@@ -17,7 +17,7 @@ fi
17
17
 
18
18
  count=0
19
19
 
20
- assign_tier() {
20
+ adr_wiki_assign_tier() {
21
21
  local adr_id="$1"
22
22
  case "$adr_id" in
23
23
  ADR-0001|ADR-0002|ADR-0003|0001-*|0002-*|0003-*) echo "core" ;;
@@ -55,7 +55,7 @@ if len(parts) >= 3:
55
55
  fi
56
56
  [ -z "$title" ] && title="$adr_name"
57
57
 
58
- tier=$(assign_tier "$adr_name")
58
+ tier=$(adr_wiki_assign_tier "$adr_name")
59
59
 
60
60
  bundle="$WIKI/${adr_name}.okf.md"
61
61
 
@@ -9,7 +9,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
9
  CLAUDE_MD="$REPO_ROOT/CLAUDE.md"
10
10
  AGENT_GUIDE="$REPO_ROOT/specs/agent-guide"
11
11
 
12
- show_help() {
12
+ agent_guide_show_help() {
13
13
  cat <<EOF
14
14
  Usage: $(basename "$0") [OPTION]
15
15
 
@@ -22,7 +22,7 @@ EOF
22
22
  }
23
23
 
24
24
  if [ "$#" -eq 1 ] && [ "$1" = "--help" ]; then
25
- show_help
25
+ agent_guide_show_help
26
26
  exit 0
27
27
  fi
28
28
 
@@ -16,7 +16,7 @@ extract() {
16
16
 
17
17
  count=0
18
18
 
19
- assign_tier() {
19
+ epics_wiki_assign_tier() {
20
20
  local epic_id="$1"
21
21
  case "$epic_id" in
22
22
  e28|e38|e40|e41|e44|e45|e46) echo "core" ;;
@@ -43,7 +43,7 @@ for epic_yaml in "$ROOT"/specs/epics/*/epic.yaml "$ROOT"/specs/epics/archive/*/e
43
43
 
44
44
  [[ -z "$epic_id" ]] && continue
45
45
 
46
- tier=$(assign_tier "$epic_id")
46
+ tier=$(epics_wiki_assign_tier "$epic_id")
47
47
 
48
48
  # Build references array from story list
49
49
  story_count=$(grep -c '^\s*- id:' "$epic_yaml" 2>/dev/null || true)
@@ -7,7 +7,7 @@ READ_MAX_BYTES=102400 # 100KB
7
7
  GREP_MAX_MATCHES=200
8
8
  BASH_WARN_PATTERNS='^(npm test|pnpm test|yarn test|cargo test|go test|jest|vitest|pytest|rake test|rspec|git log|git diff|docker logs|kubectl logs|find |rg |grep )'
9
9
 
10
- deny() {
10
+ token_mgmt_deny() {
11
11
  echo "TOKEN-MGMT BLOCK: $1" >&2
12
12
  echo "Use sqz_read_file / sqz_grep / rtk / bts compress per CLAUDE.md § Token Management." >&2
13
13
  exit 2
@@ -23,16 +23,16 @@ case "$TOOL" in
23
23
  [[ -n "$PATH_ARG" && -f "$PATH_ARG" ]] || exit 0
24
24
  SIZE="$(wc -c < "$PATH_ARG" | tr -d ' ')"
25
25
  if (( SIZE > READ_MAX_BYTES )); then
26
- deny "Read $PATH_ARG is ${SIZE} bytes (max ${READ_MAX_BYTES}). Use sqz_read_file or bts compress."
26
+ token_mgmt_deny "Read $PATH_ARG is ${SIZE} bytes (max ${READ_MAX_BYTES}). Use sqz_read_file or bts compress."
27
27
  fi
28
28
  ;;
29
29
  Grep|grep)
30
30
  LIMIT="$(echo "$TOOL_INPUT" | jq -r '.head_limit // .max_matches // empty')"
31
31
  if [[ -z "$LIMIT" || "$LIMIT" == "null" ]]; then
32
- deny "Grep without head_limit (max ${GREP_MAX_MATCHES}). Set head_limit<=${GREP_MAX_MATCHES} or use sqz_grep."
32
+ token_mgmt_deny "Grep without head_limit (max ${GREP_MAX_MATCHES}). Set head_limit<=${GREP_MAX_MATCHES} or use sqz_grep."
33
33
  fi
34
34
  if [[ "$LIMIT" =~ ^[0-9]+$ ]] && (( LIMIT > GREP_MAX_MATCHES )); then
35
- deny "Grep head_limit=${LIMIT} exceeds ${GREP_MAX_MATCHES}. Lower limit or use sqz_grep."
35
+ token_mgmt_deny "Grep head_limit=${LIMIT} exceeds ${GREP_MAX_MATCHES}. Lower limit or use sqz_grep."
36
36
  fi
37
37
  ;;
38
38
  Bash|bash|Shell|shell)
@@ -42,7 +42,7 @@ case "$TOOL" in
42
42
  exit 0
43
43
  fi
44
44
  if echo "$CMD" | grep -qE "$BASH_WARN_PATTERNS"; then
45
- deny "Bash may exceed 500 lines of output. Prefix with rtk or pipe through sqz compress."
45
+ token_mgmt_deny "Bash may exceed 500 lines of output. Prefix with rtk or pipe through sqz compress."
46
46
  fi
47
47
  ;;
48
48
  *)
@@ -13,7 +13,7 @@ usage_land() {
13
13
  exit 1
14
14
  }
15
15
 
16
- deny() {
16
+ land_branch_deny() {
17
17
  echo "ERROR: $1" >&2
18
18
  exit 1
19
19
  }
@@ -34,16 +34,16 @@ COMMIT_MSG="${ARGS[1]:-}"
34
34
  [ -n "$FEATURE_BRANCH" ] && [ -n "$COMMIT_MSG" ] || usage_land
35
35
 
36
36
  if [[ ! "$COMMIT_MSG" =~ $CONVENTIONAL_REGEX ]]; then
37
- deny "Commit message must follow Conventional Commits: <type>(<scope>): <subject>"
37
+ land_branch_deny "Commit message must follow Conventional Commits: <type>(<scope>): <subject>"
38
38
  fi
39
39
 
40
40
  if [ ${#COMMIT_MSG} -gt 72 ]; then
41
- deny "Commit subject line must be 72 characters or less"
41
+ land_branch_deny "Commit subject line must be 72 characters or less"
42
42
  fi
43
43
 
44
44
  # Primary worktree only (.git is a directory, not a gitdir pointer file)
45
45
  if [ -f .git ]; then
46
- deny "Run from the primary repository root, not a linked worktree (cd to main repo first)"
46
+ land_branch_deny "Run from the primary repository root, not a linked worktree (cd to main repo first)"
47
47
  fi
48
48
 
49
49
  detect_default_branch() {
@@ -58,7 +58,7 @@ detect_default_branch() {
58
58
  elif git show-ref --verify --quiet refs/heads/master; then
59
59
  echo "master"
60
60
  else
61
- deny "Could not detect default branch (main/master)"
61
+ land_branch_deny "Could not detect default branch (main/master)"
62
62
  fi
63
63
  }
64
64
 
@@ -69,12 +69,12 @@ echo "==> Land branch: $FEATURE_BRANCH -> $DEFAULT_BRANCH"
69
69
  echo " Repo root: $REPO_ROOT"
70
70
 
71
71
  if ! git show-ref --verify --quiet "refs/heads/$FEATURE_BRANCH"; then
72
- deny "Feature branch '$FEATURE_BRANCH' does not exist"
72
+ land_branch_deny "Feature branch '$FEATURE_BRANCH' does not exist"
73
73
  fi
74
74
 
75
75
  for protected in main master; do
76
76
  if [ "$FEATURE_BRANCH" = "$protected" ]; then
77
- deny "Cannot land protected branch '$FEATURE_BRANCH'"
77
+ land_branch_deny "Cannot land protected branch '$FEATURE_BRANCH'"
78
78
  fi
79
79
  done
80
80
 
@@ -115,15 +115,15 @@ fi
115
115
  echo "==> Updating $DEFAULT_BRANCH"
116
116
  git checkout "$DEFAULT_BRANCH"
117
117
  if ! git diff-index --quiet HEAD -- 2>/dev/null; then
118
- deny "Working tree on $DEFAULT_BRANCH is not clean. Stash or commit first."
118
+ land_branch_deny "Working tree on $DEFAULT_BRANCH is not clean. Stash or commit first."
119
119
  fi
120
120
 
121
121
  if git remote get-url origin >/dev/null 2>&1; then
122
- git pull --ff-only origin "$DEFAULT_BRANCH" || deny "git pull --ff-only failed; resolve before landing"
122
+ git pull --ff-only origin "$DEFAULT_BRANCH" || land_branch_deny "git pull --ff-only failed; resolve before landing"
123
123
  fi
124
124
 
125
125
  if ! git merge-base --is-ancestor "$DEFAULT_BRANCH" "$FEATURE_BRANCH" 2>/dev/null; then
126
- deny "Feature branch '$FEATURE_BRANCH' is not based on current $DEFAULT_BRANCH (rebase or recreate branch)"
126
+ land_branch_deny "Feature branch '$FEATURE_BRANCH' is not based on current $DEFAULT_BRANCH (rebase or recreate branch)"
127
127
  fi
128
128
 
129
129
  export GIT_BIGPOWERS_LAND=1
@@ -131,7 +131,7 @@ export GIT_BIGPOWERS_LAND=1
131
131
  echo "==> Squash merge $FEATURE_BRANCH"
132
132
  git merge --squash "$FEATURE_BRANCH"
133
133
  if git diff-index --quiet HEAD -- 2>/dev/null; then
134
- deny "Squash merge produced no changes (already merged?)"
134
+ land_branch_deny "Squash merge produced no changes (already merged?)"
135
135
  fi
136
136
 
137
137
  git commit -m "$COMMIT_MSG"
@@ -26,6 +26,7 @@ audit_write_okf_report() {
26
26
  ---
27
27
  okf_kind: verification-report
28
28
  okf_version: "0.1"
29
+ type: VerificationReport
29
30
  score: ${SCORE}
30
31
  gate_status: "${GATE}"
31
32
  threshold: 94
@@ -31,6 +31,7 @@ emit_okf_metrics() {
31
31
  printf -- '---\n'
32
32
  printf -- 'okf_kind: story-metrics\n'
33
33
  printf -- 'okf_version: "0.1"\n'
34
+ printf -- 'type: StoryMetrics\n'
34
35
  printf -- 'id: %s\n' "$story"
35
36
  printf -- 'epic: %s\n' "${story%%s*}"
36
37
  printf -- 'bcps: %s\n' "$bcps"
@@ -80,6 +80,7 @@ YAML_EOF
80
80
  ---
81
81
  okf_kind: verification-report
82
82
  okf_version: "0.1"
83
+ type: VerificationReport
83
84
  score: ${PASS_RATE}
84
85
  gate_status: "${GATE_STATUS}"
85
86
  threshold: ${THRESHOLD}
@@ -32,7 +32,11 @@ golden_suite_main() {
32
32
  esac
33
33
  done
34
34
 
35
- if $AGENT_MODE && ! command -v gh-aw &>/dev/null && ! command -v gh &>/dev/null; then
35
+ golden_agent_cli_available() {
36
+ $AGENT_MODE && ! command -v gh-aw &>/dev/null && ! command -v gh &>/dev/null
37
+ }
38
+
39
+ if golden_agent_cli_available; then
36
40
  echo -e "${GOLDEN_YELLOW}WARNING: gh-aw not installed — agent gate skipped${GOLDEN_NC}" >&2
37
41
  AGENT_MODE=false
38
42
  fi
@@ -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 "
@@ -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"
@@ -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
 
@@ -52,83 +52,85 @@ fi
52
52
  PASS=0
53
53
  FAIL=0
54
54
  TMPDIR=""
55
+ TA_PASS=0
56
+ TA_FAIL=0
57
+ TA_TMPDIR=""
55
58
 
56
- cleanup() { [[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"; }
57
- trap cleanup EXIT
59
+ source "$(dirname "${BASH_SOURCE[0]}")/lib/test-assertions.sh"
60
+ trap ta_cleanup EXIT
58
61
 
59
- pass() { echo " PASS $1"; PASS=$((PASS + 1)); }
60
- fail() { echo " FAIL $1"; FAIL=$((FAIL + 1)); }
61
62
 
62
63
  echo "=== install.sh --dry-run ==="
63
64
  DRY_OUT="$("$REPO_ROOT/scripts/install.sh" --dry-run 2>&1)"
64
65
 
65
- echo "$DRY_OUT" | grep -q 'pi →' && pass "install --dry-run includes pi" || fail "install --dry-run missing pi"
66
+ echo "$DRY_OUT" | grep -q 'pi →' && ta_pass "install --dry-run includes pi" || ta_fail "install --dry-run missing pi"
66
67
  DRY_UNINSTALL="$("$REPO_ROOT/scripts/install.sh" --dry-run --uninstall 2>&1)"
67
- echo "$DRY_UNINSTALL" | grep -q 'pi →' && pass "uninstall --dry-run includes pi" || fail "uninstall --dry-run missing pi"
68
- echo "$DRY_OUT" | grep -qi 'opencode' && fail "install references opencode" || pass "install has no opencode reference"
69
- echo "$DRY_OUT" | grep -q 'Claude Code →' && pass "install includes Claude Code" || fail "install missing Claude Code"
70
- echo "$DRY_OUT" | grep -q 'Gemini CLI →' && pass "install includes Gemini CLI" || fail "install missing Gemini CLI"
71
- echo "$DRY_OUT" | grep -q 'Cursor →' && pass "install includes Cursor" || fail "install missing Cursor"
68
+ echo "$DRY_UNINSTALL" | grep -q 'pi →' && ta_pass "uninstall --dry-run includes pi" || ta_fail "uninstall --dry-run missing pi"
69
+ echo "$DRY_OUT" | grep -qi 'opencode' && ta_fail "install references opencode" || ta_pass "install has no opencode reference"
70
+ echo "$DRY_OUT" | grep -q 'Claude Code →' && ta_pass "install includes Claude Code" || ta_fail "install missing Claude Code"
71
+ echo "$DRY_OUT" | grep -q 'Gemini CLI →' && ta_pass "install includes Gemini CLI" || ta_fail "install missing Gemini CLI"
72
+ echo "$DRY_OUT" | grep -q 'Cursor →' && ta_pass "install includes Cursor" || ta_fail "install missing Cursor"
72
73
 
73
74
  for tool in "Claude Code" "pi"; do
74
75
  section=$(echo "$DRY_OUT" | sed -n "/${tool} →/,/^$/p")
75
76
  count=$(echo "$section" | grep -o '[0-9]\+ skills installed' | grep -o '[0-9]\+' || echo "0")
76
- [[ "$count" -gt 0 ]] && pass "${tool}: $count skills" || fail "${tool}: 0 skills"
77
+ [[ "$count" -gt 0 ]] && ta_pass "${tool}: $count skills" || ta_fail "${tool}: 0 skills"
77
78
  done
78
79
 
79
80
  echo ""
80
81
  echo "=== AGENTS.md spine (e37s04) ==="
81
- [[ -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] && pass "Reach template exists" || fail "Reach template missing"
82
- grep -q 'Preflight' "$REPO_ROOT/docs/templates/AGENTS.md" && pass "template: Preflight" || fail "template: missing Preflight"
83
- grep -qi 'cline\|aider\|opencode' "$REPO_ROOT/docs/templates/AGENTS.md" && pass "template: multi-agent preamble" || fail "template: missing OSS targets"
84
- grep -q '## Test' "$REPO_ROOT/docs/templates/AGENTS.md" && pass "template: Test section" || fail "template: missing Test"
85
- grep -q '## Lint' "$REPO_ROOT/docs/templates/AGENTS.md" && pass "template: Lint section" || fail "template: missing Lint"
86
- grep -q '## Build' "$REPO_ROOT/docs/templates/AGENTS.md" && pass "template: Build section" || fail "template: missing Build"
82
+ [[ -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] && ta_pass "Reach template exists" || ta_fail "Reach template missing"
83
+ grep -q 'Preflight' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Preflight" || ta_fail "template: missing Preflight"
84
+ grep -qi 'cline\|aider\|opencode' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: multi-agent preamble" || ta_fail "template: missing OSS targets"
85
+ grep -q '## Test' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Test section" || ta_fail "template: missing Test"
86
+ grep -q '## Lint' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Lint section" || ta_fail "template: missing Lint"
87
+ grep -q '## Build' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Build section" || ta_fail "template: missing Build"
87
88
 
88
89
  echo ""
89
90
  echo "=== Cline native AGENTS.md (e37s02) ==="
90
- [[ -f "$REPO_ROOT/AGENTS.md" || -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] && pass "Cline: AGENTS.md present (native reader)" || fail "Cline: AGENTS.md missing"
91
- grep -qi 'cline' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" && pass "using-bigpowers: Cline section" || fail "using-bigpowers: missing Cline"
91
+ [[ -f "$REPO_ROOT/AGENTS.md" || -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] && ta_pass "Cline: AGENTS.md present (native reader)" || ta_fail "Cline: AGENTS.md missing"
92
+ grep -qi 'cline' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" && ta_pass "using-bigpowers: Cline section" || ta_fail "using-bigpowers: missing Cline"
92
93
 
93
94
  echo ""
94
95
  echo "=== Aider bridge (e37s03) ==="
95
- grep -qi 'aider' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && pass "seed-conventions: Aider wiring" || fail "seed-conventions: missing Aider"
96
- grep -qi 'Aider-AI' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" && pass "using-bigpowers: Aider-AI upstream" || fail "using-bigpowers: missing Aider-AI"
96
+ grep -qi 'aider' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "seed-conventions: Aider wiring" || ta_fail "seed-conventions: missing Aider"
97
+ grep -qi 'Aider-AI' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" && ta_pass "using-bigpowers: Aider-AI upstream" || ta_fail "using-bigpowers: missing Aider-AI"
97
98
 
98
99
  echo ""
99
100
  echo "=== Optional Codex wave (e37s04) ==="
100
101
  if grep -qi 'codex' "$REPO_ROOT/skills/seed-conventions/SKILL.md" 2>/dev/null; then
101
- grep -qi 'config.toml' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && pass "Codex: REFERENCE config.toml" || fail "Codex: missing config.toml"
102
- echo "$DRY_OUT" | grep -qi 'codex' && pass "Codex: install --dry-run mentions codex" || fail "Codex: install missing codex"
102
+ grep -qi 'config.toml' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && ta_pass "Codex: REFERENCE config.toml" || ta_fail "Codex: missing config.toml"
103
+ echo "$DRY_OUT" | grep -qi 'codex' && ta_pass "Codex: install --dry-run mentions codex" || ta_fail "Codex: install missing codex"
103
104
  else
104
- pass "Codex: wave absent — assertions skipped"
105
+ ta_pass "Codex: wave absent — assertions skipped"
105
106
  fi
106
107
 
107
108
  echo ""
108
109
  echo "=== seed-conventions local wiring ==="
109
- grep -qi 'local tool wiring' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && pass "SKILL.md: local tool wiring" || fail "SKILL.md: missing local tool wiring"
110
- grep -qi 'opencode.json' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && pass "REFERENCE.md: opencode.json" || fail "REFERENCE.md: missing opencode.json"
111
- grep -qi 'AGENTS.md' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && pass "SKILL.md: AGENTS.md spine" || fail "SKILL.md: missing AGENTS.md"
112
- grep -qi 'symlink' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && pass "SKILL.md: CLAUDE.md symlink" || fail "SKILL.md: missing symlink"
113
- grep -qi 'cursor/rules' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && pass "REFERENCE.md: Cursor symlink" || fail "REFERENCE.md: missing Cursor symlink"
110
+ grep -qi 'local tool wiring' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "SKILL.md: local tool wiring" || ta_fail "SKILL.md: missing local tool wiring"
111
+ grep -qi 'opencode.json' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && ta_pass "REFERENCE.md: opencode.json" || ta_fail "REFERENCE.md: missing opencode.json"
112
+ grep -qi 'AGENTS.md' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "SKILL.md: AGENTS.md spine" || ta_fail "SKILL.md: missing AGENTS.md"
113
+ grep -qi 'symlink' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "SKILL.md: CLAUDE.md symlink" || ta_fail "SKILL.md: missing symlink"
114
+ grep -qi 'cursor/rules' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && ta_pass "REFERENCE.md: Cursor symlink" || ta_fail "REFERENCE.md: missing Cursor symlink"
114
115
 
115
116
  OPENCODE_JSON=$(sed -n '/```json/,/```/p' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" | grep -v '```' || true)
116
117
  if [[ -n "$OPENCODE_JSON" ]]; then
117
118
  TMPDIR=$(mktemp -d)
119
+ TA_TMPDIR="$TMPDIR"
118
120
  echo "$OPENCODE_JSON" > "$TMPDIR/test-opencode.json"
119
- jq empty "$TMPDIR/test-opencode.json" 2>/dev/null && pass "opencode.json valid JSON" || fail "opencode.json NOT valid JSON"
121
+ jq empty "$TMPDIR/test-opencode.json" 2>/dev/null && ta_pass "opencode.json valid JSON" || ta_fail "opencode.json NOT valid JSON"
120
122
  else
121
- fail "opencode.json template not found"
123
+ ta_fail "opencode.json template not found"
122
124
  fi
123
125
 
124
126
  echo ""
125
127
  echo "=== install.sh source ==="
126
- grep -q 'install_pi()' "$REPO_ROOT/scripts/install.sh" && pass "source: install_pi()" || fail "source: missing install_pi()"
127
- grep -q 'uninstall_pi()' "$REPO_ROOT/scripts/install.sh" && pass "source: uninstall_pi()" || fail "source: missing uninstall_pi()"
128
- grep -q 'PI_SKILLS_DIR="$PI_CONFIG_DIR/agent/skills"' "$REPO_ROOT/scripts/install.sh" && pass "source: targets ~/.pi/agent/skills/" || fail "source: wrong pi target"
129
- ! grep -q 'install_opencode\|print_opencode_instructions' "$REPO_ROOT/scripts/install.sh" && pass "source: no opencode functions" || fail "source: opencode functions remain"
128
+ grep -q 'install_pi()' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: install_pi()" || ta_fail "source: missing install_pi()"
129
+ grep -q 'uninstall_pi()' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: uninstall_pi()" || ta_fail "source: missing uninstall_pi()"
130
+ grep -q 'PI_SKILLS_DIR="$PI_CONFIG_DIR/agent/skills"' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: targets ~/.pi/agent/skills/" || ta_fail "source: wrong pi target"
131
+ ! grep -q 'install_opencode\|print_opencode_instructions' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: no opencode functions" || ta_fail "source: opencode functions remain"
130
132
 
131
133
  echo ""
132
134
  echo "──────────────────────────────────────────"
133
- echo "verify-install: $PASS passed, $FAIL failed"
134
- [[ "$FAIL" -gt 0 ]] && { echo "Overall: fail"; exit 1; } || echo "Overall: pass"
135
+ echo "verify-install: $TA_PASS passed, $TA_FAIL failed"
136
+ [[ "$TA_FAIL" -gt 0 ]] && { echo "Overall: fail"; exit 1; } || echo "Overall: pass"
@@ -40,6 +40,11 @@ bash "$REPO_ROOT/scripts/sync-bugs-registry.sh" >/dev/null
40
40
  require_min_count "$REPO_ROOT/specs/bugs" 1 "bug"
41
41
  bash "$REPO_ROOT/scripts/validate-okf.sh" --dir "$REPO_ROOT/specs/bugs"
42
42
 
43
+ echo "→ validate all CI OKF directories"
44
+ for dir in specs/metrics specs/migrations specs/verifications/reports; do
45
+ bash "$REPO_ROOT/scripts/validate-okf.sh" --dir "$REPO_ROOT/$dir"
46
+ done
47
+
43
48
  echo "→ publish-to-wiki.sh --dry-run"
44
49
  OUTPUT="$(bash "$REPO_ROOT/kernel/src/publish-to-wiki.sh" --dry-run)"
45
50
  if [[ "$OUTPUT" != *"pages:"* ]]; then