Spec-driven development on OpenSpec, with mechanical spec-as-source enforcement: a custom 'spec-as-source' OpenSpec schema adds file-ownership (targets) and test-verification ([@test]) metadata to every capability spec, three scripts (link check, ownership check, manifest build) keep code and specs from drifting apart, plus requirement-gathering, spec-writer, work-review, and a session-handoff skill with a proactive context-warning hook.
68
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
#!/usr/bin/env bash
# GENERATED FROM SPEC — DO NOT EDIT DIRECTLY
# Source: openspec/specs/plan-judge/spec.md
# Runs the fixed, container-isolated Round 2 development-document workflow.
set -euo pipefail
fatal() { echo "plan-judge: $*" >&2; exit 1; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
ROUND_DIR="$SKILL_DIR/prompts/round-2"
PROJECT_ROOT="${1:-$PWD}"
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
INPUT_PLAN="${2:-$PROJECT_ROOT/.plan-judge/round-1/writer-4.md}"
if [ -f "$INPUT_PLAN" ]; then
INPUT_PLAN="$(cd "$(dirname "$INPUT_PLAN")" && pwd)/$(basename "$INPUT_PLAN")"
fi
RUN_DIR="$PROJECT_ROOT/.plan-judge/round-2"
COMPOSE_BIN="${PLAN_JUDGE_COMPOSE_BIN:-docker compose}"
CODEX_BIN="${PLAN_JUDGE_CODEX_BIN:-codex}"
CLAUDE_BIN="${PLAN_JUDGE_CLAUDE_BIN:-claude}"
sha256() { shasum -a 256 "$1" | awk '{print $1}'; }
frontmatter_value() {
awk -v key="$2" '
$0 == "---" { delimiters++; next }
delimiters == 1 && $0 ~ "^" key ": " {
sub("^" key ": ", "")
print
exit
}
' "$1"
}
compose_exec() {
if [ "$COMPOSE_BIN" = "docker compose" ]; then
docker compose exec -T code "$@"
else
"$COMPOSE_BIN" exec -T code "$@"
fi
}
preflight() {
compose_exec "$CODEX_BIN" login status >/dev/null 2>&1 \
|| fatal "PREFLIGHT: codex login status failed in container code"
compose_exec "$CLAUDE_BIN" auth status >/dev/null 2>&1 \
|| fatal "PREFLIGHT: claude auth status failed in container code"
}
validate_artifact() {
file="$1"
turn="$2"
role="$3"
provider="$4"
expected_digest="$5"
[ -s "$file" ] || fatal "missing $role artifact for pass $turn: $file"
[ "$(frontmatter_value "$file" round)" = "2" ] || fatal "invalid round in $file"
[ "$(frontmatter_value "$file" giro)" = "$turn" ] || fatal "invalid giro in $file"
[ "$(frontmatter_value "$file" ruolo)" = "$role" ] || fatal "invalid role in $file"
[ "$(frontmatter_value "$file" provider)" = "$provider" ] || fatal "invalid provider in $file"
actual_digest="$(frontmatter_value "$file" input_digest)"
[ -n "$actual_digest" ] || fatal "missing input_digest in $file"
[ "$actual_digest" = "sha256:$expected_digest" ] \
|| fatal "STALE-INPUT: $role artifact for pass $turn does not match its latest input"
}
run_provider() {
provider="$1"
role="$2"
turn="$3"
prompt="$4"
input="$5"
output="$6"
digest="$7"
instruction="Your context is fresh. Read instructions from $prompt. Read only input artifact $input. Write only output artifact $output. Its frontmatter MUST declare round: 2, giro: $turn, ruolo: $role, provider: $provider, input_digest: sha256:$digest. Do not edit openspec/PLAN.md."
if [ "$provider" = "codex" ]; then
PLAN_JUDGE_PROVIDER="$provider" PLAN_JUDGE_ROLE="$role" \
PLAN_JUDGE_TURN="$turn" PLAN_JUDGE_PROMPT="$prompt" \
PLAN_JUDGE_INPUT="$input" PLAN_JUDGE_OUTPUT="$output" \
PLAN_JUDGE_EXPECTED_DIGEST="sha256:$digest" \
compose_exec "$CODEX_BIN" exec "$instruction"
else
PLAN_JUDGE_PROVIDER="$provider" PLAN_JUDGE_ROLE="$role" \
PLAN_JUDGE_TURN="$turn" PLAN_JUDGE_PROMPT="$prompt" \
PLAN_JUDGE_INPUT="$input" PLAN_JUDGE_OUTPUT="$output" \
PLAN_JUDGE_EXPECTED_DIGEST="sha256:$digest" \
compose_exec "$CLAUDE_BIN" -p "$instruction"
fi
}
validate_document() {
file="$1"
awk '
function valid_choice() {
return has_source && has_reason && has_goal
}
/^##[[:space:]]+Choice:/ {
if (in_choice && !valid_choice()) exit 1
in_choice = 1
choices++
has_source = has_reason = has_goal = 0
next
}
in_choice && /^[[:space:]]*[-*]?[[:space:]]*Source:[[:space:]]*[^[:space:]]/ { has_source = 1 }
in_choice && /^[[:space:]]*[-*]?[[:space:]]*Reason:[[:space:]]*[^[:space:]]/ { has_reason = 1 }
in_choice && /^[[:space:]]*[-*]?[[:space:]]*Goal advantage:[[:space:]]*[^[:space:]]/ { has_goal = 1 }
END { if (!choices || !valid_choice()) exit 1 }
' "$file" || fatal "INCOMPLETE-CHOICE: every technical choice needs Source, Reason and Goal advantage"
}
[ -f "$INPUT_PLAN" ] || fatal "missing rewritten Round 1 plan: $INPUT_PLAN"
for prompt in research.md verify.md writer.md goal-review.md; do
[ -f "$ROUND_DIR/$prompt" ] || fatal "missing Round 2 prompt: $prompt"
done
preflight
mkdir -p "$RUN_DIR"
seed="$RUN_DIR/input-0.md"
cp "$INPUT_PLAN" "$seed"
current_input="$seed"
roles=(research verify writer goal-review)
# Each provider takes one producing role (research, writer) and one checking
# role (verify, goal-review): giving both producing roles to one provider would
# put only that model's generative output into the document, leaving the other
# to object without ever proposing an alternative.
providers=(codex claude claude codex)
prompts=(research.md verify.md writer.md goal-review.md)
for index in 0 1 2 3; do
turn=$((index + 1))
role="${roles[$index]}"
provider="${providers[$index]}"
prompt="$ROUND_DIR/${prompts[$index]}"
output="$RUN_DIR/$role-$turn.md"
digest="$(sha256 "$current_input")"
run_provider "$provider" "$role" "$turn" "$prompt" "$current_input" "$output" "$digest"
validate_artifact "$output" "$turn" "$role" "$provider" "$digest"
current_input="$output"
done
final_document="$RUN_DIR/DOCUMENTOSVILUPPO.MD"
cp "$current_input" "$final_document"
validate_document "$final_document"
echo "plan-judge: Round 2 complete: $final_document".tessl-plugin
rules
skills
handoff
handoff-skill
openspec-apply-change
openspec-archive-change
openspec-explore
openspec-propose
openspec-sync-specs
plan-judge
plan-mode
prompt-engineer
prompt-loop
requirement-gathering
spec-as-source-setup
templates
openspec-schema
spec-as-source
templates
spec-ci-sync
spec-loop
spec-rebuild
spec-verify
spec-writer
work-review