feat: add repo-local subminer workflow plugin

This commit is contained in:
2026-03-27 00:27:13 -07:00
parent 3fe63a6afa
commit d2cfa1b871
14 changed files with 1218 additions and 973 deletions

View File

@@ -0,0 +1,141 @@
---
name: 'subminer-change-verification'
description: 'Use when working in the SubMiner repo and you need to verify code changes actually work. Covers targeted regression checks during debugging and pre-handoff verification, with cheap-first lane selection for config, docs, launcher/plugin, runtime-compat, and optional real-runtime escalation.'
---
# SubMiner Change Verification
Canonical source: this plugin path.
Use this skill for SubMiner code changes. Default to cheap, repo-native verification first. Escalate only when the changed behavior actually depends on Electron, mpv, overlay/window tracking, or other GUI-sensitive runtime behavior.
## Scripts
- `scripts/classify_subminer_diff.sh`
- Emits suggested lanes and flags from explicit paths or current git changes.
- `scripts/verify_subminer_change.sh`
- Runs selected lanes, captures artifacts, and writes a compact summary.
If you need an explicit installed path, use the directory that contains this `SKILL.md`. The helper scripts live under:
```bash
export SUBMINER_VERIFY_SKILL="<path-to-plugin-skill>"
```
## Default workflow
1. Inspect the changed files or user-requested area.
2. Run the classifier unless you already know the right lane.
3. Run the verifier with the cheapest sufficient lane set.
4. If the classifier emits `flag:real-runtime-candidate`, do not jump straight to runtime verification. First run the non-runtime lanes.
5. Escalate to explicit `--lane real-runtime --allow-real-runtime` only when cheaper lanes cannot validate the behavior claim.
6. Return:
- verification summary
- exact commands run
- artifact paths
- skipped lanes and blockers
## Quick start
Plugin-source quick start:
```bash
bash plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
```
Installed-skill quick start:
```bash
bash "$SUBMINER_VERIFY_SKILL/scripts/classify_subminer_diff.sh"
```
Compatibility entrypoint:
```bash
bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
```
Classify explicit files:
```bash
bash plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh \
launcher/main.ts \
plugin/subminer/lifecycle.lua \
src/main/runtime/mpv-client-runtime-service.ts
```
Run automatic lane selection:
```bash
bash plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh
```
Installed-skill form:
```bash
bash "$SUBMINER_VERIFY_SKILL/scripts/verify_subminer_change.sh"
```
Compatibility entrypoint:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh
```
Run targeted lanes:
```bash
bash plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh \
--lane launcher-plugin \
--lane runtime-compat
```
Dry-run to inspect planned commands and artifact layout:
```bash
bash plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh \
--dry-run \
launcher/main.ts \
src/main.ts
```
## Lane guidance
- `docs`
- For `docs-site/`, `docs/`, and doc-only edits.
- `config`
- For `src/config/` and config-template-sensitive edits.
- `core`
- For general source changes where `typecheck` + `test:fast` is the best cheap signal.
- `launcher-plugin`
- For `launcher/`, `plugin/subminer/`, plugin gating scripts, and wrapper/mpv routing work.
- `runtime-compat`
- For `src/main*`, runtime/composer wiring, mpv/overlay services, window trackers, and dist-sensitive behavior.
- `real-runtime`
- Only after deliberate escalation.
## Real Runtime Escalation
Escalate only when the change claim depends on actual runtime behavior, for example:
- overlay appears, hides, or tracks a real mpv window
- mpv launch flags or pause-until-ready behavior
- plugin/socket/auto-start handshake under a real player
- macOS/window-tracker/focus-sensitive behavior
If the environment cannot support authoritative runtime verification, report the blocker explicitly. Do not silently downgrade a runtime-required claim to a pass.
## Artifact contract
The verifier writes under `.tmp/skill-verification/<timestamp>/`:
- `summary.json`
- `summary.txt`
- `classification.txt`
- `env.txt`
- `lanes.txt`
- `steps.tsv`
- `steps/*.stdout.log`
- `steps/*.stderr.log`
On failure, quote the exact failing command and point at the artifact directory.

View File

@@ -0,0 +1,163 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: classify_subminer_diff.sh [path ...]
Emit suggested verification lanes for explicit paths or current local git changes.
Output format:
lane:<name>
flag:<name>
reason:<text>
EOF
}
has_item() {
local needle=$1
shift || true
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0
fi
done
return 1
}
add_lane() {
local lane=$1
if ! has_item "$lane" "${LANES[@]:-}"; then
LANES+=("$lane")
fi
}
add_flag() {
local flag=$1
if ! has_item "$flag" "${FLAGS[@]:-}"; then
FLAGS+=("$flag")
fi
}
add_reason() {
REASONS+=("$1")
}
collect_git_paths() {
local top_level
if ! top_level=$(git rev-parse --show-toplevel 2>/dev/null); then
return 0
fi
(
cd "$top_level"
if git rev-parse --verify HEAD >/dev/null 2>&1; then
git diff --name-only --relative HEAD --
git diff --name-only --relative --cached --
else
git diff --name-only --relative --
git diff --name-only --relative --cached --
fi
git ls-files --others --exclude-standard
) | awk 'NF' | sort -u
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
declare -a PATHS=()
declare -a LANES=()
declare -a FLAGS=()
declare -a REASONS=()
if [[ $# -gt 0 ]]; then
while [[ $# -gt 0 ]]; do
PATHS+=("$1")
shift
done
else
while IFS= read -r line; do
[[ -n "$line" ]] && PATHS+=("$line")
done < <(collect_git_paths)
fi
if [[ ${#PATHS[@]} -eq 0 ]]; then
add_lane "core"
add_reason "no changed paths detected -> default to core"
fi
for path in "${PATHS[@]}"; do
specialized=0
case "$path" in
docs-site/*|docs/*|changes/*|README.md)
add_lane "docs"
add_reason "$path -> docs"
specialized=1
;;
esac
case "$path" in
src/config/*|src/generate-config-example.ts|src/verify-config-example.ts|docs-site/public/config.example.jsonc|config.example.jsonc)
add_lane "config"
add_reason "$path -> config"
specialized=1
;;
esac
case "$path" in
launcher/*|plugin/subminer/*|plugin/subminer.conf|scripts/test-plugin-*|scripts/get-mpv-window-*|scripts/configure-plugin-binary-path.mjs)
add_lane "launcher-plugin"
add_reason "$path -> launcher-plugin"
add_flag "real-runtime-candidate"
add_reason "$path -> real-runtime-candidate"
specialized=1
;;
esac
case "$path" in
src/main.ts|src/main-entry.ts|src/preload.ts|src/main/*|src/core/services/mpv*|src/core/services/overlay*|src/renderer/*|src/window-trackers/*|scripts/prepare-build-assets.mjs)
add_lane "runtime-compat"
add_reason "$path -> runtime-compat"
add_flag "real-runtime-candidate"
add_reason "$path -> real-runtime-candidate"
specialized=1
;;
esac
if [[ "$specialized" == "0" ]]; then
case "$path" in
src/*|package.json|tsconfig*.json|scripts/*|Makefile)
add_lane "core"
add_reason "$path -> core"
;;
esac
fi
case "$path" in
package.json|src/main.ts|src/main-entry.ts|src/preload.ts)
add_flag "broad-impact"
add_reason "$path -> broad-impact"
;;
esac
done
if [[ ${#LANES[@]} -eq 0 ]]; then
add_lane "core"
add_reason "no lane-specific matches -> default to core"
fi
for lane in "${LANES[@]}"; do
printf 'lane:%s\n' "$lane"
done
for flag in "${FLAGS[@]}"; do
printf 'flag:%s\n' "$flag"
done
for reason in "${REASONS[@]}"; do
printf 'reason:%s\n' "$reason"
done

View File

@@ -0,0 +1,502 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: verify_subminer_change.sh [options] [path ...]
Options:
--lane <name> Force a verification lane. Repeatable.
--artifact-dir <dir> Use an explicit artifact directory.
--allow-real-runtime Allow explicit real-runtime execution.
--allow-real-gui Deprecated alias for --allow-real-runtime.
--dry-run Record planned steps without executing commands.
--help Show this help text.
If no lanes are supplied, the script classifies the provided paths. If no paths are
provided, it classifies the current local git changes.
Authoritative real-runtime verification should be requested with explicit path
arguments instead of relying on inferred local git changes.
EOF
}
timestamp() {
date +%Y%m%d-%H%M%S
}
timestamp_iso() {
date -u +%Y-%m-%dT%H:%M:%SZ
}
generate_session_id() {
local tmp_dir
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/subminer-verify-$(timestamp)-XXXXXX")
basename "$tmp_dir"
rmdir "$tmp_dir"
}
has_item() {
local needle=$1
shift || true
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0
fi
done
return 1
}
normalize_lane_name() {
case "$1" in
real-gui)
printf '%s' "real-runtime"
;;
*)
printf '%s' "$1"
;;
esac
}
add_lane() {
local lane
lane=$(normalize_lane_name "$1")
if ! has_item "$lane" "${SELECTED_LANES[@]:-}"; then
SELECTED_LANES+=("$lane")
fi
}
add_blocker() {
BLOCKERS+=("$1")
BLOCKED=1
}
append_step_record() {
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" >>"$STEPS_TSV"
}
record_env() {
{
printf 'repo_root=%s\n' "$REPO_ROOT"
printf 'session_id=%s\n' "$SESSION_ID"
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
printf 'path_selection_mode=%s\n' "$PATH_SELECTION_MODE"
printf 'dry_run=%s\n' "$DRY_RUN"
printf 'allow_real_runtime=%s\n' "$ALLOW_REAL_RUNTIME"
printf 'session_home=%s\n' "$SESSION_HOME"
printf 'session_xdg_config_home=%s\n' "$SESSION_XDG_CONFIG_HOME"
printf 'session_mpv_dir=%s\n' "$SESSION_MPV_DIR"
printf 'session_logs_dir=%s\n' "$SESSION_LOGS_DIR"
printf 'session_mpv_log=%s\n' "$SESSION_MPV_LOG"
printf 'pwd=%s\n' "$(pwd)"
git rev-parse --short HEAD 2>/dev/null | sed 's/^/git_head=/' || true
git status --short 2>/dev/null || true
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
printf 'requested_paths=\n'
printf ' %s\n' "${PATH_ARGS[@]}"
fi
} >"$ARTIFACT_DIR/env.txt"
}
run_step() {
local lane=$1
local name=$2
local command=$3
local note=${4:-}
local slug=${name//[^a-zA-Z0-9_-]/-}
local stdout_rel="steps/${slug}.stdout.log"
local stderr_rel="steps/${slug}.stderr.log"
local stdout_path="$ARTIFACT_DIR/$stdout_rel"
local stderr_path="$ARTIFACT_DIR/$stderr_rel"
local status exit_code
COMMANDS_RUN+=("$command")
printf '%s\n' "$command" >"$ARTIFACT_DIR/steps/${slug}.command.txt"
if [[ "$DRY_RUN" == "1" ]]; then
printf '[dry-run] %s\n' "$command" >"$stdout_path"
: >"$stderr_path"
status="dry-run"
exit_code=0
else
if bash -lc "cd \"$REPO_ROOT\" && $command" >"$stdout_path" 2>"$stderr_path"; then
status="passed"
exit_code=0
EXECUTED_REAL_STEPS=1
else
exit_code=$?
status="failed"
FAILED=1
fi
fi
append_step_record "$lane" "$name" "$status" "$exit_code" "$command" "$stdout_rel" "$stderr_rel" "$note"
printf '%s\t%s\t%s\n' "$lane" "$name" "$status"
if [[ "$status" == "failed" ]]; then
FAILURE_STEP="$name"
FAILURE_COMMAND="$command"
FAILURE_STDOUT="$stdout_rel"
FAILURE_STDERR="$stderr_rel"
return "$exit_code"
fi
}
record_nonpassing_step() {
local lane=$1
local name=$2
local status=$3
local note=$4
local slug=${name//[^a-zA-Z0-9_-]/-}
local stdout_rel="steps/${slug}.stdout.log"
local stderr_rel="steps/${slug}.stderr.log"
printf '%s\n' "$note" >"$ARTIFACT_DIR/$stdout_rel"
: >"$ARTIFACT_DIR/$stderr_rel"
append_step_record "$lane" "$name" "$status" "0" "" "$stdout_rel" "$stderr_rel" "$note"
printf '%s\t%s\t%s\n' "$lane" "$name" "$status"
}
record_skipped_step() {
record_nonpassing_step "$1" "$2" "skipped" "$3"
}
record_blocked_step() {
add_blocker "$3"
record_nonpassing_step "$1" "$2" "blocked" "$3"
}
record_failed_step() {
FAILED=1
FAILURE_STEP=$2
FAILURE_COMMAND=${FAILURE_COMMAND:-"(validation)"}
FAILURE_STDOUT="steps/${2//[^a-zA-Z0-9_-]/-}.stdout.log"
FAILURE_STDERR="steps/${2//[^a-zA-Z0-9_-]/-}.stderr.log"
add_blocker "$3"
record_nonpassing_step "$1" "$2" "failed" "$3"
}
find_real_runtime_helper() {
local candidate
for candidate in \
"$SCRIPT_DIR/run_real_runtime_smoke.sh" \
"$SCRIPT_DIR/run_real_mpv_smoke.sh"; do
if [[ -x "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi
done
return 1
}
acquire_real_runtime_lease() {
local lease_root="$REPO_ROOT/.tmp/skill-verification/locks"
local lease_dir="$lease_root/exclusive-real-runtime"
mkdir -p "$lease_root"
if mkdir "$lease_dir" 2>/dev/null; then
REAL_RUNTIME_LEASE_DIR="$lease_dir"
printf '%s\n' "$SESSION_ID" >"$lease_dir/session_id"
return 0
fi
local owner=""
if [[ -f "$lease_dir/session_id" ]]; then
owner=$(cat "$lease_dir/session_id")
fi
add_blocker "real-runtime lease already held${owner:+ by $owner}"
return 1
}
release_real_runtime_lease() {
if [[ -n "$REAL_RUNTIME_LEASE_DIR" && -d "$REAL_RUNTIME_LEASE_DIR" ]]; then
if [[ -f "$REAL_RUNTIME_LEASE_DIR/session_id" ]]; then
local owner
owner=$(cat "$REAL_RUNTIME_LEASE_DIR/session_id")
if [[ "$owner" != "$SESSION_ID" ]]; then
return 0
fi
fi
rm -rf "$REAL_RUNTIME_LEASE_DIR"
fi
}
compute_final_status() {
if [[ "$FAILED" == "1" ]]; then
FINAL_STATUS="failed"
elif [[ "$BLOCKED" == "1" ]]; then
FINAL_STATUS="blocked"
elif [[ "$EXECUTED_REAL_STEPS" == "1" ]]; then
FINAL_STATUS="passed"
else
FINAL_STATUS="skipped"
fi
}
write_summary_files() {
local lane_lines
lane_lines=$(printf '%s\n' "${SELECTED_LANES[@]}")
printf '%s\n' "$lane_lines" >"$ARTIFACT_DIR/lanes.txt"
printf '%s\n' "${BLOCKERS[@]}" >"$ARTIFACT_DIR/blockers.txt"
printf '%s\n' "${PATH_ARGS[@]}" >"$ARTIFACT_DIR/requested-paths.txt"
ARTIFACT_DIR_ENV="$ARTIFACT_DIR" \
SESSION_ID_ENV="$SESSION_ID" \
FINAL_STATUS_ENV="$FINAL_STATUS" \
PATH_SELECTION_MODE_ENV="$PATH_SELECTION_MODE" \
ALLOW_REAL_RUNTIME_ENV="$ALLOW_REAL_RUNTIME" \
SESSION_HOME_ENV="$SESSION_HOME" \
SESSION_XDG_CONFIG_HOME_ENV="$SESSION_XDG_CONFIG_HOME" \
SESSION_MPV_DIR_ENV="$SESSION_MPV_DIR" \
SESSION_LOGS_DIR_ENV="$SESSION_LOGS_DIR" \
SESSION_MPV_LOG_ENV="$SESSION_MPV_LOG" \
STARTED_AT_ENV="$STARTED_AT" \
FINISHED_AT_ENV="$FINISHED_AT" \
FAILED_ENV="$FAILED" \
FAILURE_COMMAND_ENV="${FAILURE_COMMAND:-}" \
FAILURE_STDOUT_ENV="${FAILURE_STDOUT:-}" \
FAILURE_STDERR_ENV="${FAILURE_STDERR:-}" \
bun -e '
const fs = require("fs");
const path = require("path");
const lines = fs
.readFileSync(path.join(process.env.ARTIFACT_DIR_ENV, "steps.tsv"), "utf8")
.trim()
.split("\n")
.filter(Boolean)
.slice(1)
.map((line) => {
const [lane, name, status, exitCode, command, stdout, stderr, note] = line.split("\t");
return { lane, name, status, exitCode: Number(exitCode), command, stdout, stderr, note };
});
const payload = {
sessionId: process.env.SESSION_ID_ENV,
startedAt: process.env.STARTED_AT_ENV,
finishedAt: process.env.FINISHED_AT_ENV,
status: process.env.FINAL_STATUS_ENV,
pathSelectionMode: process.env.PATH_SELECTION_MODE_ENV,
allowRealRuntime: process.env.ALLOW_REAL_RUNTIME_ENV === "1",
sessionHome: process.env.SESSION_HOME_ENV,
sessionXdgConfigHome: process.env.SESSION_XDG_CONFIG_HOME_ENV,
sessionMpvDir: process.env.SESSION_MPV_DIR_ENV,
sessionLogsDir: process.env.SESSION_LOGS_DIR_ENV,
sessionMpvLog: process.env.SESSION_MPV_LOG_ENV,
failed: process.env.FAILED_ENV === "1",
failure: process.env.FAILURE_COMMAND_ENV
? {
command: process.env.FAILURE_COMMAND_ENV,
stdout: process.env.FAILURE_STDOUT_ENV,
stderr: process.env.FAILURE_STDERR_ENV,
}
: null,
blockers: fs
.readFileSync(path.join(process.env.ARTIFACT_DIR_ENV, "blockers.txt"), "utf8")
.split("\n")
.filter(Boolean),
lanes: fs
.readFileSync(path.join(process.env.ARTIFACT_DIR_ENV, "lanes.txt"), "utf8")
.split("\n")
.filter(Boolean),
requestedPaths: fs
.readFileSync(path.join(process.env.ARTIFACT_DIR_ENV, "requested-paths.txt"), "utf8")
.split("\n")
.filter(Boolean),
steps: lines,
};
fs.writeFileSync(
path.join(process.env.ARTIFACT_DIR_ENV, "summary.json"),
JSON.stringify(payload, null, 2) + "\n",
);
const summaryLines = [
`status: ${payload.status}`,
`session: ${payload.sessionId}`,
`artifacts: ${process.env.ARTIFACT_DIR_ENV}`,
`lanes: ${payload.lanes.join(", ") || "(none)"}`,
];
if (payload.requestedPaths.length > 0) {
summaryLines.push("requested paths:");
for (const entry of payload.requestedPaths) {
summaryLines.push(`- ${entry}`);
}
}
if (payload.failure) {
summaryLines.push(`failure command: ${payload.failure.command}`);
summaryLines.push(`failure stdout: ${payload.failure.stdout}`);
summaryLines.push(`failure stderr: ${payload.failure.stderr}`);
}
if (payload.blockers.length > 0) {
summaryLines.push("blockers:");
for (const blocker of payload.blockers) {
summaryLines.push(`- ${blocker}`);
}
}
summaryLines.push("steps:");
for (const step of payload.steps) {
summaryLines.push(`- ${step.lane}/${step.name}: ${step.status}`);
}
fs.writeFileSync(
path.join(process.env.ARTIFACT_DIR_ENV, "summary.txt"),
summaryLines.join("\n") + "\n",
);
'
}
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
SKILL_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
declare -a PATH_ARGS=()
declare -a SELECTED_LANES=()
declare -a COMMANDS_RUN=()
declare -a BLOCKERS=()
ALLOW_REAL_RUNTIME=0
DRY_RUN=0
FAILED=0
BLOCKED=0
EXECUTED_REAL_STEPS=0
FAILURE_STEP=""
FAILURE_COMMAND=""
FAILURE_STDOUT=""
FAILURE_STDERR=""
REAL_RUNTIME_LEASE_DIR=""
PATH_SELECTION_MODE="auto"
while [[ $# -gt 0 ]]; do
case "$1" in
--lane)
shift
[[ $# -gt 0 ]] || {
echo "Missing value for --lane" >&2
exit 2
}
add_lane "$1"
PATH_SELECTION_MODE="explicit-lanes"
;;
--artifact-dir)
shift
[[ $# -gt 0 ]] || {
echo "Missing value for --artifact-dir" >&2
exit 2
}
ARTIFACT_DIR=$1
;;
--allow-real-runtime|--allow-real-gui)
ALLOW_REAL_RUNTIME=1
;;
--dry-run)
DRY_RUN=1
;;
--help|-h)
usage
exit 0
;;
*)
PATH_ARGS+=("$1")
;;
esac
shift || true
done
if [[ -z "${ARTIFACT_DIR:-}" ]]; then
SESSION_ID=$(generate_session_id)
ARTIFACT_DIR="$REPO_ROOT/.tmp/skill-verification/$SESSION_ID"
else
SESSION_ID=$(basename "$ARTIFACT_DIR")
fi
mkdir -p "$ARTIFACT_DIR/steps"
STEPS_TSV="$ARTIFACT_DIR/steps.tsv"
printf 'lane\tstep\tstatus\texit_code\tcommand\tstdout\tstderr\tnote\n' >"$STEPS_TSV"
STARTED_AT=$(timestamp_iso)
SESSION_HOME="$REPO_ROOT/.tmp/skill-verification/runtime/$SESSION_ID/home"
SESSION_XDG_CONFIG_HOME="$REPO_ROOT/.tmp/skill-verification/runtime/$SESSION_ID/xdg-config"
SESSION_MPV_DIR="$SESSION_XDG_CONFIG_HOME/mpv"
SESSION_LOGS_DIR="$REPO_ROOT/.tmp/skill-verification/runtime/$SESSION_ID/logs"
SESSION_MPV_LOG="$SESSION_LOGS_DIR/mpv.log"
mkdir -p "$SESSION_HOME" "$SESSION_MPV_DIR" "$SESSION_LOGS_DIR"
CLASSIFIER_OUTPUT="$ARTIFACT_DIR/classification.txt"
if [[ ${#SELECTED_LANES[@]} -eq 0 ]]; then
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
PATH_SELECTION_MODE="explicit-paths"
fi
if "$SCRIPT_DIR/classify_subminer_diff.sh" "${PATH_ARGS[@]}" >"$CLASSIFIER_OUTPUT"; then
while IFS= read -r line; do
case "$line" in
lane:*)
add_lane "${line#lane:}"
;;
esac
done <"$CLASSIFIER_OUTPUT"
else
record_failed_step "meta" "classify" "classification failed"
fi
else
: >"$CLASSIFIER_OUTPUT"
fi
record_env
if [[ ${#SELECTED_LANES[@]} -eq 0 ]]; then
add_lane "core"
fi
for lane in "${SELECTED_LANES[@]}"; do
case "$lane" in
docs)
run_step "$lane" "docs-kb" "bun run test:docs:kb" || break
;;
config)
run_step "$lane" "config" "bun run test:config" || break
;;
core)
run_step "$lane" "typecheck" "bun run typecheck" || break
run_step "$lane" "fast-tests" "bun run test:fast" || break
;;
launcher-plugin)
run_step "$lane" "launcher" "bun run test:launcher" || break
run_step "$lane" "plugin-src" "bun run test:plugin:src" || break
;;
runtime-compat)
run_step "$lane" "runtime-compat" "bun run test:runtime:compat" || break
;;
real-runtime)
if [[ "$ALLOW_REAL_RUNTIME" != "1" ]]; then
record_blocked_step "$lane" "real-runtime" "real-runtime requested without --allow-real-runtime"
continue
fi
if ! acquire_real_runtime_lease; then
record_blocked_step "$lane" "real-runtime-lease" "${BLOCKERS[-1]}"
continue
fi
helper=$(find_real_runtime_helper || true)
if [[ -z "${helper:-}" ]]; then
record_blocked_step "$lane" "real-runtime-helper" "no real-runtime helper script available in $SCRIPT_DIR"
continue
fi
run_step "$lane" "real-runtime" "\"$helper\" \"$SESSION_ID\" \"$ARTIFACT_DIR\"" || break
;;
*)
record_blocked_step "$lane" "unknown-lane" "unknown lane: $lane"
;;
esac
done
release_real_runtime_lease
FINISHED_AT=$(timestamp_iso)
compute_final_status
write_summary_files
printf 'summary:%s\n' "$ARTIFACT_DIR/summary.txt"
cat "$ARTIFACT_DIR/summary.txt"