mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-27 06:12:05 -07:00
feat: add repo-local subminer workflow plugin
This commit is contained in:
20
.agents/plugins/marketplace.json
Normal file
20
.agents/plugins/marketplace.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,127 +1,22 @@
|
|||||||
---
|
---
|
||||||
name: "subminer-change-verification"
|
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."
|
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`
|
Canonical helper scripts:
|
||||||
- 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:
|
- `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
|
When this shim is invoked:
|
||||||
export SUBMINER_VERIFY_SKILL="<path-to-skill>"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Default workflow
|
1. Read the canonical plugin-owned skill.
|
||||||
|
2. Follow the plugin-owned skill as the source of truth.
|
||||||
1. Inspect the changed files or user-requested area.
|
3. Use the wrapper scripts in this shim directory only for compatibility with existing commands, docs, and backlog history.
|
||||||
2. Run the classifier unless you already know the right lane.
|
4. Do not duplicate workflow changes here; update the plugin-owned skill and scripts instead.
|
||||||
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/<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.
|
|
||||||
|
|||||||
@@ -1,163 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
usage() {
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
cat <<'EOF'
|
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
|
||||||
Usage: classify_subminer_diff.sh [path ...]
|
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.
|
if [[ ! -x "$TARGET" ]]; then
|
||||||
|
echo "Missing canonical script: $TARGET" >&2
|
||||||
Output format:
|
exit 1
|
||||||
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
|
fi
|
||||||
|
|
||||||
declare -a PATHS=()
|
exec "$TARGET" "$@"
|
||||||
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
|
|
||||||
|
|||||||
@@ -1,566 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
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");
|
|
||||||
|
|
||||||
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)
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
|
||||||
SESSION_ID=$(generate_session_id)
|
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh"
|
||||||
PATH_SELECTION_MODE="git-inferred"
|
|
||||||
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
|
|
||||||
PATH_SELECTION_MODE="explicit"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$ARTIFACT_DIR" ]]; then
|
if [[ ! -x "$TARGET" ]]; then
|
||||||
mkdir -p "$REPO_ROOT/.tmp/skill-verification"
|
echo "Missing canonical script: $TARGET" >&2
|
||||||
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
|
exit 1
|
||||||
;;
|
fi
|
||||||
blocked)
|
|
||||||
printf 'result=blocked\n'
|
exec "$TARGET" "$@"
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
printf 'result=ok\n'
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|||||||
@@ -1,146 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: "subminer-scrum-master"
|
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."
|
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.
|
1. Read the canonical plugin-owned skill.
|
||||||
2. If backlog is needed, search first. Update existing work when it clearly matches.
|
2. Follow the plugin-owned skill as the source of truth.
|
||||||
3. If backlog is not needed, keep the process light. Do not invent ticket ceremony.
|
3. Do not duplicate workflow changes here; update the plugin-owned skill instead.
|
||||||
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 Decision
|
This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow.
|
||||||
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
|
||||||
|
- [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.
|
||||||
|
<!-- SECTION:END -->
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
<!-- SECTION:PLAN:BEGIN -->
|
||||||
|
|
||||||
|
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/<name>/` 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.
|
||||||
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
|
||||||
|
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/`
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
@@ -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
|
- [Planning](./planning.md) - when to write a lightweight plan vs a full execution plan
|
||||||
- [Verification](./verification.md) - maintained test/build lanes and handoff gate
|
- [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
|
- [Release Guide](../RELEASING.md) - tagged release workflow
|
||||||
|
|
||||||
## Default Flow
|
## Default Flow
|
||||||
|
|||||||
32
docs/workflow/agent-plugins.md
Normal file
32
docs/workflow/agent-plugins.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!-- read_when: using or modifying repo-local agent plugins -->
|
||||||
|
|
||||||
|
# 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.
|
||||||
30
plugins/subminer-workflow/.codex-plugin/plugin.json
Normal file
30
plugins/subminer-workflow/.codex-plugin/plugin.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
plugins/subminer-workflow/README.md
Normal file
49
plugins/subminer-workflow/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!-- read_when: migrating or using the repo-local SubMiner workflow plugin -->
|
||||||
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
162
plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md
Normal file
162
plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user