status — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited status (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Display a live status dashboard for active Rune agent teams (review, audit, work, plan, mend, debug, forge, dispatch) plus detailed reporting for any background-dispatched workers.
/rune:status # Show all active teams + dispatch summaries in this session
/rune:status <team-name> # Filter the team dashboards to a specific team
/rune:status <timestamp> # Show all active team dashboards + the detailed dispatch report for this timestampteam-name: Optional. Filter the team-dashboards section to a specific team (e.g., rune-review-1234). Matches state.team_name.timestamp: Optional. Dispatch timestamp from /rune:strive --background (e.g., 20260226-014500). Triggers the detailed dispatch report with pending-question detection. The active-team dashboards still render — the dispatch report is appended.const CHOME = Bash(`cd "\${CLAUDE_CONFIG_DIR:-$HOME/.claude}" 2>/dev/null && pwd -P`).trim()
const ownerPid = Bash(`echo $PPID`).trim()
const arg = $ARGUMENTS[0] || null
// Classify argument: timestamp vs team-name (SEC-004: validate before any path construction)
// Note: Step 4 re-validates state.team_name against the same regex. The duplication is
// intentional defense-in-depth — Step 1 guards the user-supplied argument; Step 4 guards
// state-file content (which may have been written by another session). Do not collapse them.
let dispatchTimestamp = null
let filterTeam = null
if (arg) {
if (/^\d{8}-\d{6}$/.test(arg)) {
dispatchTimestamp = arg // YYYYMMDD-HHMMSS shape → dispatch mode
} else if (/^[a-zA-Z0-9_-]+$/.test(arg)) {
filterTeam = arg // SEC-004: validated name → team filter
} else {
error(`Invalid argument "${arg}". Expected timestamp (YYYYMMDD-HHMMSS) or team name ([a-zA-Z0-9_-]+).`)
return
}
}Scan for active workflow state files. Both legacy inline teams and SDK-managed teams are supported.
const legacyStateFiles = Glob("tmp/.rune-*.json") || []
const sdkHandleFiles = Glob("tmp/.rune-handle-*.json") || []
const allStateFiles = [...legacyStateFiles, ...sdkHandleFiles]
if (allStateFiles.length === 0 && !dispatchTimestamp) {
log("No active Rune teams found in this session.")
log("Start a workflow with /rune:appraise, /rune:strive, /rune:devise, etc.")
return
}const activeTeams = []
for (const stateFile of allStateFiles) {
let state
try {
state = JSON.parse(Read(stateFile))
} catch {
continue // Skip corrupt state files
}
// Session ownership check: config_dir must match
if (state.config_dir && state.config_dir !== CHOME) {
continue
}
// PID liveness check: owner_pid must be alive
if (state.owner_pid) {
const pidAlive = Bash(`kill -0 ${state.owner_pid} 2>/dev/null && echo alive || echo dead`).trim()
if (pidAlive === "dead") {
state._orphan = true
}
}
// Filter by team name if specified
if (filterTeam && state.team_name !== filterTeam) {
continue
}
// Skip completed/cancelled/failed workflows
if (["completed", "cancelled", "failed"].includes(state.status)) {
continue
}
activeTeams.push({ stateFile, state })
}
if (activeTeams.length === 0 && !dispatchTimestamp) {
if (filterTeam) {
log(`No active team found matching "${filterTeam}".`)
} else {
log("No active Rune teams in current session.")
}
return
}For each active team, read team config and tasks, then render the dashboard.
for (const { stateFile, state } of activeTeams) {
const teamName = state.team_name
// SEC-4: Validate team name before path construction
if (!/^[a-zA-Z0-9_-]+$/.test(teamName)) {
warn(`Skipping team with invalid name: ${teamName}`)
continue
}
// Read team config (may not exist if team was cleaned up)
let teamConfig = null
try {
teamConfig = JSON.parse(Read(`${CHOME}/teams/${teamName}/config.json`))
} catch {
// Team config missing — team may have been deleted
}
// Read task list
let tasks = []
try { tasks = TaskList() } catch {}
// Read signal directory for fast progress check
const signalDir = `tmp/.rune-signals/${teamName}`
const signalFiles = Glob(`${signalDir}/*.done`) || []
// Detect workflow type from state file name
const workflowMatch = stateFile.match(/tmp\/\.rune-(review|work|plan|mend|debug|audit|forge|dispatch|handle)-/)
const workflowType = workflowMatch ? workflowMatch[1] : "unknown"
// Compute duration
const startedAt = state.started || state.started_at
const durationMs = startedAt ? Date.now() - new Date(startedAt).getTime() : 0
const durationMin = Math.round(durationMs / 60_000)
// Compute task progress
const completedTasks = tasks.filter(t => t.status === "completed")
const inProgressTasks = tasks.filter(t => t.status === "in_progress")
const pendingTasks = tasks.filter(t => t.status === "pending")
const totalTasks = tasks.length || state.expected_task_count || state.expected_workers || 0
const completedCount = completedTasks.length
const progressPercent = totalTasks > 0 ? Math.round((completedCount / totalTasks) * 100) : 0
// Health indicators
const staleThresholdMs = 5 * 60 * 1000 // 5 min default staleWarnMs
const staleTasks = inProgressTasks.filter(t => durationMs > staleThresholdMs && inProgressTasks.length > 0)
// Members from team config
const members = teamConfig?.members
? teamConfig.members.map(m => m.name).filter(n => n && /^[a-zA-Z0-9_-]+$/.test(n))
: []
renderTeamDashboard({
teamName, workflowType, state, startedAt, durationMin,
members, completedCount, totalTasks, progressPercent,
inProgressTasks, pendingTasks, signalFiles, staleTasks
})
}function renderTeamDashboard(info) {
const {
teamName, workflowType, state, startedAt, durationMin,
members, completedCount, totalTasks, progressPercent,
inProgressTasks, pendingTasks, signalFiles, staleTasks
} = info
// Build progress bar (20 chars wide)
const filled = Math.round(progressPercent / 5)
const empty = 20 - filled
const bar = "█".repeat(filled) + "░".repeat(empty)
// Health status
let healthLabel = "healthy"
if (state._orphan) healthLabel = "ORPHAN (owner PID dead)"
else if (staleTasks.length > 0) healthLabel = `WARNING (${staleTasks.length} stale)`
else if (durationMin > 30 && progressPercent < 50) healthLabel = "SLOW"
log(`
/rune:status — Team: ${teamName}
═══════════════════════════════════════════════
Workflow: ${workflowType}
Started: ${startedAt || "unknown"}
Duration: ${durationMin} min
Health: ${healthLabel}
Progress: ${completedCount}/${totalTasks} tasks
[${bar}] ${progressPercent}%
Members: ${members.length > 0 ? members.join(", ") : "(unavailable)"}
───────────────────────────────────────────────
In progress (${inProgressTasks.length}):
${inProgressTasks.map(t => ` - #${t.id} ${t.subject} (${t.owner || "unassigned"})`).join("\n") || " (none)"}
Pending (${pendingTasks.length}):
${pendingTasks.map(t => ` - #${t.id} ${t.subject}`).join("\n") || " (none)"}
Signals: ${signalFiles.length} done files
═══════════════════════════════════════════════
`)
}If any teams have dead owner PIDs, advise on cleanup:
const orphanTeams = activeTeams.filter(t => t.state._orphan)
if (orphanTeams.length > 0) {
warn(`Found ${orphanTeams.length} orphaned team(s) with dead owner PIDs.`)
warn("These teams belong to a crashed or exited session.")
warn("Run /rune:rest to clean up, or manually remove state files from tmp/.")
}Runs only when dispatchTimestamp is set (user passed a YYYYMMDD-HHMMSS arg). Resolves the dispatch state file, scans the signal directory for live progress, detects pending questions written by background workers, and reports stale workers.
if (!dispatchTimestamp) return
// SEC-004: Validate already performed in Step 1
const stateFile = `tmp/.rune-dispatch-${dispatchTimestamp}.json`
const stateRaw = Read(stateFile)
if (!stateRaw) {
log(`Dispatch state file not found: ${stateFile}`)
log("The dispatch may have completed. Run /rune:strive --collect to gather results.")
return
}
let state
try { state = JSON.parse(stateRaw) }
catch (e) { error(`Dispatch state file is corrupt: ${stateFile}`); return }
// SEC-009: Validate state.timestamp before any path construction
if (state.timestamp && !/^\d{8}-\d{6}$/.test(state.timestamp)) {
error(`Invalid timestamp format in state file: ${state.timestamp}`)
return
}
if (state.config_dir && state.config_dir !== CHOME) {
warn(`Dispatch belongs to a different config dir (${state.config_dir}). Showing read-only status.`)
}
// Stale dispatch detection (>2h)
const dispatchAge = Date.now() - new Date(state.started_at).getTime()
const TWO_HOURS_MS = 2 * 60 * 60 * 1000
if (dispatchAge > TWO_HOURS_MS) {
warn(`Dispatch started ${Math.round(dispatchAge / 3600000 * 10) / 10}h ago — may be stale.`)
warn(`Consider running /rune:strive --collect ${state.timestamp} to gather partial results.`)
}
// Signal directory for live progress (PERF-002)
const signalDir = `tmp/.rune-signals/${state.team_name}/`
const signalFiles = Glob(`${signalDir}*.done`) || []
const completedFromSignals = signalFiles.length
// Pending questions (workers cannot use AskUserQuestion in background mode)
const questionFiles = Glob(`tmp/work/${state.timestamp}/questions/*.question`) || []
const unansweredQuestions = questionFiles.filter(f => {
const answerFile = f.replace('.question', '.answer')
return !fileExists(answerFile)
})
// Stale worker detection (PERF-003)
const workerLogs = Glob(`tmp/work/${state.timestamp}/worker-logs/*.md`) || []
const staleWorkers = []
const WORKER_STALE_MS = 15 * 60 * 1000
for (const logFile of workerLogs) {
const mtime = parseInt(Bash(`stat -f "%m" "${logFile}" 2>/dev/null || stat -c "%Y" "${logFile}" 2>/dev/null`).trim(), 10) * 1000
if (Date.now() - mtime > WORKER_STALE_MS) {
const rawName = logFile.split('/').pop().replace('.md', '')
const safeName = rawName.replace(/[^a-zA-Z0-9_-]/g, '_') // SEC-003
staleWorkers.push(safeName)
}
}
if (staleWorkers.length > 0) {
warn(`Stale workers detected (no activity >15min): ${staleWorkers.join(', ')}`)
}
log(`
/rune:status — Dispatch: ${state.timestamp}
═══════════════════════════════════════════════
Team: ${state.team_name}
Started: ${state.started_at}
Plan: ${state.plan_path}
Progress: ${completedFromSignals}/${state.expected_task_count} tasks complete
[████████░░░░░░░░] ${Math.round(completedFromSignals/state.expected_task_count*100)}%
Workers: ${state.worker_count} spawned, ${staleWorkers.length} stale
───────────────────────────────────────────────
Pending questions: ${unansweredQuestions.length}
${unansweredQuestions.map(f => ` ? ${Read(f).trim()}`).join('\n') || ' (none)'}
───────────────────────────────────────────────
${dispatchAge > TWO_HOURS_MS ? '⚠ WARNING: Dispatch is >2h old — may be stale' : 'Status: active'}
═══════════════════════════════════════════════
`)When pending questions are detected in dispatch mode, answer them by writing .answer files:
# Write answer to a pending question
echo "Use the existing auth module" > tmp/work/{timestamp}/questions/{task-id}.answerOr use /rune:strive --collect {timestamp} once the dispatch is complete to gather results.
The command discovers teams via these state-file patterns:
| Pattern | Source | Description |
|---|---|---|
tmp/.rune-review-{id}.json | appraise | Code review team |
tmp/.rune-audit-{id}.json | audit | Full codebase audit team |
tmp/.rune-work-{id}.json | strive | Work execution team |
tmp/.rune-plan-{id}.json | devise | Planning/research team |
tmp/.rune-mend-{id}.json | mend | Finding resolution team |
tmp/.rune-debug-{id}.json | debug | Hypothesis investigation team |
tmp/.rune-forge-{id}.json | forge | Enrichment team |
tmp/.rune-dispatch-{id}.json | strive --background | Background dispatch |
tmp/.rune-handle-{id}.json | team-sdk (future) | SDK-managed team handle |
/^[a-zA-Z0-9_-]+$/ before path construction[^a-zA-Z0-9_-]/g, '_')\d{8}-\d{6}) before path constructionmkdir -m 700 — owner-only accessstate.timestamp from state-file content before path constructionconfig_dir and owner_pid liveness${CLAUDE_CONFIG_DIR:-$HOME/.claude} for config resolution (see plugin CLAUDE.md Core Rule #17)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.