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.
- package/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
- package/.mcp.json +2 -2
- package/.pi/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/scripts/build-receipts.sh +3 -2
- package/scripts/lib/__pycache__/link_utils.cpython-310.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-310.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_okf.cpython-310.pyc +0 -0
- package/scripts/lib/srp-engine.py +1 -60
- package/scripts/lib/srp_engine_okf.py +65 -0
- package/scripts/lib/story-verify-memo.sh +56 -0
- package/scripts/lib/test-assertions.sh +6 -0
- package/scripts/mcp-smoke.ts +48 -43
- package/scripts/run-story-verify.sh +3 -50
- package/scripts/test-golden-gate-runner.sh +18 -18
- package/scripts/test-project-runtime.sh +10 -19
- package/scripts/validate-docs-base-path.sh +15 -22
- package/scripts/validate-mcp-package.sh +18 -20
- package/scripts/validate-omp-extension.sh +23 -30
package/.mcp.json
CHANGED
package/.pi/package.json
CHANGED
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
|
@@ -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('
|
|
47
|
-
|
|
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'}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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 : $*"; }
|
package/scripts/mcp-smoke.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// story: e32s06
|
|
2
|
-
//
|
|
3
|
-
// Verifies Node resolves @modelcontextprotocol/sdk from the root
|
|
4
|
-
// and the server reaches its startup log line without
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
}
|
|
39
|
-
});
|
|
43
|
+
done({ ok: false, detail: `timeout waiting for startup (stderr: ${stderr.trim()})` });
|
|
44
|
+
}, 5000);
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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=""
|
|
38
|
+
out=$(GOLDEN_SKIP_GATES="" ggr_run_fixture)
|
|
39
39
|
echo "$out" | grep -q 'marker-line-from-failing-gate' \
|
|
40
|
-
&&
|
|
41
|
-
||
|
|
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
|
-
&&
|
|
44
|
-
||
|
|
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"
|
|
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
|
-
&&
|
|
49
|
-
||
|
|
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
|
-
&&
|
|
52
|
-
||
|
|
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"
|
|
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
|
-
&&
|
|
57
|
-
||
|
|
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
|
-
[ "$
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
18
|
+
ta_pass "$name"
|
|
28
19
|
else
|
|
29
|
-
|
|
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
|
-
|
|
29
|
+
ta_fail "$name — command unexpectedly succeeded"
|
|
39
30
|
elif [[ "$output" == *"$expected"* ]]; then
|
|
40
|
-
|
|
31
|
+
ta_pass "$name"
|
|
41
32
|
else
|
|
42
|
-
|
|
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: $
|
|
91
|
-
[[ "$
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
ta_err "website npm ci failed — cannot run build check"
|
|
32
25
|
return 1
|
|
33
26
|
fi
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
ta_ok "hero Browse skills link includes SITE_BASE"
|
|
45
38
|
else
|
|
46
|
-
|
|
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
|
-
|
|
44
|
+
ta_err "index.mdx still contains root-absolute /skills/ links"
|
|
52
45
|
else
|
|
53
|
-
|
|
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
|
-
|
|
51
|
+
ta_err "missing $LLMS_TXT — run cd website && npm run generate"
|
|
59
52
|
elif grep -q '/bigpowers/bigpowers/' "$LLMS_TXT"; then
|
|
60
|
-
|
|
53
|
+
ta_err "llms.txt double-prefixes SITE_BASE in URLs"
|
|
61
54
|
else
|
|
62
|
-
|
|
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
|
-
|
|
65
|
+
ta_ok "dist/index.html hero href includes /bigpowers/skills/"
|
|
73
66
|
else
|
|
74
|
-
|
|
67
|
+
ta_err "dist/index.html missing href=\"/bigpowers/skills/\" for Browse skills"
|
|
75
68
|
fi
|
|
76
69
|
else
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
ta_err ".mcp.json uses workspaceFolder — not valid in OMP env expansion (#123)"
|
|
28
21
|
else
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
+
ta_ok "root dependencies include $dep"
|
|
42
40
|
else
|
|
43
|
-
|
|
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
|
-
|
|
47
|
+
ta_ok "bigpowers-mcp/build/index.js exists"
|
|
50
48
|
else
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
ta_ok "MCP server loads from simulated OMP plugin root"
|
|
63
61
|
else
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
+
ta_ok "pi.extensions has exactly 1 entry"
|
|
29
22
|
else
|
|
30
|
-
|
|
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
|
-
|
|
30
|
+
ta_err "No pi.extensions entry found"
|
|
38
31
|
else
|
|
39
32
|
if [[ -f "$EXT_PATH" ]]; then
|
|
40
|
-
|
|
33
|
+
ta_ok "extension file '$EXT_PATH' exists"
|
|
41
34
|
else
|
|
42
|
-
|
|
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
|
-
|
|
44
|
+
ta_ok "symbol '$sym' found in extension source"
|
|
52
45
|
else
|
|
53
|
-
|
|
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
|
-
|
|
50
|
+
ta_err "extension registers commands already supplied by Pi prompt templates"
|
|
58
51
|
else
|
|
59
|
-
|
|
52
|
+
ta_ok "extension leaves slash-command registration to Pi prompt templates"
|
|
60
53
|
fi
|
|
61
54
|
else
|
|
62
|
-
|
|
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
|
-
|
|
60
|
+
ta_ok "pi.prompts has exactly 1 canonical prompt-template entry"
|
|
68
61
|
else
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
ta_err "no skills found under skills/"
|
|
93
86
|
elif [[ "$MISSING" -eq 0 ]]; then
|
|
94
|
-
|
|
87
|
+
ta_ok "$TOTAL source skills all at skills/<name>/SKILL.md"
|
|
95
88
|
else
|
|
96
|
-
|
|
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
|
-
|
|
97
|
+
ta_ok "extension calls discoverSkills with skills/ subdirectory"
|
|
105
98
|
else
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
ta_ok "extension loads without calling action methods during load"
|
|
125
118
|
else
|
|
126
|
-
|
|
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
|