@prism-d1/cli 1.0.84 → 1.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/coding-agent/README.md +824 -0
- package/dist/assets/coding-agent/agent.py +381 -0
- package/dist/assets/coding-agent/config.py +338 -0
- package/dist/assets/coding-agent/deploy/Dockerfile +86 -0
- package/dist/assets/coding-agent/deploy/create_harness.py +144 -0
- package/dist/assets/coding-agent/deploy/deploy-harness.sh +201 -0
- package/dist/assets/coding-agent/deploy/prism-coding-agent.yml +399 -0
- package/dist/assets/coding-agent/eval/run_eval.py +430 -0
- package/dist/assets/coding-agent/eval/run_harness_eval.py +262 -0
- package/dist/assets/coding-agent/eval/stubs/001-tags-element-validation.json +11 -0
- package/dist/assets/coding-agent/eval/stubs/002-status-filter.json +11 -0
- package/dist/assets/coding-agent/eval/stubs/003-refuse-test-deletion.json +11 -0
- package/dist/assets/coding-agent/fixtures/001-tags-element-validation.json +12 -0
- package/dist/assets/coding-agent/fixtures/002-status-filter.json +12 -0
- package/dist/assets/coding-agent/fixtures/003-refuse-test-deletion.json +10 -0
- package/dist/assets/coding-agent/pyproject.toml +28 -0
- package/dist/assets/coding-agent/requirements.txt +8 -0
- package/dist/assets/coding-agent/system_prompt.py +290 -0
- package/dist/assets/coding-agent/tools/__init__.py +11 -0
- package/dist/assets/coding-agent/tools/create_pr.py +141 -0
- package/dist/assets/coding-agent/tools/git_ops.py +197 -0
- package/dist/assets/github-workflows/prism-agent-eval.yml +2 -1
- package/dist/assets/github-workflows/prism-ai-metrics.yml +12 -3
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +4 -3
- package/dist/assets/github-workflows/prism-eval-gate.yml +3 -2
- package/dist/assets/gitlab-workflows/prism-agent-eval.yml +2 -1
- package/dist/assets/gitlab-workflows/prism-ai-metrics.yml +3 -2
- package/dist/assets/gitlab-workflows/prism-eval-gate.yml +2 -1
- package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +134 -2
- package/dist/assets/infra/lib/dashboard-stack.ts +79 -95
- package/dist/assets/infra/lib/lambda/metrics-processor.ts +99 -1
- package/dist/assets/infra/lib/lambda/otel-receiver.ts +318 -29
- package/dist/assets/infra/lib/lambda/productivity-widget.ts +85 -4
- package/dist/assets/infra/lib/lambda/velocity-widget.ts +265 -0
- package/dist/assets/infra/lib/metrics-pipeline-stack.ts +5 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.d.ts +61 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.d.ts.map +1 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.js +599 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.js.map +1 -0
- package/dist/src/commands/bootstrapper/setup-github-oidc.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-github-oidc.js +23 -0
- package/dist/src/commands/bootstrapper/setup-github-oidc.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# deploy-harness.sh — build the agent image and create the AgentCore harness in
|
|
4
|
+
# your own AWS account. Run once per account/region, not per repository.
|
|
5
|
+
#
|
|
6
|
+
# Every repository that installs the coding agent then points at the one harness
|
|
7
|
+
# ARN this prints, via the PRISM_HARNESS_ARN org variable. Nothing about the agent
|
|
8
|
+
# is copied into those repositories.
|
|
9
|
+
#
|
|
10
|
+
# ./deploy-harness.sh --account 123456789012 --region us-west-2
|
|
11
|
+
# ./deploy-harness.sh --profile my-dev --tag v2 # rebuild and update
|
|
12
|
+
#
|
|
13
|
+
# Idempotent: re-running rebuilds the image, pushes a new tag, and updates the
|
|
14
|
+
# existing harness rather than creating a second one.
|
|
15
|
+
#
|
|
16
|
+
# WHY THIS IS A SCRIPT AND NOT A CDK STACK
|
|
17
|
+
# CloudFormation has no resource type for an AgentCore harness, and the AWS CLI
|
|
18
|
+
# has no `create-harness` (checked on 2.36.19 — the operation exists only in
|
|
19
|
+
# boto3). So the create/update call goes through a small inline Python block.
|
|
20
|
+
# When a CFN resource type ships, this becomes a stack.
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
AGENT_DIR="$(dirname "${HERE}")"
|
|
26
|
+
|
|
27
|
+
REGION="us-west-2"
|
|
28
|
+
ACCOUNT=""
|
|
29
|
+
PROFILE=""
|
|
30
|
+
TAG="v1"
|
|
31
|
+
NAME="PrismCodingAgent"
|
|
32
|
+
ECR_REPO="prism/coding-agent-harness"
|
|
33
|
+
ROLE_NAME="PrismCodingAgentHarnessRole"
|
|
34
|
+
MODEL_ID="us.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
35
|
+
# Matches agentcore.contract.MAX_ITERATIONS. The client passes maxIterations on
|
|
36
|
+
# every InvokeHarness call, so this is only the fallback for callers that do not.
|
|
37
|
+
MAX_ITERATIONS=100
|
|
38
|
+
TIMEOUT_SECONDS=1800
|
|
39
|
+
|
|
40
|
+
usage() {
|
|
41
|
+
sed -n '2,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
42
|
+
exit "${1:-0}"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
while [[ $# -gt 0 ]]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--region) REGION="$2"; shift 2 ;;
|
|
48
|
+
--account) ACCOUNT="$2"; shift 2 ;;
|
|
49
|
+
--profile) PROFILE="$2"; shift 2 ;;
|
|
50
|
+
--tag) TAG="$2"; shift 2 ;;
|
|
51
|
+
--name) NAME="$2"; shift 2 ;;
|
|
52
|
+
--model) MODEL_ID="$2"; shift 2 ;;
|
|
53
|
+
-h|--help) usage 0 ;;
|
|
54
|
+
*) echo "Unknown argument: $1" >&2; usage 1 ;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
[[ -n "${PROFILE}" ]] && export AWS_PROFILE="${PROFILE}"
|
|
59
|
+
export AWS_DEFAULT_REGION="${REGION}"
|
|
60
|
+
|
|
61
|
+
for tool in aws docker python3; do
|
|
62
|
+
command -v "${tool}" >/dev/null || { echo "Missing required tool: ${tool}" >&2; exit 1; }
|
|
63
|
+
done
|
|
64
|
+
python3 -c 'import boto3' 2>/dev/null || {
|
|
65
|
+
echo "boto3 is required (the AWS CLI has no create-harness): pip install boto3" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if [[ -z "${ACCOUNT}" ]]; then
|
|
70
|
+
ACCOUNT="$(aws sts get-caller-identity --query Account --output text)"
|
|
71
|
+
fi
|
|
72
|
+
REGISTRY="${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com"
|
|
73
|
+
IMAGE="${REGISTRY}/${ECR_REPO}:${TAG}"
|
|
74
|
+
|
|
75
|
+
echo "Account : ${ACCOUNT}"
|
|
76
|
+
echo "Region : ${REGION}"
|
|
77
|
+
echo "Image : ${IMAGE}"
|
|
78
|
+
echo "Harness : ${NAME}"
|
|
79
|
+
echo
|
|
80
|
+
|
|
81
|
+
# ---- 1. ECR repository -------------------------------------------------------
|
|
82
|
+
echo "==> ECR repository"
|
|
83
|
+
if ! aws ecr describe-repositories --repository-names "${ECR_REPO}" >/dev/null 2>&1; then
|
|
84
|
+
aws ecr create-repository \
|
|
85
|
+
--repository-name "${ECR_REPO}" \
|
|
86
|
+
--image-scanning-configuration scanOnPush=true \
|
|
87
|
+
--encryption-configuration encryptionType=AES256 >/dev/null
|
|
88
|
+
echo " created ${ECR_REPO}"
|
|
89
|
+
else
|
|
90
|
+
echo " ${ECR_REPO} exists"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ---- 2. Build and push (arm64) ----------------------------------------------
|
|
94
|
+
# AgentCore runs arm64. Building the host's native arch on an x86 laptop produces
|
|
95
|
+
# an image the harness cannot start, and the failure surfaces at invoke time
|
|
96
|
+
# rather than at build time -- so the platform is pinned explicitly.
|
|
97
|
+
echo "==> Build and push (linux/arm64)"
|
|
98
|
+
aws ecr get-login-password --region "${REGION}" \
|
|
99
|
+
| docker login --username AWS --password-stdin "${REGISTRY}" >/dev/null
|
|
100
|
+
docker buildx build \
|
|
101
|
+
--platform linux/arm64 \
|
|
102
|
+
--tag "${IMAGE}" \
|
|
103
|
+
--file "${HERE}/Dockerfile" \
|
|
104
|
+
--push \
|
|
105
|
+
"${HERE}"
|
|
106
|
+
echo " pushed ${IMAGE}"
|
|
107
|
+
|
|
108
|
+
# ---- 3. Execution role -------------------------------------------------------
|
|
109
|
+
echo "==> Execution role ${ROLE_NAME}"
|
|
110
|
+
TRUST='{"Version":"2012-10-17","Statement":[{"Effect":"Allow",
|
|
111
|
+
"Principal":{"Service":"bedrock-agentcore.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
|
|
112
|
+
|
|
113
|
+
# The memory statement is the one that is easy to miss and hard to diagnose. A
|
|
114
|
+
# harness provisions its own AgentCore Memory resource on first invocation, so the
|
|
115
|
+
# actions cannot be scoped to an ARN that exists when the role is written --
|
|
116
|
+
# omitting them fails the first real invocation as AccessDenied on ListEvents
|
|
117
|
+
# against a resource that did not exist yet, wrapped in a runtimeClientError.
|
|
118
|
+
POLICY=$(cat <<JSON
|
|
119
|
+
{"Version":"2012-10-17","Statement":[
|
|
120
|
+
{"Sid":"EcrPull","Effect":"Allow","Action":[
|
|
121
|
+
"ecr:GetAuthorizationToken","ecr:BatchCheckLayerAvailability",
|
|
122
|
+
"ecr:GetDownloadUrlForLayer","ecr:BatchGetImage"],"Resource":"*"},
|
|
123
|
+
{"Sid":"Logs","Effect":"Allow","Action":[
|
|
124
|
+
"logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents",
|
|
125
|
+
"logs:DescribeLogGroups","logs:DescribeLogStreams"],
|
|
126
|
+
"Resource":"arn:aws:logs:${REGION}:${ACCOUNT}:log-group:/aws/bedrock-agentcore/*"},
|
|
127
|
+
{"Sid":"KmsForLogGroups","Effect":"Allow","Action":["kms:DescribeKey"],"Resource":"*"},
|
|
128
|
+
{"Sid":"InvokeModel","Effect":"Allow","Action":[
|
|
129
|
+
"bedrock:InvokeModel","bedrock:InvokeModelWithResponseStream",
|
|
130
|
+
"bedrock:Converse","bedrock:ConverseStream"],"Resource":"*"},
|
|
131
|
+
{"Sid":"HarnessMemory","Effect":"Allow","Action":[
|
|
132
|
+
"bedrock-agentcore:ListEvents","bedrock-agentcore:CreateEvent",
|
|
133
|
+
"bedrock-agentcore:GetEvent","bedrock-agentcore:DeleteEvent",
|
|
134
|
+
"bedrock-agentcore:ListActors","bedrock-agentcore:ListSessions",
|
|
135
|
+
"bedrock-agentcore:RetrieveMemoryRecords","bedrock-agentcore:ListMemoryRecords",
|
|
136
|
+
"bedrock-agentcore:GetMemoryRecord","bedrock-agentcore:GetMemory",
|
|
137
|
+
"bedrock-agentcore:CreateMemory","bedrock-agentcore:ListMemories"],
|
|
138
|
+
"Resource":"*"}
|
|
139
|
+
]}
|
|
140
|
+
JSON
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if ! aws iam get-role --role-name "${ROLE_NAME}" >/dev/null 2>&1; then
|
|
144
|
+
aws iam create-role --role-name "${ROLE_NAME}" \
|
|
145
|
+
--assume-role-policy-document "${TRUST}" \
|
|
146
|
+
--description "Execution role for the PRISM coding agent AgentCore harness" >/dev/null
|
|
147
|
+
echo " created ${ROLE_NAME}"
|
|
148
|
+
else
|
|
149
|
+
echo " ${ROLE_NAME} exists"
|
|
150
|
+
fi
|
|
151
|
+
aws iam put-role-policy --role-name "${ROLE_NAME}" \
|
|
152
|
+
--policy-name PrismCodingAgentHarness --policy-document "${POLICY}"
|
|
153
|
+
ROLE_ARN="arn:aws:iam::${ACCOUNT}:role/${ROLE_NAME}"
|
|
154
|
+
echo " policy applied"
|
|
155
|
+
|
|
156
|
+
# ---- 4. Create or update the harness ----------------------------------------
|
|
157
|
+
echo "==> Harness"
|
|
158
|
+
HARNESS_ARN=$(
|
|
159
|
+
AGENTCORE_REGION="${REGION}" \
|
|
160
|
+
AGENTCORE_NAME="${NAME}" \
|
|
161
|
+
AGENTCORE_IMAGE="${IMAGE}" \
|
|
162
|
+
AGENTCORE_ROLE="${ROLE_ARN}" \
|
|
163
|
+
AGENTCORE_MODEL="${MODEL_ID}" \
|
|
164
|
+
AGENTCORE_MAX_ITERATIONS="${MAX_ITERATIONS}" \
|
|
165
|
+
AGENTCORE_TIMEOUT="${TIMEOUT_SECONDS}" \
|
|
166
|
+
python3 "${HERE}/create_harness.py"
|
|
167
|
+
)
|
|
168
|
+
echo " ${HARNESS_ARN}"
|
|
169
|
+
|
|
170
|
+
# ---- 5. Write the ARN to SSM so it is discoverable without manual copying ----
|
|
171
|
+
echo "==> SSM parameter"
|
|
172
|
+
aws ssm put-parameter \
|
|
173
|
+
--name /prism/d1/harness-arn \
|
|
174
|
+
--value "${HARNESS_ARN}" \
|
|
175
|
+
--type String \
|
|
176
|
+
--overwrite \
|
|
177
|
+
--description "AgentCore harness ARN — written by deploy-harness.sh" >/dev/null
|
|
178
|
+
echo " /prism/d1/harness-arn = ${HARNESS_ARN}"
|
|
179
|
+
|
|
180
|
+
cat <<EOF
|
|
181
|
+
|
|
182
|
+
✅ Harness ready
|
|
183
|
+
|
|
184
|
+
${HARNESS_ARN}
|
|
185
|
+
|
|
186
|
+
The ARN is stored in SSM at /prism/d1/harness-arn. For the workflow's early-fail
|
|
187
|
+
check (which runs before AWS credentials are available), also set it as a GitHub
|
|
188
|
+
org variable — this is the one variable that cannot come from SSM alone:
|
|
189
|
+
|
|
190
|
+
gh variable set PRISM_HARNESS_ARN --org <your-org> \\
|
|
191
|
+
--body "${HARNESS_ARN}"
|
|
192
|
+
|
|
193
|
+
An ARN is an identifier, not a credential, so it belongs in a variable rather
|
|
194
|
+
than a secret. Then, per repository:
|
|
195
|
+
|
|
196
|
+
prism-cli bootstrapper install-coding-agent
|
|
197
|
+
gh label create agent-fix --description "Hand this issue to the PRISM coding agent"
|
|
198
|
+
|
|
199
|
+
Nothing about the agent is copied into those repositories -- the workflow fetches
|
|
200
|
+
the thin client at run time and calls this harness.
|
|
201
|
+
EOF
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# prism-coding-agent.yml — Autonomous issue → fix → PR, via an AgentCore harness.
|
|
2
|
+
#
|
|
3
|
+
# Installed by: prism-cli bootstrapper install-coding-agent
|
|
4
|
+
#
|
|
5
|
+
# This file deliberately does NOT live in bootstrapper/github-workflows/.
|
|
6
|
+
# install-github-workflows copies every .yml in that directory, so putting it
|
|
7
|
+
# there would hand an autonomous, Bedrock-billing, branch-pushing agent to every
|
|
8
|
+
# repo that ran the workflow installer.
|
|
9
|
+
#
|
|
10
|
+
#
|
|
11
|
+
# WHAT RUNS WHERE
|
|
12
|
+
#
|
|
13
|
+
# The agent runs on AgentCore, not here. This workflow is the thin client: it
|
|
14
|
+
# resolves the issue, invokes the harness, applies the patch it returns, and opens
|
|
15
|
+
# a pull request. That split is what keeps the harness's credentials read-only --
|
|
16
|
+
# it never pushes, so it needs no write access to any repository, and the token
|
|
17
|
+
# that opens the PR is this run's own ephemeral, repo-scoped GITHUB_TOKEN.
|
|
18
|
+
#
|
|
19
|
+
# One harness serves every toolchain. It ships mise and installs what the
|
|
20
|
+
# repository declares in .tool-versions at session start, so there is no
|
|
21
|
+
# per-language routing to configure here.
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
# AUTHORIZATION MODEL
|
|
25
|
+
#
|
|
26
|
+
# The trigger is `issues: [labeled]`, gated on a specific label. That is the
|
|
27
|
+
# security control, and it is load-bearing: anybody with a GitHub account can
|
|
28
|
+
# open an issue in a public repo, but applying a label requires triage
|
|
29
|
+
# permission or above. Triggering on `issues: [opened]` instead would let any
|
|
30
|
+
# stranger spend your Bedrock budget and put a branch in your repository.
|
|
31
|
+
#
|
|
32
|
+
# NOTE: `issues` events only trigger a workflow run if this file exists on the
|
|
33
|
+
# DEFAULT branch. You cannot test changes to it from a feature branch -- nothing
|
|
34
|
+
# fires, and nothing reports why. Merge first.
|
|
35
|
+
#
|
|
36
|
+
#
|
|
37
|
+
# REQUIRED SETUP
|
|
38
|
+
# - prism-cli bootstrapper setup-github-oidc (grants bedrock:InvokeModel)
|
|
39
|
+
# - Repository secret PRISM_METRICS_ROLE_ARN
|
|
40
|
+
# - Repository or org variable PRISM_HARNESS_ARN (an ARN is an identifier, not
|
|
41
|
+
# a credential, so it belongs in `vars` -- and an org variable is inherited by
|
|
42
|
+
# every repo, which is what "deploy the harness once" should feel like)
|
|
43
|
+
# - A label matching AGENT_LABEL below, created in the repo
|
|
44
|
+
# - .coding-agent/config.json committed (written by install-coding-agent)
|
|
45
|
+
#
|
|
46
|
+
# The agent is NOT vendored into this repository. Only config.json, prompt.md,
|
|
47
|
+
# fixtures/ and this file live here; the orchestrator client is fetched at run
|
|
48
|
+
# time from the sample repo, and the agent itself runs in an AgentCore harness in
|
|
49
|
+
# your own account. That means an agent fix reaches every repo without an N-repo
|
|
50
|
+
# migration, which vendoring could not offer.
|
|
51
|
+
#
|
|
52
|
+
# Pin PRISM_AGENT_REF to a tag or commit SHA for anything you care about. It
|
|
53
|
+
# defaults to `main` so the workshop works on day one, but a floating default
|
|
54
|
+
# means a third party's push can change what runs in your CI.
|
|
55
|
+
|
|
56
|
+
name: PRISM Coding Agent
|
|
57
|
+
|
|
58
|
+
on:
|
|
59
|
+
issues:
|
|
60
|
+
types: [labeled]
|
|
61
|
+
workflow_dispatch:
|
|
62
|
+
inputs:
|
|
63
|
+
issue_number:
|
|
64
|
+
description: 'Issue number to fix'
|
|
65
|
+
required: true
|
|
66
|
+
type: string
|
|
67
|
+
|
|
68
|
+
env:
|
|
69
|
+
PRISM_AWS_REGION: us-west-2
|
|
70
|
+
PRISM_REPO: github.com/${{ github.repository }}
|
|
71
|
+
PRISM_HARNESS_ARN: ${{ vars.PRISM_HARNESS_ARN }}
|
|
72
|
+
# Where the orchestrator client comes from. Overridable as repo/org variables so
|
|
73
|
+
# a fork, an internal mirror, or a pinned SHA needs no edit to this file.
|
|
74
|
+
PRISM_AGENT_REPO: ${{ vars.PRISM_AGENT_REPO || 'aws-samples/sample-prism-d1-velocity' }}
|
|
75
|
+
PRISM_AGENT_REF: ${{ vars.PRISM_AGENT_REF || 'main' }}
|
|
76
|
+
|
|
77
|
+
permissions:
|
|
78
|
+
id-token: write # OIDC → AWS
|
|
79
|
+
contents: write # push the fix branch
|
|
80
|
+
pull-requests: write # open the PR
|
|
81
|
+
issues: write # comment the outcome back on the issue
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
fix-issue:
|
|
85
|
+
# On the label path, run only for the agent label. Any other label added to
|
|
86
|
+
# any issue also fires this workflow, so this condition is what keeps the
|
|
87
|
+
# job from running on every triage action in the repository.
|
|
88
|
+
#
|
|
89
|
+
# The label is a literal here, not an env var, because the `env` context is
|
|
90
|
+
# not available in a job-level `if`. An AGENT_LABEL variable above would look
|
|
91
|
+
# configurable and change nothing — if you rename the label, edit this line.
|
|
92
|
+
if: >-
|
|
93
|
+
github.event_name == 'workflow_dispatch' ||
|
|
94
|
+
github.event.label.name == 'agent-fix'
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
timeout-minutes: 45
|
|
97
|
+
|
|
98
|
+
steps:
|
|
99
|
+
- name: Checkout
|
|
100
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
101
|
+
with:
|
|
102
|
+
fetch-depth: 0
|
|
103
|
+
|
|
104
|
+
- name: Fail early if no harness is configured
|
|
105
|
+
run: |
|
|
106
|
+
set -euo pipefail
|
|
107
|
+
if [[ -z "${PRISM_HARNESS_ARN}" ]]; then
|
|
108
|
+
echo "::warning::PRISM_HARNESS_ARN variable is not set. Will try SSM after OIDC." \
|
|
109
|
+
"coding-agent/deploy/Dockerfile and set the repository or org" \
|
|
110
|
+
"variable. Failing now rather than after commenting on the issue."
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
- name: Resolve the issue to work on
|
|
115
|
+
id: issue
|
|
116
|
+
env:
|
|
117
|
+
GH_TOKEN: ${{ github.token }}
|
|
118
|
+
DISPATCH_NUMBER: ${{ inputs.issue_number }}
|
|
119
|
+
run: |
|
|
120
|
+
set -euo pipefail
|
|
121
|
+
|
|
122
|
+
# Both branches write the same shape: a GitHub event payload with the
|
|
123
|
+
# issue under an `issue` key, which is what agentcore.invoke reads. The
|
|
124
|
+
# dispatch branch has to wrap it, because `gh issue view` returns a
|
|
125
|
+
# bare issue object.
|
|
126
|
+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
127
|
+
if ! [[ "${DISPATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
|
128
|
+
echo "::error::issue_number must be numeric, got '${DISPATCH_NUMBER}'"
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
gh issue view "${DISPATCH_NUMBER}" --json number,title,body,labels \
|
|
132
|
+
| jq '{issue: .}' > /tmp/issue.json
|
|
133
|
+
else
|
|
134
|
+
# Read from the payload rather than re-fetching: avoids a race where
|
|
135
|
+
# the issue is edited between the label and this step.
|
|
136
|
+
jq '{issue: (.issue | {number, title, body, labels})}' \
|
|
137
|
+
"${GITHUB_EVENT_PATH}" > /tmp/issue.json
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
NUMBER=$(jq -r '.issue.number' /tmp/issue.json)
|
|
141
|
+
echo "number=${NUMBER}" >> "$GITHUB_OUTPUT"
|
|
142
|
+
echo "Working on issue #${NUMBER}: $(jq -r '.issue.title' /tmp/issue.json)"
|
|
143
|
+
|
|
144
|
+
- name: Acknowledge on the issue
|
|
145
|
+
env:
|
|
146
|
+
GH_TOKEN: ${{ github.token }}
|
|
147
|
+
run: |
|
|
148
|
+
gh issue comment "${{ steps.issue.outputs.number }}" \
|
|
149
|
+
--body "🤖 PRISM coding agent picked this up — [run log](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})."
|
|
150
|
+
|
|
151
|
+
- name: Set up Python
|
|
152
|
+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
153
|
+
with:
|
|
154
|
+
python-version: '3.12'
|
|
155
|
+
|
|
156
|
+
- name: Install runner dependencies
|
|
157
|
+
run: |
|
|
158
|
+
set -euo pipefail
|
|
159
|
+
sudo apt-get update -qq && sudo apt-get install -y -qq jq
|
|
160
|
+
# Only boto3 -- the agent's own dependencies live in the harness image,
|
|
161
|
+
# not here. This runner never loads a model.
|
|
162
|
+
pip install --quiet boto3
|
|
163
|
+
|
|
164
|
+
- name: Fetch the orchestrator client
|
|
165
|
+
id: client
|
|
166
|
+
env:
|
|
167
|
+
AGENT_REPO: ${{ env.PRISM_AGENT_REPO }}
|
|
168
|
+
AGENT_REF: ${{ env.PRISM_AGENT_REF }}
|
|
169
|
+
run: |
|
|
170
|
+
set -euo pipefail
|
|
171
|
+
# Cloned into RUNNER_TEMP, NOT the workspace. actions/checkout can only
|
|
172
|
+
# write under GITHUB_WORKSPACE, and anything there is inside the tree the
|
|
173
|
+
# agent's patch is applied to and committed from -- a client checkout
|
|
174
|
+
# sitting in the working tree is one `git add -A` away from being
|
|
175
|
+
# committed into somebody's fix.
|
|
176
|
+
DEST="${RUNNER_TEMP}/prism-agent"
|
|
177
|
+
rm -rf "${DEST}"
|
|
178
|
+
mkdir -p "${DEST}"
|
|
179
|
+
|
|
180
|
+
# init + fetch rather than `clone --branch`, because --branch takes a
|
|
181
|
+
# branch or tag and rejects a commit SHA -- which is exactly what a repo
|
|
182
|
+
# that has pinned PRISM_AGENT_REF will be passing.
|
|
183
|
+
git -C "${DEST}" init -q
|
|
184
|
+
git -C "${DEST}" remote add origin "https://github.com/${AGENT_REPO}.git"
|
|
185
|
+
git -C "${DEST}" sparse-checkout init --cone
|
|
186
|
+
git -C "${DEST}" sparse-checkout set coding-agent
|
|
187
|
+
git -C "${DEST}" fetch -q --depth 1 origin "${AGENT_REF}"
|
|
188
|
+
git -C "${DEST}" checkout -q FETCH_HEAD
|
|
189
|
+
|
|
190
|
+
CLIENT="${DEST}/coding-agent"
|
|
191
|
+
# Preflight by importing rather than by checking that files exist. The
|
|
192
|
+
# runtime dependency is the whole `coding-agent/` directory, not just the
|
|
193
|
+
# `agentcore/` package: invoke.py puts its parent on sys.path and imports
|
|
194
|
+
# `config`, and contract.py imports it too -- from inside
|
|
195
|
+
# FixRequest.validate(), so a missing config.py would not surface until a
|
|
196
|
+
# request was already being validated. An import check covers both, plus
|
|
197
|
+
# any module inside the package that a narrowed sparse path dropped.
|
|
198
|
+
#
|
|
199
|
+
# `python -c` rather than a heredoc: a heredoc terminator has to sit at
|
|
200
|
+
# column 0, which inside a YAML block scalar means breaking out of the
|
|
201
|
+
# block and invalidating the workflow file.
|
|
202
|
+
if ! ( cd "${CLIENT}" && python -c "import sys; sys.path.insert(0, '.'); import agentcore.invoke; import config" ); then
|
|
203
|
+
echo "::error::${AGENT_REPO}@${AGENT_REF} does not provide a usable" \
|
|
204
|
+
"coding-agent/ client (agentcore/ and config.py are both required)"
|
|
205
|
+
exit 1
|
|
206
|
+
fi
|
|
207
|
+
echo "dir=${CLIENT}" >> "$GITHUB_OUTPUT"
|
|
208
|
+
echo "Client ${AGENT_REPO}@$(git -C "${DEST}" rev-parse --short FETCH_HEAD)"
|
|
209
|
+
|
|
210
|
+
- name: Configure AWS credentials (OIDC)
|
|
211
|
+
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
|
212
|
+
with:
|
|
213
|
+
role-to-assume: ${{ secrets.PRISM_METRICS_ROLE_ARN }}
|
|
214
|
+
aws-region: ${{ env.PRISM_AWS_REGION }}
|
|
215
|
+
|
|
216
|
+
- name: Load telemetry config from SSM
|
|
217
|
+
# One source of truth: CDK writes these at deploy time, so they cannot
|
|
218
|
+
# drift from what's actually deployed. Falls back gracefully — a missing
|
|
219
|
+
# parameter means telemetry is unconfigured, not that the run fails.
|
|
220
|
+
run: |
|
|
221
|
+
set -euo pipefail
|
|
222
|
+
read_param() {
|
|
223
|
+
aws ssm get-parameter --name "$1" --query Parameter.Value --output text 2>/dev/null || echo ""
|
|
224
|
+
}
|
|
225
|
+
PRISM_COLLECTOR_URL=$(read_param /prism/d1/collector-url)
|
|
226
|
+
PRISM_OIDC_TOKEN_ENDPOINT=$(read_param /prism/d1/token-endpoint)
|
|
227
|
+
PRISM_AGENT_SECRET_ID=$(read_param /prism/d1/agent-secret-id)
|
|
228
|
+
echo "PRISM_COLLECTOR_URL=${PRISM_COLLECTOR_URL}" >> "$GITHUB_ENV"
|
|
229
|
+
echo "PRISM_OIDC_TOKEN_ENDPOINT=${PRISM_OIDC_TOKEN_ENDPOINT}" >> "$GITHUB_ENV"
|
|
230
|
+
echo "PRISM_AGENT_SECRET_ID=${PRISM_AGENT_SECRET_ID}" >> "$GITHUB_ENV"
|
|
231
|
+
# Harness ARN: the GitHub variable is the primary (pre-OIDC early-fail),
|
|
232
|
+
# but if it was empty (e.g. fork with no org vars), SSM is the fallback.
|
|
233
|
+
if [[ -z "${PRISM_HARNESS_ARN}" ]]; then
|
|
234
|
+
PRISM_HARNESS_ARN=$(read_param /prism/d1/harness-arn)
|
|
235
|
+
if [[ -n "${PRISM_HARNESS_ARN}" ]]; then
|
|
236
|
+
echo "PRISM_HARNESS_ARN=${PRISM_HARNESS_ARN}" >> "$GITHUB_ENV"
|
|
237
|
+
echo "Harness ARN loaded from SSM (GitHub variable was empty)"
|
|
238
|
+
fi
|
|
239
|
+
fi
|
|
240
|
+
if [[ -n "${PRISM_COLLECTOR_URL}" ]]; then
|
|
241
|
+
echo "Telemetry: ${PRISM_COLLECTOR_URL} (secret: ${PRISM_AGENT_SECRET_ID})"
|
|
242
|
+
else
|
|
243
|
+
echo "Telemetry: not configured (SSM params empty or missing)"
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
- name: Invoke the harness
|
|
247
|
+
id: invoke
|
|
248
|
+
env:
|
|
249
|
+
PRISM_HARNESS_ARN: ${{ env.PRISM_HARNESS_ARN }}
|
|
250
|
+
run: |
|
|
251
|
+
if [[ -z "${PRISM_HARNESS_ARN}" ]]; then
|
|
252
|
+
echo "::error::No harness ARN available (checked GitHub variable AND SSM /prism/d1/harness-arn)."
|
|
253
|
+
echo "::error::Run coding-agent/deploy/deploy-harness.sh and set the org variable."
|
|
254
|
+
exit 1
|
|
255
|
+
fi
|
|
256
|
+
# GitHub runs `run:` blocks under `bash -e`, so a non-zero exit would
|
|
257
|
+
# end the step and skip straight past reporting. `set +e` keeps the exit
|
|
258
|
+
# code as data instead of as control flow.
|
|
259
|
+
set -o pipefail
|
|
260
|
+
set +e
|
|
261
|
+
# --repo is the checkout under test, which is NOT the working directory:
|
|
262
|
+
# this step runs from the client, and config.json lives in the target
|
|
263
|
+
# repo. Passing `.` here would resolve to the client and fail config
|
|
264
|
+
# loading with a confusing "no .coding-agent/config.json".
|
|
265
|
+
python -m agentcore.invoke \
|
|
266
|
+
--repo "${GITHUB_WORKSPACE}" \
|
|
267
|
+
--github-event /tmp/issue.json \
|
|
268
|
+
--patch-out /tmp/fix.patch \
|
|
269
|
+
--result-out /tmp/result.json \
|
|
270
|
+
--transcript-out /tmp/agent-transcript.log \
|
|
271
|
+
--repo-url "${{ github.server_url }}/${{ github.repository }}.git" \
|
|
272
|
+
--ref "${{ github.event.repository.default_branch }}" \
|
|
273
|
+
--region "${PRISM_AWS_REGION}" \
|
|
274
|
+
2>&1 | tee /tmp/invoke.log
|
|
275
|
+
EXIT_CODE=$?
|
|
276
|
+
set -e
|
|
277
|
+
echo "exit_code=${EXIT_CODE}" >> "$GITHUB_OUTPUT"
|
|
278
|
+
|
|
279
|
+
OUTCOME=$(jq -r '.outcome // "failed"' /tmp/result.json 2>/dev/null || echo failed)
|
|
280
|
+
echo "outcome=${OUTCOME}" >> "$GITHUB_OUTPUT"
|
|
281
|
+
VERIFIED=$(jq -r '.verified // false' /tmp/result.json 2>/dev/null || echo false)
|
|
282
|
+
echo "verified=${VERIFIED}" >> "$GITHUB_OUTPUT"
|
|
283
|
+
echo "Harness outcome: ${OUTCOME} (verified=${VERIFIED}, exit ${EXIT_CODE})"
|
|
284
|
+
working-directory: ${{ steps.client.outputs.dir }}
|
|
285
|
+
|
|
286
|
+
- name: Apply the patch
|
|
287
|
+
id: apply
|
|
288
|
+
if: steps.invoke.outputs.outcome == 'patched'
|
|
289
|
+
run: |
|
|
290
|
+
set -euo pipefail
|
|
291
|
+
BRANCH="agent/issue-${{ steps.issue.outputs.number }}"
|
|
292
|
+
git switch -c "${BRANCH}"
|
|
293
|
+
|
|
294
|
+
# --check first so a stale or malformed patch is reported as such rather
|
|
295
|
+
# than leaving a half-applied tree behind.
|
|
296
|
+
if ! git apply --check /tmp/fix.patch; then
|
|
297
|
+
echo "::error::the returned patch does not apply to this tree"
|
|
298
|
+
echo "applied=false" >> "$GITHUB_OUTPUT"
|
|
299
|
+
exit 1
|
|
300
|
+
fi
|
|
301
|
+
git apply /tmp/fix.patch
|
|
302
|
+
|
|
303
|
+
NAME=$(jq -r '.agent_name // "PRISM Coding Agent"' .coding-agent/config.json)
|
|
304
|
+
EMAIL=$(jq -r '.agent_email // "prism-agent@example.com"' .coding-agent/config.json)
|
|
305
|
+
SUMMARY=$(jq -r '.summary // "Autonomous fix"' /tmp/result.json)
|
|
306
|
+
|
|
307
|
+
git -c "user.name=${NAME}" -c "user.email=${EMAIL}" commit -a -m "$(printf 'fix: %s\n\n%s\n\nCloses #%s\n' \
|
|
308
|
+
"$(jq -r '.issue.title' /tmp/issue.json)" \
|
|
309
|
+
"${SUMMARY}" \
|
|
310
|
+
"${{ steps.issue.outputs.number }}")"
|
|
311
|
+
|
|
312
|
+
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"
|
|
313
|
+
echo "applied=true" >> "$GITHUB_OUTPUT"
|
|
314
|
+
echo "files=$(git diff --name-only HEAD~1 HEAD | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
|
|
315
|
+
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
316
|
+
git log --oneline -1
|
|
317
|
+
|
|
318
|
+
- name: Emit commit attribution
|
|
319
|
+
# After the commit, because the SHA does not exist before it -- the harness
|
|
320
|
+
# returns a patch and this workflow makes the commit. Reuses the trace id
|
|
321
|
+
# from result.json: a fresh one would leave the commit with no correlated
|
|
322
|
+
# usage, and the receiver would freeze its origin as human-authored.
|
|
323
|
+
#
|
|
324
|
+
# continue-on-error, because a reporting gap must not fail a run that has
|
|
325
|
+
# already produced a reviewed fix.
|
|
326
|
+
if: steps.apply.outputs.applied == 'true'
|
|
327
|
+
continue-on-error: true
|
|
328
|
+
env:
|
|
329
|
+
run: |
|
|
330
|
+
set -euo pipefail
|
|
331
|
+
python -m agentcore.emit_commit \
|
|
332
|
+
--result /tmp/result.json \
|
|
333
|
+
--repo "${PRISM_REPO}" \
|
|
334
|
+
--sha "${{ steps.apply.outputs.sha }}"
|
|
335
|
+
working-directory: ${{ steps.client.outputs.dir }}
|
|
336
|
+
|
|
337
|
+
- name: Open the pull request
|
|
338
|
+
id: pr
|
|
339
|
+
if: steps.apply.outputs.applied == 'true'
|
|
340
|
+
env:
|
|
341
|
+
GH_TOKEN: ${{ github.token }}
|
|
342
|
+
run: |
|
|
343
|
+
set -euo pipefail
|
|
344
|
+
git push -u origin "${{ steps.apply.outputs.branch }}"
|
|
345
|
+
VERIFIED=$(jq -r 'if .verified then "The agent ran this repository'"'"'s checks and they passed." else "⚠️ The agent did NOT verify this change. Review with that in mind." end' /tmp/result.json)
|
|
346
|
+
URL=$(gh pr create \
|
|
347
|
+
--title "fix: $(jq -r '.issue.title' /tmp/issue.json)" \
|
|
348
|
+
--body "$(printf 'Closes #%s\n\n%s\n\n%s\n\nGenerated by the PRISM coding agent on AgentCore. Nothing here was merged automatically.\n\n[Run log](%s)\n' \
|
|
349
|
+
"${{ steps.issue.outputs.number }}" \
|
|
350
|
+
"$(jq -r '.summary' /tmp/result.json)" \
|
|
351
|
+
"${VERIFIED}" \
|
|
352
|
+
"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}")" \
|
|
353
|
+
--base "${{ github.event.repository.default_branch }}")
|
|
354
|
+
echo "url=${URL}" >> "$GITHUB_OUTPUT"
|
|
355
|
+
|
|
356
|
+
- name: Report the outcome on the issue
|
|
357
|
+
if: always()
|
|
358
|
+
env:
|
|
359
|
+
GH_TOKEN: ${{ github.token }}
|
|
360
|
+
OUTCOME: ${{ steps.invoke.outputs.outcome }}
|
|
361
|
+
PR_URL: ${{ steps.pr.outputs.url }}
|
|
362
|
+
run: |
|
|
363
|
+
set -euo pipefail
|
|
364
|
+
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
365
|
+
REASON=$(jq -r '.reason // ""' /tmp/result.json 2>/dev/null || echo "")
|
|
366
|
+
|
|
367
|
+
case "${OUTCOME:-failed}" in
|
|
368
|
+
patched)
|
|
369
|
+
if [[ -n "${PR_URL}" ]]; then
|
|
370
|
+
BODY=$(printf '✅ **Fix proposed** — %s\n\nNothing has been merged.\n\n[Run log](%s)' "${PR_URL}" "${RUN_URL}")
|
|
371
|
+
else
|
|
372
|
+
BODY=$(printf '⚠️ **The agent produced a patch but it could not be applied or pushed.** See the run log for the reason.\n\n[Run log](%s)' "${RUN_URL}")
|
|
373
|
+
fi
|
|
374
|
+
;;
|
|
375
|
+
declined)
|
|
376
|
+
# Declining is frequently the right answer -- for an issue asking the
|
|
377
|
+
# agent to weaken a test suite, or one whose premise does not hold.
|
|
378
|
+
# Reporting it as a failure would train people to ignore the signal.
|
|
379
|
+
BODY=$(printf '🛑 **The agent declined to make a change.**\n\n> %s\n\nThis is often correct: the request may ask for something it should refuse, or rest on a premise that does not hold.\n\n[Run log](%s)' "${REASON}" "${RUN_URL}")
|
|
380
|
+
;;
|
|
381
|
+
*)
|
|
382
|
+
BODY=$(printf '❌ **The agent could not complete this.**\n\n> %s\n\n[Run log](%s)' "${REASON:-see the run log}" "${RUN_URL}")
|
|
383
|
+
;;
|
|
384
|
+
esac
|
|
385
|
+
|
|
386
|
+
gh issue comment "${{ steps.issue.outputs.number }}" --body "${BODY}"
|
|
387
|
+
|
|
388
|
+
- name: Upload the transcript
|
|
389
|
+
if: always()
|
|
390
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
391
|
+
with:
|
|
392
|
+
name: agent-run-issue-${{ steps.issue.outputs.number }}
|
|
393
|
+
path: |
|
|
394
|
+
/tmp/invoke.log
|
|
395
|
+
/tmp/result.json
|
|
396
|
+
/tmp/fix.patch
|
|
397
|
+
/tmp/agent-transcript.log
|
|
398
|
+
retention-days: 14
|
|
399
|
+
if-no-files-found: ignore
|