@prism-d1/cli 1.0.84 → 1.0.86

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.
Files changed (43) hide show
  1. package/dist/assets/coding-agent/README.md +824 -0
  2. package/dist/assets/coding-agent/agent.py +381 -0
  3. package/dist/assets/coding-agent/config.py +338 -0
  4. package/dist/assets/coding-agent/deploy/Dockerfile +86 -0
  5. package/dist/assets/coding-agent/deploy/create_harness.py +144 -0
  6. package/dist/assets/coding-agent/deploy/deploy-harness.sh +201 -0
  7. package/dist/assets/coding-agent/deploy/prism-coding-agent.yml +399 -0
  8. package/dist/assets/coding-agent/eval/run_eval.py +430 -0
  9. package/dist/assets/coding-agent/eval/run_harness_eval.py +262 -0
  10. package/dist/assets/coding-agent/eval/stubs/001-tags-element-validation.json +11 -0
  11. package/dist/assets/coding-agent/eval/stubs/002-status-filter.json +11 -0
  12. package/dist/assets/coding-agent/eval/stubs/003-refuse-test-deletion.json +11 -0
  13. package/dist/assets/coding-agent/fixtures/001-tags-element-validation.json +12 -0
  14. package/dist/assets/coding-agent/fixtures/002-status-filter.json +12 -0
  15. package/dist/assets/coding-agent/fixtures/003-refuse-test-deletion.json +10 -0
  16. package/dist/assets/coding-agent/pyproject.toml +28 -0
  17. package/dist/assets/coding-agent/requirements.txt +8 -0
  18. package/dist/assets/coding-agent/system_prompt.py +290 -0
  19. package/dist/assets/coding-agent/tools/__init__.py +11 -0
  20. package/dist/assets/coding-agent/tools/create_pr.py +141 -0
  21. package/dist/assets/coding-agent/tools/git_ops.py +197 -0
  22. package/dist/assets/github-workflows/prism-agent-eval.yml +2 -1
  23. package/dist/assets/github-workflows/prism-ai-metrics.yml +12 -3
  24. package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +4 -3
  25. package/dist/assets/github-workflows/prism-eval-gate.yml +3 -2
  26. package/dist/assets/gitlab-workflows/prism-agent-eval.yml +2 -1
  27. package/dist/assets/gitlab-workflows/prism-ai-metrics.yml +3 -2
  28. package/dist/assets/gitlab-workflows/prism-eval-gate.yml +2 -1
  29. package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +134 -2
  30. package/dist/assets/infra/lib/dashboard-stack.ts +79 -95
  31. package/dist/assets/infra/lib/lambda/metrics-processor.ts +99 -1
  32. package/dist/assets/infra/lib/lambda/otel-receiver.ts +318 -29
  33. package/dist/assets/infra/lib/lambda/productivity-widget.ts +85 -4
  34. package/dist/assets/infra/lib/lambda/velocity-widget.ts +265 -0
  35. package/dist/assets/infra/lib/metrics-pipeline-stack.ts +5 -0
  36. package/dist/src/commands/bootstrapper/install-coding-agent.d.ts +62 -0
  37. package/dist/src/commands/bootstrapper/install-coding-agent.d.ts.map +1 -0
  38. package/dist/src/commands/bootstrapper/install-coding-agent.js +614 -0
  39. package/dist/src/commands/bootstrapper/install-coding-agent.js.map +1 -0
  40. package/dist/src/commands/bootstrapper/setup-github-oidc.d.ts.map +1 -1
  41. package/dist/src/commands/bootstrapper/setup-github-oidc.js +23 -0
  42. package/dist/src/commands/bootstrapper/setup-github-oidc.js.map +1 -1
  43. 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