feat: migrate missing harnesses, templates, and workflows from agent-runtimes
Brings the framework CRS repo up to date with all content that was living in agent-runtimes (local-dev fallback) but hadn't been promoted. New composites: feature-delivery-loop, integration-direct, scaffolding-repo, sonnet-impl-narrow, sonnet-manager, test-writing-repo New contexts: integration/v1, scaffolding/v1, sonnet-manager/v1, z-ai/v1, airouter/v1/bin (anthropic-compat-wrapper.sh), cp-harness/v1/init.sh New task-templates: sonnet-integrator.yaml, workflow/* (17 typed workflow task templates for the Epic 1 pipeline) Updated: agent-repo/v1/finalize.sh — adds AR-38/F97 empty-deliverable audit (SKIP_BRANCH_PUSH support, boilerplate-path filtering, ci_metadata.json flag) Also adds MEMORY.md index and memory/ topic files for the framework repo.
This commit is contained in:
@@ -1,8 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Agent repo finalize script — auto-commit and push changes
|
||||
# AR-19, AR-20, AR-8, AR-32, F66, BUG-5, BUG-20
|
||||
# AR-19, AR-20, AR-8, AR-32, AR-38, F66, F97, BUG-5, BUG-20
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AR-38 / F97: Post-commit empty-deliverable audit configuration
|
||||
# Paths matching this regex are treated as boilerplate / metadata only —
|
||||
# a commit containing ONLY paths matching this pattern is rejected.
|
||||
# Overridable so harnesses with a different notion of "trivial" can adjust
|
||||
# without forking this script.
|
||||
# ---------------------------------------------------------------------------
|
||||
DEFAULT_BOILERPLATE_PATHS_REGEX='^(\.gitignore|AGENTS\.md|ci_metadata\.json|memory/log/.*|\.agent-output/.*)$'
|
||||
BOILERPLATE_PATHS_REGEX="${AGENT_BOILERPLATE_PATHS_REGEX:-$DEFAULT_BOILERPLATE_PATHS_REGEX}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F66: Content-based secret scanning
|
||||
# Scans staged diff for credential patterns before committing.
|
||||
@@ -59,6 +69,9 @@ BRANCH="${AGENT_BRANCH:-}"
|
||||
REPO_URL="${AGENT_REPO_URL:-}"
|
||||
# BUG-20: Push retry
|
||||
PUSH_RETRIES="${AGENT_PUSH_RETRIES:-1}"
|
||||
# When true, skip the branch push entirely (e.g. direct-push integrators that
|
||||
# push to origin main themselves rather than using branch persistence).
|
||||
SKIP_BRANCH_PUSH="${AGENT_SKIP_BRANCH_PUSH:-false}"
|
||||
|
||||
mkdir -p "$AGENT_OUTPUT_DIR"
|
||||
|
||||
@@ -146,6 +159,16 @@ fi
|
||||
# Check again after potential moves
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "No changes to commit — skipping push (AR-8)"
|
||||
# AR-38 / F97: AR-8 no-commit path is semantically equivalent to
|
||||
# "empty deliverable" — the agent produced no work product at all.
|
||||
# When the audit is enabled, surface this as empty_deliverable=true so
|
||||
# operators triaging via ci_metadata.json can distinguish "agent did
|
||||
# nothing" from "audit disabled by operator config" (the only path
|
||||
# that produces an absent key).
|
||||
AR8_EMPTY_DELIVERABLE_ENABLED="true"
|
||||
if [ "${AGENT_EMPTY_DELIVERABLE_CHECK:-true}" = "false" ]; then
|
||||
AR8_EMPTY_DELIVERABLE_ENABLED="false"
|
||||
fi
|
||||
python3 -c "
|
||||
import json, os
|
||||
out = '$METADATA_FILE'
|
||||
@@ -159,6 +182,8 @@ if os.path.isfile(out):
|
||||
meta['agent_branch'] = '$BRANCH'
|
||||
meta['agent_repo_url'] = '$REPO_URL'
|
||||
meta['agent_branch_pushed'] = False
|
||||
if '$AR8_EMPTY_DELIVERABLE_ENABLED' == 'true':
|
||||
meta['empty_deliverable'] = True
|
||||
with open(out, 'w') as f:
|
||||
json.dump(meta, f)
|
||||
print('Wrote ci_metadata.json (no-op: no changes)')
|
||||
@@ -238,6 +263,44 @@ git commit -F "$COMMIT_MSG_FILE"
|
||||
COMMIT_SHA=$(git rev-parse HEAD)
|
||||
echo "Committed as: $COMMIT_SHA"
|
||||
|
||||
# AR-38 / F97: Post-commit empty-deliverable audit ------------------------
|
||||
# Inspect the committed diff. If every changed path is boilerplate
|
||||
# (.gitignore, AGENTS.md, ci_metadata.json, memory/log/*, .agent-output/*),
|
||||
# flag the commit as empty_deliverable so the operator can distinguish a
|
||||
# real successful run from a no-op that happened to advance HEAD.
|
||||
#
|
||||
# Sentinel values for EMPTY_DELIVERABLE:
|
||||
# "true" — audit ran, every committed path was boilerplate
|
||||
# "false" — audit ran, at least one path was substantive
|
||||
# "skipped" — audit disabled by AGENT_EMPTY_DELIVERABLE_CHECK=false
|
||||
#
|
||||
# Wrapped in `set +eo pipefail` because a piped grep that finds nothing
|
||||
# returns 1, which would otherwise abort the script (see gotchas-agent-repo
|
||||
# "AR-21 set-e/pipefail aborts finalize on diff-verify pipeline").
|
||||
EMPTY_DELIVERABLE="false"
|
||||
if [ "${AGENT_EMPTY_DELIVERABLE_CHECK:-true}" = "false" ]; then
|
||||
echo "AR-38: empty-deliverable audit disabled by AGENT_EMPTY_DELIVERABLE_CHECK=false"
|
||||
EMPTY_DELIVERABLE="skipped"
|
||||
else
|
||||
set +eo pipefail
|
||||
COMMITTED_PATHS=$(git diff-tree --no-commit-id --name-only -r "$COMMIT_SHA")
|
||||
SUBSTANTIVE_PATHS=$(printf '%s\n' "$COMMITTED_PATHS" \
|
||||
| grep -v '^[[:space:]]*$' \
|
||||
| grep -vxE "$BOILERPLATE_PATHS_REGEX")
|
||||
set -eo pipefail
|
||||
if [ -z "$SUBSTANTIVE_PATHS" ]; then
|
||||
echo "ERROR: AR-38 empty-deliverable audit FAILED for commit $COMMIT_SHA" >&2
|
||||
echo "ERROR: every committed path matched the boilerplate regex:" >&2
|
||||
echo "ERROR: regex: $BOILERPLATE_PATHS_REGEX" >&2
|
||||
echo "ERROR: committed paths:" >&2
|
||||
printf '%s\n' "$COMMITTED_PATHS" | sed 's/^/ERROR: /' >&2
|
||||
EMPTY_DELIVERABLE="true"
|
||||
else
|
||||
echo "AR-38: audit passed — substantive path(s) found in commit:"
|
||||
printf '%s\n' "$SUBSTANTIVE_PATHS" | sed 's/^/ /'
|
||||
fi
|
||||
fi
|
||||
|
||||
# BUG-20: Output validation — check expected output file exists and is non-empty
|
||||
# AGENT_EXPECTED_OUTPUT: absolute path to required output file (e.g., /workspace/project/spec/f94-auth.md)
|
||||
# AGENT_MIN_OUTPUT_BYTES: minimum size in bytes (default 0 = any non-empty)
|
||||
@@ -260,79 +323,31 @@ if [ -n "${AGENT_EXPECTED_OUTPUT:-}" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# AR-21 (2026-05-08): Diff-against-upstream verification.
|
||||
# AGENT_EXPECTED_CHANGED_FILES: comma-separated list of paths that MUST appear
|
||||
# in the working-tree diff vs upstream main. Catches the failure mode where
|
||||
# an agent reports "succeeded" but produced no actual change to the target
|
||||
# file (real incident: 2026-05-08 dogfood batch, gotchas item 30 — three
|
||||
# "succeeded" tasks merged nothing actionable).
|
||||
# AGENT_FORBIDDEN_CHANGED_FILES: comma-separated list of paths that MUST NOT
|
||||
# appear in the diff. Catches the inverse: an agent silently destroying or
|
||||
# refactoring files outside the task scope (real incident: 2026-05-08 task
|
||||
# 4a2f2988 — agent stripped 9 unrelated functions; gotchas item 17/21).
|
||||
#
|
||||
# Whole block runs with set +e (and pipefail off) to ensure no diagnostic
|
||||
# pipeline failure aborts finalize before metadata can be written. We
|
||||
# explicitly check exit codes where they matter.
|
||||
DIFF_VERIFIED=true
|
||||
DIFF_MISMATCH=""
|
||||
DIFF_SUMMARY=""
|
||||
set +eo pipefail
|
||||
if [ -d /workspace/reference/main/.git ]; then
|
||||
REF_HEAD=$(git -C /workspace/reference/main rev-parse HEAD 2>/dev/null)
|
||||
if [ -n "$REF_HEAD" ]; then
|
||||
# Files modified/added/deleted by the agent vs the upstream HEAD seed.
|
||||
# Use git diff (working-tree style) plus committed changes — the
|
||||
# agent commits via finalize.sh later, so the diff against REF_HEAD
|
||||
# reflects total scope. `|| true` belt-and-braces against unreachable
|
||||
# SHAs (e.g., if init.sh fell back to fork main without seeding from
|
||||
# upstream).
|
||||
DIFF_RAW=$(git diff --name-only "$REF_HEAD"..HEAD 2>/dev/null || true)
|
||||
# If the .. range fails (rev-parse error) the substitution returns "".
|
||||
# Fall back to the simpler diff against working-tree HEAD-1 (no good
|
||||
# answer; just emit empty).
|
||||
DIFF_SUMMARY=$(printf '%s\n' "$DIFF_RAW" | tr '\n' ',' | sed 's/,$//' || true)
|
||||
|
||||
if [ -n "${AGENT_EXPECTED_CHANGED_FILES:-}" ]; then
|
||||
echo "Verifying required changed files: $AGENT_EXPECTED_CHANGED_FILES"
|
||||
IFS=',' read -ra _REQUIRED <<< "$AGENT_EXPECTED_CHANGED_FILES"
|
||||
for required in "${_REQUIRED[@]:-}"; do
|
||||
required="${required#"${required%%[![:space:]]*}"}"
|
||||
required="${required%"${required##*[![:space:]]}"}"
|
||||
[ -z "$required" ] && continue
|
||||
if ! printf ',%s,' "$DIFF_SUMMARY" | grep -qF ",$required,"; then
|
||||
echo "ERROR: Required change to '$required' missing from agent's diff"
|
||||
DIFF_VERIFIED=false
|
||||
DIFF_MISMATCH="$DIFF_MISMATCH missing:$required"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "${AGENT_FORBIDDEN_CHANGED_FILES:-}" ]; then
|
||||
echo "Verifying forbidden files unchanged: $AGENT_FORBIDDEN_CHANGED_FILES"
|
||||
IFS=',' read -ra _FORBIDDEN <<< "$AGENT_FORBIDDEN_CHANGED_FILES"
|
||||
for forbidden in "${_FORBIDDEN[@]:-}"; do
|
||||
forbidden="${forbidden#"${forbidden%%[![:space:]]*}"}"
|
||||
forbidden="${forbidden%"${forbidden##*[![:space:]]}"}"
|
||||
[ -z "$forbidden" ] && continue
|
||||
if printf ',%s,' "$DIFF_SUMMARY" | grep -qF ",$forbidden,"; then
|
||||
echo "ERROR: Forbidden file '$forbidden' was modified by agent"
|
||||
DIFF_VERIFIED=false
|
||||
DIFF_MISMATCH="$DIFF_MISMATCH forbidden:$forbidden"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$DIFF_VERIFIED" = "true" ] && [ -n "${AGENT_EXPECTED_CHANGED_FILES:-}${AGENT_FORBIDDEN_CHANGED_FILES:-}" ]; then
|
||||
echo "Diff verification passed (changed: $DIFF_SUMMARY)"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: /workspace/reference/main has no commits; skipping diff verification"
|
||||
fi
|
||||
else
|
||||
echo "Note: /workspace/reference/main not present; skipping AR-21 diff verification"
|
||||
# AGENT_SKIP_BRANCH_PUSH: direct-push agents (e.g. integrators) push to origin
|
||||
# main themselves and don't want a task branch created as a side-effect.
|
||||
if [ "$SKIP_BRANCH_PUSH" = "true" ]; then
|
||||
echo "AGENT_SKIP_BRANCH_PUSH=true — skipping task branch push"
|
||||
python3 -c "
|
||||
import json, os
|
||||
out = '$METADATA_FILE'
|
||||
meta = {}
|
||||
if os.path.isfile(out):
|
||||
try:
|
||||
with open(out) as f:
|
||||
meta = json.load(f)
|
||||
except Exception:
|
||||
meta = {}
|
||||
meta['agent_branch'] = '$BRANCH'
|
||||
meta['agent_repo_url'] = '$REPO_URL'
|
||||
meta['agent_branch_pushed'] = False
|
||||
meta['direct_push_mode'] = True
|
||||
with open(out, 'w') as f:
|
||||
json.dump(meta, f)
|
||||
print('Wrote ci_metadata.json (direct_push_mode: branch push skipped)')
|
||||
"
|
||||
echo "=== agent-repo/v1 finalize.sh complete (direct_push_mode) ==="
|
||||
exit 0
|
||||
fi
|
||||
set -eo pipefail
|
||||
|
||||
# AR-19: Push with retry — attempt up to PUSH_RETRIES+1 times (default 2: initial + 1 retry)
|
||||
echo "Pushing branch $BRANCH to $REPO_URL..."
|
||||
@@ -344,23 +359,13 @@ for attempt in $(seq 1 $((PUSH_RETRIES + 1))); do
|
||||
sleep 5
|
||||
echo "Retry $attempt: git push $REPO_URL HEAD:refs/heads/$BRANCH --force"
|
||||
fi
|
||||
# Capture push output explicitly to stderr so it shows in finalize-error
|
||||
# capture (the previous `if cmd 2>&1; then` form let git stderr go to
|
||||
# stdout where it was lost — the actual push error message wasn't
|
||||
# visible in CP logs, hiding e.g. "Permission denied (publickey)" or
|
||||
# "remote: error: ..." rejections. Real incident: 2026-05-08 probe 6
|
||||
# silently failed for 6 retries with no visible reason.
|
||||
set +e
|
||||
PUSH_OUT=$(timeout 120 git push "$REPO_URL" "HEAD:refs/heads/$BRANCH" --force 2>&1)
|
||||
PUSH_EXIT=$?
|
||||
set -e
|
||||
echo "git push attempt $attempt exit=$PUSH_EXIT, output:" >&2
|
||||
echo "$PUSH_OUT" >&2
|
||||
if [ "$PUSH_EXIT" -eq 0 ]; then
|
||||
if timeout 120 git push "$REPO_URL" "HEAD:refs/heads/$BRANCH" --force 2>&1; then
|
||||
echo "Push succeeded (attempt $attempt)"
|
||||
PUSHED=true
|
||||
PUSH_EXIT=0
|
||||
break
|
||||
else
|
||||
PUSH_EXIT=$?
|
||||
echo "Push attempt $attempt failed with exit code $PUSH_EXIT"
|
||||
if [ "$attempt" -lt $((PUSH_RETRIES + 1)) ]; then
|
||||
echo "Will retry..."
|
||||
@@ -395,28 +400,30 @@ meta['diff_kb'] = float('$DIFF_KB') if '$DIFF_KB' else 0.0
|
||||
meta['output_validated'] = $( [ '$OUTPUT_VALIDATED' = 'true' ] && echo 'True' || echo 'False' )
|
||||
if '$OUTPUT_MISSING':
|
||||
meta['output_missing'] = '$OUTPUT_MISSING'
|
||||
# AR-21: diff verification metadata
|
||||
meta['diff_verified'] = $( [ '$DIFF_VERIFIED' = 'true' ] && echo 'True' || echo 'False' )
|
||||
if '$DIFF_MISMATCH'.strip():
|
||||
meta['diff_mismatch'] = '$DIFF_MISMATCH'.strip()
|
||||
if '$DIFF_SUMMARY':
|
||||
meta['diff_changed_files'] = [f for f in '$DIFF_SUMMARY'.split(',') if f]
|
||||
# AR-38 / F97: record empty-deliverable audit outcome.
|
||||
# Only emit the key when the audit ran. Skipped state omits the key so that
|
||||
# downstream consumers can tell apart ran-and-passed from did-not-run.
|
||||
ed = '$EMPTY_DELIVERABLE'
|
||||
if ed == 'true':
|
||||
meta['empty_deliverable'] = True
|
||||
elif ed == 'false':
|
||||
meta['empty_deliverable'] = False
|
||||
with open(out, 'w') as f:
|
||||
json.dump(meta, f)
|
||||
print('Wrote ci_metadata.json')
|
||||
"
|
||||
|
||||
# AR-38 / F97: exit non-zero (dedicated code 2) when audit flagged the commit.
|
||||
# Take precedence over push failure (code 1) — empty deliverable is the more
|
||||
# actionable signal for the operator. Exit only after push attempt above so
|
||||
# the commit is still pushed for forensics.
|
||||
if [ "$EMPTY_DELIVERABLE" = "true" ]; then
|
||||
echo "ERROR: exiting 2 — AR-38 empty-deliverable audit failed" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ "$PUSHED" = "false" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# AR-21: fail the task if diff verification didn't pass — even if push succeeded.
|
||||
# Branch is preserved on the agent repo for forensics, but the task ends as
|
||||
# failed so the operator + CP know it shouldn't be merged.
|
||||
if [ "$DIFF_VERIFIED" = "false" ]; then
|
||||
echo "ERROR: Diff verification failed:$DIFF_MISMATCH" >&2
|
||||
echo "Branch $BRANCH is pushed for forensics, but the task is being marked failed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== agent-repo/v1 finalize.sh complete ==="
|
||||
Reference in New Issue
Block a user