diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..55b4353 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "subminer-local", + "interface": { + "displayName": "SubMiner Local" + }, + "plugins": [ + { + "name": "subminer-workflow", + "source": { + "source": "local", + "path": "./plugins/subminer-workflow" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.agents/skills/subminer-change-verification/SKILL.md b/.agents/skills/subminer-change-verification/SKILL.md index f5dd17a..3a78d55 100644 --- a/.agents/skills/subminer-change-verification/SKILL.md +++ b/.agents/skills/subminer-change-verification/SKILL.md @@ -1,127 +1,22 @@ --- -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." +name: 'subminer-change-verification' +description: 'Compatibility shim. Canonical SubMiner change verification workflow now lives in the repo-local subminer-workflow plugin.' --- -# SubMiner Change Verification +# Compatibility Shim -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. +Canonical source: -## Scripts +- `plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md` -- `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. +Canonical helper scripts: -If you need an explicit installed path, use the directory that contains this `SKILL.md`. The helper scripts live under: +- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh` +- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh` -```bash -export SUBMINER_VERIFY_SKILL="" -``` +When this shim is invoked: -## 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 - -Repo-source quick start: - -```bash -bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh -``` - -Installed-skill quick start: - -```bash -bash "$SUBMINER_VERIFY_SKILL/scripts/classify_subminer_diff.sh" -``` - -Classify explicit files: - -```bash -bash .agents/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 .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh -``` - -Installed-skill form: - -```bash -bash "$SUBMINER_VERIFY_SKILL/scripts/verify_subminer_change.sh" -``` - -Run targeted lanes: - -```bash -bash .agents/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 .agents/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//`: - -- `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. +1. Read the canonical plugin-owned skill. +2. Follow the plugin-owned skill as the source of truth. +3. Use the wrapper scripts in this shim directory only for compatibility with existing commands, docs, and backlog history. +4. Do not duplicate workflow changes here; update the plugin-owned skill and scripts instead. diff --git a/.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh b/.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh index a983ff3..4c7acce 100755 --- a/.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh +++ b/.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh @@ -1,163 +1,13 @@ #!/usr/bin/env bash set -euo pipefail -usage() { - cat <<'EOF' -Usage: classify_subminer_diff.sh [path ...] +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd) +TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh" -Emit suggested verification lanes for explicit paths or current local git changes. - -Output format: - lane: - flag: - reason: -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 +if [[ ! -x "$TARGET" ]]; then + echo "Missing canonical script: $TARGET" >&2 + exit 1 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 +exec "$TARGET" "$@" diff --git a/.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh b/.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh index d34dc8f..58cdd64 100755 --- a/.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh +++ b/.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh @@ -1,566 +1,13 @@ #!/usr/bin/env bash set -euo pipefail -usage() { - cat <<'EOF' -Usage: verify_subminer_change.sh [options] [path ...] - -Options: - --lane Force a verification lane. Repeatable. - --artifact-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"); - - function readLines(filePath) { - if (!fs.existsSync(filePath)) return []; - return fs.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean); - } - - const artifactDir = process.env.ARTIFACT_DIR_ENV; - const reportsDir = path.join(artifactDir, "reports"); - const lanes = readLines(path.join(artifactDir, "lanes.txt")); - const blockers = readLines(path.join(artifactDir, "blockers.txt")); - const requestedPaths = readLines(path.join(artifactDir, "requested-paths.txt")); - const steps = readLines(path.join(artifactDir, "steps.tsv")).map((line) => { - const [lane, name, status, exitCode, command, stdout, stderr, note] = line.split("\t"); - return { - lane, - name, - status, - exitCode: Number(exitCode || 0), - command, - stdout, - stderr, - note, - }; - }); - const summary = { - sessionId: process.env.SESSION_ID_ENV || "", - artifactDir, - reportsDir, - status: process.env.FINAL_STATUS_ENV || "failed", - selectedLanes: lanes, - failed: process.env.FAILED_ENV === "1", - failure: - process.env.FAILED_ENV === "1" - ? { - command: process.env.FAILURE_COMMAND_ENV || "", - stdout: process.env.FAILURE_STDOUT_ENV || "", - stderr: process.env.FAILURE_STDERR_ENV || "", - } - : null, - blockers, - pathSelectionMode: process.env.PATH_SELECTION_MODE_ENV || "git-inferred", - requestedPaths, - allowRealRuntime: process.env.ALLOW_REAL_RUNTIME_ENV === "1", - startedAt: process.env.STARTED_AT_ENV || "", - finishedAt: process.env.FINISHED_AT_ENV || "", - env: { - home: process.env.SESSION_HOME_ENV || "", - xdgConfigHome: process.env.SESSION_XDG_CONFIG_HOME_ENV || "", - mpvDir: process.env.SESSION_MPV_DIR_ENV || "", - logsDir: process.env.SESSION_LOGS_DIR_ENV || "", - mpvLog: process.env.SESSION_MPV_LOG_ENV || "", - }, - steps, - }; - - const summaryJson = JSON.stringify(summary, null, 2) + "\n"; - fs.writeFileSync(path.join(artifactDir, "summary.json"), summaryJson); - fs.writeFileSync(path.join(reportsDir, "summary.json"), summaryJson); - - const lines = []; - lines.push(`session_id: ${summary.sessionId}`); - lines.push(`artifact_dir: ${artifactDir}`); - lines.push(`selected_lanes: ${lanes.join(", ") || "(none)"}`); - lines.push(`status: ${summary.status}`); - lines.push(`path_selection_mode: ${summary.pathSelectionMode}`); - if (requestedPaths.length > 0) { - lines.push(`requested_paths: ${requestedPaths.join(", ")}`); - } - if (blockers.length > 0) { - lines.push(`blockers: ${blockers.join(" | ")}`); - } - for (const step of steps) { - lines.push(`${step.lane}/${step.name}: ${step.status}`); - if (step.command) lines.push(` command: ${step.command}`); - lines.push(` stdout: ${step.stdout}`); - lines.push(` stderr: ${step.stderr}`); - if (step.note) lines.push(` note: ${step.note}`); - } - if (summary.failed) { - lines.push(`failure_command: ${process.env.FAILURE_COMMAND_ENV || ""}`); - } - const summaryText = lines.join("\n") + "\n"; - fs.writeFileSync(path.join(artifactDir, "summary.txt"), summaryText); - fs.writeFileSync(path.join(reportsDir, "summary.txt"), summaryText); - ' -} - -cleanup() { - release_real_runtime_lease -} - -CLASSIFIER_OUTPUT="" -ARTIFACT_DIR="" -ALLOW_REAL_RUNTIME=0 -DRY_RUN=0 -FAILED=0 -BLOCKED=0 -EXECUTED_REAL_STEPS=0 -FINAL_STATUS="" -FAILURE_STEP="" -FAILURE_COMMAND="" -FAILURE_STDOUT="" -FAILURE_STDERR="" -REAL_RUNTIME_LEASE_DIR="" -STARTED_AT="" -FINISHED_AT="" - -declare -a EXPLICIT_LANES=() -declare -a SELECTED_LANES=() -declare -a PATH_ARGS=() -declare -a COMMANDS_RUN=() -declare -a BLOCKERS=() - -while [[ $# -gt 0 ]]; do - case "$1" in - --lane) - EXPLICIT_LANES+=("$(normalize_lane_name "$2")") - shift 2 - ;; - --artifact-dir) - ARTIFACT_DIR=$2 - shift 2 - ;; - --allow-real-runtime|--allow-real-gui) - ALLOW_REAL_RUNTIME=1 - shift - ;; - --dry-run) - DRY_RUN=1 - shift - ;; - --help|-h) - usage - exit 0 - ;; - --) - shift - while [[ $# -gt 0 ]]; do - PATH_ARGS+=("$1") - shift - done - ;; - *) - PATH_ARGS+=("$1") - shift - ;; - esac -done - SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) -SESSION_ID=$(generate_session_id) -PATH_SELECTION_MODE="git-inferred" -if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then - PATH_SELECTION_MODE="explicit" +REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd) +TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh" + +if [[ ! -x "$TARGET" ]]; then + echo "Missing canonical script: $TARGET" >&2 + exit 1 fi -if [[ -z "$ARTIFACT_DIR" ]]; then - mkdir -p "$REPO_ROOT/.tmp/skill-verification" - ARTIFACT_DIR="$REPO_ROOT/.tmp/skill-verification/$SESSION_ID" -fi - -SESSION_HOME="$ARTIFACT_DIR/home" -SESSION_XDG_CONFIG_HOME="$ARTIFACT_DIR/xdg" -SESSION_MPV_DIR="$ARTIFACT_DIR/mpv" -SESSION_LOGS_DIR="$ARTIFACT_DIR/logs" -SESSION_MPV_LOG="$SESSION_LOGS_DIR/mpv.log" - -mkdir -p "$ARTIFACT_DIR/steps" "$ARTIFACT_DIR/reports" "$SESSION_HOME" "$SESSION_XDG_CONFIG_HOME" "$SESSION_MPV_DIR" "$SESSION_LOGS_DIR" -STEPS_TSV="$ARTIFACT_DIR/steps.tsv" -: >"$STEPS_TSV" - -trap cleanup EXIT -STARTED_AT=$(timestamp_iso) - -if [[ ${#EXPLICIT_LANES[@]} -gt 0 ]]; then - local_lane="" - for local_lane in "${EXPLICIT_LANES[@]}"; do - add_lane "$local_lane" - done - printf 'reason:explicit lanes supplied\n' >"$ARTIFACT_DIR/classification.txt" -else - if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then - CLASSIFIER_OUTPUT=$(bash "$SCRIPT_DIR/classify_subminer_diff.sh" "${PATH_ARGS[@]}") - else - CLASSIFIER_OUTPUT=$(bash "$SCRIPT_DIR/classify_subminer_diff.sh") - fi - printf '%s\n' "$CLASSIFIER_OUTPUT" >"$ARTIFACT_DIR/classification.txt" - while IFS= read -r line; do - case "$line" in - lane:*) - add_lane "${line#lane:}" - ;; - esac - done <<<"$CLASSIFIER_OUTPUT" -fi - -record_env - -printf 'artifact_dir=%s\n' "$ARTIFACT_DIR" -printf 'selected_lanes=%s\n' "$(IFS=,; echo "${SELECTED_LANES[*]}")" - -for lane in "${SELECTED_LANES[@]}"; do - case "$lane" in - docs) - run_step "$lane" "docs-test" "bun run docs:test" || break - [[ "$FAILED" == "1" ]] && break - run_step "$lane" "docs-build" "bun run docs:build" || break - ;; - config) - run_step "$lane" "test-config" "bun run test:config" || break - ;; - core) - run_step "$lane" "typecheck" "bun run typecheck" || break - [[ "$FAILED" == "1" ]] && break - run_step "$lane" "test-fast" "bun run test:fast" || break - ;; - launcher-plugin) - run_step "$lane" "launcher-smoke-src" "bun run test:launcher:smoke:src" || break - [[ "$FAILED" == "1" ]] && break - run_step "$lane" "plugin-src" "bun run test:plugin:src" || break - ;; - runtime-compat) - run_step "$lane" "build" "bun run build" || break - [[ "$FAILED" == "1" ]] && break - run_step "$lane" "test-runtime-compat" "bun run test:runtime:compat" || break - [[ "$FAILED" == "1" ]] && break - run_step "$lane" "test-smoke-dist" "bun run test:smoke:dist" || break - ;; - real-runtime) - if [[ "$PATH_SELECTION_MODE" != "explicit" ]]; then - record_blocked_step \ - "$lane" \ - "real-runtime-guard" \ - "real-runtime lane requires explicit paths; inferred local git changes are non-authoritative" - break - fi - - if [[ "$ALLOW_REAL_RUNTIME" != "1" ]]; then - record_blocked_step \ - "$lane" \ - "real-runtime-guard" \ - "real-runtime lane requested but --allow-real-runtime was not supplied" - break - fi - - if ! acquire_real_runtime_lease; then - record_blocked_step \ - "$lane" \ - "real-runtime-lease" \ - "real-runtime lease already held; rerun after the active runtime verification finishes" - break - fi - - if ! REAL_RUNTIME_HELPER=$(find_real_runtime_helper); then - record_blocked_step \ - "$lane" \ - "real-runtime-helper" \ - "real-runtime helper not implemented yet" - break - fi - - printf -v REAL_RUNTIME_COMMAND \ - 'SESSION_ID=%q HOME=%q XDG_CONFIG_HOME=%q SUBMINER_MPV_LOG=%q bash %q' \ - "$SESSION_ID" \ - "$SESSION_HOME" \ - "$SESSION_XDG_CONFIG_HOME" \ - "$SESSION_MPV_LOG" \ - "$REAL_RUNTIME_HELPER" - - run_step "$lane" "real-runtime-smoke" "$REAL_RUNTIME_COMMAND" || break - ;; - *) - record_failed_step "$lane" "lane-validation" "unknown lane: $lane" - break - ;; - esac - - if [[ "$FAILED" == "1" || "$BLOCKED" == "1" ]]; then - break - fi -done - -FINISHED_AT=$(timestamp_iso) -compute_final_status -write_summary_files - -printf 'status=%s\n' "$FINAL_STATUS" -printf 'artifact_dir=%s\n' "$ARTIFACT_DIR" - -case "$FINAL_STATUS" in - failed) - printf 'result=failed\n' - printf 'failure_command=%s\n' "$FAILURE_COMMAND" - exit 1 - ;; - blocked) - printf 'result=blocked\n' - exit 2 - ;; - *) - printf 'result=ok\n' - exit 0 - ;; -esac +exec "$TARGET" "$@" diff --git a/.agents/skills/subminer-scrum-master/SKILL.md b/.agents/skills/subminer-scrum-master/SKILL.md index 75e1308..94dad07 100644 --- a/.agents/skills/subminer-scrum-master/SKILL.md +++ b/.agents/skills/subminer-scrum-master/SKILL.md @@ -1,146 +1,18 @@ --- -name: "subminer-scrum-master" -description: "Use in the SubMiner repo when a request should be turned into planned work and driven through execution. Assesses whether backlog tracking is warranted, creates or updates tasks when needed, records a plan, dispatches one or more subagents, and requires verification before handoff." +name: 'subminer-scrum-master' +description: 'Compatibility shim. Canonical SubMiner scrum-master workflow now lives in the repo-local subminer-workflow plugin.' --- -# SubMiner Scrum Master +# Compatibility Shim -Own workflow, not code by default. +Canonical source: -Use this skill when the user gives a feature request, bug report, issue, refactor, or implementation ask and the agent should manage intake, planning, backlog hygiene, worker dispatch, and verification through completion. +- `plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md` -## Core Rules +When this shim is invoked: -1. Decide first whether backlog tracking is warranted. -2. If backlog is needed, search first. Update existing work when it clearly matches. -3. If backlog is not needed, keep the process light. Do not invent ticket ceremony. -4. Record a plan before dispatching coding work. -5. Use parent + subtasks for multi-part work when backlog is used. -6. Dispatch conservatively. Parallelize only disjoint write scopes. -7. Require verification before handoff, typically via `subminer-change-verification`. -8. Report backlog actions, dispatched workers, verification, blockers, and remaining risks. +1. Read the canonical plugin-owned skill. +2. Follow the plugin-owned skill as the source of truth. +3. Do not duplicate workflow changes here; update the plugin-owned skill instead. -## Backlog Decision - -Skip backlog when the request is: -- question only -- obvious mechanical edit -- tiny isolated change with no real planning - -Use backlog when the work: -- needs planning or scope decisions -- spans multiple phases or subsystems -- is likely to need subagent dispatch -- should remain traceable for handoff/resume - -If backlog is used: -- search existing tasks first -- create/update a standalone task for one focused deliverable -- create/update a parent task plus subtasks for multi-part work -- record the implementation plan in the task before implementation begins - -## Intake Workflow - -1. Parse the request. - Classify it as question, mechanical edit, bugfix, feature, refactor, investigation, or follow-up. -2. Decide whether backlog is needed. -3. If backlog is needed: - - search first - - update existing task if clearly relevant - - otherwise create the right structure - - write the implementation plan before dispatch -4. If backlog is skipped: - - write a short working plan in-thread - - proceed without fake ticketing -5. Choose execution mode: - - no subagents for trivial work - - one worker for focused work - - parallel workers only for disjoint scopes -6. Run verification before handoff. - -## Dispatch Rules - -The scrum master orchestrates. Workers implement. - -- Do not become the default implementer unless delegation is unnecessary. -- Do not parallelize overlapping files or tightly coupled runtime work. -- Give every worker explicit ownership of files/modules. -- Tell every worker other agents may be active and they must not revert unrelated edits. -- Require each worker to report: - - changed files - - tests run - - blockers - -Use worker agents for implementation and explorer agents only for bounded codebase questions. - -## Verification - -Every nontrivial code task gets verification. - -Preferred flow: -1. use `subminer-change-verification` -2. start with the cheapest sufficient lane -3. escalate only when needed -4. if worker verification is sufficient, accept it or run one final consolidating pass - -Never hand off nontrivial work without stating what was verified and what was skipped. - -## Pre-Handoff Policy Checks (Required) - -Before handoff, always ask and answer both of these questions explicitly: - -1. **Docs update required?** -2. **Changelog fragment required?** - -Rules: -- Do not assume silence implies "no." Record an explicit yes/no decision for each item. -- If the answer is yes, either complete the update or report the blocker before handoff. -- Include the final answers in the handoff summary even when both answers are "no." - -## Failure / Scope Handling - -- If a worker hits ambiguity, pause and ask the user. -- If verification fails, either: - - send the worker back with exact failure context, or - - fix it directly if it is tiny and clearly in scope -- If new scope appears, revisit backlog structure before silently expanding work. - -## Representative Flows - -### Trivial no-ticket work - -- decide backlog is unnecessary -- keep a short plan -- implement directly or with one worker if helpful -- run targeted verification -- report outcome concisely - -### Single-task implementation - -- search/create/update one task -- record plan -- dispatch one worker -- integrate -- verify -- update task and report outcome - -### Parent + subtasks execution - -- search/create/update parent task -- create subtasks for distinct deliverables/phases -- record sequencing in the plan -- dispatch workers only where scopes are disjoint -- integrate -- run consolidated verification -- update task state and report outcome - -## Output Expectations - -At the end, report: -- whether backlog was used and what changed -- which workers were dispatched and what they owned -- what verification ran -- explicit answers to: - - docs update required? - - changelog fragment required? -- blockers, skips, and risks +This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow. diff --git a/backlog/tasks/task-240 - Migrate-SubMiner-agent-skills-into-a-repo-local-plugin-workflow.md b/backlog/tasks/task-240 - Migrate-SubMiner-agent-skills-into-a-repo-local-plugin-workflow.md new file mode 100644 index 0000000..cf2c44c --- /dev/null +++ b/backlog/tasks/task-240 - Migrate-SubMiner-agent-skills-into-a-repo-local-plugin-workflow.md @@ -0,0 +1,81 @@ +--- +id: TASK-240 +title: Migrate SubMiner agent skills into a repo-local plugin workflow +status: Done +assignee: + - codex +created_date: '2026-03-26 00:00' +updated_date: '2026-03-26 23:23' +labels: + - skills + - plugin + - workflow + - backlog + - tooling +dependencies: + - TASK-159 + - TASK-160 +priority: high +ordinal: 24000 +--- + +## Description + + + +Turn the current SubMiner-specific repo skills into a reproducible repo-local plugin workflow. The plugin should become the canonical source of truth for the SubMiner scrum-master and change-verification skills, bundle the scripts and metadata needed to test and validate changes, and preserve compatibility for existing repo references through thin `.agents/skills/` shims while the migration settles. + + + +## Acceptance Criteria + + + +- [x] #1 A repo-local plugin scaffold exists for the SubMiner workflow, with manifest and marketplace metadata wired according to the repo-local plugin layout. +- [x] #2 `subminer-scrum-master` and `subminer-change-verification` live under the plugin as the canonical skill sources, along with any helper scripts or supporting files needed for reproducible use. +- [x] #3 Existing repo-level `.agents/skills/` entrypoints are reduced to compatibility shims or redirects instead of remaining as duplicate sources of truth. +- [x] #4 The plugin-owned workflow explicitly documents backlog-first orchestration and change verification expectations, including how the skills work together. +- [x] #5 The migration is validated with the cheapest sufficient repo-native verification lane and the task records the exact commands and any skips/blockers. + + +## Implementation Plan + + + +1. Inspect the plugin-creator contract and current repo skill/script layout, then choose the plugin name, directory structure, and migration boundaries. +2. Scaffold a repo-local plugin plus marketplace entry, keeping the plugin payload under `plugins//` and the catalog entry under `.agents/plugins/marketplace.json`. +3. Move the two SubMiner-specific skills and their helper scripts into the plugin as the canonical source, adding any plugin docs or supporting metadata needed for reproducible testing/validation. +4. Replace the existing `.agents/skills/subminer-*` surfaces with minimal compatibility shims that point agents at the plugin-owned sources without duplicating logic. +5. Update internal docs or references that should now describe the plugin-first workflow. +6. Run the cheapest sufficient verification lane for plugin/internal-doc changes and record the results in this task. + + +## Implementation Notes + + + +2026-03-26: User approved the migration shape where the plugin becomes the canonical source of truth and `.agents/skills/` stays only as compatibility shims. Repo-local plugin chosen over home-local plugin. + +2026-03-26: Backlog MCP resources/tools are not available in this Codex session (`MCP startup failed`), so this task is being initialized directly in the repo-local `backlog/` files instead of through the live Backlog MCP interface. + +2026-03-26: Scaffolded `plugins/subminer-workflow/` plus `.agents/plugins/marketplace.json`, moved the scrum-master and change-verification skill definitions into the plugin as the canonical sources, and converted the old `.agents/skills/` surfaces into compatibility shims. Preserved the old verifier script entrypoints as wrappers because backlog/docs history already calls them directly. + +2026-03-26: Verification passed. + +- `bash -n plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh` +- `bash -n plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh` +- `bash -n .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh` +- `bash -n .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh` +- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh plugins/subminer-workflow/.codex-plugin/plugin.json docs/workflow/agent-plugins.md .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh` +- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane docs plugins/subminer-workflow .agents/skills/subminer-scrum-master/SKILL.md .agents/skills/subminer-change-verification/SKILL.md .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh .agents/plugins/marketplace.json docs/workflow/README.md docs/workflow/agent-plugins.md 'backlog/tasks/task-240 - Migrate-SubMiner-agent-skills-into-a-repo-local-plugin-workflow.md'` +- Verifier artifacts: `.tmp/skill-verification/subminer-verify-20260326-232300-E2NQVX/` + + + +## Final Summary + + + +Created a repo-local `subminer-workflow` plugin as the canonical packaging for the SubMiner scrum-master and change-verification workflow. The plugin now owns both skills, the verifier helper scripts, plugin metadata, and workflow docs. The old `.agents/skills/` surfaces remain only as compatibility shims, and the old verifier script paths now forward to the plugin-owned scripts so existing docs and backlog commands continue to work. Targeted plugin/docs verification passed, including wrapper-script syntax checks and a real verifier run through the legacy entrypoint. + + diff --git a/docs/workflow/README.md b/docs/workflow/README.md index df4e327..4143402 100644 --- a/docs/workflow/README.md +++ b/docs/workflow/README.md @@ -13,6 +13,7 @@ This section is the internal workflow map for contributors and agents. - [Planning](./planning.md) - when to write a lightweight plan vs a full execution plan - [Verification](./verification.md) - maintained test/build lanes and handoff gate +- [Agent Plugins](./agent-plugins.md) - repo-local plugin ownership for agent workflow skills - [Release Guide](../RELEASING.md) - tagged release workflow ## Default Flow diff --git a/docs/workflow/agent-plugins.md b/docs/workflow/agent-plugins.md new file mode 100644 index 0000000..44d6238 --- /dev/null +++ b/docs/workflow/agent-plugins.md @@ -0,0 +1,32 @@ + + +# Agent Plugins + +Status: active +Last verified: 2026-03-26 +Owner: Kyle Yasuda +Read when: packaging or migrating repo-local agent workflow skills into plugins + +## SubMiner Workflow Plugin + +- Canonical plugin path: `plugins/subminer-workflow/` +- Marketplace catalog: `.agents/plugins/marketplace.json` +- Canonical skill sources: + - `plugins/subminer-workflow/skills/subminer-scrum-master/` + - `plugins/subminer-workflow/skills/subminer-change-verification/` + +## Migration Rule + +- Plugin-owned skills are the source of truth. +- `.agents/skills/subminer-*` remain only as compatibility shims. +- Existing script entrypoints under `.agents/skills/subminer-change-verification/scripts/` stay as wrappers so historical commands do not break. + +## Backlog + +- Prefer Backlog.md MCP when the host session exposes it. +- If MCP is unavailable, use repo-local `backlog/` files and record that fallback. + +## Verification + +- For plugin/docs-only changes, start with `bun run test:docs:kb`. +- Use the plugin-owned verifier when the change crosses from docs into scripts or workflow logic. diff --git a/plugins/subminer-workflow/.codex-plugin/plugin.json b/plugins/subminer-workflow/.codex-plugin/plugin.json new file mode 100644 index 0000000..9b5db65 --- /dev/null +++ b/plugins/subminer-workflow/.codex-plugin/plugin.json @@ -0,0 +1,30 @@ +{ + "name": "subminer-workflow", + "version": "0.1.0", + "description": "Repo-local SubMiner agent workflow plugin for backlog-first orchestration and change verification.", + "author": { + "name": "Kyle Yasuda", + "email": "suda@sudacode.com", + "url": "https://github.com/sudacode" + }, + "homepage": "https://github.com/sudacode/SubMiner/tree/main/plugins/subminer-workflow", + "repository": "https://github.com/sudacode/SubMiner", + "license": "GPL-3.0-or-later", + "keywords": ["subminer", "workflow", "backlog", "verification", "skills"], + "skills": "./skills/", + "interface": { + "displayName": "SubMiner Workflow", + "shortDescription": "Backlog-first SubMiner orchestration and verification.", + "longDescription": "Canonical repo-local plugin for SubMiner agent workflow packaging. Owns the scrum-master and change-verification skills plus helper scripts used to plan, verify, and validate changes reproducibly inside this repo.", + "developerName": "Kyle Yasuda", + "category": "Productivity", + "capabilities": ["Interactive", "Write"], + "websiteURL": "https://github.com/sudacode/SubMiner", + "defaultPrompt": [ + "Use SubMiner workflow to plan and ship a feature.", + "Verify a SubMiner change with the plugin-owned verifier.", + "Run backlog-first intake for this SubMiner task." + ], + "brandColor": "#2F6B4F" + } +} diff --git a/plugins/subminer-workflow/README.md b/plugins/subminer-workflow/README.md new file mode 100644 index 0000000..5eea07f --- /dev/null +++ b/plugins/subminer-workflow/README.md @@ -0,0 +1,49 @@ + + +# SubMiner Workflow Plugin + +Status: active +Last verified: 2026-03-26 +Owner: Kyle Yasuda +Read when: using or updating the repo-local plugin that owns SubMiner agent workflow skills + +This plugin is the canonical source of truth for the SubMiner agent workflow packaging. + +## Contents + +- `skills/subminer-scrum-master/` + - backlog-first intake, planning, dispatch, and handoff workflow +- `skills/subminer-change-verification/` + - cheap-first verification workflow plus helper scripts + +## Backlog MCP + +- This plugin assumes Backlog.md MCP is available in the host environment when the client exposes it. +- Canonical backlog behavior remains: + - read `backlog://workflow/overview` when resources are available + - otherwise use the matching backlog tool overview +- If backlog MCP is unavailable in the current session, fall back to direct repo-local `backlog/` edits and record that blocker in the task or handoff. + +## Compatibility + +- `.agents/skills/subminer-scrum-master/` is a compatibility shim that redirects to the plugin-owned skill. +- `.agents/skills/subminer-change-verification/` is a compatibility shim. +- `.agents/skills/subminer-change-verification/scripts/*.sh` remain as wrapper entrypoints so existing docs, backlog tasks, and shell history keep working. + +## Verification + +For plugin/doc/shim changes, prefer: + +```bash +bun run test:docs:kb +bash plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane docs --lane core \ + plugins/subminer-workflow \ + .agents/skills/subminer-scrum-master/SKILL.md \ + .agents/skills/subminer-change-verification/SKILL.md \ + .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh \ + .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh \ + .agents/plugins/marketplace.json \ + docs/workflow/README.md \ + docs/workflow/agent-plugins.md \ + backlog/tasks/task-240\ -\ Migrate-SubMiner-agent-skills-into-a-repo-local-plugin-workflow.md +``` diff --git a/plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md b/plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md new file mode 100644 index 0000000..585a764 --- /dev/null +++ b/plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md @@ -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="" +``` + +## 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//`: + +- `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. diff --git a/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh b/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh new file mode 100755 index 0000000..a983ff3 --- /dev/null +++ b/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh @@ -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: + flag: + reason: +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 diff --git a/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh b/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh new file mode 100755 index 0000000..3284ece --- /dev/null +++ b/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh @@ -0,0 +1,502 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: verify_subminer_change.sh [options] [path ...] + +Options: + --lane Force a verification lane. Repeatable. + --artifact-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" diff --git a/plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md b/plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md new file mode 100644 index 0000000..7e76e42 --- /dev/null +++ b/plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md @@ -0,0 +1,162 @@ +--- +name: 'subminer-scrum-master' +description: 'Use in the SubMiner repo when a request should be turned into planned work and driven through execution. Assesses whether backlog tracking is warranted, creates or updates tasks when needed, records a plan, dispatches one or more subagents, and requires verification before handoff.' +--- + +# SubMiner Scrum Master + +Canonical source: this plugin path. + +Own workflow, not code by default. + +Use this skill when the user gives a feature request, bug report, issue, refactor, or implementation ask and the agent should manage intake, planning, backlog hygiene, worker dispatch, and verification through completion. + +## Core Rules + +1. Decide first whether backlog tracking is warranted. +2. If backlog is needed, search first. Update existing work when it clearly matches. +3. If backlog is not needed, keep the process light. Do not invent ticket ceremony. +4. Record a plan before dispatching coding work. +5. Use parent + subtasks for multi-part work when backlog is used. +6. Dispatch conservatively. Parallelize only disjoint write scopes. +7. Require verification before handoff, typically via `subminer-change-verification`. +8. Report backlog actions, dispatched workers, verification, blockers, and remaining risks. + +## Backlog Workflow + +Preferred order: + +1. Read `backlog://workflow/overview` when MCP resources are available. +2. If resources are unavailable, use the corresponding backlog tool overview. +3. If backlog MCP is unavailable in the session, work directly in repo-local `backlog/` files and record that constraint explicitly. + +## Backlog Decision + +Skip backlog when the request is: + +- question only +- obvious mechanical edit +- tiny isolated change with no real planning + +Use backlog when the work: + +- needs planning or scope decisions +- spans multiple phases or subsystems +- is likely to need subagent dispatch +- should remain traceable for handoff/resume + +If backlog is used: + +- search existing tasks first +- create/update a standalone task for one focused deliverable +- create/update a parent task plus subtasks for multi-part work +- record the implementation plan in the task before implementation begins + +## Intake Workflow + +1. Parse the request. + Classify it as question, mechanical edit, bugfix, feature, refactor, investigation, or follow-up. +2. Decide whether backlog is needed. +3. If backlog is needed: + - search first + - update existing task if clearly relevant + - otherwise create the right structure + - write the implementation plan before dispatch +4. If backlog is skipped: + - write a short working plan in-thread + - proceed without fake ticketing +5. Choose execution mode: + - no subagents for trivial work + - one worker for focused work + - parallel workers only for disjoint scopes +6. Run verification before handoff. + +## Dispatch Rules + +The scrum master orchestrates. Workers implement. + +- Do not become the default implementer unless delegation is unnecessary. +- Do not parallelize overlapping files or tightly coupled runtime work. +- Give every worker explicit ownership of files/modules. +- Tell every worker other agents may be active and they must not revert unrelated edits. +- Require each worker to report: + - changed files + - tests run + - blockers + +Use worker agents for implementation and explorer agents only for bounded codebase questions. + +## Verification + +Every nontrivial code task gets verification. + +Preferred flow: + +1. use `subminer-change-verification` +2. start with the cheapest sufficient lane +3. escalate only when needed +4. if worker verification is sufficient, accept it or run one final consolidating pass + +Never hand off nontrivial work without stating what was verified and what was skipped. + +## Pre-Handoff Policy Checks + +Before handoff, always ask and answer both questions explicitly: + +1. Docs update required? +2. Changelog fragment required? + +Rules: + +- Do not assume silence implies "no." +- If the answer is yes, complete the update or report the blocker. +- Include final yes/no answers in the handoff summary even when both answers are "no." + +## Failure / Scope Handling + +- If a worker hits ambiguity, pause and ask the user. +- If verification fails, either: + - send the worker back with exact failure context, or + - fix it directly if it is tiny and clearly in scope +- If new scope appears, revisit backlog structure before silently expanding work. + +## Representative Flows + +### Trivial no-ticket work + +- decide backlog is unnecessary +- keep a short plan +- implement directly or with one worker if helpful +- run targeted verification +- report outcome concisely + +### Single-task implementation + +- search/create/update one task +- record plan +- dispatch one worker +- integrate +- verify +- update task and report outcome + +### Parent + subtasks execution + +- search/create/update parent task +- create subtasks for distinct deliverables/phases +- record sequencing in the plan +- dispatch workers only where scopes are disjoint +- integrate +- run consolidated verification +- update task state and report outcome + +## Output Expectations + +At the end, report: + +- whether backlog was used and what changed +- which workers were dispatched and what they owned +- what verification ran +- explicit answers to: + - docs update required? + - changelog fragment required? +- blockers, skips, and risks