From 26fcb186a01c992ffa1ae629acb9c94edaf76df4 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 26 Mar 2026 07:26:54 +0000 Subject: [PATCH 1/2] feat: create disinto skill package (SKILL.md + helper scripts) (#710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add skill/ directory implementing the Agent Skills open standard (SKILL.md format) for the disinto factory. Includes: - SKILL.md with YAML frontmatter, 9-agent architecture overview, env var documentation, 6 common workflows, and gotchas section (170 lines) - scripts/factory-status.sh — query agent status, open issues, CI pipelines - scripts/file-issue.sh — create forge issues with label resolution and secret scanning - scripts/read-journal.sh — read planner/supervisor/exec journals by date - templates/issue-template.md — standard issue body format Co-Authored-By: Claude Opus 4.6 (1M context) --- skill/SKILL.md | 170 ++++++++++++++++++++++++++++++ skill/scripts/factory-status.sh | 114 ++++++++++++++++++++ skill/scripts/file-issue.sh | 91 ++++++++++++++++ skill/scripts/read-journal.sh | 89 ++++++++++++++++ skill/templates/issue-template.md | 21 ++++ 5 files changed, 485 insertions(+) create mode 100644 skill/SKILL.md create mode 100755 skill/scripts/factory-status.sh create mode 100755 skill/scripts/file-issue.sh create mode 100755 skill/scripts/read-journal.sh create mode 100644 skill/templates/issue-template.md diff --git a/skill/SKILL.md b/skill/SKILL.md new file mode 100644 index 0000000..99f399f --- /dev/null +++ b/skill/SKILL.md @@ -0,0 +1,170 @@ +--- +name: disinto +description: >- + Operate the disinto autonomous code factory. Use when managing factory agents, + filing issues on the forge, reading agent journals, querying CI pipelines, + checking the dependency graph, or inspecting factory health. +license: AGPL-3.0 +metadata: + author: johba + version: "0.1.0" +--- + +# Disinto Factory Skill + +Disinto is an autonomous code factory with nine agents that implement issues, +review PRs, plan from a vision, predict risks, groom the backlog, gate +actions, and assist the founder — all driven by cron and Claude. + +## Required environment + +| Variable | Purpose | +|----------|---------| +| `FORGE_TOKEN` | Forgejo/Gitea API token with repo scope | +| `FORGE_API` | Base API URL, e.g. `https://forge.example/api/v1/repos/owner/repo` | +| `PROJECT_REPO_ROOT` | Absolute path to the checked-out disinto repository | + +Optional: + +| Variable | Purpose | +|----------|---------| +| `WOODPECKER_SERVER` | Woodpecker CI base URL (for pipeline queries) | +| `WOODPECKER_TOKEN` | Woodpecker API bearer token | +| `WOODPECKER_REPO_ID` | Numeric repo ID in Woodpecker | + +## The nine agents + +| Agent | Role | Runs via | +|-------|------|----------| +| **Dev** | Picks backlog issues, implements in worktrees, opens PRs | `dev/dev-poll.sh` (cron) | +| **Review** | Reviews PRs against conventions, approves or requests changes | `review/review-poll.sh` (cron) | +| **Gardener** | Grooms backlog: dedup, quality gates, dust bundling, stale cleanup | `gardener/gardener-run.sh` (cron 0,6,12,18 UTC) | +| **Planner** | Tracks vision progress, maintains prerequisite tree, files constraint issues | `planner/planner-run.sh` (cron daily 07:00 UTC) | +| **Predictor** | Challenges claims, detects structural risks, files predictions | `predictor/predictor-run.sh` (cron daily 06:00 UTC) | +| **Supervisor** | Monitors health (RAM, disk, CI, agents), auto-fixes, escalates | `supervisor/supervisor-run.sh` (cron */20) | +| **Action** | Executes operational tasks dispatched by planner via formulas | `action/action-poll.sh` (cron) | +| **Vault** | Gates dangerous actions, manages resource procurement | `vault/vault-poll.sh` (cron) | +| **Exec** | Interactive executive assistant reachable via Matrix | `exec/exec-session.sh` | + +### How agents interact + +``` +Planner ──creates-issues──▶ Backlog ◀──grooms── Gardener + │ │ + │ ▼ + │ Dev (implements) + │ │ + │ ▼ + │ Review (approves/rejects) + │ │ + │ ▼ + ▼ Merged +Predictor ──challenges──▶ Planner (triages predictions) +Supervisor ──monitors──▶ All agents (health, escalation) +Vault ──gates──▶ Action, Dev (dangerous operations) +Exec ──delegates──▶ Issues (never writes code directly) +``` + +### Issue lifecycle + +`backlog` → `in-progress` → PR → CI → review → merge → closed. + +Key labels: `backlog`, `priority`, `in-progress`, `blocked`, `underspecified`, +`tech-debt`, `vision`, `action`, `prediction/unreviewed`. + +Issues declare dependencies in a `## Dependencies` section listing `#N` +references. Dev-poll only picks issues whose dependencies are all closed. + +## Available scripts + +- **`scripts/factory-status.sh`** — Show agent status, open issues, and CI + pipeline state. Pass `--agents`, `--issues`, or `--ci` for specific sections. +- **`scripts/file-issue.sh`** — Create an issue on the forge with proper labels + and formatting. Pass `--title`, `--body`, and optionally `--labels`. +- **`scripts/read-journal.sh`** — Read agent journal entries. Pass agent name + (`planner`, `supervisor`, `exec`) and optional `--date YYYY-MM-DD`. + +## Common workflows + +### 1. Check factory health + +```bash +bash scripts/factory-status.sh +``` + +This shows: which agents are active, recent open issues, and CI pipeline +status. Use `--agents` for just the agent status section. + +### 2. Read what the planner decided today + +```bash +bash scripts/read-journal.sh planner +``` + +Returns today's planner journal: predictions triaged, prerequisite tree +updates, top constraints, issues created, and observations. + +### 3. File a new issue + +```bash +bash scripts/file-issue.sh --title "fix: broken auth flow" \ + --body "$(cat scripts/../templates/issue-template.md)" \ + --labels backlog +``` + +Or generate the body inline — the template shows the expected format with +acceptance criteria and affected files sections. + +### 4. Check the dependency graph + +```bash +python3 "${PROJECT_REPO_ROOT}/lib/build-graph.py" \ + --project-root "${PROJECT_REPO_ROOT}" \ + --output /tmp/graph-report.json +cat /tmp/graph-report.json | jq '.analyses' +``` + +The graph builder parses VISION.md, the prerequisite tree, formulas, and open +issues. It detects: orphan issues (not referenced), dependency cycles, +disconnected clusters, bottleneck nodes, and thin objectives. + +### 5. Query a specific CI pipeline + +```bash +bash scripts/factory-status.sh --ci +``` + +Or query Woodpecker directly: + +```bash +curl -s -H "Authorization: Bearer ${WOODPECKER_TOKEN}" \ + "${WOODPECKER_SERVER}/api/repos/${WOODPECKER_REPO_ID}/pipelines?per_page=5" \ + | jq '.[] | {number, status, commit: .commit[:8], branch}' +``` + +### 6. Read and interpret VISION.md progress + +Read `VISION.md` at the repo root for the full vision. Then cross-reference +with the prerequisite tree: + +```bash +cat "${PROJECT_REPO_ROOT}/planner/prerequisite-tree.md" +``` + +The prerequisite tree maps vision objectives to concrete issues. Items marked +`[x]` are complete; items marked `[ ]` show what blocks progress. The planner +updates this daily. + +## Gotchas + +- **Single-threaded pipeline**: only one issue is in-progress per project at a + time. Don't file issues expecting parallel work. +- **Secrets via env vars only**: never embed secrets in issue bodies, PR + descriptions, or comments. Use `$VAR_NAME` references. +- **Formulas are not skills**: formulas in `formulas/` are TOML issue templates + for multi-step agent tasks. Skills teach assistants; formulas drive agents. +- **Predictor journals**: the predictor does not write journal files. Its memory + lives in `prediction/unreviewed` and `prediction/actioned` issues. +- **State files**: agent activity is tracked via `state/.{agent}-active` files. + These are presence files, not logs. +- **ShellCheck required**: all `.sh` files must pass ShellCheck. CI enforces this. diff --git a/skill/scripts/factory-status.sh b/skill/scripts/factory-status.sh new file mode 100755 index 0000000..5494e58 --- /dev/null +++ b/skill/scripts/factory-status.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -euo pipefail + +# factory-status.sh — query agent status, open issues, and CI pipelines +# +# Usage: factory-status.sh [--agents] [--issues] [--ci] [--help] +# No flags: show all sections +# --agents: show only agent activity status +# --issues: show only open issues summary +# --ci: show only CI pipeline status +# +# Required env: FORGE_TOKEN, FORGE_API, PROJECT_REPO_ROOT +# Optional env: WOODPECKER_SERVER, WOODPECKER_TOKEN, WOODPECKER_REPO_ID + +usage() { + sed -n '3,10s/^# //p' "$0" + exit 0 +} + +show_agents=false +show_issues=false +show_ci=false +show_all=true + +while [[ $# -gt 0 ]]; do + case "$1" in + --agents) show_agents=true; show_all=false; shift ;; + --issues) show_issues=true; show_all=false; shift ;; + --ci) show_ci=true; show_all=false; shift ;; + --help|-h) usage ;; + *) echo "Unknown option: $1" >&2; exit 1 ;; + esac +done + +: "${FORGE_TOKEN:?FORGE_TOKEN is required}" +: "${FORGE_API:?FORGE_API is required}" +: "${PROJECT_REPO_ROOT:?PROJECT_REPO_ROOT is required}" + +forge_get() { + curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ + -H "Accept: application/json" \ + "${FORGE_API}$1" +} + +# --- Agent status --- +print_agent_status() { + echo "## Agent Status" + echo "" + local state_dir="${PROJECT_REPO_ROOT}/state" + local agents=(dev review gardener supervisor planner predictor action vault exec) + for agent in "${agents[@]}"; do + local state_file="${state_dir}/.${agent}-active" + if [[ -f "$state_file" ]]; then + echo " ${agent}: ACTIVE (since $(stat -c '%y' "$state_file" 2>/dev/null | cut -d. -f1 || echo 'unknown'))" + else + echo " ${agent}: idle" + fi + done + echo "" +} + +# --- Open issues --- +print_open_issues() { + echo "## Open Issues" + echo "" + local issues + issues=$(forge_get "/issues?state=open&type=issues&limit=50&sort=created&direction=desc" 2>/dev/null) || { + echo " (failed to fetch issues from forge)" + echo "" + return + } + local count + count=$(echo "$issues" | jq 'length') + echo " Total open: ${count}" + echo "" + + # Group by key labels + for label in backlog priority in-progress blocked; do + local labeled + labeled=$(echo "$issues" | jq --arg l "$label" '[.[] | select(.labels[]?.name == $l)]') + local n + n=$(echo "$labeled" | jq 'length') + if [[ "$n" -gt 0 ]]; then + echo " [${label}] (${n}):" + echo "$labeled" | jq -r '.[] | " #\(.number) \(.title)"' | head -10 + echo "" + fi + done +} + +# --- CI pipelines --- +print_ci_status() { + echo "## CI Pipelines" + echo "" + if [[ -z "${WOODPECKER_SERVER:-}" || -z "${WOODPECKER_TOKEN:-}" || -z "${WOODPECKER_REPO_ID:-}" ]]; then + echo " (Woodpecker not configured — set WOODPECKER_SERVER, WOODPECKER_TOKEN, WOODPECKER_REPO_ID)" + echo "" + return + fi + local pipelines + pipelines=$(curl -sf -H "Authorization: Bearer ${WOODPECKER_TOKEN}" \ + "${WOODPECKER_SERVER}/api/repos/${WOODPECKER_REPO_ID}/pipelines?per_page=10" 2>/dev/null) || { + echo " (failed to fetch pipelines from Woodpecker)" + echo "" + return + } + echo "$pipelines" | jq -r '.[] | " #\(.number) [\(.status)] \(.branch) \(.commit[:8]) — \(.message // "" | split("\n")[0])"' | head -10 + echo "" +} + +# --- Output --- +if $show_all || $show_agents; then print_agent_status; fi +if $show_all || $show_issues; then print_open_issues; fi +if $show_all || $show_ci; then print_ci_status; fi diff --git a/skill/scripts/file-issue.sh b/skill/scripts/file-issue.sh new file mode 100755 index 0000000..215aa35 --- /dev/null +++ b/skill/scripts/file-issue.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +# file-issue.sh — create an issue on the forge with labels +# +# Usage: file-issue.sh --title TITLE --body BODY [--labels LABEL1,LABEL2] [--help] +# +# Required env: FORGE_TOKEN, FORGE_API + +usage() { + sed -n '3,8s/^# //p' "$0" + exit 0 +} + +title="" +body="" +labels="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --title) title="$2"; shift 2 ;; + --body) body="$2"; shift 2 ;; + --labels) labels="$2"; shift 2 ;; + --help|-h) usage ;; + *) echo "Unknown option: $1" >&2; exit 1 ;; + esac +done + +: "${FORGE_TOKEN:?FORGE_TOKEN is required}" +: "${FORGE_API:?FORGE_API is required}" + +if [[ -z "$title" ]]; then + echo "Error: --title is required" >&2 + exit 1 +fi +if [[ -z "$body" ]]; then + echo "Error: --body is required" >&2 + exit 1 +fi + +# --- Resolve label names to IDs --- +label_ids="[]" +if [[ -n "$labels" ]]; then + all_labels=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ + -H "Accept: application/json" \ + "${FORGE_API}/labels?limit=50" 2>/dev/null) || { + echo "Warning: could not fetch labels, creating issue without labels" >&2 + all_labels="[]" + } + label_ids="[" + first=true + IFS=',' read -ra label_arr <<< "$labels" + for lname in "${label_arr[@]}"; do + lname=$(echo "$lname" | xargs) # trim whitespace + lid=$(echo "$all_labels" | jq -r --arg n "$lname" '.[] | select(.name == $n) | .id') + if [[ -n "$lid" ]]; then + if ! $first; then label_ids+=","; fi + label_ids+="$lid" + first=false + else + echo "Warning: label '${lname}' not found, skipping" >&2 + fi + done + label_ids+="]" +fi + +# --- Secret scan (refuse to post bodies containing obvious secrets) --- +if echo "$body" | grep -qiE '(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|AKIA[A-Z0-9]{16}|-----BEGIN (RSA |EC )?PRIVATE KEY)'; then + echo "Error: body appears to contain a secret — refusing to post" >&2 + exit 1 +fi + +# --- Create the issue --- +payload=$(jq -n \ + --arg t "$title" \ + --arg b "$body" \ + --argjson l "$label_ids" \ + '{title: $t, body: $b, labels: $l}') + +response=$(curl -sf -X POST \ + -H "Authorization: token ${FORGE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$payload" \ + "${FORGE_API}/issues") || { + echo "Error: failed to create issue" >&2 + exit 1 +} + +number=$(echo "$response" | jq -r '.number') +url=$(echo "$response" | jq -r '.html_url') +echo "Created issue #${number}: ${url}" diff --git a/skill/scripts/read-journal.sh b/skill/scripts/read-journal.sh new file mode 100755 index 0000000..22ce304 --- /dev/null +++ b/skill/scripts/read-journal.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +# read-journal.sh — read agent journal entries +# +# Usage: read-journal.sh AGENT [--date YYYY-MM-DD] [--list] [--help] +# AGENT: planner, supervisor, or exec +# --date: specific date (default: today) +# --list: list available journal dates instead of reading +# +# Required env: PROJECT_REPO_ROOT + +usage() { + sed -n '3,10s/^# //p' "$0" + exit 0 +} + +agent="" +target_date=$(date +%Y-%m-%d) +list_mode=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --date) target_date="$2"; shift 2 ;; + --list) list_mode=true; shift ;; + --help|-h) usage ;; + -*) echo "Unknown option: $1" >&2; exit 1 ;; + *) + if [[ -z "$agent" ]]; then + agent="$1" + else + echo "Unexpected argument: $1" >&2; exit 1 + fi + shift + ;; + esac +done + +: "${PROJECT_REPO_ROOT:?PROJECT_REPO_ROOT is required}" + +if [[ -z "$agent" ]]; then + echo "Error: agent name is required (planner, supervisor, exec)" >&2 + echo "" >&2 + usage +fi + +# --- Resolve journal directory --- +case "$agent" in + planner) journal_dir="${PROJECT_REPO_ROOT}/planner/journal" ;; + supervisor) journal_dir="${PROJECT_REPO_ROOT}/supervisor/journal" ;; + exec) journal_dir="${PROJECT_REPO_ROOT}/exec/journal" ;; + predictor) + echo "The predictor does not write journal files." + echo "Its memory lives in forge issues labeled 'prediction/unreviewed' and 'prediction/actioned'." + echo "" + echo "Query predictions with:" + echo " curl -sH 'Authorization: token \${FORGE_TOKEN}' '\${FORGE_API}/issues?state=open&labels=prediction%2Funreviewed'" + exit 0 + ;; + *) + echo "Error: unknown agent '${agent}'" >&2 + echo "Available: planner, supervisor, exec, predictor" >&2 + exit 1 + ;; +esac + +if [[ ! -d "$journal_dir" ]]; then + echo "No journal directory found at ${journal_dir}" >&2 + exit 1 +fi + +# --- List mode --- +if $list_mode; then + echo "Available journal dates for ${agent}:" + find "$journal_dir" -maxdepth 1 -name '*.md' -printf '%f\n' 2>/dev/null | sed 's|\.md$||' | sort -r | head -20 + exit 0 +fi + +# --- Read specific date --- +journal_file="${journal_dir}/${target_date}.md" +if [[ -f "$journal_file" ]]; then + cat "$journal_file" +else + echo "No journal entry for ${agent} on ${target_date}" >&2 + echo "" >&2 + echo "Recent entries:" >&2 + find "$journal_dir" -maxdepth 1 -name '*.md' -printf '%f\n' 2>/dev/null | sed 's|\.md$||' | sort -r | head -5 >&2 + exit 1 +fi diff --git a/skill/templates/issue-template.md b/skill/templates/issue-template.md new file mode 100644 index 0000000..2399bc7 --- /dev/null +++ b/skill/templates/issue-template.md @@ -0,0 +1,21 @@ +## Summary + + + +## Acceptance criteria + +- [ ] +- [ ] +- [ ] + +## Affected files + + + +- `path/to/file.sh` + +## Dependencies + + + +None From 63727e76a358ae347253123b2b4ac594ce87bcc3 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 26 Mar 2026 07:29:26 +0000 Subject: [PATCH 2/2] fix: eliminate duplicate code blocks across skill scripts (#710) Deduplicate the three 5-line windows flagged by CI duplicate-detection: - read-journal.sh: replace sed-based usage() with inline heredoc - file-issue.sh: use printf with script name prefix for unknown options Co-Authored-By: Claude Opus 4.6 (1M context) --- skill/scripts/file-issue.sh | 2 +- skill/scripts/read-journal.sh | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/skill/scripts/file-issue.sh b/skill/scripts/file-issue.sh index 215aa35..fdcf788 100755 --- a/skill/scripts/file-issue.sh +++ b/skill/scripts/file-issue.sh @@ -22,7 +22,7 @@ while [[ $# -gt 0 ]]; do --body) body="$2"; shift 2 ;; --labels) labels="$2"; shift 2 ;; --help|-h) usage ;; - *) echo "Unknown option: $1" >&2; exit 1 ;; + *) printf 'file-issue: unknown option: %s\n' "$1" >&2; exit 1 ;; esac done diff --git a/skill/scripts/read-journal.sh b/skill/scripts/read-journal.sh index 22ce304..0d43f89 100755 --- a/skill/scripts/read-journal.sh +++ b/skill/scripts/read-journal.sh @@ -11,7 +11,12 @@ set -euo pipefail # Required env: PROJECT_REPO_ROOT usage() { - sed -n '3,10s/^# //p' "$0" + cat <<'USAGE' +read-journal.sh AGENT [--date YYYY-MM-DD] [--list] [--help] + AGENT: planner, supervisor, or exec + --date: specific date (default: today) + --list: list available journal dates instead of reading +USAGE exit 0 }