mirror of
https://github.com/allexanderbergmns/xh1-research.git
synced 2026-08-26 17:47:02 +00:00
1915 lines
37 KiB
Bash
Executable File
1915 lines
37 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
# ============================================================
|
|
# XH-1 Research Harness v3
|
|
#
|
|
# Pure Bash
|
|
# OpenAI-compatible providers
|
|
# OpenRouter + Ollama supported
|
|
#
|
|
# Pipeline:
|
|
#
|
|
# Research → Review
|
|
# │
|
|
# ┌─────┴─────┐
|
|
# PASS FAIL
|
|
# │ │
|
|
# accept Revision
|
|
# │
|
|
# ▼
|
|
# Review
|
|
#
|
|
# API failures are NOT research failures.
|
|
#
|
|
# ============================================================
|
|
|
|
ROOT="${XH1_RESEARCH_ROOT:-research}"
|
|
CONFIG="${XH1_RESEARCH_CONFIG:-xh1-research.conf}"
|
|
STATE="${ROOT}/.xh1"
|
|
|
|
mkdir -p \
|
|
"$STATE/logs" \
|
|
"$STATE/responses" \
|
|
"$STATE/runs"
|
|
|
|
RUN_ID=""
|
|
|
|
# ------------------------------------------------------------
|
|
# Defaults
|
|
# ------------------------------------------------------------
|
|
|
|
API_BASE_URL="${API_BASE_URL:-https://openrouter.ai/api/v1}"
|
|
API_KEY_ENV="${API_KEY_ENV:-OPENROUTER_API_KEY}"
|
|
|
|
RESEARCH_PROVIDER="${RESEARCH_PROVIDER:-openrouter}"
|
|
RESEARCH_MODEL="${RESEARCH_MODEL:-}"
|
|
|
|
REVIEW_PROVIDER="${REVIEW_PROVIDER:-ollama}"
|
|
REVIEW_MODEL="${REVIEW_MODEL:-gemma4:12b}"
|
|
|
|
REVISION_PROVIDER="${REVISION_PROVIDER:-openrouter}"
|
|
REVISION_MODEL="${REVISION_MODEL:-}"
|
|
|
|
MAX_RESEARCH_ROUNDS="${MAX_RESEARCH_ROUNDS:-3}"
|
|
MAX_API_RETRIES="${MAX_API_RETRIES:-4}"
|
|
|
|
DELAY_SECONDS="${DELAY_SECONDS:-5}"
|
|
MAX_ITERATIONS="${MAX_ITERATIONS:-0}"
|
|
|
|
REVIEW_ENABLED="${REVIEW_ENABLED:-true}"
|
|
AUTO_COMMIT="${AUTO_COMMIT:-false}"
|
|
|
|
OLLAMA_CONTEXT="${OLLAMA_CONTEXT:-16384}"
|
|
|
|
# ------------------------------------------------------------
|
|
# Provider defaults
|
|
# ------------------------------------------------------------
|
|
|
|
OPENROUTER_URL="${OPENROUTER_URL:-https://openrouter.ai/api/v1}"
|
|
OLLAMA_URL="${OLLAMA_URL:-http://127.0.0.1:11434/v1}"
|
|
|
|
OPENROUTER_KEY_ENV="${OPENROUTER_KEY_ENV:-OPENROUTER_API_KEY}"
|
|
|
|
# ------------------------------------------------------------
|
|
# Logging
|
|
# ------------------------------------------------------------
|
|
|
|
timestamp() {
|
|
date -u '+%Y%m%dT%H%M%SZ'
|
|
}
|
|
|
|
now() {
|
|
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
|
}
|
|
|
|
log() {
|
|
local message="$*"
|
|
|
|
echo "[$(now)] $message" |
|
|
tee -a "$STATE/logs/harness.log"
|
|
}
|
|
|
|
warn() {
|
|
log "WARNING: $*"
|
|
}
|
|
|
|
die() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Dependencies
|
|
# ------------------------------------------------------------
|
|
|
|
require() {
|
|
command -v "$1" >/dev/null 2>&1 ||
|
|
die "Required command not found: $1"
|
|
}
|
|
|
|
require_dependencies() {
|
|
require bash
|
|
require curl
|
|
require jq
|
|
require find
|
|
require grep
|
|
require sed
|
|
require awk
|
|
require date
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Configuration
|
|
# ------------------------------------------------------------
|
|
|
|
load_config() {
|
|
|
|
if [[ -f "$CONFIG" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG"
|
|
fi
|
|
}
|
|
|
|
init_config() {
|
|
|
|
if [[ -f "$CONFIG" ]]; then
|
|
echo "Configuration already exists:"
|
|
echo " $CONFIG"
|
|
return 0
|
|
fi
|
|
|
|
cat > "$CONFIG" <<'CONFIG'
|
|
# ============================================================
|
|
# XH-1 Research Harness Configuration
|
|
# ============================================================
|
|
|
|
# ------------------------------------------------------------
|
|
# Researcher
|
|
# ------------------------------------------------------------
|
|
|
|
RESEARCH_PROVIDER="openrouter"
|
|
RESEARCH_MODEL="minimax/minimax-m3:free"
|
|
|
|
# ------------------------------------------------------------
|
|
# Reviewer
|
|
#
|
|
# Recommended for 16GB Apple Silicon:
|
|
#
|
|
# Ollama + Gemma 4 12B
|
|
# ------------------------------------------------------------
|
|
|
|
REVIEW_PROVIDER="ollama"
|
|
REVIEW_MODEL="gemma4:12b"
|
|
|
|
# ------------------------------------------------------------
|
|
# Revision
|
|
# ------------------------------------------------------------
|
|
|
|
REVISION_PROVIDER="openrouter"
|
|
REVISION_MODEL="minimax/minimax-m3:free"
|
|
|
|
# ------------------------------------------------------------
|
|
# OpenRouter
|
|
# ------------------------------------------------------------
|
|
|
|
OPENROUTER_URL="https://openrouter.ai/api/v1"
|
|
|
|
OPENROUTER_KEY_ENV="OPENROUTER_API_KEY"
|
|
|
|
# ------------------------------------------------------------
|
|
# Ollama
|
|
# ------------------------------------------------------------
|
|
|
|
OLLAMA_URL="http://127.0.0.1:11434/v1"
|
|
|
|
# Context size for local Ollama models.
|
|
#
|
|
# 8192 = safer memory usage
|
|
# 16384 = recommended starting point
|
|
# 32768 = larger context, more memory
|
|
#
|
|
OLLAMA_CONTEXT=16384
|
|
|
|
# ------------------------------------------------------------
|
|
# Research behavior
|
|
# ------------------------------------------------------------
|
|
|
|
# Maximum Research -> Review -> Revision cycles.
|
|
MAX_RESEARCH_ROUNDS=3
|
|
|
|
# Maximum API attempts for a request.
|
|
MAX_API_RETRIES=4
|
|
|
|
# Delay between successful topic processing.
|
|
DELAY_SECONDS=5
|
|
|
|
# Maximum topics processed by run-all.
|
|
#
|
|
# 0 = unlimited
|
|
MAX_ITERATIONS=0
|
|
|
|
# Enable independent reviewer.
|
|
REVIEW_ENABLED=true
|
|
|
|
# Automatically commit accepted documents.
|
|
AUTO_COMMIT=false
|
|
CONFIG
|
|
|
|
echo "Created $CONFIG"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Provider handling
|
|
# ------------------------------------------------------------
|
|
|
|
provider_url() {
|
|
|
|
local provider="$1"
|
|
|
|
case "$provider" in
|
|
|
|
openrouter)
|
|
printf '%s' "$OPENROUTER_URL"
|
|
;;
|
|
|
|
ollama)
|
|
printf '%s' "$OLLAMA_URL"
|
|
;;
|
|
|
|
*)
|
|
local variable="PROVIDER_${provider}_URL"
|
|
local value="${!variable:-}"
|
|
|
|
[[ -n "$value" ]] ||
|
|
die "No URL configured for provider: $provider"
|
|
|
|
printf '%s' "$value"
|
|
;;
|
|
|
|
esac
|
|
}
|
|
|
|
provider_key() {
|
|
|
|
local provider="$1"
|
|
|
|
case "$provider" in
|
|
|
|
ollama)
|
|
printf ''
|
|
;;
|
|
|
|
openrouter)
|
|
local env="${OPENROUTER_KEY_ENV:-OPENROUTER_API_KEY}"
|
|
printf '%s' "${!env:-}"
|
|
;;
|
|
|
|
*)
|
|
local env_var="PROVIDER_${provider}_KEY_ENV"
|
|
local env="${!env_var:-}"
|
|
|
|
if [[ -n "$env" ]]; then
|
|
printf '%s' "${!env:-}"
|
|
fi
|
|
;;
|
|
|
|
esac
|
|
}
|
|
|
|
provider_extra_json() {
|
|
|
|
local provider="$1"
|
|
|
|
if [[ "$provider" == "ollama" ]]; then
|
|
|
|
jq -n \
|
|
--argjson ctx "$OLLAMA_CONTEXT" \
|
|
'{
|
|
num_ctx: $ctx
|
|
}'
|
|
|
|
else
|
|
|
|
printf '{}'
|
|
|
|
fi
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# API request
|
|
# ------------------------------------------------------------
|
|
|
|
api_request() {
|
|
|
|
local provider="$1"
|
|
local model="$2"
|
|
local system_prompt="$3"
|
|
local user_prompt="$4"
|
|
local output_file="$5"
|
|
|
|
local base_url
|
|
local endpoint
|
|
local key
|
|
|
|
base_url="$(provider_url "$provider")"
|
|
endpoint="${base_url%/}/chat/completions"
|
|
|
|
key="$(provider_key "$provider")"
|
|
|
|
local attempt
|
|
local response_file
|
|
local http_code
|
|
local content
|
|
local bytes
|
|
|
|
for ((attempt=1; attempt<=MAX_API_RETRIES; attempt++)); do
|
|
|
|
log "API: provider=$provider model=$model attempt=$attempt"
|
|
|
|
response_file="$STATE/responses/${RUN_ID:-manual}-$(timestamp)-${RANDOM}.json"
|
|
|
|
local extra
|
|
extra="$(provider_extra_json "$provider")"
|
|
|
|
local payload
|
|
|
|
payload="$(
|
|
jq -n \
|
|
--arg model "$model" \
|
|
--arg system "$system_prompt" \
|
|
--arg user "$user_prompt" \
|
|
--argjson extra "$extra" \
|
|
'{
|
|
model: $model,
|
|
messages: [
|
|
{
|
|
role: "system",
|
|
content: $system
|
|
},
|
|
{
|
|
role: "user",
|
|
content: $user
|
|
}
|
|
],
|
|
temperature: 0.2
|
|
} + $extra'
|
|
)"
|
|
|
|
local curl_args=(
|
|
--silent
|
|
--show-error
|
|
--connect-timeout 30
|
|
--max-time 1800
|
|
-H "Content-Type: application/json"
|
|
)
|
|
|
|
if [[ -n "$key" ]]; then
|
|
curl_args+=(
|
|
-H "Authorization: Bearer $key"
|
|
)
|
|
fi
|
|
|
|
if curl "${curl_args[@]}" \
|
|
-o "$response_file" \
|
|
-w '%{http_code}' \
|
|
--data "$payload" \
|
|
"$endpoint" \
|
|
> "${response_file}.code"
|
|
then
|
|
http_code="$(cat "${response_file}.code")"
|
|
else
|
|
http_code="$(cat "${response_file}.code" 2>/dev/null || echo "000")"
|
|
fi
|
|
|
|
rm -f "${response_file}.code"
|
|
|
|
if [[ "$http_code" != "200" ]]; then
|
|
|
|
log "API HTTP status: $http_code"
|
|
|
|
case "$http_code" in
|
|
|
|
429)
|
|
warn "Rate limited by $provider."
|
|
|
|
local wait
|
|
wait=$((attempt * attempt * 5))
|
|
|
|
log "Waiting ${wait}s before retry."
|
|
|
|
sleep "$wait"
|
|
;;
|
|
|
|
000)
|
|
warn "Network/API connection failure."
|
|
|
|
sleep $((attempt * 3))
|
|
;;
|
|
|
|
4*)
|
|
warn "Client/API error from $provider."
|
|
|
|
if (( attempt < MAX_API_RETRIES )); then
|
|
sleep $((attempt * 3))
|
|
fi
|
|
;;
|
|
|
|
5*)
|
|
warn "Provider server error."
|
|
|
|
sleep $((attempt * 5))
|
|
;;
|
|
|
|
*)
|
|
warn "Unexpected HTTP status."
|
|
|
|
sleep $((attempt * 3))
|
|
;;
|
|
esac
|
|
|
|
continue
|
|
fi
|
|
|
|
# ----------------------------------------------------
|
|
# Extract response
|
|
# ----------------------------------------------------
|
|
|
|
if ! content="$(
|
|
jq -er \
|
|
'.choices[0].message.content // empty' \
|
|
"$response_file"
|
|
)"; then
|
|
|
|
warn "Provider returned malformed response."
|
|
|
|
sleep $((attempt * 2))
|
|
continue
|
|
fi
|
|
|
|
bytes="${#content}"
|
|
|
|
# Empty responses are API failures.
|
|
if (( bytes < 10 )); then
|
|
|
|
warn "Provider returned an empty/near-empty response (${bytes} bytes)."
|
|
|
|
sleep $((attempt * 2))
|
|
continue
|
|
fi
|
|
|
|
printf '%s\n' "$content" > "$output_file"
|
|
|
|
log "Response received: ${bytes} bytes"
|
|
|
|
return 0
|
|
done
|
|
|
|
warn "API request exhausted all retries."
|
|
|
|
return 1
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Research discovery
|
|
# ------------------------------------------------------------
|
|
|
|
is_soon() {
|
|
|
|
grep -qE '^SOON[[:space:]]*$' "$1"
|
|
}
|
|
|
|
find_next_task() {
|
|
|
|
while IFS= read -r -d '' file; do
|
|
|
|
[[ "$file" == "$STATE"/* ]] && continue
|
|
|
|
if is_soon "$file"; then
|
|
|
|
if [[ -z "$RUN_ID" ]] ||
|
|
{
|
|
! run_has_completed "$file" &&
|
|
! run_has_failed "$file"
|
|
}
|
|
then
|
|
printf '%s\n' "$file"
|
|
return 0
|
|
fi
|
|
|
|
fi
|
|
|
|
done < <(
|
|
find "$ROOT" \
|
|
-type f \
|
|
-name '*.md' \
|
|
-not -path "$STATE/*" \
|
|
-print0
|
|
)
|
|
|
|
return 1
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Context
|
|
# ------------------------------------------------------------
|
|
|
|
build_context() {
|
|
|
|
local file="$1"
|
|
|
|
local relative="${file#"$ROOT"/}"
|
|
local area="${relative%%/*}"
|
|
|
|
cat <<EOF
|
|
XH-1 RESEARCH PROJECT
|
|
=====================
|
|
|
|
Project:
|
|
XH-1
|
|
|
|
Architecture:
|
|
Custom 128-core RISC-V processor.
|
|
|
|
Repository:
|
|
XH-1 Research
|
|
|
|
Current document:
|
|
$file
|
|
|
|
Research area:
|
|
$area
|
|
|
|
The XH-1 processor is a research CPU project.
|
|
It is a custom 128-core RISC-V architecture.
|
|
|
|
This document is one part of the project's research
|
|
documentation.
|
|
|
|
Do not invent implementation facts.
|
|
|
|
Clearly distinguish:
|
|
|
|
- established facts
|
|
- RISC-V specification requirements
|
|
- published research
|
|
- industry practice
|
|
- reasonable engineering proposals
|
|
- assumptions
|
|
- unresolved questions
|
|
|
|
Prefer technically precise terminology.
|
|
|
|
If evidence is unavailable, explicitly say so.
|
|
|
|
CURRENT DOCUMENT
|
|
================
|
|
|
|
EOF
|
|
|
|
cat "$file"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Research prompt
|
|
# ------------------------------------------------------------
|
|
|
|
research_prompt() {
|
|
|
|
local file="$1"
|
|
|
|
cat <<EOF
|
|
You are the primary research engineer for the XH-1 CPU project.
|
|
|
|
XH-1 is a custom 128-core RISC-V processor.
|
|
|
|
Research the topic represented by the supplied document.
|
|
|
|
Your task is to produce a technically rigorous research document
|
|
that can eventually become part of the XH-1 architecture documentation.
|
|
|
|
TOPIC
|
|
=====
|
|
|
|
$file
|
|
|
|
REQUIREMENTS
|
|
============
|
|
|
|
1. Explain the topic thoroughly.
|
|
|
|
2. Relate the topic specifically to a 128-core RISC-V processor.
|
|
|
|
3. Identify relevant RISC-V architectural requirements.
|
|
|
|
4. Discuss realistic implementation approaches.
|
|
|
|
5. Compare meaningful alternatives.
|
|
|
|
6. Discuss advantages and disadvantages.
|
|
|
|
7. Identify scalability problems caused by 128 cores.
|
|
|
|
8. Identify interactions with:
|
|
- pipeline
|
|
- cache hierarchy
|
|
- memory system
|
|
- interconnect
|
|
- coherence
|
|
- interrupts
|
|
- operating system
|
|
- verification
|
|
- performance
|
|
|
|
9. Do not present assumptions as facts.
|
|
|
|
10. Cite standards, papers, manuals, or reputable technical
|
|
sources where possible.
|
|
|
|
11. Do not fabricate citations.
|
|
|
|
12. Identify unresolved design questions.
|
|
|
|
13. Make recommendations only when sufficient evidence exists.
|
|
|
|
14. If the XH-1 implementation is not yet decided, explicitly
|
|
mark it as a proposal rather than an existing feature.
|
|
|
|
15. Prefer quantitative analysis where reasonable.
|
|
|
|
OUTPUT FORMAT
|
|
=============
|
|
|
|
Return ONLY the proposed Markdown document.
|
|
|
|
Do not include:
|
|
- conversational commentary
|
|
- "here is the document"
|
|
- analysis outside the document
|
|
- review commentary
|
|
|
|
The result should be suitable for directly replacing the
|
|
SOON placeholder if it passes independent review.
|
|
|
|
CURRENT DOCUMENT
|
|
================
|
|
|
|
$(build_context "$file")
|
|
EOF
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Review prompt
|
|
# ------------------------------------------------------------
|
|
|
|
review_prompt() {
|
|
|
|
local file="$1"
|
|
local candidate="$2"
|
|
|
|
cat <<EOF
|
|
You are the INDEPENDENT TECHNICAL REVIEWER for the XH-1 CPU project.
|
|
|
|
XH-1 is a custom 128-core RISC-V processor.
|
|
|
|
Your task is to critically review the proposed research document.
|
|
|
|
You are NOT the author.
|
|
You must NOT rewrite the document.
|
|
|
|
Your job is to identify:
|
|
|
|
- factual errors
|
|
- RISC-V specification errors
|
|
- incorrect terminology
|
|
- unsupported claims
|
|
- missing evidence
|
|
- bad citations
|
|
- internal contradictions
|
|
- missing design alternatives
|
|
- unrealistic assumptions
|
|
- scalability problems
|
|
- 128-core implications
|
|
- memory-system interactions
|
|
- cache/coherence interactions
|
|
- interconnect implications
|
|
- verification problems
|
|
- power/area implications
|
|
- software implications
|
|
- recommendations that do not follow from the evidence
|
|
|
|
IMPORTANT
|
|
|
|
Do not reject a document merely because XH-1 has not made
|
|
a final architectural decision.
|
|
|
|
A research document may contain:
|
|
|
|
- proposals
|
|
- assumptions
|
|
- alternatives
|
|
- open questions
|
|
- preliminary recommendations
|
|
|
|
Those are acceptable when clearly identified.
|
|
|
|
Do reject statements that present speculation as established fact.
|
|
|
|
Do not invent facts.
|
|
|
|
Do not invent citations.
|
|
|
|
Do not claim that RISC-V requires something unless it actually does.
|
|
|
|
Pay particular attention to whether claims are:
|
|
|
|
FACT
|
|
SPECIFICATION
|
|
RESEARCH
|
|
ASSUMPTION
|
|
PROPOSAL
|
|
OPEN QUESTION
|
|
|
|
DOCUMENT
|
|
========
|
|
|
|
$file
|
|
|
|
$candidate
|
|
|
|
============================================================
|
|
REVIEW CRITERIA
|
|
============================================================
|
|
|
|
1. Technical correctness
|
|
|
|
2. RISC-V compliance
|
|
|
|
3. Microarchitectural correctness
|
|
|
|
4. Memory-model correctness
|
|
|
|
5. Cache/coherence correctness
|
|
|
|
6. Interconnect/scalability
|
|
|
|
7. 128-core implications
|
|
|
|
8. Performance
|
|
|
|
9. Area
|
|
|
|
10. Power/energy
|
|
|
|
11. Verification
|
|
|
|
12. Software/OS implications
|
|
|
|
13. Evidence and citations
|
|
|
|
14. Internal consistency
|
|
|
|
15. Quality of recommendations
|
|
|
|
============================================================
|
|
VERDICT RULE
|
|
============================================================
|
|
|
|
PASS means:
|
|
|
|
The document is technically credible for its research stage.
|
|
|
|
Minor improvements may exist.
|
|
|
|
FAIL means:
|
|
|
|
There are substantive technical errors, unsupported claims,
|
|
contradictions, missing critical alternatives, or other issues
|
|
that should be corrected before acceptance.
|
|
|
|
============================================================
|
|
CRITICAL OUTPUT REQUIREMENT
|
|
============================================================
|
|
|
|
Your response MUST contain a machine-readable JSON object
|
|
as the FINAL thing in your response.
|
|
|
|
Do not omit it.
|
|
|
|
The JSON MUST have exactly this general structure:
|
|
|
|
{
|
|
"verdict": "PASS",
|
|
"confidence": "HIGH",
|
|
"issues": [],
|
|
"required_fixes": []
|
|
}
|
|
|
|
For a failing document:
|
|
|
|
{
|
|
"verdict": "FAIL",
|
|
"confidence": "HIGH",
|
|
"issues": [
|
|
"Description of issue"
|
|
],
|
|
"required_fixes": [
|
|
"Description of required fix"
|
|
]
|
|
}
|
|
|
|
The value of "verdict" MUST be exactly:
|
|
|
|
PASS
|
|
|
|
or:
|
|
|
|
FAIL
|
|
|
|
The value of "confidence" MUST be:
|
|
|
|
HIGH
|
|
|
|
MEDIUM
|
|
|
|
or:
|
|
|
|
LOW
|
|
|
|
The JSON object MUST be valid JSON.
|
|
|
|
Do not put Markdown fences around the JSON.
|
|
|
|
You may provide a normal human-readable review before the JSON.
|
|
|
|
The FINAL JSON object is the authoritative verdict.
|
|
|
|
============================================================
|
|
|
|
Begin your review now.
|
|
|
|
EOF
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Revision prompt
|
|
# ------------------------------------------------------------
|
|
|
|
revision_prompt() {
|
|
|
|
local file="$1"
|
|
local candidate="$2"
|
|
local review="$3"
|
|
|
|
cat <<EOF
|
|
You are the revision engineer for the XH-1 CPU research project.
|
|
|
|
XH-1 is a custom 128-core RISC-V processor.
|
|
|
|
Revise the research document using the independent review.
|
|
|
|
Do NOT blindly accept every reviewer statement.
|
|
|
|
If the reviewer is technically incorrect, correct the issue
|
|
using sound engineering reasoning.
|
|
|
|
Do not invent facts.
|
|
|
|
Do not fabricate citations.
|
|
|
|
Clearly mark assumptions and proposals.
|
|
|
|
Preserve useful research from the existing document.
|
|
|
|
The revised result must be a complete standalone Markdown
|
|
document.
|
|
|
|
OUTPUT ONLY THE REVISED MARKDOWN DOCUMENT.
|
|
|
|
TOPIC
|
|
=====
|
|
|
|
$file
|
|
|
|
CURRENT DOCUMENT
|
|
================
|
|
|
|
$candidate
|
|
|
|
INDEPENDENT REVIEW
|
|
==================
|
|
|
|
$review
|
|
EOF
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Review verdict
|
|
# ------------------------------------------------------------
|
|
parse_verdict() {
|
|
local review="$1"
|
|
|
|
python3 - "$review" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
path = sys.argv[1]
|
|
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
text = f.read()
|
|
|
|
# ------------------------------------------------------------
|
|
# 1. Find JSON objects containing a "verdict" field.
|
|
# ------------------------------------------------------------
|
|
|
|
matches = re.findall(
|
|
r'\{[\s\S]*?"verdict"\s*:\s*"(?:PASS|FAIL)"[\s\S]*?\}',
|
|
text,
|
|
re.IGNORECASE
|
|
)
|
|
|
|
for candidate in reversed(matches):
|
|
try:
|
|
obj = json.loads(candidate)
|
|
verdict = str(obj.get("verdict", "")).upper()
|
|
|
|
if verdict in ("PASS", "FAIL"):
|
|
print(verdict)
|
|
sys.exit(0)
|
|
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
# ------------------------------------------------------------
|
|
# 2. Legacy format.
|
|
# ------------------------------------------------------------
|
|
|
|
matches = re.findall(
|
|
r'VERDICT\s*:\s*(PASS|FAIL)',
|
|
text,
|
|
re.IGNORECASE
|
|
)
|
|
|
|
if matches:
|
|
print(matches[-1].upper())
|
|
sys.exit(0)
|
|
|
|
print("INVALID")
|
|
PY
|
|
}
|
|
|
|
repair_review_verdict() {
|
|
|
|
local original="$1"
|
|
local repaired="$2"
|
|
|
|
local prompt
|
|
|
|
prompt=$(cat <<EOF
|
|
You are a review-output formatter.
|
|
|
|
The following is a technical review of an XH-1 RISC-V CPU research
|
|
document.
|
|
|
|
The review itself has already been completed.
|
|
|
|
Your ONLY task is to determine whether the reviewer intended
|
|
PASS or FAIL and return valid JSON.
|
|
|
|
Return ONLY this JSON object:
|
|
|
|
{
|
|
"verdict": "PASS",
|
|
"confidence": "HIGH",
|
|
"issues": [],
|
|
"required_fixes": []
|
|
}
|
|
|
|
OR:
|
|
|
|
{
|
|
"verdict": "FAIL",
|
|
"confidence": "HIGH",
|
|
"issues": ["..."],
|
|
"required_fixes": ["..."]
|
|
}
|
|
|
|
Do not add Markdown.
|
|
|
|
Do not add explanation.
|
|
|
|
Do not use code fences.
|
|
|
|
Original review:
|
|
|
|
$original
|
|
EOF
|
|
)
|
|
|
|
api_request \
|
|
"$REVIEW_PROVIDER" \
|
|
"$REVIEW_MODEL" \
|
|
"You convert technical review output into strict JSON." \
|
|
"$prompt" \
|
|
"$repaired"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Atomic document replacement
|
|
# ------------------------------------------------------------
|
|
|
|
accept_document() {
|
|
|
|
local file="$1"
|
|
local candidate="$2"
|
|
|
|
local temp="${file}.xh1.tmp"
|
|
|
|
cp "$candidate" "$temp"
|
|
|
|
mv "$temp" "$file"
|
|
|
|
log "Accepted research document: $file"
|
|
|
|
if [[ "$AUTO_COMMIT" == "true" ]]; then
|
|
|
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
|
git add "$file"
|
|
|
|
git commit \
|
|
-m "research: complete ${file#"$ROOT"/}" \
|
|
|| warn "Git commit failed."
|
|
|
|
else
|
|
warn "AUTO_COMMIT enabled but repository is not Git."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Run state
|
|
# ------------------------------------------------------------
|
|
|
|
start_run() {
|
|
|
|
RUN_ID="$(timestamp)"
|
|
|
|
mkdir -p "$STATE/runs/$RUN_ID"
|
|
|
|
: > "$STATE/runs/$RUN_ID/completed"
|
|
: > "$STATE/runs/$RUN_ID/failed"
|
|
: > "$STATE/runs/$RUN_ID/attempts"
|
|
|
|
log "Started run: $RUN_ID"
|
|
}
|
|
|
|
run_has_completed() {
|
|
|
|
local file="$1"
|
|
|
|
grep -Fxq "$file" \
|
|
"$STATE/runs/$RUN_ID/completed" \
|
|
2>/dev/null
|
|
}
|
|
|
|
run_has_failed() {
|
|
|
|
local file="$1"
|
|
|
|
grep -Fxq "$file" \
|
|
"$STATE/runs/$RUN_ID/failed" \
|
|
2>/dev/null
|
|
}
|
|
|
|
mark_completed() {
|
|
|
|
local file="$1"
|
|
|
|
printf '%s\n' "$file" \
|
|
>> "$STATE/runs/$RUN_ID/completed"
|
|
}
|
|
|
|
mark_failed() {
|
|
|
|
local file="$1"
|
|
|
|
printf '%s\n' "$file" \
|
|
>> "$STATE/runs/$RUN_ID/failed"
|
|
}
|
|
|
|
record_attempt() {
|
|
|
|
local file="$1"
|
|
local round="$2"
|
|
local phase="$3"
|
|
local result="$4"
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"$(now)" \
|
|
"$file" \
|
|
"$round" \
|
|
"$phase" \
|
|
"$result" \
|
|
>> "$STATE/runs/$RUN_ID/attempts"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Single research topic
|
|
# ------------------------------------------------------------
|
|
|
|
run_single() {
|
|
|
|
local requested="${1:-}"
|
|
|
|
if [[ -n "$requested" ]]; then
|
|
file="$requested"
|
|
else
|
|
file="$(find_next_task || true)"
|
|
fi
|
|
|
|
[[ -n "${file:-}" ]] ||
|
|
die "No SOON research topics found."
|
|
|
|
[[ -f "$file" ]] ||
|
|
die "Research document not found: $file"
|
|
|
|
if [[ -z "$RUN_ID" ]]; then
|
|
start_run
|
|
fi
|
|
|
|
log "=================================================="
|
|
log "Researching: $file"
|
|
log "=================================================="
|
|
|
|
local round
|
|
local candidate
|
|
local review
|
|
local revision
|
|
local verdict
|
|
|
|
candidate="$STATE/runs/$RUN_ID/candidate.md"
|
|
review="$STATE/runs/$RUN_ID/review.md"
|
|
revision="$STATE/runs/$RUN_ID/revision.md"
|
|
|
|
for ((round=1; round<=MAX_RESEARCH_ROUNDS; round++)); do
|
|
|
|
log "Research round $round/$MAX_RESEARCH_ROUNDS"
|
|
|
|
# ----------------------------------------------------
|
|
# Initial research
|
|
# ----------------------------------------------------
|
|
|
|
if (( round == 1 )); then
|
|
|
|
log "Running researcher."
|
|
|
|
if ! api_request \
|
|
"$RESEARCH_PROVIDER" \
|
|
"$RESEARCH_MODEL" \
|
|
"You are a senior CPU architecture research engineer." \
|
|
"$(research_prompt "$file")" \
|
|
"$candidate"
|
|
then
|
|
|
|
warn "Research API failed."
|
|
warn "Topic will NOT be marked as failed."
|
|
|
|
return 2
|
|
fi
|
|
|
|
record_attempt "$file" "$round" "research" "success"
|
|
|
|
else
|
|
|
|
log "Running revision agent."
|
|
|
|
if ! api_request \
|
|
"$REVISION_PROVIDER" \
|
|
"$REVISION_MODEL" \
|
|
"You are a senior CPU architecture revision engineer." \
|
|
"$(revision_prompt "$file" "$candidate" "$review")" \
|
|
"$revision"
|
|
then
|
|
|
|
warn "Revision API failed."
|
|
warn "Research round was NOT counted as a valid revision."
|
|
|
|
return 2
|
|
fi
|
|
|
|
mv "$revision" "$candidate"
|
|
|
|
record_attempt "$file" "$round" "revision" "success"
|
|
fi
|
|
|
|
# ----------------------------------------------------
|
|
# Reviewer
|
|
# ----------------------------------------------------
|
|
|
|
if [[ "$REVIEW_ENABLED" != "true" ]]; then
|
|
|
|
log "Review disabled."
|
|
|
|
accept_document "$file" "$candidate"
|
|
|
|
mark_completed "$file"
|
|
|
|
return 0
|
|
fi
|
|
|
|
log "Running reviewer."
|
|
|
|
if ! api_request \
|
|
"$REVIEW_PROVIDER" \
|
|
"$REVIEW_MODEL" \
|
|
"You are an independent and highly skeptical CPU architecture reviewer." \
|
|
"$(review_prompt "$file" "$(cat "$candidate")")" \
|
|
"$review"
|
|
then
|
|
|
|
warn "Reviewer API failed."
|
|
|
|
# IMPORTANT:
|
|
#
|
|
# Do not mark the research as failed.
|
|
# Do not trigger revision.
|
|
#
|
|
# Return a special status so run-all can retry
|
|
# the topic later.
|
|
|
|
record_attempt "$file" "$round" "review" "api-error"
|
|
|
|
return 2
|
|
fi
|
|
|
|
verdict="$(parse_verdict "$review")"
|
|
|
|
if [[ "$verdict" == "INVALID" ]]; then
|
|
|
|
log "Reviewer did not provide a machine-readable verdict."
|
|
log "Requesting verdict-format repair."
|
|
|
|
local repaired_review
|
|
repaired_review="$STATE/runs/$RUN_ID/review-repaired.md"
|
|
|
|
if repair_review_verdict \
|
|
"$(cat "$review")" \
|
|
"$repaired_review"
|
|
then
|
|
|
|
cat "$repaired_review" >> "$review"
|
|
|
|
verdict="$(parse_verdict "$repaired_review")"
|
|
|
|
if [[ "$verdict" != "INVALID" ]]; then
|
|
log "Recovered reviewer verdict: $verdict"
|
|
fi
|
|
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
echo "=========================================="
|
|
echo "RESEARCH REVIEW"
|
|
echo "=========================================="
|
|
|
|
cat "$review"
|
|
|
|
echo "=========================================="
|
|
echo
|
|
|
|
case "$verdict" in
|
|
|
|
PASS)
|
|
|
|
log "Research passed review."
|
|
|
|
record_attempt "$file" "$round" "review" "PASS"
|
|
|
|
accept_document "$file" "$candidate"
|
|
|
|
mark_completed "$file"
|
|
|
|
return 0
|
|
;;
|
|
|
|
FAIL)
|
|
|
|
log "Research rejected by reviewer."
|
|
|
|
record_attempt "$file" "$round" "review" "FAIL"
|
|
|
|
if (( round < MAX_RESEARCH_ROUNDS )); then
|
|
|
|
log "Preparing revision round $((round + 1))."
|
|
|
|
sleep "$DELAY_SECONDS"
|
|
|
|
else
|
|
|
|
log "Maximum research rounds reached."
|
|
log "Leaving original document unchanged."
|
|
|
|
mark_failed "$file"
|
|
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
INVALID)
|
|
|
|
warn "Reviewer returned no valid VERDICT."
|
|
|
|
record_attempt "$file" "$round" "review" "invalid"
|
|
|
|
warn "This is a reviewer failure, not a research failure."
|
|
|
|
return 2
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Run all
|
|
# ------------------------------------------------------------
|
|
|
|
run_all() {
|
|
|
|
start_run
|
|
|
|
local count=0
|
|
local file
|
|
local result
|
|
|
|
while true; do
|
|
|
|
if (( MAX_ITERATIONS > 0 &&
|
|
count >= MAX_ITERATIONS )); then
|
|
|
|
log "MAX_ITERATIONS reached: $MAX_ITERATIONS"
|
|
|
|
break
|
|
fi
|
|
|
|
file="$(find_next_task || true)"
|
|
|
|
if [[ -z "$file" ]]; then
|
|
|
|
log "No remaining research topics."
|
|
|
|
break
|
|
fi
|
|
|
|
count=$((count + 1))
|
|
|
|
run_single "$file"
|
|
result=$?
|
|
|
|
case "$result" in
|
|
|
|
0)
|
|
log "Topic completed successfully."
|
|
;;
|
|
|
|
1)
|
|
log "Topic failed after maximum research rounds."
|
|
;;
|
|
|
|
2)
|
|
log "Topic paused because of an API/provider failure."
|
|
log "It remains eligible for a future run."
|
|
;;
|
|
|
|
*)
|
|
warn "Unexpected result code: $result"
|
|
;;
|
|
esac
|
|
|
|
sleep "$DELAY_SECONDS"
|
|
|
|
done
|
|
|
|
log "=================================================="
|
|
log "Run complete."
|
|
log "Topics attempted: $count"
|
|
log "Run ID: $RUN_ID"
|
|
log "=================================================="
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Status
|
|
# ------------------------------------------------------------
|
|
|
|
status() {
|
|
|
|
local total=0
|
|
local completed=0
|
|
local remaining=0
|
|
|
|
while IFS= read -r -d '' file; do
|
|
|
|
[[ "$file" == "$STATE"/* ]] && continue
|
|
|
|
total=$((total + 1))
|
|
|
|
if is_soon "$file"; then
|
|
remaining=$((remaining + 1))
|
|
else
|
|
completed=$((completed + 1))
|
|
fi
|
|
|
|
done < <(
|
|
find "$ROOT" \
|
|
-type f \
|
|
-name '*.md' \
|
|
-not -path "$STATE/*" \
|
|
-print0
|
|
)
|
|
|
|
echo
|
|
echo "╔══════════════════════════════════════════╗"
|
|
echo "║ XH-1 RESEARCH STATUS ║"
|
|
echo "╚══════════════════════════════════════════╝"
|
|
echo
|
|
echo " Total documents : $total"
|
|
echo " Completed : $completed"
|
|
echo " Remaining SOON : $remaining"
|
|
echo
|
|
|
|
echo "Next topic:"
|
|
|
|
local next
|
|
next="$(find_next_task || true)"
|
|
|
|
if [[ -n "$next" ]]; then
|
|
echo " $next"
|
|
else
|
|
echo " None"
|
|
fi
|
|
|
|
echo
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Reset failure
|
|
# ------------------------------------------------------------
|
|
|
|
reset_failure() {
|
|
|
|
local file="${1:-}"
|
|
|
|
[[ -n "$file" ]] ||
|
|
die "Usage: ./xh1-research reset-failure <file>"
|
|
|
|
[[ -f "$file" ]] ||
|
|
die "File not found: $file"
|
|
|
|
if [[ -n "$RUN_ID" &&
|
|
-f "$STATE/runs/$RUN_ID/failed" ]]; then
|
|
|
|
sed -i.bak "\|^${file}$|d" \
|
|
"$STATE/runs/$RUN_ID/failed"
|
|
|
|
rm -f "$STATE/runs/$RUN_ID/failed.bak"
|
|
fi
|
|
|
|
echo "Failure state reset for:"
|
|
echo " $file"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Manual review
|
|
# ------------------------------------------------------------
|
|
|
|
review_command() {
|
|
|
|
local file="${1:-}"
|
|
|
|
[[ -n "$file" ]] ||
|
|
die "Usage: ./xh1-research review <file>"
|
|
|
|
[[ -f "$file" ]] ||
|
|
die "File not found: $file"
|
|
|
|
RUN_ID="manual-$(timestamp)"
|
|
|
|
mkdir -p "$STATE/runs/$RUN_ID"
|
|
|
|
local review="$STATE/runs/$RUN_ID/review.md"
|
|
|
|
log "Manual review: $file"
|
|
|
|
if ! api_request \
|
|
"$REVIEW_PROVIDER" \
|
|
"$REVIEW_MODEL" \
|
|
"You are an independent and highly skeptical CPU architecture reviewer." \
|
|
"$(review_prompt "$file" "$(cat "$file")")" \
|
|
"$review"
|
|
then
|
|
die "Reviewer API failed."
|
|
fi
|
|
|
|
echo
|
|
echo "=========================================="
|
|
echo "RESEARCH REVIEW"
|
|
echo "=========================================="
|
|
|
|
cat "$review"
|
|
|
|
echo "=========================================="
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Providers
|
|
# ------------------------------------------------------------
|
|
|
|
providers() {
|
|
|
|
echo
|
|
echo "XH-1 RESEARCH PROVIDERS"
|
|
echo "======================="
|
|
echo
|
|
|
|
printf '%-15s %-45s %s\n' \
|
|
"PROVIDER" "URL" "MODEL"
|
|
|
|
printf '%-15s %-45s %s\n' \
|
|
"openrouter" \
|
|
"$OPENROUTER_URL" \
|
|
"$RESEARCH_MODEL"
|
|
|
|
printf '%-15s %-45s %s\n' \
|
|
"ollama" \
|
|
"$OLLAMA_URL" \
|
|
"$REVIEW_MODEL"
|
|
|
|
echo
|
|
echo "Research:"
|
|
echo " $RESEARCH_PROVIDER / $RESEARCH_MODEL"
|
|
|
|
echo
|
|
echo "Review:"
|
|
echo " $REVIEW_PROVIDER / $REVIEW_MODEL"
|
|
|
|
echo
|
|
echo "Revision:"
|
|
echo " $REVISION_PROVIDER / $REVISION_MODEL"
|
|
|
|
echo
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Help
|
|
# ------------------------------------------------------------
|
|
|
|
help() {
|
|
|
|
cat <<'EOF'
|
|
|
|
XH-1 RESEARCH HARNESS
|
|
=====================
|
|
|
|
Autonomous research/review/revision pipeline for XH-1.
|
|
|
|
USAGE
|
|
|
|
./xh1-research <command>
|
|
|
|
|
|
COMMANDS
|
|
|
|
init
|
|
|
|
Create the default configuration.
|
|
|
|
|
|
status
|
|
|
|
Show research progress and next topic.
|
|
|
|
|
|
providers
|
|
|
|
Show configured AI providers and models.
|
|
|
|
|
|
next
|
|
|
|
Show the next SOON document.
|
|
|
|
|
|
run
|
|
|
|
Research the next available topic.
|
|
|
|
|
|
run <file>
|
|
|
|
Research a specific document.
|
|
|
|
|
|
run-all
|
|
|
|
Process all available research topics.
|
|
|
|
API/provider failures do NOT mark a topic failed.
|
|
|
|
The topic remains eligible for a future run.
|
|
|
|
|
|
loop
|
|
|
|
Alias for run-all.
|
|
|
|
|
|
resume
|
|
|
|
Alias for run-all.
|
|
|
|
|
|
review <file>
|
|
|
|
Manually review a document.
|
|
|
|
|
|
reset-failure <file>
|
|
|
|
Allow a failed topic to be retried.
|
|
|
|
|
|
help
|
|
|
|
Show this help.
|
|
|
|
|
|
RESEARCH PIPELINE
|
|
|
|
Research
|
|
|
|
|
v
|
|
Review
|
|
|
|
|
+---- PASS ----> Replace SOON document
|
|
|
|
|
+---- FAIL ----> Revision
|
|
|
|
|
v
|
|
Review
|
|
|
|
|
+---- PASS
|
|
|
|
|
+---- FAIL
|
|
|
|
|
v
|
|
Maximum rounds
|
|
|
|
|
|
IMPORTANT SAFETY
|
|
|
|
A research document is ONLY replaced after:
|
|
|
|
VERDICT: PASS
|
|
|
|
API failures are NOT research failures.
|
|
|
|
Examples:
|
|
|
|
429
|
|
timeout
|
|
connection failure
|
|
malformed response
|
|
empty reviewer response
|
|
invalid reviewer verdict
|
|
|
|
These pause the topic instead of consuming a research round.
|
|
|
|
If all research rounds genuinely fail:
|
|
|
|
The original SOON document remains unchanged.
|
|
|
|
The topic is marked failed for that run.
|
|
|
|
|
|
PROVIDERS
|
|
|
|
Built-in:
|
|
|
|
openrouter
|
|
ollama
|
|
|
|
Other OpenAI-compatible providers can be configured with:
|
|
|
|
PROVIDER_<name>_URL
|
|
PROVIDER_<name>_KEY_ENV
|
|
|
|
|
|
ENVIRONMENT
|
|
|
|
XH1_RESEARCH_ROOT
|
|
|
|
Default:
|
|
research
|
|
|
|
|
|
XH1_RESEARCH_CONFIG
|
|
|
|
Default:
|
|
xh1-research.conf
|
|
|
|
|
|
CONFIGURATION
|
|
|
|
RESEARCH_PROVIDER
|
|
|
|
Provider used for research.
|
|
|
|
|
|
RESEARCH_MODEL
|
|
|
|
Model used for research.
|
|
|
|
|
|
REVIEW_PROVIDER
|
|
|
|
Provider used for independent review.
|
|
|
|
|
|
REVIEW_MODEL
|
|
|
|
Model used for independent review.
|
|
|
|
|
|
REVISION_PROVIDER
|
|
|
|
Provider used for revision.
|
|
|
|
|
|
REVISION_MODEL
|
|
|
|
Model used for revision.
|
|
|
|
|
|
OPENROUTER_URL
|
|
|
|
Default:
|
|
|
|
https://openrouter.ai/api/v1
|
|
|
|
|
|
OPENROUTER_KEY_ENV
|
|
|
|
Default:
|
|
|
|
OPENROUTER_API_KEY
|
|
|
|
|
|
OLLAMA_URL
|
|
|
|
Default:
|
|
|
|
http://127.0.0.1:11434/v1
|
|
|
|
|
|
OLLAMA_CONTEXT
|
|
|
|
Ollama context size.
|
|
|
|
Recommended for 16GB Apple Silicon:
|
|
|
|
16384
|
|
|
|
|
|
MAX_RESEARCH_ROUNDS
|
|
|
|
Maximum research/revision cycles per topic.
|
|
|
|
Default:
|
|
|
|
3
|
|
|
|
|
|
MAX_API_RETRIES
|
|
|
|
API retry attempts.
|
|
|
|
Default:
|
|
|
|
4
|
|
|
|
|
|
DELAY_SECONDS
|
|
|
|
Delay between topics/rounds.
|
|
|
|
Default:
|
|
|
|
5
|
|
|
|
|
|
MAX_ITERATIONS
|
|
|
|
Maximum topics processed by run-all.
|
|
|
|
0 = unlimited.
|
|
|
|
|
|
REVIEW_ENABLED
|
|
|
|
true/false.
|
|
|
|
|
|
AUTO_COMMIT
|
|
|
|
true/false.
|
|
|
|
|
|
EXAMPLE
|
|
|
|
export OPENROUTER_API_KEY="..."
|
|
|
|
./xh1-research init
|
|
|
|
./xh1-research providers
|
|
|
|
./xh1-research status
|
|
|
|
./xh1-research run
|
|
|
|
./xh1-research run-all
|
|
|
|
|
|
RECOMMENDED XH-1 SETUP
|
|
|
|
RESEARCH_PROVIDER="openrouter"
|
|
RESEARCH_MODEL="minimax/minimax-m3:free"
|
|
|
|
REVIEW_PROVIDER="ollama"
|
|
REVIEW_MODEL="gemma4:12b"
|
|
|
|
REVISION_PROVIDER="openrouter"
|
|
REVISION_MODEL="minimax/minimax-m3:free"
|
|
|
|
OLLAMA_CONTEXT=16384
|
|
|
|
|
|
EOF
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Main
|
|
# ------------------------------------------------------------
|
|
|
|
load_config
|
|
require_dependencies
|
|
|
|
case "${1:-status}" in
|
|
|
|
init)
|
|
init_config
|
|
;;
|
|
|
|
status)
|
|
status
|
|
;;
|
|
|
|
providers)
|
|
providers
|
|
;;
|
|
|
|
next)
|
|
find_next_task || true
|
|
;;
|
|
|
|
run)
|
|
run_single "${2:-}"
|
|
;;
|
|
|
|
run-all|loop|resume)
|
|
run_all
|
|
;;
|
|
|
|
review)
|
|
review_command "${2:-}"
|
|
;;
|
|
|
|
reset-failure)
|
|
reset_failure "${2:-}"
|
|
;;
|
|
|
|
help|-h|--help)
|
|
help
|
|
;;
|
|
|
|
*)
|
|
die "Unknown command: $1. Use ./xh1-research help"
|
|
;;
|
|
|
|
esac
|