feat: create disinto skill package (SKILL.md + helper scripts) (#710)

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) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-26 07:26:54 +00:00
parent 805fa69770
commit 26fcb186a0
5 changed files with 485 additions and 0 deletions

170
skill/SKILL.md Normal file
View file

@ -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.

114
skill/scripts/factory-status.sh Executable file
View file

@ -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

91
skill/scripts/file-issue.sh Executable file
View file

@ -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}"

89
skill/scripts/read-journal.sh Executable file
View file

@ -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

View file

@ -0,0 +1,21 @@
## Summary
<!-- One or two sentences: what and why -->
## Acceptance criteria
- [ ] <!-- Criterion 1 -->
- [ ] <!-- Criterion 2 -->
- [ ] <!-- Criterion 3 -->
## Affected files
<!-- List files/directories this issue will touch -->
- `path/to/file.sh`
## Dependencies
<!-- List issue numbers this depends on, or "None" -->
None