bigpowers 2.88.16 → 2.88.18

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/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
  2. package/.mcp.json +2 -2
  3. package/.pi/package.json +1 -1
  4. package/CHANGELOG.md +18 -0
  5. package/bigpowers-mcp/build/lib/git-context.js +52 -36
  6. package/bigpowers-mcp/build/lib/git-context.js.map +1 -1
  7. package/bigpowers-mcp/src/lib/git-context.ts +9 -5
  8. package/dashboard/src/web/client.html +15 -8
  9. package/dashboard/src/web/server.js +2 -1
  10. package/package.json +1 -1
  11. package/scripts/bp-read-agents.sh +7 -5
  12. package/scripts/lib/__pycache__/link_utils.cpython-310.pyc +0 -0
  13. package/scripts/lib/__pycache__/srp_engine_links.cpython-310.pyc +0 -0
  14. package/scripts/lib/__pycache__/srp_engine_okf.cpython-310.pyc +0 -0
  15. package/scripts/lib/golden-suite-gates.sh +6 -0
  16. package/scripts/lib/migrate-version-common.sh +15 -5
  17. package/scripts/lib/migrate-version-plan.sh +7 -7
  18. package/scripts/lib/migrate-version-run.sh +3 -0
  19. package/scripts/lib/srp-engine.py +1 -60
  20. package/scripts/lib/srp_engine_okf.py +65 -0
  21. package/scripts/lib/story-verify-memo.sh +56 -0
  22. package/scripts/lib/test-assertions.sh +6 -0
  23. package/scripts/mcp-smoke.ts +48 -43
  24. package/scripts/run-story-verify.sh +3 -50
  25. package/scripts/test-golden-gate-runner.sh +18 -18
  26. package/scripts/test-project-runtime.sh +10 -19
  27. package/scripts/validate-docs-base-path.sh +15 -22
  28. package/scripts/validate-mcp-package.sh +18 -20
  29. package/scripts/validate-omp-extension.sh +23 -30
  30. package/tests/security/agents-import.cjs +36 -0
  31. package/tests/security/dashboard-bind.cjs +31 -0
  32. package/tests/security/dashboard-render.cjs +60 -0
  33. package/tests/security/git-context.cjs +44 -0
  34. package/tests/security/migration.cjs +76 -0
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # story: BUG-2026-10-30-compliance-akita-masked
3
+ # Memoized verify execution for run-story-verify.sh. The caller defines
4
+ # run_with_timeout and may set MEMO_DIR (one per gate_all run).
5
+
6
+ if [ -n "${STORY_VERIFY_MEMO_LOADED:-}" ]; then return 0; fi
7
+ STORY_VERIFY_MEMO_LOADED=1
8
+
9
+ # Many stories share one verify (e61s02…e76s02 all run the same installer
10
+ # test); same command at the same commit gives the same result, so run it once
11
+ # per gate_all and reuse the verdict for every story that declares it.
12
+ # Sets RUN_RC, RUN_OUT, and RUN_REUSED (1 when nothing new ran).
13
+ run_memoized() {
14
+ local cmd="$1" key
15
+ key=$(printf '%s' "$cmd" | cksum | tr -d ' ')
16
+ if [ -n "${MEMO_DIR:-}" ] && [ -f "$MEMO_DIR/$key.rc" ]; then
17
+ RUN_RC=$(cat "$MEMO_DIR/$key.rc")
18
+ RUN_OUT=$(cat "$MEMO_DIR/$key.out")
19
+ return 0
20
+ fi
21
+ RUN_REUSED=0
22
+ RUN_OUT=$(run_with_timeout "$cmd") && RUN_RC=0 || RUN_RC=$?
23
+ if [ -n "${MEMO_DIR:-}" ]; then
24
+ printf '%s' "$RUN_RC" > "$MEMO_DIR/$key.rc"
25
+ printf '%s' "$RUN_OUT" > "$MEMO_DIR/$key.out"
26
+ fi
27
+ }
28
+
29
+ # A plain `a && b && c` chain shares no shell state between segments when it
30
+ # has no quoting, expansion, grouping, pipes, `;`, cd, export or assignment —
31
+ # so each segment can be cached on its own (test-adapter-render.sh prefixes ten
32
+ # different story verifies). Anything else is cached as one whole command.
33
+ is_plain_and_chain() {
34
+ case "$1" in
35
+ *\'*|*\"*|*\$*|*\`*|*\(*|*\)*|*\{*|*\}*|*\|*|*\;*|*=*|*\<*) return 1 ;;
36
+ esac
37
+ printf '%s' "$1" | grep -qE '(^|&&)[[:space:]]*(cd|export|source|\.)[[:space:]]' && return 1
38
+ return 0
39
+ }
40
+
41
+ run_memoized_chain() {
42
+ local cmd="$1" seg
43
+ RUN_REUSED=1
44
+ if ! is_plain_and_chain "$cmd"; then
45
+ run_memoized "$cmd"
46
+ return
47
+ fi
48
+ local rest="$cmd"
49
+ while :; do
50
+ seg=${rest%%&&*}
51
+ run_memoized "$(printf '%s' "$seg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
52
+ [ "$RUN_RC" -eq 0 ] || return
53
+ [ "$seg" = "$rest" ] && return
54
+ rest=${rest#*&&}
55
+ done
56
+ }
@@ -23,3 +23,9 @@ ta_fail() { echo " FAIL $1"; TA_FAIL=$((TA_FAIL + 1)); }
23
23
  # Strip ANSI color codes — used by scripts that grep the colored output of a
24
24
  # gate/skill they're testing, where the escape codes would break the match.
25
25
  ta_strip_ansi() { sed -E 's/\x1b\[[0-9;]*m//g'; }
26
+
27
+ # Validator style for validate-*.sh gates: "FAIL: msg" / "ok : msg", counting
28
+ # into the caller's ERRORS so its summary can report "N error(s)".
29
+ ta_err() { echo "FAIL: $*"; ERRORS=$((ERRORS + 1)); }
30
+
31
+ ta_ok() { echo "ok : $*"; }
@@ -1,11 +1,12 @@
1
1
  // story: e32s06
2
- // Simulates OMP spawning bigpowers-mcp from the installed plugin root (#123).
3
- // Verifies Node resolves @modelcontextprotocol/sdk from the root package graph
4
- // and the server reaches its startup log line without module-not-found.
2
+ // Spawns bigpowers-mcp from the shipped .mcp.json the way each host expands it
3
+ // (#123, #168). Verifies Node resolves @modelcontextprotocol/sdk from the root
4
+ // package graph and the server reaches its startup log line without
5
+ // module-not-found, whether or not the host injects OMP_PLUGIN_ROOT.
5
6
 
6
7
  import { readFileSync } from "node:fs";
7
8
  import { spawn } from "node:child_process";
8
- import { dirname, join } from "node:path";
9
+ import { dirname, join, resolve } from "node:path";
9
10
  import { fileURLToPath } from "node:url";
10
11
 
11
12
  const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -15,53 +16,57 @@ const manifest = JSON.parse(readFileSync(join(repoRoot, ".mcp.json"), "utf8")) a
15
16
  const server = manifest.mcpServers?.["bigpowers-mcp"];
16
17
  if (!server) throw new Error("bigpowers-mcp entry missing from .mcp.json");
17
18
 
18
- const entry = join(repoRoot, "bigpowers-mcp/build/index.js");
19
- const started = await new Promise<{ ok: boolean; detail: string }>((resolve) => {
20
- const child = spawn("node", [entry], {
21
- cwd: repoRoot,
22
- stdio: ["ignore", "pipe", "pipe"],
23
- env: { ...process.env, OMP_PLUGIN_ROOT: repoRoot },
19
+ // POSIX ${VAR} / ${VAR:-default}; an unset ${VAR} stays literal (omp and Claude Code both do this).
20
+ function expand(value: string, env: Record<string, string | undefined>): string {
21
+ return value.replace(/\$\{([^}:]+)(?::-([^}]*))?\}/g, (whole, name: string, fallback?: string) => {
22
+ const v = env[name];
23
+ if (v !== undefined && (fallback === undefined || v !== "")) return v;
24
+ return fallback ?? whole;
24
25
  });
26
+ }
25
27
 
26
- let stderr = "";
27
- const timer = setTimeout(() => {
28
- child.kill("SIGTERM");
29
- resolve({ ok: false, detail: `timeout waiting for startup (stderr: ${stderr.trim()})` });
30
- }, 5000);
28
+ // omp injects OMP_PLUGIN_ROOT and honours cwd; Claude Code project scope sets
29
+ // neither and spawns from the project root.
30
+ const { OMP_PLUGIN_ROOT: _ambient, ...baseEnv } = process.env;
31
+ const hosts = [
32
+ { name: "omp", env: { ...baseEnv, OMP_PLUGIN_ROOT: repoRoot }, honoursCwd: true },
33
+ { name: "claude-code", env: baseEnv, honoursCwd: false },
34
+ ];
31
35
 
32
- child.stderr.on("data", (chunk: Buffer) => {
33
- stderr += chunk.toString();
34
- if (stderr.includes("bigpowers-mcp started")) {
35
- clearTimeout(timer);
36
+ function startServer(entry: string, cwd: string, env: NodeJS.ProcessEnv): Promise<{ ok: boolean; detail: string }> {
37
+ return new Promise((done) => {
38
+ const child = spawn("node", [entry], { cwd, stdio: ["ignore", "pipe", "pipe"], env });
39
+
40
+ let stderr = "";
41
+ const timer = setTimeout(() => {
36
42
  child.kill("SIGTERM");
37
- resolve({ ok: true, detail: "bigpowers-mcp started" });
38
- }
39
- });
43
+ done({ ok: false, detail: `timeout waiting for startup (stderr: ${stderr.trim()})` });
44
+ }, 5000);
40
45
 
41
- child.on("exit", (code, signal) => {
42
- if (stderr.includes("ERR_MODULE_NOT_FOUND")) {
46
+ child.stderr.on("data", (chunk: Buffer) => {
47
+ stderr += chunk.toString();
48
+ if (stderr.includes("bigpowers-mcp started")) {
49
+ clearTimeout(timer);
50
+ child.kill("SIGTERM");
51
+ done({ ok: true, detail: "bigpowers-mcp started" });
52
+ }
53
+ });
54
+
55
+ child.on("exit", (code, signal) => {
56
+ if (signal === "SIGTERM" && stderr.includes("bigpowers-mcp started")) return;
43
57
  clearTimeout(timer);
44
- resolve({ ok: false, detail: stderr.trim() });
45
- return;
46
- }
47
- if (signal === "SIGTERM" && stderr.includes("bigpowers-mcp started")) return;
48
- clearTimeout(timer);
49
- resolve({
50
- ok: false,
51
- detail: `exit code=${code ?? "null"} signal=${signal ?? "null"} stderr=${stderr.trim()}`,
58
+ done({ ok: false, detail: `exit code=${code ?? "null"} signal=${signal ?? "null"} stderr=${stderr.trim()}` });
52
59
  });
53
60
  });
54
- });
61
+ }
55
62
 
56
- if (!started.ok) {
57
- throw new Error(started.detail);
63
+ const results = [];
64
+ for (const host of hosts) {
65
+ const cwd = host.honoursCwd && server.cwd ? resolve(repoRoot, expand(server.cwd, host.env)) : repoRoot;
66
+ const entry = resolve(cwd, expand(server.args?.[0] ?? "", host.env));
67
+ const started = await startServer(entry, cwd, host.env);
68
+ if (!started.ok) throw new Error(`[${host.name}] ${entry}: ${started.detail}`);
69
+ results.push({ host: host.name, entry, startup: started.detail });
58
70
  }
59
71
 
60
- console.log(
61
- JSON.stringify({
62
- pluginRoot: repoRoot,
63
- entry,
64
- ompPluginRootInManifest: server.cwd?.includes("OMP_PLUGIN_ROOT") ?? false,
65
- startup: started.detail,
66
- }),
67
- );
72
+ console.log(JSON.stringify({ pluginRoot: repoRoot, results }));
@@ -20,6 +20,8 @@ cd "$REPO_ROOT"
20
20
  source "$REPO_ROOT/scripts/lib/fail-open-detect.sh"
21
21
  # shellcheck source=lib/python-env.sh
22
22
  source "$REPO_ROOT/scripts/lib/python-env.sh"
23
+ # shellcheck source=lib/story-verify-memo.sh
24
+ source "$REPO_ROOT/scripts/lib/story-verify-memo.sh"
23
25
  resolve_python || exit 1
24
26
 
25
27
  VERIFY_TIMEOUT_SECONDS=120
@@ -58,55 +60,6 @@ reject_non_executable() {
58
60
  FAIL=$((FAIL + 1))
59
61
  }
60
62
 
61
- # Many stories share one verify (e61s02…e76s02 all run the same installer
62
- # test); same command at the same commit gives the same result, so run it once
63
- # per gate_all and reuse the verdict for every story that declares it.
64
- # Sets RUN_RC, RUN_OUT, and RUN_REUSED (1 when nothing new ran).
65
- run_memoized() {
66
- local cmd="$1" key
67
- key=$(printf '%s' "$cmd" | cksum | tr -d ' ')
68
- if [ -n "${MEMO_DIR:-}" ] && [ -f "$MEMO_DIR/$key.rc" ]; then
69
- RUN_RC=$(cat "$MEMO_DIR/$key.rc")
70
- RUN_OUT=$(cat "$MEMO_DIR/$key.out")
71
- return 0
72
- fi
73
- RUN_REUSED=0
74
- RUN_OUT=$(run_with_timeout "$cmd") && RUN_RC=0 || RUN_RC=$?
75
- if [ -n "${MEMO_DIR:-}" ]; then
76
- printf '%s' "$RUN_RC" > "$MEMO_DIR/$key.rc"
77
- printf '%s' "$RUN_OUT" > "$MEMO_DIR/$key.out"
78
- fi
79
- }
80
-
81
- # A plain `a && b && c` chain shares no shell state between segments when it
82
- # has no quoting, expansion, grouping, pipes, `;`, cd, export or assignment —
83
- # so each segment can be cached on its own (test-adapter-render.sh prefixes ten
84
- # different story verifies). Anything else is cached as one whole command.
85
- is_plain_and_chain() {
86
- case "$1" in
87
- *\'*|*\"*|*\$*|*\`*|*\(*|*\)*|*\{*|*\}*|*\|*|*\;*|*=*|*\<*) return 1 ;;
88
- esac
89
- printf '%s' "$1" | grep -qE '(^|&&)[[:space:]]*(cd|export|source|\.)[[:space:]]' && return 1
90
- return 0
91
- }
92
-
93
- run_verify_cmd() {
94
- local cmd="$1" seg
95
- RUN_REUSED=1
96
- if ! is_plain_and_chain "$cmd"; then
97
- run_memoized "$cmd"
98
- return
99
- fi
100
- local rest="$cmd"
101
- while :; do
102
- seg=${rest%%&&*}
103
- run_memoized "$(printf '%s' "$seg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
104
- [ "$RUN_RC" -eq 0 ] || return
105
- [ "$seg" = "$rest" ] && return
106
- rest=${rest#*&&}
107
- done
108
- }
109
-
110
63
  run_story_verify() {
111
64
  local sid="$1" src="$2" cmd="$3"
112
65
 
@@ -119,7 +72,7 @@ run_story_verify() {
119
72
  return 1
120
73
  fi
121
74
 
122
- run_verify_cmd "$cmd"
75
+ run_memoized_chain "$cmd"
123
76
  local note=""
124
77
  [ "$RUN_REUSED" -eq 1 ] && note=" (shared result)"
125
78
  if [ "$RUN_RC" -eq 0 ]; then
@@ -16,13 +16,13 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
16
16
  resolve_repo_root
17
17
  cd "$REPO_ROOT"
18
18
 
19
- FAILURES=0
20
- pass() { echo "PASS $1"; }
21
- fail() { echo "FAIL $1"; FAILURES=$((FAILURES + 1)); }
19
+ source scripts/lib/test-assertions.sh
20
+ TA_PASS=0
21
+ TA_FAIL=0
22
22
 
23
23
  # Run the runner in a subshell with a fixture gate list; prints its stdout and
24
24
  # the final counters as "COUNTS pass fail skip".
25
- run_fixture() {
25
+ ggr_run_fixture() {
26
26
  (
27
27
  source scripts/lib/golden-suite-gates.sh
28
28
  GOLDEN_GATES=(
@@ -35,25 +35,25 @@ run_fixture() {
35
35
  )
36
36
  }
37
37
 
38
- out=$(GOLDEN_SKIP_GATES="" run_fixture)
38
+ out=$(GOLDEN_SKIP_GATES="" ggr_run_fixture)
39
39
  echo "$out" | grep -q 'marker-line-from-failing-gate' \
40
- && pass "(a) failing gate output is printed" \
41
- || fail "(a) failing gate output is missing"
40
+ && ta_pass "(a) failing gate output is printed" \
41
+ || ta_fail "(a) failing gate output is missing"
42
42
  echo "$out" | grep -q '^COUNTS 2 1 0$' \
43
- && pass "(d) no skip list runs every gate" \
44
- || fail "(d) default run counts wrong: $(echo "$out" | grep COUNTS)"
43
+ && ta_pass "(d) no skip list runs every gate" \
44
+ || ta_fail "(d) default run counts wrong: $(echo "$out" | grep COUNTS)"
45
45
 
46
- out=$(GOLDEN_SKIP_GATES="covered-gate" GOLDEN_SKIP_REASON="runs in its own CI job" run_fixture)
46
+ out=$(GOLDEN_SKIP_GATES="covered-gate" GOLDEN_SKIP_REASON="runs in its own CI job" ggr_run_fixture)
47
47
  echo "$out" | grep -q '^\[covered-gate\] .*SKIP.*runs in its own CI job' \
48
- && pass "(b) skipped gate reported with reason" \
49
- || fail "(b) skipped gate not reported with reason"
48
+ && ta_pass "(b) skipped gate reported with reason" \
49
+ || ta_fail "(b) skipped gate not reported with reason"
50
50
  echo "$out" | grep -q '^COUNTS 1 1 1$' \
51
- && pass "(b) skipped gate counted as skip, not pass" \
52
- || fail "(b) skip counts wrong: $(echo "$out" | grep COUNTS)"
51
+ && ta_pass "(b) skipped gate counted as skip, not pass" \
52
+ || ta_fail "(b) skip counts wrong: $(echo "$out" | grep COUNTS)"
53
53
 
54
- out=$(GOLDEN_SKIP_GATES="no-such-gate" run_fixture)
54
+ out=$(GOLDEN_SKIP_GATES="no-such-gate" ggr_run_fixture)
55
55
  echo "$out" | grep -q 'no-such-gate' && echo "$out" | grep -q '^COUNTS 2 2 0$' \
56
- && pass "(c) unknown skip name fails the run" \
57
- || fail "(c) unknown skip name accepted: $(echo "$out" | grep COUNTS)"
56
+ && ta_pass "(c) unknown skip name fails the run" \
57
+ || ta_fail "(c) unknown skip name accepted: $(echo "$out" | grep COUNTS)"
58
58
 
59
- [ "$FAILURES" -eq 0 ] && echo "OK: golden gate runner" || { echo "$FAILURES check(s) failed"; exit 1; }
59
+ [ "$TA_FAIL" -eq 0 ] && echo "OK: golden gate runner" || { echo "$TA_FAIL check(s) failed"; exit 1; }
@@ -6,27 +6,18 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
6
  # shellcheck disable=SC1091
7
7
  source "$ROOT/scripts/lib/project-runtime.sh"
8
8
 
9
- PASS=0
10
- FAIL=0
9
+ source "$ROOT/scripts/lib/test-assertions.sh"
10
+ TA_PASS=0
11
+ TA_FAIL=0
11
12
  TMPDIR_ROOT="$(mktemp -d)"
12
13
  trap 'rm -rf "$TMPDIR_ROOT"' EXIT
13
14
 
14
- pass() {
15
- echo " PASS $1"
16
- PASS=$((PASS + 1))
17
- }
18
-
19
- fail() {
20
- echo " FAIL $1" >&2
21
- FAIL=$((FAIL + 1))
22
- }
23
-
24
15
  assert_eq() {
25
16
  local name="$1" expected="$2" actual="$3"
26
17
  if [[ "$actual" == "$expected" ]]; then
27
- pass "$name"
18
+ ta_pass "$name"
28
19
  else
29
- fail "$name — expected '$expected', got '$actual'"
20
+ ta_fail "$name — expected '$expected', got '$actual'"
30
21
  fi
31
22
  }
32
23
 
@@ -35,11 +26,11 @@ assert_fails_with() {
35
26
  shift 2
36
27
  local output
37
28
  if output="$("$@" 2>&1)"; then
38
- fail "$name — command unexpectedly succeeded"
29
+ ta_fail "$name — command unexpectedly succeeded"
39
30
  elif [[ "$output" == *"$expected"* ]]; then
40
- pass "$name"
31
+ ta_pass "$name"
41
32
  else
42
- fail "$name — expected error containing '$expected', got '$output'"
33
+ ta_fail "$name — expected error containing '$expected', got '$output'"
43
34
  fi
44
35
  }
45
36
 
@@ -87,5 +78,5 @@ assert_fails_with "empty specs directory is rejected" \
87
78
  "specs_dir must not be empty" bp_specs_dir "$EMPTY_PROJECT"
88
79
 
89
80
  echo "---"
90
- echo "test-project-runtime: $PASS passed, $FAIL failed"
91
- [[ "$FAIL" -eq 0 ]]
81
+ echo "test-project-runtime: $TA_PASS passed, $TA_FAIL failed"
82
+ [[ "$TA_FAIL" -eq 0 ]]
@@ -9,29 +9,22 @@ WEBSITE="$REPO_ROOT/website"
9
9
  SITE_BASE="/bigpowers"
10
10
  ERRORS=0
11
11
 
12
- fail() {
13
- echo "FAIL: $*"
14
- ERRORS=$((ERRORS + 1))
15
- }
16
-
17
- pass() {
18
- echo "ok : $*"
19
- }
12
+ source "$REPO_ROOT/scripts/lib/test-assertions.sh"
20
13
 
21
14
  ensure_website_deps() {
22
15
  if [[ -d "$WEBSITE/node_modules/astro" ]]; then
23
16
  return 0
24
17
  fi
25
18
  if ! command -v npm >/dev/null 2>&1; then
26
- fail "npm unavailable and website/node_modules missing — cannot run build check"
19
+ ta_err "npm unavailable and website/node_modules missing — cannot run build check"
27
20
  return 1
28
21
  fi
29
22
  echo "info: installing website deps (golden-suite only installs repo root npm ci)"
30
23
  if ! (cd "$WEBSITE" && npm ci >/dev/null 2>&1); then
31
- fail "website npm ci failed — cannot run build check"
24
+ ta_err "website npm ci failed — cannot run build check"
32
25
  return 1
33
26
  fi
34
- pass "website deps installed for build check"
27
+ ta_ok "website deps installed for build check"
35
28
  }
36
29
 
37
30
  INDEX_MDX="$WEBSITE/src/content/docs/index.mdx"
@@ -39,27 +32,27 @@ LLMS_TXT="$WEBSITE/public/llms.txt"
39
32
 
40
33
  echo "--- [docs] generated landing hero link ---"
41
34
  if [[ ! -f "$INDEX_MDX" ]]; then
42
- fail "missing $INDEX_MDX — run cd website && npm run generate"
35
+ ta_err "missing $INDEX_MDX — run cd website && npm run generate"
43
36
  elif grep -q "link: ${SITE_BASE}/skills/" "$INDEX_MDX"; then
44
- pass "hero Browse skills link includes SITE_BASE"
37
+ ta_ok "hero Browse skills link includes SITE_BASE"
45
38
  else
46
- fail "hero link missing ${SITE_BASE}/skills/ prefix in index.mdx"
39
+ ta_err "hero link missing ${SITE_BASE}/skills/ prefix in index.mdx"
47
40
  fi
48
41
 
49
42
  echo "--- [docs] no root-absolute skill links in landing page ---"
50
43
  if [[ -f "$INDEX_MDX" ]] && grep -E '\]\(/skills/' "$INDEX_MDX" >/dev/null 2>&1; then
51
- fail "index.mdx still contains root-absolute /skills/ links"
44
+ ta_err "index.mdx still contains root-absolute /skills/ links"
52
45
  else
53
- pass "index.mdx skill links are base-prefixed or absent"
46
+ ta_ok "index.mdx skill links are base-prefixed or absent"
54
47
  fi
55
48
 
56
49
  echo "--- [docs] llms.txt URL shape ---"
57
50
  if [[ ! -f "$LLMS_TXT" ]]; then
58
- fail "missing $LLMS_TXT — run cd website && npm run generate"
51
+ ta_err "missing $LLMS_TXT — run cd website && npm run generate"
59
52
  elif grep -q '/bigpowers/bigpowers/' "$LLMS_TXT"; then
60
- fail "llms.txt double-prefixes SITE_BASE in URLs"
53
+ ta_err "llms.txt double-prefixes SITE_BASE in URLs"
61
54
  else
62
- pass "llms.txt has no /bigpowers/bigpowers/ double prefix"
55
+ ta_ok "llms.txt has no /bigpowers/bigpowers/ double prefix"
63
56
  fi
64
57
 
65
58
  echo "--- [docs] production build hero href ---"
@@ -69,12 +62,12 @@ elif ensure_website_deps; then
69
62
  BUILD_ERR=$(mktemp)
70
63
  if (cd "$WEBSITE" && npm run build >/dev/null 2>"$BUILD_ERR"); then
71
64
  if grep -q 'href="/bigpowers/skills/"' "$WEBSITE/dist/index.html" 2>/dev/null; then
72
- pass "dist/index.html hero href includes /bigpowers/skills/"
65
+ ta_ok "dist/index.html hero href includes /bigpowers/skills/"
73
66
  else
74
- fail "dist/index.html missing href=\"/bigpowers/skills/\" for Browse skills"
67
+ ta_err "dist/index.html missing href=\"/bigpowers/skills/\" for Browse skills"
75
68
  fi
76
69
  else
77
- fail "website build failed — $(head -3 "$BUILD_ERR" 2>/dev/null | tr '\n' ' ')"
70
+ ta_err "website build failed — $(head -3 "$BUILD_ERR" 2>/dev/null | tr '\n' ' ')"
78
71
  fi
79
72
  rm -f "$BUILD_ERR"
80
73
  fi
@@ -9,59 +9,57 @@ cd "$REPO_ROOT"
9
9
 
10
10
  ERRORS=0
11
11
 
12
- fail() {
13
- echo "FAIL: $*"
14
- ERRORS=$((ERRORS + 1))
15
- }
16
-
17
- pass() {
18
- echo "ok : $*"
19
- }
12
+ source "$REPO_ROOT/scripts/lib/test-assertions.sh"
20
13
 
21
14
  MCP_JSON="$REPO_ROOT/.mcp.json"
22
15
  echo "--- [MCP] shipped .mcp.json OMP compatibility ---"
23
16
  if [[ ! -f "$MCP_JSON" ]]; then
24
- fail ".mcp.json missing at repo root"
17
+ ta_err ".mcp.json missing at repo root"
25
18
  else
26
19
  if grep -q 'workspaceFolder' "$MCP_JSON" 2>/dev/null; then
27
- fail ".mcp.json uses workspaceFolder — not valid in OMP env expansion (#123)"
20
+ ta_err ".mcp.json uses workspaceFolder — not valid in OMP env expansion (#123)"
28
21
  else
29
- pass ".mcp.json does not reference workspaceFolder"
22
+ ta_ok ".mcp.json does not reference workspaceFolder"
30
23
  fi
31
24
  if grep -q 'OMP_PLUGIN_ROOT' "$MCP_JSON" 2>/dev/null; then
32
- pass ".mcp.json references OMP_PLUGIN_ROOT for plugin installs"
25
+ ta_ok ".mcp.json references OMP_PLUGIN_ROOT for plugin installs"
26
+ else
27
+ ta_err ".mcp.json must reference OMP_PLUGIN_ROOT for OMP plugin cwd/args"
28
+ fi
29
+ if grep -q '\${OMP_PLUGIN_ROOT}' "$MCP_JSON" 2>/dev/null; then
30
+ ta_err ".mcp.json uses \${OMP_PLUGIN_ROOT} without a :- default — hosts that do not set it spawn a literal path (#168)"
33
31
  else
34
- fail ".mcp.json must reference OMP_PLUGIN_ROOT for OMP plugin cwd/args"
32
+ ta_ok ".mcp.json OMP_PLUGIN_ROOT references carry a :- default"
35
33
  fi
36
34
  fi
37
35
 
38
36
  echo "--- [MCP] root runtime dependencies ---"
39
37
  for dep in "@modelcontextprotocol/sdk" "unified" "remark-parse" "zod"; do
40
38
  if node -e "const p=require('./package.json'); process.exit(p.dependencies?.['$dep'] ? 0 : 1)"; then
41
- pass "root dependencies include $dep"
39
+ ta_ok "root dependencies include $dep"
42
40
  else
43
- fail "root dependencies missing $dep (bigpowers-mcp runtime)"
41
+ ta_err "root dependencies missing $dep (bigpowers-mcp runtime)"
44
42
  fi
45
43
  done
46
44
 
47
45
  echo "--- [MCP] built server entry ---"
48
46
  if [[ -f "$REPO_ROOT/bigpowers-mcp/build/index.js" ]]; then
49
- pass "bigpowers-mcp/build/index.js exists"
47
+ ta_ok "bigpowers-mcp/build/index.js exists"
50
48
  else
51
- fail "bigpowers-mcp/build/index.js missing — run cd bigpowers-mcp && npm run build"
49
+ ta_err "bigpowers-mcp/build/index.js missing — run cd bigpowers-mcp && npm run build"
52
50
  fi
53
51
 
54
52
  echo "--- [MCP] runtime load smoke (scripts/mcp-smoke.ts) ---"
55
53
  if ! command -v node >/dev/null 2>&1; then
56
54
  echo "warn: node unavailable — skipping runtime load smoke"
57
55
  elif [[ ! -f "$REPO_ROOT/scripts/mcp-smoke.ts" ]]; then
58
- fail "scripts/mcp-smoke.ts not found"
56
+ ta_err "scripts/mcp-smoke.ts not found"
59
57
  else
60
58
  SMOKE_ERR=$(mktemp)
61
59
  if node "$REPO_ROOT/scripts/mcp-smoke.ts" >/dev/null 2>"$SMOKE_ERR"; then
62
- pass "MCP server loads from simulated OMP plugin root"
60
+ ta_ok "MCP server loads from simulated OMP plugin root"
63
61
  else
64
- fail "MCP smoke failed — $(head -3 "$SMOKE_ERR" 2>/dev/null | tr '\n' ' ')"
62
+ ta_err "MCP smoke failed — $(head -3 "$SMOKE_ERR" 2>/dev/null | tr '\n' ' ')"
65
63
  fi
66
64
  rm -f "$SMOKE_ERR"
67
65
  fi