Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/agent-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: Agent checks
on:
pull_request:
branches: [agents, main]
types: [opened, synchronize, reopened, ready_for_review, edited]
push:
branches: [agents]
permissions:
Expand All @@ -27,3 +28,5 @@ jobs:
run: python -m unittest discover -s experiments/command_specialist -p test_bindings.py -v
- name: Native and PowerShell contract invariants
run: python -m unittest discover -s experiments/command_specialist -p test_contract.py -v
- name: Frozen main candidate and patch notes
run: python scripts/check_release.py
32 changes: 22 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
branch. Jon authorized agents-branch automation on September 10, 2026: agents may
prepare owned changes, verify them, open ready PRs against `agents`, and merge them
when the gates below pass. This replaces per-PR human merge requests for `agents`.
Merging into `main`, publication, deployments, model/weight changes, and production
data changes still require explicit approval for the concrete batch.
Only Jon personally merges release PRs into `main`. Agents must never merge main,
enable its auto-merge, or push directly to it, even after chat approval. Publication,
deployments, model/weight changes, and production data changes need separate approval.
This agents automation applies to every agent acting for a maintainer with live
repository write, maintain, or admin permission; no particular model/host is privileged.

Start from fetched `agents` in an owned worktree. Preserve other sessions and reuse
existing PRs for the same work. Run commands from the repository root with Python
Expand All @@ -27,8 +30,11 @@ into both branches and pushes to `agents`. It runs on a hosted Windows runner wi
read-only permissions and no persisted checkout credentials. PR code never receives
a privileged merge token. `agents` requires this GitHub Actions check and an up-to-date PR, prevents force
pushes/deletion, and applies protection to administrators. No approving review is
required for `agents`. Main protection is pending Jon's choice of GitHub review
enforcement versus explicit chat approval; do not claim it is server-enforced.
required for `agents`. Main requires the same CI/PR gate; Jon performs the merge
himself. A separate approving reviewer is not required because Jon authors these
PRs through the same account. The platform cannot distinguish Jon from an agent
using his credentials; the human-only merge rule is enforced by this workflow and
the coordinator's unconditional refusal of main.

The local coordinator is `scripts/integrate_agents.py`. After reviewing the diff,
verifying the current head locally, and waiting for CI, run:
Expand All @@ -37,14 +43,19 @@ verifying the current head locally, and waiting for CI, run:
python scripts/integrate_agents.py --pr <number> --verified-head <verified-head>
```

It accepts only ready same-repository PRs authored by Noisemaker111 targeting
`agents`; checks the exact head, workflow identity/result, and mergeability; and
It accepts only ready same-repository PRs targeting `agents` when both the PR author
and the acting account have live write/maintain/admin permission; checks the exact head, workflow identity/result, and mergeability; and
merges with GitHub's expected-head condition. It refuses `main`. `--check-only`
checks eligibility without merging. Invoke it from a trusted checkout; passing a
head is the agent's attestation of actual local verification, not proof supplied
by CI. This is agent-operated automation, not an installed unattended scheduler.
by CI. Every maintainer's agent uses this same automatic integration path; it does not
require Jon to run the command. It is agent-operated, not an unattended scheduler.
The agent continues through merge and merged-revision verification without asking
Jon for routine agents integration approval.
Jon for routine agents integration approval. Each successful merge automatically
runs `scripts/prepare_release.py`: it generates patch notes from merged change
titles and opens a frozen main release PR. An existing open release batch is
preserved; later agents changes wait for the next batch. Release preparation may
be resumed with `python scripts/prepare_release.py`; this never merges main.

This repository is a local skill/CLI, with no hosted frontend, backend, database,
queue, production service, or deployment trigger configured here. Dev validation
Expand All @@ -57,8 +68,9 @@ reporting dev verification.
For stable promotion, branch a frozen release candidate from a verified agents
revision and open a ready PR into `main`. Include the full diff, user-facing notes,
verification evidence, and rollback target (the prior main revision). Later agents
changes must not join that candidate silently. Ask Jon to approve that concrete
batch before merging. The agents GitHub PR/check gates are enforced, but this account is shared
changes must not join that candidate silently. CI checks the candidate identity
and generated patch notes via `scripts/check_release.py`, in addition to core
checks. Ask Jon to merge the CI-green release PR himself; agents stop before main. The agents GitHub PR/check gates are enforced, but this account is shared
by Jon and agents: human authorization is a workflow rule, not an independently
verified GitHub reviewer identity. The coordinator cannot merge stable. No package
or runtime deployment is implied by either merge.
25 changes: 25 additions & 0 deletions scripts/check_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Validate that a main PR is an explicitly pinned release with patch notes."""
import json
import os
from pathlib import Path
import re


def validate(pr):
if pr['base']['ref'] != 'main':
return
if not pr['head']['ref'].startswith('release/agents-'):
raise ValueError('Main accepts a frozen agents release candidate; prepare its release PR')
marker = f"<!-- release-candidate: {pr['head']['sha']} -->"
body = pr.get('body') or ''
if marker not in body or '## Patch notes' not in body or not re.search(r'^- .+', body, re.MULTILINE):
raise ValueError('Patch notes and exact candidate identity must be present in the release PR')
if not re.search(r'<!-- release-base: [0-9a-f]{40} -->', body):
raise ValueError('Release must record the prior stable source')


if __name__ == '__main__':
event = json.loads(Path(os.environ['GITHUB_EVENT_PATH']).read_text(encoding='utf-8'))
if 'pull_request' in event:
validate(event['pull_request'])
print('Release candidate metadata checked; only Jon may merge main.')
23 changes: 17 additions & 6 deletions scripts/integrate_agents.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Integrate a locally verified, CI-green owner PR into agents; never main.
"""Integrate a locally verified, CI-green maintainer PR into agents; never main.

Run from a trusted agents checkout after inspecting the diff and completing the
real user-operation check. No PR code is downloaded or executed by this tool.
Expand All @@ -9,19 +9,25 @@
import subprocess

REPO = "Noisemaker111/shell-forensics"
OWNER = "Noisemaker111"
MAINTAINER_PERMISSIONS = {"write", "maintain", "admin"}


def gh(*args):
result = subprocess.run(["gh", *args], check=True, capture_output=True, text=True, encoding="utf-8", timeout=60)
return json.loads(result.stdout) if result.stdout.strip() else None


def require_maintainer(login):
permission = gh("api", f"repos/{REPO}/collaborators/{login}/permission")["permission"]
if permission not in MAINTAINER_PERMISSIONS:
raise ValueError(f"{login} does not have repository write/maintain/admin permission")


def eligible(pr, verified_head):
if pr["baseRefName"] != "agents":
raise ValueError("Only agents PRs may be integrated; main needs Jon's explicit batch approval")
if pr["state"] != "OPEN" or pr["isDraft"] or pr["isCrossRepository"] or pr["author"]["login"].lower() != OWNER.lower():
raise ValueError("Require an open, ready, same-repository owner PR")
raise ValueError("Only agents PRs may be integrated; only Jon may perform the main merge")
if pr["state"] != "OPEN" or pr["isDraft"] or pr["isCrossRepository"]:
raise ValueError("Require an open, ready, same-repository maintainer PR")
if pr["headRefOid"] != verified_head:
raise ValueError("Head changed or was not locally verified; recheck the actual user operation")
if pr["mergeable"] != "MERGEABLE" or pr["mergeStateStatus"] != "CLEAN":
Expand All @@ -44,6 +50,8 @@ def main():
fields = "state,isDraft,isCrossRepository,author,baseRefName,headRefOid,mergeable,mergeStateStatus,statusCheckRollup"
pr = gh("pr", "view", str(args.pr), "--repo", REPO, "--json", fields)
checks = eligible(pr, args.verified_head)
require_maintainer(pr["author"]["login"])
require_maintainer(gh("api", "user")["login"])
for check in checks:
match = re.fullmatch(r"https://github\.com/Noisemaker111/shell-forensics/actions/runs/(\d+)/job/\d+", check.get("detailsUrl", ""))
if not match:
Expand All @@ -62,7 +70,10 @@ def main():
receipt = gh("pr", "view", str(args.pr), "--repo", REPO, "--json", "state,baseRefName,mergeCommit,url")
if receipt["state"] != "MERGED" or receipt["baseRefName"] != "agents":
raise RuntimeError("Merge receipt did not confirm agents integration")
print(json.dumps(receipt))
print(json.dumps(receipt), flush=True)
# Prepare the human's next release batch; this helper never merges main.
from prepare_release import prepare
print(json.dumps(prepare(receipt["mergeCommit"]["oid"])))


if __name__ == "__main__":
Expand Down
61 changes: 61 additions & 0 deletions scripts/prepare_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Generate patch notes and a frozen release PR; only Jon merges main."""
import argparse
import json
from pathlib import Path
import re
import subprocess

REPO = "Noisemaker111/shell-forensics"


def command(*args):
return subprocess.run(args, check=True, capture_output=True, text=True, encoding="utf-8", timeout=60).stdout.strip()


def prepare(candidate=None):
command("git", "fetch", "origin", "agents", "main")
candidate = candidate or command("git", "rev-parse", "origin/agents")
if not re.fullmatch(r"[0-9a-f]{40}", candidate):
raise ValueError("candidate must be a full commit ID")
command("git", "merge-base", "--is-ancestor", candidate, "origin/agents")
stable = command("git", "rev-parse", "origin/main")
command("git", "merge-base", "--is-ancestor", stable, candidate)
if not command("git", "diff", "--name-only", stable, candidate):
return {"release": "no changes"}
existing = json.loads(command("gh", "pr", "list", "--repo", REPO, "--base", "main", "--state", "open", "--json", "headRefName,url"))
releases = [p for p in existing if p["headRefName"].startswith("release/agents-")]
if releases:
return {"release": "existing batch preserved for Jon", "url": releases[0]["url"]}
subjects = command("git", "log", "--first-parent", "--reverse", "--format=%s", f"{stable}..{candidate}").splitlines()
if not subjects:
raise ValueError("No release changes were found")
notes = (f"<!-- release-candidate: {candidate} -->\n<!-- release-base: {stable} -->\n"
"## Patch notes\n\n" + "\n".join(f"- {subject}" for subject in subjects) +
f"\n\n[Full release diff](https://github.com/{REPO}/compare/{stable}...{candidate})\n\n"
"## Merge and verification\n\nOnly Jon merges this PR into main. Agents must not enable auto-merge or perform the stable merge. "
"This candidate is frozen; later agents changes belong to a later batch.\n\n"
"Required CI runs the Windows binding/contract checks, syntax validation, and frozen-candidate metadata check. "
"Review the candidate's included PRs for their affected-user-operation evidence; hosted CI does not run the private model. "
"These patch notes are generated from merged change titles and do not imply additional performance or deployment validation.\n\n"
f"Previous stable source / rollback target: `{stable}`. No deployment, package publication, or data migration is performed by this helper.\n")
out = Path("work/releases") / candidate
out.mkdir(parents=True, exist_ok=True)
body = out / "patch-notes.md"
body.write_text(notes, encoding="utf-8")
branch = f"release/agents-{candidate[:12]}"
remote = command("git", "ls-remote", "origin", f"refs/heads/{branch}")
if remote and remote.split()[0] != candidate:
raise ValueError("Release branch already points elsewhere; refusing to move it")
if not remote:
command("git", "push", "origin", f"{candidate}:refs/heads/{branch}")
url = command("gh", "pr", "create", "--repo", REPO, "--base", "main", "--head", branch,
"--title", "Release verified agents changes to main", "--body-file", str(body))
receipt = {"release": "ready for Jon after CI", "url": url, "candidate": candidate, "notes": str(body.resolve())}
(out / "receipt.json").write_text(json.dumps(receipt, indent=2), encoding="utf-8")
return receipt


if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--candidate")
print(json.dumps(prepare(parser.parse_args().candidate)))
Loading