Back to skills
SKILL.md
Post Findings
ASecurityPost Rune review findings to a GitHub PR as a formatted comment. Parses TOME.md findings, formats as collapsible markdown, posts via gh api. Use after /rune:appraise or /rune:arc to share findings with team. Trigger keywords: post findings, PR comments, share review, post to PR, post review to GitHub, comment on PR with findings, share findings.
- 6 stars
- 0 votes
- 0 copies
- 4 views
- Added May 28, 2026
Works with
Security analysis
100/100npx -y skills add vinhnxv/rune --skill post-findings --agent claude-codeAre you the author of Post Findings?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/vinhnxv-post-findings)---
name: post-findings
description: |
Post Rune review findings to a GitHub PR as a formatted comment.
Parses TOME.md findings, formats as collapsible markdown, posts via gh api.
Use after /rune:appraise or /rune:arc to share findings with team.
Trigger keywords: post findings, PR comments, share review, post to PR,
post review to GitHub, comment on PR with findings, share findings.
user-invocable: true
disable-model-invocation: false
argument-hint: "[TOME path] [PR#] [--dry-run] [--force]"
allowed-tools:
- Read
- Write
- Bash
- Glob
- Grep
- AskUserQuestion
---
# /rune:post-findings -- Post Review Findings to GitHub PR
Post structured review findings from a TOME file to a GitHub PR as a formatted, collapsible markdown comment. Supports auto-detection of TOME path and PR number, baked-in v3.x severity filtering, dry-run preview, and idempotency.
## Usage
```
/rune:post-findings # Auto-detect latest TOME + current branch PR
/rune:post-findings tmp/reviews/abc123/TOME.md # Specific TOME, auto-detect PR
/rune:post-findings tmp/reviews/abc123/TOME.md 42 # Specific TOME + specific PR#
/rune:post-findings --dry-run # Preview comment without posting
/rune:post-findings --force # Post even if already posted (skip idempotency)
```
## Algorithm
### Phase 0: Parse Arguments
```javascript
const args = "$ARGUMENTS".split(/\s+/).filter(Boolean)
let tomePath = null
let prNumber = null
let dryRun = false
let forcePost = false
for (const arg of args) {
if (arg === "--dry-run") { dryRun = true; continue }
if (arg === "--force") { forcePost = true; continue }
if (arg.endsWith(".md") || arg.includes("TOME") || arg.includes("tome")) {
tomePath = arg
} else if (/^[1-9]\d*$/.test(arg)) {
prNumber = arg
}
}
```
### Phase 1: Resolve TOME Path
If no TOME path provided, auto-detect the latest TOME from known output directories.
```javascript
if (!tomePath) {
// Search order: arc output → review output → audit output
const searchDirs = ["tmp/arc/", "tmp/reviews/", "tmp/audit/"]
const tomeNames = ["TOME.md", "tome.md"]
for (const dir of searchDirs) {
// Glob for TOME files, sort by modification time (newest first)
const candidates = Glob(`${dir}**/TOME.md`) || Glob(`${dir}**/tome.md`) || []
if (candidates.length > 0) {
tomePath = candidates[0] // Most recently modified
break
}
}
if (!tomePath) {
error("No TOME file found. Run /rune:appraise or /rune:arc first, or specify a path.")
return
}
log(`Auto-detected TOME: ${tomePath}`)
}
// Validate TOME exists and is non-empty
if (!exists(tomePath)) {
error(`TOME file not found: ${tomePath}`)
return
}
```
### Phase 2: Resolve PR Number
If no PR number provided, detect from the current branch.
```javascript
if (!prNumber) {
// Use gh pr view to find PR for current branch
const prResult = Bash(`GH_PROMPT_DISABLED=1 gh pr view --json number -q '.number' 2>/dev/null`).trim()
if (prResult && /^[1-9]\d*$/.test(prResult)) {
prNumber = prResult
log(`Auto-detected PR: #${prNumber}`)
} else {
error("No PR found for current branch. Specify a PR number or push your branch first.")
return
}
}
// Validate PR number format (SEC: injection guard)
if (!/^[1-9]\d*$/.test(prNumber)) {
error(`Invalid PR number: ${prNumber}`)
return
}
```
### Phase 3: Filter Defaults (v3.x baked-in)
<!-- v3.x: defaults baked from former talisman.pr_comment; see references/v3-defaults.md -->
```javascript
// v3.x bakes pr_comment defaults — there is no user config layer.
const severityFilter = ["P1", "P2"]
const confidenceThreshold = 50
const maxFindings = 30
const includeTraces = true
const includeFixSuggestions = true
const collapseThreshold = 5
const showFooter = true
```
### Phase 4: Parse TOME
```javascript
// Use tome-parser.sh to extract structured findings as JSON
const configJson = JSON.stringify({
severity_filter: severityFilter,
confidence_threshold: confidenceThreshold,
max_findings: maxFindings,
include_traces: includeTraces,
include_fix_suggestions: includeFixSuggestions
})
// SEC-001: Validate tomePath against strict allowlist (no shell metacharacters)
if (!/^[a-zA-Z0-9._\-\/]+\.md$/.test(tomePath)) {
error(`Invalid TOME path (contains disallowed characters): ${tomePath}`)
return
}
// SEC-004: Pass configJson via env var to avoid single-quote injection
const findingsJson = Bash(`RUNE_PR_CONFIG='${configJson.replace(/'/g, "'\\''")}' bash "${RUNE_PLUGIN_ROOT}/scripts/lib/tome-parser.sh" "${tomePath}" "$RUNE_PR_CONFIG"`)
// Validate parser output
let findings
try {
findings = JSON.parse(findingsJson)
} catch (e) {
error(`TOME parser returned invalid JSON. Check ${tomePath} for valid RUNE:FINDING markers.`)
return
}
if (!Array.isArray(findings) || findings.length === 0) {
log("No findings matched the configured filters. Nothing to post.")
return
}
log(`Parsed ${findings.length} findings (filter: ${severityFilter.join("+")} >= confidence ${confidenceThreshold})`)
```
### Phase 5: Format Comment
```javascript
// Use pr-comment-formatter.sh to transform findings into GitHub markdown
const findingsFile = `tmp/.rune-pr-comment-findings-${Date.now()}.json`
Write(findingsFile, findingsJson)
const commentFile = `tmp/.rune-pr-comment-body-${Date.now()}.md`
// QUAL-003: Formatter reads findings from stdin, config from $1
const formatterConfig = JSON.stringify({ collapse_threshold: collapseThreshold, footer: showFooter })
Bash(`bash "${RUNE_PLUGIN_ROOT}/scripts/lib/pr-comment-formatter.sh" '${formatterConfig.replace(/'/g, "'\\''")}' < "${findingsFile}" > "${commentFile}"`)
// Validate output
if (!exists(commentFile) || Bash(`wc -c < "${commentFile}"`).trim() === "0") {
error("Comment formatter produced empty output.")
return
}
```
### Phase 6: Dry-Run Check
```javascript
if (dryRun) {
const preview = Read(commentFile)
log("=== DRY RUN — Comment Preview ===")
log(preview)
log("=== End Preview ===")
log(`Would post to PR #${prNumber}. Run without --dry-run to post.`)
// Cleanup temp files
Bash(`rm -f "${findingsFile}" "${commentFile}"`)
return
}
```
### Phase 7: Post to PR
```javascript
// Build poster arguments
const forceArg = forcePost ? "--force" : ""
const postResult = Bash(`bash "${RUNE_PLUGIN_ROOT}/scripts/lib/pr-comment-poster.sh" "${prNumber}" "${commentFile}" ${forceArg}`)
if (postResult.includes("ERROR")) {
error(`Failed to post: ${postResult}`)
// Cleanup temp files
Bash(`rm -f "${findingsFile}" "${commentFile}"`)
return
}
// Cleanup temp files
Bash(`rm -f "${findingsFile}" "${commentFile}"`)
```
### Phase 8: Report
```javascript
if (postResult.includes("already posted")) {
log(`Findings already posted to PR #${prNumber}. Use --force to post again.`)
} else {
// Get PR URL for user convenience
const prUrl = Bash(`GH_PROMPT_DISABLED=1 gh pr view ${prNumber} --json url -q '.url' 2>/dev/null`).trim()
log(`Review findings posted to PR #${prNumber}`)
if (prUrl) log(`View: ${prUrl}`)
log(`${findings.length} findings posted (${severityFilter.join("+")} severity, >= ${confidenceThreshold} confidence)`)
}
```
## Error Handling
| Error | Recovery |
|-------|----------|
| No TOME found | Suggest running `/rune:appraise` or `/rune:arc` first |
| No PR for branch | Suggest specifying PR# or pushing branch with `gh pr create` |
| `gh` not authenticated | Suggest `gh auth login` or `gh auth refresh -s repo` |
| TOME parse failure | Check TOME for valid `RUNE:FINDING` markers |
| Formatter empty output | Check TOME has findings matching configured filters |
| Post failure (API error) | Check `gh auth status` for `repo` scope |
| Comment already exists | Use `--force` flag to override idempotency check |
| Body exceeds 65K chars | Automatic truncation by poster script |
## Skip Conditions
The skill exits early (with a clear message) when:
1. **No TOME file found** — no argument provided and no TOME in `tmp/arc/`, `tmp/reviews/`, or `tmp/audit/`
2. **No PR exists** — no argument provided and `gh pr view` fails for current branch
3. **Invalid PR number** — argument doesn't match `^[1-9]\d*$`
4. **Empty findings** — TOME has 0 findings after applying severity/confidence filters
5. **Already posted** — idempotency marker found on PR (unless `--force`)
## Security
- **No `eval`** — all shell scripts use direct invocation, never `eval`
- **`GH_PROMPT_DISABLED=1`** — prevents interactive gh prompts (SEC-DECREE-003)
- **PR number validation** — must match `^[1-9]\d*$` (rejects injection attempts)
- **Path traversal guard** — poster rejects body files with `..` or outside `tmp/`
- **`--body-file`** — poster uses file-based body, never string interpolation
- **Idempotency** — `<!-- rune-review-findings -->` HTML marker prevents duplicate comments
- **Temp file cleanup** — findings JSON and comment body removed after posting
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.