bigpowers 2.88.15 → 2.88.17

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.15",
3
+ "version": "2.88.17",
4
4
  "description": "81 skills — 73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "gemini-extension"
package/.mcp.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "mcpServers": {
3
3
  "bigpowers-mcp": {
4
4
  "command": "node",
5
- "args": ["${OMP_PLUGIN_ROOT}/bigpowers-mcp/build/index.js"],
6
- "cwd": "${OMP_PLUGIN_ROOT}"
5
+ "args": ["${OMP_PLUGIN_ROOT:-.}/bigpowers-mcp/build/index.js"],
6
+ "cwd": "${OMP_PLUGIN_ROOT:-.}"
7
7
  }
8
8
  }
9
9
  }
package/.pi/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.15",
3
+ "version": "2.88.17",
4
4
  "description": "81 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,17 @@
1
+ ## [2.88.17](https://github.com/danielvm-git/bigpowers/compare/v2.88.16...v2.88.17) (2026-10-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **compliance:** restore akita.feature to 0 FAIL — dedupe helpers, split oversized files ([#171](https://github.com/danielvm-git/bigpowers/issues/171)) ([4ed9a26](https://github.com/danielvm-git/bigpowers/commit/4ed9a2663c39689de04fd2ff796b09777a78cfcf)), closes [#118](https://github.com/danielvm-git/bigpowers/issues/118) [123/#125](https://github.com/danielvm-git/bigpowers/issues/125) [#161](https://github.com/danielvm-git/bigpowers/issues/161) [#155](https://github.com/danielvm-git/bigpowers/issues/155) [#161](https://github.com/danielvm-git/bigpowers/issues/161) [#168](https://github.com/danielvm-git/bigpowers/issues/168) [#169](https://github.com/danielvm-git/bigpowers/issues/169) [#168](https://github.com/danielvm-git/bigpowers/issues/168) [#168](https://github.com/danielvm-git/bigpowers/issues/168)
7
+
8
+ ## [2.88.16](https://github.com/danielvm-git/bigpowers/compare/v2.88.15...v2.88.16) (2026-10-25)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **receipts:** read golden suite counts from deterministic block ([#166](https://github.com/danielvm-git/bigpowers/issues/166)) ([56ecb93](https://github.com/danielvm-git/bigpowers/commit/56ecb9373e730ecbdca45e7a08129dfc0324d27e))
14
+
1
15
  ## [2.88.15](https://github.com/danielvm-git/bigpowers/compare/v2.88.14...v2.88.15) (2026-10-25)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.15",
3
+ "version": "2.88.17",
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": {
@@ -43,8 +43,9 @@ if os.path.isdir(gd):
43
43
  try:
44
44
  with open(reports[-1]) as f:
45
45
  import yaml; data = yaml.safe_load(f)
46
- sm = data.get('summary',{})
47
- sections['golden_suite'] = {'value':{'pass_rate':sm.get('pass_rate'),'passed':sm.get('passed'),'total':sm.get('total'),'overall':sm.get('overall','unknown'),'timestamp':data.get('timestamp',''),'commit':data.get('git_commit','')},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/benchmarks/reports/GOLDEN-*.yaml (latest)'}
46
+ sm = data.get('deterministic',{})
47
+ verdict = (data.get('summary') or {}).get('combined_verdict','unknown')
48
+ sections['golden_suite'] = {'value':{'pass_rate':sm.get('pass_rate'),'passed':sm.get('passed'),'total':sm.get('total'),'overall':verdict,'timestamp':data.get('timestamp',''),'commit':data.get('git_commit','')},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/benchmarks/reports/GOLDEN-*.yaml (latest)'}
48
49
  except Exception as e:
49
50
  print(f'build-receipts: golden error: {e}', file=sys.stderr)
50
51
  sections['golden_suite'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/benchmarks/reports/GOLDEN-*.yaml'}
@@ -17,6 +17,7 @@ if _LIB_DIR not in sys.path:
17
17
 
18
18
  from link_utils import LINK_RE, EXTERNAL_RE # noqa: E402
19
19
  from srp_engine_links import rewrite_links_for_pi # noqa: E402
20
+ from srp_engine_okf import render_okf_concept # noqa: E402
20
21
 
21
22
  # End-user setup (bigpowers setup) may run on a python3 without PyYAML.
22
23
  # Prefer PyYAML for exact parsing; fall back to the dependency-free
@@ -133,66 +134,6 @@ def iterate_skills(repo_root):
133
134
  skills.append(skill_md)
134
135
  return sorted(skills)
135
136
 
136
- def render_okf_concept(skill_data, okf_wiki_skills):
137
- name = skill_data['name']
138
- model = skill_data['model']
139
- effort = skill_data['effort']
140
- description = skill_data['description']
141
-
142
- os.makedirs(okf_wiki_skills, exist_ok=True)
143
- okf_file = os.path.join(okf_wiki_skills, f"{name}.md")
144
-
145
- phase = "utility"
146
- critical_path_skills = {
147
- "survey-context", "elaborate-spec", "model-domain", "define-language",
148
- "design-interface", "plan-work", "plan-release", "slice-tasks",
149
- "kickoff-branch", "develop-tdd", "verify-work", "audit-code",
150
- "commit-message", "release-branch", "security-review",
151
- "assess-impact", "investigate-bug", "validate-fix", "dispatch-agents",
152
- "request-review", "respond-review", "hook-commits", "guard-git",
153
- "wire-observability", "wire-ci", "smoke-test", "deploy", "enforce-first"
154
- }
155
- if name in critical_path_skills:
156
- phase = "critical-path"
157
-
158
- title = name.replace("-", " ").title()
159
- desc_escaped = description.replace("\\", "\\\\").replace('"', '\\"')
160
-
161
- lines = [
162
- "---",
163
- "okf_kind: concept",
164
- "type: Skill",
165
- f"id: {name}",
166
- f'title: "{title}"',
167
- f"name: {name}",
168
- "category: skills"
169
- ]
170
- if model:
171
- lines.append(f"model: {model}")
172
- lines.extend([
173
- f"effort: {effort}",
174
- f"phase: {phase}",
175
- f'description: "{desc_escaped}"',
176
- "references: []",
177
- "---",
178
- "",
179
- f"# {title}",
180
- "",
181
- f"**Phase:** {phase}"
182
- ])
183
- if model:
184
- lines.append(f"**Model:** {model}")
185
- lines.extend([
186
- f"**Effort:** {effort}",
187
- "",
188
- description,
189
- "",
190
- f"> Auto-generated by sync-skills.sh --okf (e39s04) from skills/{name}/SKILL.md"
191
- ])
192
-
193
- with open(okf_file, 'w', encoding='utf-8') as f:
194
- f.write('\n'.join(lines) + '\n')
195
-
196
137
  def resolve_bash():
197
138
  """Return an explicit path to a usable bash interpreter.
198
139
 
@@ -0,0 +1,65 @@
1
+ """OKF concept-page rendering for srp-engine (sync-skills.sh --okf)."""
2
+ # story: e39s04
3
+ # story: BUG-2026-10-30-compliance-akita-masked
4
+ import os
5
+
6
+
7
+ def render_okf_concept(skill_data, okf_wiki_skills):
8
+ name = skill_data['name']
9
+ model = skill_data['model']
10
+ effort = skill_data['effort']
11
+ description = skill_data['description']
12
+
13
+ os.makedirs(okf_wiki_skills, exist_ok=True)
14
+ okf_file = os.path.join(okf_wiki_skills, f"{name}.md")
15
+
16
+ phase = "utility"
17
+ critical_path_skills = {
18
+ "survey-context", "elaborate-spec", "model-domain", "define-language",
19
+ "design-interface", "plan-work", "plan-release", "slice-tasks",
20
+ "kickoff-branch", "develop-tdd", "verify-work", "audit-code",
21
+ "commit-message", "release-branch", "security-review",
22
+ "assess-impact", "investigate-bug", "validate-fix", "dispatch-agents",
23
+ "request-review", "respond-review", "hook-commits", "guard-git",
24
+ "wire-observability", "wire-ci", "smoke-test", "deploy", "enforce-first"
25
+ }
26
+ if name in critical_path_skills:
27
+ phase = "critical-path"
28
+
29
+ title = name.replace("-", " ").title()
30
+ desc_escaped = description.replace("\\", "\\\\").replace('"', '\\"')
31
+
32
+ lines = [
33
+ "---",
34
+ "okf_kind: concept",
35
+ "type: Skill",
36
+ f"id: {name}",
37
+ f'title: "{title}"',
38
+ f"name: {name}",
39
+ "category: skills"
40
+ ]
41
+ if model:
42
+ lines.append(f"model: {model}")
43
+ lines.extend([
44
+ f"effort: {effort}",
45
+ f"phase: {phase}",
46
+ f'description: "{desc_escaped}"',
47
+ "references: []",
48
+ "---",
49
+ "",
50
+ f"# {title}",
51
+ "",
52
+ f"**Phase:** {phase}"
53
+ ])
54
+ if model:
55
+ lines.append(f"**Model:** {model}")
56
+ lines.extend([
57
+ f"**Effort:** {effort}",
58
+ "",
59
+ description,
60
+ "",
61
+ f"> Auto-generated by sync-skills.sh --okf (e39s04) from skills/{name}/SKILL.md"
62
+ ])
63
+
64
+ with open(okf_file, 'w', encoding='utf-8') as f:
65
+ f.write('\n'.join(lines) + '\n')
@@ -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
@@ -10,14 +10,7 @@ cd "$REPO_ROOT"
10
10
 
11
11
  ERRORS=0
12
12
 
13
- fail() {
14
- echo "FAIL: $*"
15
- ERRORS=$((ERRORS + 1))
16
- }
17
-
18
- pass() {
19
- echo "ok : $*"
20
- }
13
+ source "$REPO_ROOT/scripts/lib/test-assertions.sh"
21
14
 
22
15
  # ── 1. Package has exactly one pi.extensions entry ─────────────────────────
23
16
  # pi reads extension paths from the `pi` manifest key (readPiManifest). The
@@ -25,21 +18,21 @@ pass() {
25
18
  echo "--- [OMP] manifest: single extension entry ---"
26
19
  EXT_COUNT=$(node -e "const p = require('./package.json'); const exts = p.pi?.extensions; if (!exts) { console.log(0); } else { console.log(exts.length); }")
27
20
  if [[ "$EXT_COUNT" -eq 1 ]]; then
28
- pass "pi.extensions has exactly 1 entry"
21
+ ta_ok "pi.extensions has exactly 1 entry"
29
22
  else
30
- fail "pi.extensions has $EXT_COUNT entries (expected 1)"
23
+ ta_err "pi.extensions has $EXT_COUNT entries (expected 1)"
31
24
  fi
32
25
 
33
26
  # ── 2. Extension file exists ───────────────────────────────────────────────
34
27
  echo "--- [OMP] extension file exists ---"
35
28
  EXT_PATH=$(node -e "const p = require('./package.json'); console.log(p.pi?.extensions?.[0] || '')")
36
29
  if [[ -z "$EXT_PATH" ]]; then
37
- fail "No pi.extensions entry found"
30
+ ta_err "No pi.extensions entry found"
38
31
  else
39
32
  if [[ -f "$EXT_PATH" ]]; then
40
- pass "extension file '$EXT_PATH' exists"
33
+ ta_ok "extension file '$EXT_PATH' exists"
41
34
  else
42
- fail "extension file '$EXT_PATH' does not exist"
35
+ ta_err "extension file '$EXT_PATH' does not exist"
43
36
  fi
44
37
  fi
45
38
 
@@ -48,32 +41,32 @@ echo "--- [OMP] separated runtime responsibilities ---"
48
41
  if [[ -n "$EXT_PATH" && -f "$EXT_PATH" ]]; then
49
42
  for sym in "registerTool" "tool_call"; do
50
43
  if grep -q "$sym" "$EXT_PATH" 2>/dev/null; then
51
- pass "symbol '$sym' found in extension source"
44
+ ta_ok "symbol '$sym' found in extension source"
52
45
  else
53
- fail "symbol '$sym' NOT found in extension source"
46
+ ta_err "symbol '$sym' NOT found in extension source"
54
47
  fi
55
48
  done
56
49
  if grep -q 'pi\.registerCommand' "$EXT_PATH" 2>/dev/null; then
57
- fail "extension registers commands already supplied by Pi prompt templates"
50
+ ta_err "extension registers commands already supplied by Pi prompt templates"
58
51
  else
59
- pass "extension leaves slash-command registration to Pi prompt templates"
52
+ ta_ok "extension leaves slash-command registration to Pi prompt templates"
60
53
  fi
61
54
  else
62
- fail "Cannot scan extension symbols — file not resolved"
55
+ ta_err "Cannot scan extension symbols — file not resolved"
63
56
  fi
64
57
 
65
58
  PROMPT_COUNT=$(node -e "const p = require('./package.json'); console.log(p.pi?.prompts?.length || 0)")
66
59
  if [[ "$PROMPT_COUNT" -eq 1 ]]; then
67
- pass "pi.prompts has exactly 1 canonical prompt-template entry"
60
+ ta_ok "pi.prompts has exactly 1 canonical prompt-template entry"
68
61
  else
69
- fail "pi.prompts has $PROMPT_COUNT entries (expected 1)"
62
+ ta_err "pi.prompts has $PROMPT_COUNT entries (expected 1)"
70
63
  fi
71
64
 
72
65
  # ── 4. Skill discovery root is skills/ ────────────────────────────────────
73
66
  echo "--- [OMP] source skill discoverability (skills/<name>/SKILL.md) ---"
74
67
  SKILLS_ROOT="$REPO_ROOT/skills"
75
68
  if [[ ! -d "$SKILLS_ROOT" ]]; then
76
- fail "skills/ directory not found at $SKILLS_ROOT"
69
+ ta_err "skills/ directory not found at $SKILLS_ROOT"
77
70
  else
78
71
  TOTAL=0
79
72
  MISSING=0
@@ -81,7 +74,7 @@ else
81
74
  rel="${skill_md#"$SKILLS_ROOT"/}"
82
75
  # Each should be exactly <name>/SKILL.md (one level of nesting).
83
76
  if [[ "$rel" != */SKILL.md || "${rel%%/*}" == "" ]]; then
84
- fail "unexpected SKILL.md path: $rel"
77
+ ta_err "unexpected SKILL.md path: $rel"
85
78
  MISSING=$((MISSING + 1))
86
79
  continue
87
80
  fi
@@ -89,11 +82,11 @@ else
89
82
  done < <(find "$SKILLS_ROOT" -maxdepth 2 -name SKILL.md 2>/dev/null | sort)
90
83
 
91
84
  if [[ "$TOTAL" -eq 0 ]]; then
92
- fail "no skills found under skills/"
85
+ ta_err "no skills found under skills/"
93
86
  elif [[ "$MISSING" -eq 0 ]]; then
94
- pass "$TOTAL source skills all at skills/<name>/SKILL.md"
87
+ ta_ok "$TOTAL source skills all at skills/<name>/SKILL.md"
95
88
  else
96
- fail "$MISSING/$TOTAL skills at unexpected paths"
89
+ ta_err "$MISSING/$TOTAL skills at unexpected paths"
97
90
  fi
98
91
  fi
99
92
 
@@ -101,9 +94,9 @@ fi
101
94
  echo "--- [OMP] extension targets skills/ root ---"
102
95
  if [[ -n "$EXT_PATH" && -f "$EXT_PATH" ]]; then
103
96
  if grep -q 'join(pluginRoot(), "skills")' "$EXT_PATH" 2>/dev/null; then
104
- pass "extension calls discoverSkills with skills/ subdirectory"
97
+ ta_ok "extension calls discoverSkills with skills/ subdirectory"
105
98
  else
106
- fail "extension does not target skills/ — check discoverSkills call in $EXT_PATH"
99
+ ta_err "extension does not target skills/ — check discoverSkills call in $EXT_PATH"
107
100
  fi
108
101
  fi
109
102
 
@@ -115,15 +108,15 @@ fi
115
108
  echo "--- [OMP] runtime load smoke (scripts/omp-smoke.ts) ---"
116
109
  SMOKE="$REPO_ROOT/scripts/omp-smoke.ts"
117
110
  if [[ ! -f "$SMOKE" ]]; then
118
- fail "omp-smoke.ts not found at $SMOKE"
111
+ ta_err "omp-smoke.ts not found at $SMOKE"
119
112
  elif ! command -v node >/dev/null 2>&1; then
120
113
  echo "warn: node unavailable — skipping runtime load smoke"
121
114
  else
122
115
  SMOKE_ERR=$(mktemp)
123
116
  if node "$SMOKE" >/dev/null 2>"$SMOKE_ERR"; then
124
- pass "extension loads without calling action methods during load"
117
+ ta_ok "extension loads without calling action methods during load"
125
118
  else
126
- fail "runtime load smoke failed — $(head -3 "$SMOKE_ERR" 2>/dev/null | tr '\n' ' ')"
119
+ ta_err "runtime load smoke failed — $(head -3 "$SMOKE_ERR" 2>/dev/null | tr '\n' ' ')"
127
120
  fi
128
121
  rm -f "$SMOKE_ERR"
129
122
  fi