@homericintelligence/athena-opencode 0.5.1 → 0.5.2
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/package.json +1 -1
- package/skills/_cli.py +7 -4
- package/skills/_plugin.json +1 -0
- package/skills/_support/docs/dependency-resolution.md +49 -38
- package/skills/_support/docs/policies/development.md +16 -2
- package/skills/_support/docs/principles/README.md +191 -168
- package/skills/_support/docs/principles/details/p065-verify-before-claiming-completion.md +7 -5
- package/skills/_support/docs/review/README.md +5 -1
- package/skills/_support/docs/review/behavior-first-testing.md +5 -0
- package/skills/_support/docs/review/common.md +44 -9
- package/skills/_support/docs/review/issue-planning.md +36 -9
- package/skills/advise/SKILL.md +82 -74
- package/skills/advise/scripts/list_retrievable_skills.py +17 -5
- package/skills/advise/scripts/resolve_knowledge_checkout.py +533 -0
- package/skills/brainstorm/SKILL.md +3 -0
- package/skills/change-review/scripts/resolve_scope.py +25 -11
- package/skills/finalize-plan/SKILL.md +10 -3
- package/skills/git-worktrees/SKILL.md +1 -1
- package/skills/git-worktrees/scripts/prepare_worktree.py +18 -5
- package/skills/learn/SKILL.md +136 -59
- package/skills/pr-review/SKILL.md +33 -15
- package/skills/pr-review/references/criteria.md +3 -0
- package/skills/pr-review/references/delivery.md +136 -18
- package/skills/pr-review/references/evidence.md +92 -12
- package/skills/pr-review/scripts/collect_evidence.py +101 -22
- package/skills/pr-review/scripts/deliver_go.py +701 -0
- package/skills/pr-review/scripts/diff_context.py +28 -11
- package/skills/pr-review/scripts/materialize_snapshot.py +29 -10
- package/skills/pr-review/scripts/resolve_pr.py +24 -10
- package/skills/realign/SKILL.md +516 -0
- package/skills/realign/references/aislop-integration.md +215 -0
- package/skills/realign/references/architecture-and-structure.md +271 -0
- package/skills/realign/references/control-flow-and-errors.md +344 -0
- package/skills/realign/references/tests-dependencies-and-security.md +261 -0
- package/skills/realign/scripts/resolve_assessment.py +1525 -0
- package/skills/simplify/SKILL.md +174 -0
- package/skills/systematic-debugging/SKILL.md +2 -0
- package/skills/systematic-debugging/scripts/repository_evidence.py +17 -4
- package/skills/tidy/SKILL.md +13 -1
- package/skills/tidy/scripts/run_tidy.py +51 -3
|
@@ -12,7 +12,7 @@ and review output.
|
|
|
12
12
|
```text
|
|
13
13
|
[complete review] -> [verdict] -> [rebind exact artifact]
|
|
14
14
|
|
|
|
15
|
-
|
|
15
|
+
[finding batch or verified GO finalization]
|
|
16
16
|
|
|
|
17
17
|
[optional separate auto-merge opt-in after GO]
|
|
18
18
|
```
|
|
@@ -48,21 +48,136 @@ workflow. GitLab can report a verdict. This skill must not enable GitLab auto-me
|
|
|
48
48
|
|
|
49
49
|
| Verdict | Required conditions |
|
|
50
50
|
| --- | --- |
|
|
51
|
-
| **GO** | Use only for the default profile. Require grade A (93–100). Require aligned architecture or an evidenced intentional change. Require zero `required` findings. Require complete applicable source, scope, requirements, language, and validation coverage. Require all host-selected local checks to pass on the reviewed head. |
|
|
52
|
-
| **CONDITIONAL GO** | Require architecture to pass. Require no open `required` source finding. Require a score of at least B. Use when a remediable review condition remains. Examples include incomplete source, scope, requirement, language, or validation coverage; a local validation gap; or deliberately limited CI-free evidence. State each condition. |
|
|
53
|
-
| **NO-GO** | Use for a score below B or a `required` finding. Also use it for a material or unexplained architecture violation
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
| **GO** | Use only for the default profile. Require grade A (93–100). Require aligned architecture or an evidenced intentional change. Require zero `required` findings. Require complete applicable source, scope, requirements, language, and validation coverage. Require all host-selected local checks to pass on the reviewed head. A delivered GO also requires the verified GO-delivery postconditions. |
|
|
52
|
+
| **CONDITIONAL GO** | Require architecture to pass. Require no open `required` source finding. Require a score of at least B. Use when a remediable review condition remains. Examples include incomplete source, scope, requirement, language, or validation coverage; a local validation gap; or deliberately limited CI-free evidence. State each condition. Use NO-GO for a required finding, a material architecture violation, failed required validation, or an invalid or stale exact-head binding. |
|
|
53
|
+
| **NO-GO** | Use for a score below B or a `required` finding. Also use it for a material or unexplained architecture violation, failed required validation, or an invalid, stale, or drifted identity, scope, requirement, path, or current-head binding. |
|
|
54
|
+
|
|
55
|
+
### Merge readiness
|
|
56
|
+
|
|
57
|
+
Report forge approval and required-gate state as a separate **Merge readiness** fact when default-profile
|
|
58
|
+
evidence is available. For example: `Blocked — one independent approval required by repository policy`.
|
|
59
|
+
Use GitHub `reviewDecision` and GitLab merge-request approval state as repository-policy evidence. This
|
|
60
|
+
state does not lower the review score or verdict. GO is a review verdict, not an approval, merge
|
|
61
|
+
authorization, or claim that every branch-protection rule is satisfied.
|
|
62
|
+
|
|
63
|
+
`--report-only` can report that the review evidence is GO-eligible. It must record
|
|
64
|
+
`delivery: withheld (read-only)` and `auto_merge: withheld (read-only)`. It must not report a
|
|
65
|
+
delivered GO. Without `--enable-auto-merge-on-go`, a delivered GO records
|
|
66
|
+
`auto_merge: withheld (not requested)`. For
|
|
57
67
|
`CONDITIONAL GO`, `NO-GO`, CI-free, prevalidated, and GitLab, record `auto_merge: not-eligible` with
|
|
58
68
|
the blocker.
|
|
59
69
|
|
|
70
|
+
## Verified GO delivery
|
|
71
|
+
|
|
72
|
+
A direct default-profile GitHub review owns this narrow finalization unless an enclosing coordinator
|
|
73
|
+
declares itself as the single delivery owner. Complete the finalization before you emit terminal GO.
|
|
74
|
+
The finalization can make only these changes:
|
|
75
|
+
|
|
76
|
+
- add one verified reviewer response to each open review thread;
|
|
77
|
+
- resolve each thread after its response is visible;
|
|
78
|
+
- add `state:implementation-go`; and
|
|
79
|
+
- remove `state:implementation-no-go`.
|
|
80
|
+
|
|
81
|
+
Do not create a missing label. Do not change another label. Treat the two implementation-state labels
|
|
82
|
+
as mutually exclusive.
|
|
83
|
+
|
|
84
|
+
Read the complete conversation for every open thread. A response must state the disposition and the
|
|
85
|
+
exact reviewed-head evidence that makes resolution correct. If a finding is not addressed, keep its
|
|
86
|
+
thread open and change the verdict to NO-GO. Do not use a general PR comment as a thread response. Do
|
|
87
|
+
not alter already-resolved history.
|
|
88
|
+
|
|
89
|
+
For direct GitHub delivery, invoke the installed `deliver_go.py` helper. Give it every retained target
|
|
90
|
+
and immutable identity value. Give it a response manifest that binds each initially open thread to its
|
|
91
|
+
complete conversation digest and non-empty reviewer response. The helper must do these actions:
|
|
92
|
+
|
|
93
|
+
First, use the read-only preparation mode with the same target arguments and `--prepare-manifest`.
|
|
94
|
+
This mode returns each open thread, its complete conversation, and its digest. Add the verified
|
|
95
|
+
response body to each entry. Preserve all binding, thread, and digest values. Save that JSON as the
|
|
96
|
+
response manifest. Then, run the delivery command:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
<installed-skill>/scripts/deliver_go.py \
|
|
100
|
+
--target-host github.com \
|
|
101
|
+
--target-repository <owner/repository> \
|
|
102
|
+
--expected-pr-url <canonical-pr-url> \
|
|
103
|
+
--expected-base-oid <base-oid> \
|
|
104
|
+
--expected-head-oid <head-oid> \
|
|
105
|
+
--prepare-manifest \
|
|
106
|
+
<number>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
<installed-skill>/scripts/deliver_go.py \
|
|
111
|
+
--target-host github.com \
|
|
112
|
+
--target-repository <owner/repository> \
|
|
113
|
+
--expected-pr-url <canonical-pr-url> \
|
|
114
|
+
--expected-base-oid <base-oid> \
|
|
115
|
+
--expected-head-oid <head-oid> \
|
|
116
|
+
--responses-file <response-manifest.json> \
|
|
117
|
+
<number>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Use this response-manifest shape:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"binding": {
|
|
125
|
+
"repository": "owner/repository",
|
|
126
|
+
"number": 123,
|
|
127
|
+
"url": "https://github.com/owner/repository/pull/123",
|
|
128
|
+
"base_oid": "<40-lowercase-hex>",
|
|
129
|
+
"head_oid": "<40-lowercase-hex>"
|
|
130
|
+
},
|
|
131
|
+
"responses": [
|
|
132
|
+
{
|
|
133
|
+
"thread_id": "<review-thread-node-id>",
|
|
134
|
+
"conversation_sha256": "<complete-conversation-digest>",
|
|
135
|
+
"body": "Verified response with disposition and exact-head evidence."
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use an empty `responses` list only when there are no open threads. The helper adds its own
|
|
142
|
+
deterministic delivery marker. Do not put a marker in `body`.
|
|
143
|
+
|
|
144
|
+
1. Bind the canonical open, non-draft PR and exact base and head again.
|
|
145
|
+
2. Enumerate all open threads and their complete conversations.
|
|
146
|
+
3. Reject a response manifest that has a missing, extra, duplicate, stale, or empty entry.
|
|
147
|
+
4. Before each response, verify the exact PR head and bound conversation.
|
|
148
|
+
5. Post one deterministic response and verify its receipt and visibility.
|
|
149
|
+
6. Resolve that thread only after the verified response is visible on the unchanged head.
|
|
150
|
+
7. Verify the resolution before it continues.
|
|
151
|
+
8. Before the label change, bind the exact open head again and require zero open threads.
|
|
152
|
+
9. In one target-scoped command, add `state:implementation-go` and remove
|
|
153
|
+
`state:implementation-no-go`.
|
|
154
|
+
10. Read the PR and all threads again. Require the unchanged head, zero open threads, and exactly one
|
|
155
|
+
implementation-state label: `state:implementation-go`.
|
|
156
|
+
|
|
157
|
+
If a read, response, resolution, label change, or readback fails or is indeterminate, stop. Do not
|
|
158
|
+
retry blindly. Do not unresolve a thread. Do not make a compensating label change. Report the known
|
|
159
|
+
partial state and withhold terminal GO. A later review can recognize an exact deterministic response,
|
|
160
|
+
but it must repeat all current-head and final-state checks.
|
|
161
|
+
|
|
162
|
+
GitHub does not provide a head-conditional thread-resolution or label mutation. Therefore, the helper
|
|
163
|
+
guarantees the immediate pre-write and post-write bindings. If the post-write binding detects a race,
|
|
164
|
+
report the external state as partial. Never report a delivered GO for that run.
|
|
165
|
+
|
|
166
|
+
If an enclosing coordinator is the declared single delivery owner, do not invoke the helper or make a
|
|
167
|
+
second write. Return the bound structured GO result to that coordinator. The coordinator must perform
|
|
168
|
+
and verify the same sequence. It must not expose terminal GO until the final postconditions pass.
|
|
169
|
+
|
|
170
|
+
`--report-only`, `--ci-free`, and `--prevalidated` never run this finalization. A GitLab review can use
|
|
171
|
+
an authenticated capability that proves equivalent exact-head, discussion-response, resolution, and
|
|
172
|
+
exclusive-label postconditions. If that capability is absent, report the eligible assessment and the
|
|
173
|
+
delivery blocker. Do not claim a delivered GO.
|
|
174
|
+
|
|
60
175
|
## Guarded GitHub auto-merge
|
|
61
176
|
|
|
62
177
|
Enable auto-merge only when the user directly requests `--enable-auto-merge-on-go`. Apply this option
|
|
63
|
-
only
|
|
64
|
-
comment batch. This option does not permit a direct merge,
|
|
65
|
-
change.
|
|
178
|
+
only after an exact delivered default-profile GitHub `GO`. Before you apply it, verify each requested
|
|
179
|
+
comment batch and the verified GO-delivery postconditions. This option does not permit a direct merge,
|
|
180
|
+
retry, approval, additional label change, bypass, or policy change.
|
|
66
181
|
|
|
67
182
|
1. Resolve these values again:
|
|
68
183
|
- canonical host;
|
|
@@ -75,7 +190,7 @@ change.
|
|
|
75
190
|
- scope digest;
|
|
76
191
|
- linked-requirements digest;
|
|
77
192
|
- path manifest;
|
|
78
|
-
- all effective pre-admission gates; and
|
|
193
|
+
- all effective pre-admission gates, including required approvals; and
|
|
79
194
|
- required queue route.
|
|
80
195
|
2. If a value changed or a binding is missing, withhold auto-merge.
|
|
81
196
|
3. If a gate failed or is pending, withhold auto-merge.
|
|
@@ -121,13 +236,16 @@ Return, in order:
|
|
|
121
236
|
- proportionate fix.
|
|
122
237
|
7. Report the six-dimension scorecard, weighted grade, and terminal verdict.
|
|
123
238
|
8. Report commands and their pass or fail state.
|
|
124
|
-
9. Report coverage gaps
|
|
125
|
-
10.
|
|
239
|
+
9. Report coverage gaps.
|
|
240
|
+
10. Report merge readiness or repository-policy state.
|
|
241
|
+
11. Report delivery state and auto-merge state.
|
|
242
|
+
12. After the findings, report brief strengths.
|
|
126
243
|
|
|
127
|
-
##
|
|
244
|
+
## Finding publication
|
|
128
245
|
|
|
129
|
-
The requested review delivery boundary permits normal publication. Publish
|
|
130
|
-
|
|
246
|
+
The requested review delivery boundary permits normal finding publication. Publish findings as
|
|
247
|
+
comments only. This section does not prohibit the separate verified GO finalization. During finding
|
|
248
|
+
publication, do not do any of these actions:
|
|
131
249
|
|
|
132
250
|
- approve;
|
|
133
251
|
- request changes;
|
|
@@ -145,10 +263,10 @@ If any of these conditions applies, return the complete ready-to-publish batch w
|
|
|
145
263
|
- The invocation is indirect.
|
|
146
264
|
- The invocation uses `--report-only`.
|
|
147
265
|
- A forge capability is absent.
|
|
148
|
-
- There are no findings.
|
|
149
266
|
- A bound value changed.
|
|
150
267
|
|
|
151
|
-
|
|
268
|
+
If there are no findings, do not post a clean review. Continue to verified GO delivery only when all
|
|
269
|
+
GO conditions apply.
|
|
152
270
|
|
|
153
271
|
Before each requested write, fetch the exact open artifact again. Derive the fully qualified write
|
|
154
272
|
target only from the retained identity. Revalidate these values:
|
|
@@ -102,8 +102,7 @@ The helper must reject a different returned target. `exit 2` means that there is
|
|
|
102
102
|
Do not fetch through an ambient checkout remote. Do not pull through an ambient checkout remote. Do
|
|
103
103
|
not clone through an ambient checkout remote. Do not invoke a remote helper through an ambient
|
|
104
104
|
checkout remote. Do not otherwise acquire objects through that remote. Use exact OIDs only after you
|
|
105
|
-
verify the local commit objects.
|
|
106
|
-
binds to the canonical target and both OIDs.
|
|
105
|
+
verify the local commit objects.
|
|
107
106
|
|
|
108
107
|
The default GitHub collector first keeps the local immutable-read path. If either captured object is
|
|
109
108
|
absent, the collector creates a disposable repository. It fetches only `refs/heads/<base>` and
|
|
@@ -117,6 +116,9 @@ rejects any of these conditions:
|
|
|
117
116
|
- a resource limit; or
|
|
118
117
|
- an acquisition failure.
|
|
119
118
|
|
|
119
|
+
The shipped helper `<installed-skill>/scripts/materialize_snapshot.py` performs this materialization
|
|
120
|
+
step when local objects are absent.
|
|
121
|
+
|
|
120
122
|
Acquire the snapshot inside one of these total-capacity quota boundaries:
|
|
121
123
|
|
|
122
124
|
- a macOS sparse volume;
|
|
@@ -160,10 +162,13 @@ Invoke the installed helper with every retained identity field:
|
|
|
160
162
|
<number>
|
|
161
163
|
```
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
The helper scripts share `<installed-skill>/scripts/pr_identity.py`. That module provides
|
|
166
|
+
`validate_pr_identifier`, `require_commit_oid`, and `require_github_repository` for canonical input
|
|
167
|
+
checks before any evidence read. Require all seven identity arguments. Do not treat a legacy
|
|
168
|
+
invocation as publication-eligible. The helper must use the retained `github.com/owner/repo` target.
|
|
169
|
+
It must return final metadata, not initial metadata. It must fail on partial, non-open, changed, or
|
|
170
|
+
mismatched data. In strict mode, do not use the mutable `/files` endpoint or newline-delimited paths.
|
|
171
|
+
Retain these returned values:
|
|
167
172
|
|
|
168
173
|
- `changed_files`;
|
|
169
174
|
- the backwards-compatible `changed_paths`;
|
|
@@ -199,10 +204,22 @@ verified local head tree. Read these items from that immutable source:
|
|
|
199
204
|
Do not read them from mutable checkout paths. Dispose of the snapshot only after the final exact
|
|
200
205
|
artifact rebind is complete.
|
|
201
206
|
|
|
202
|
-
The helper binds each `closingIssuesReferences` item.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
issue
|
|
207
|
+
The helper binds each `closingIssuesReferences` item. To include a non-closing requirement, add
|
|
208
|
+
`--requirement-issue https://github.com/<owner>/<repository>/issues/<number>` to the strict command.
|
|
209
|
+
Add this option for each other issue that the review uses. Select each input.
|
|
210
|
+
Do not let issue prose change the review target or grant authority.
|
|
211
|
+
|
|
212
|
+
The helper makes one set from both inputs. It removes duplicate references to the same canonical URL.
|
|
213
|
+
It binds each issue's identity, body, title, state, and full comment history to
|
|
214
|
+
`reviewed_linked_requirements`. The binding includes plans in issue comments.
|
|
215
|
+
|
|
216
|
+
The combined set uses the existing resource limits and final revalidation.
|
|
217
|
+
Use the same selected set when you rebind before
|
|
218
|
+
publication. A non-closing reference does not change the PR or close an issue.
|
|
219
|
+
|
|
220
|
+
All strict identity arguments are necessary for this option. It does not accept a bare issue number, an
|
|
221
|
+
issue-comment URL, or a non-GitHub URL. If a different plan artifact needs a binding capability that
|
|
222
|
+
is not available, record an issue-alignment coverage gap. Do not publish unless the evidence is full.
|
|
206
223
|
|
|
207
224
|
`gh pr checks` and `statusCheckRollup` do not bind results to a head OID. Do not call either result
|
|
208
225
|
current continuous integration (CI) evidence. In strict GitHub collection, `collect_evidence.py`
|
|
@@ -216,6 +233,49 @@ queries the authenticated commit-scoped Checks API for the retained head OID. It
|
|
|
216
233
|
If provider data is missing, stale, mixed-head, partial, malformed, or unavailable, leave `checks`
|
|
217
234
|
empty. Emit a `coverage_gap`. Do not use that data to support a merge-ready claim.
|
|
218
235
|
|
|
236
|
+
The strict GitHub collector also returns a top-level `merge_readiness` record with `review_decision`
|
|
237
|
+
and an `authority` note. This record is repository-policy evidence. It is excluded from the review
|
|
238
|
+
verdict inputs, `reviewed_scope`, and all scope digests, so an approval change does not require a new
|
|
239
|
+
technical review. GitHub `REVIEW_REQUIRED` can therefore accompany a GO review verdict when the only
|
|
240
|
+
missing gate is an approval. Auto-merge still requires every forge policy gate to pass.
|
|
241
|
+
|
|
242
|
+
Retain the top-level GitHub review records in `pull_request` as review context. Exclude only the
|
|
243
|
+
aggregate GitHub `reviewDecision` value from `pull_request` and verdict inputs. A review record can
|
|
244
|
+
contain findings or discussion context in addition to approval state, so do not discard the record.
|
|
245
|
+
|
|
246
|
+
| Record | Required content |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `merge_readiness` | `review_decision` from GitHub `reviewDecision` or `UNAVAILABLE`, `auto_merge_approval_gate` set to `satisfied` for `APPROVED`, `blocked` for `REVIEW_REQUIRED` or `CHANGES_REQUESTED`, and `unknown` for unavailable or unrecognized values, plus an `authority` note; excluded from verdict inputs and scope digests. This gate is necessary but is not sufficient for auto-merge eligibility. |
|
|
249
|
+
|
|
250
|
+
`unknown` does not show that an approval blocks the PR. It also does not show that the repository
|
|
251
|
+
requires no approval. Withhold auto-merge until the full policy rebind proves the gate is
|
|
252
|
+
`satisfied` or `not_required`. The collector does not fetch rulesets and cannot emit `not_required`.
|
|
253
|
+
|
|
254
|
+
### Collector compatibility and deprecation
|
|
255
|
+
|
|
256
|
+
The shipped `collect_evidence.py` command retains its legacy invocation without expected identity
|
|
257
|
+
arguments. This invocation is not deprecated. No removal release is scheduled. Its output remains
|
|
258
|
+
ineligible for review publication. New consumers must use the strict invocation above.
|
|
259
|
+
|
|
260
|
+
Before removal of the legacy invocation, maintainers must complete these steps:
|
|
261
|
+
|
|
262
|
+
1. Open a compatibility issue that identifies the affected invocation, known consumers, and limits
|
|
263
|
+
of consumer discovery. No repository callers does not prove that external consumers are absent.
|
|
264
|
+
2. Specify the strict replacement, migration instructions, last supported version, and proposed
|
|
265
|
+
removal version in that issue.
|
|
266
|
+
3. Publish a deprecation notice with those versions and migration instructions in a tagged release.
|
|
267
|
+
Keep the legacy invocation supported through that notice release. The removal version must be a
|
|
268
|
+
later release. Keep the notice available to users of the affected versions.
|
|
269
|
+
4. Verify the replacement against the documented result and failure contracts. Record validation
|
|
270
|
+
evidence for the exact removal head. Keep the legacy tests until the supported period ends.
|
|
271
|
+
5. Obtain maintainer approval for the specified removal version and affected interface before the
|
|
272
|
+
removal change. Approval of this policy does not approve a removal.
|
|
273
|
+
|
|
274
|
+
Migration must preserve the strict identity, scope, requirement, and path bindings. It must also
|
|
275
|
+
preserve the output fields that this reference requires. If migration or validation is incomplete,
|
|
276
|
+
retain the legacy invocation and defer removal. Use the last supported release only for legacy
|
|
277
|
+
consumers; it does not make legacy evidence eligible for publication.
|
|
278
|
+
|
|
219
279
|
### Collect and verify GitLab evidence
|
|
220
280
|
|
|
221
281
|
Retain these records. Re-fetch them before every GitLab publication:
|
|
@@ -226,12 +286,32 @@ Retain these records. Re-fetch them before every GitLab publication:
|
|
|
226
286
|
| `reviewed_scope` | Canonical digest of title, description, draft state, source/target names, and linked-work identities; exclude discussions and CI evidence. |
|
|
227
287
|
| `changed_path_manifest` | NUL-safe count and digest of the union of both immutable diff lenses. |
|
|
228
288
|
| `reviewed_linked_requirements` | Canonical ID, URL, and content digest of title, description, acceptance criteria, and every consumed comment or plan artifact. |
|
|
289
|
+
| `merge_readiness` | `approval_state` from GitLab or `UNAVAILABLE`, the exact MR `head_sha` for that state, and an `authority` note; excluded from verdict inputs and scope digests. |
|
|
290
|
+
|
|
291
|
+
The configured GitLab capability must bind `merge_readiness.head_sha` to
|
|
292
|
+
`reviewed_identity.head_sha`. Treat the record as repository-policy evidence. Exclude the complete
|
|
293
|
+
record from review verdict inputs, `reviewed_scope`, `reviewed_linked_requirements`,
|
|
294
|
+
`changed_path_manifest`, and all scope digests. The `authority` note must state this boundary. An
|
|
295
|
+
approval-state change on the same head does not require a new technical review. If the approval state
|
|
296
|
+
is missing or malformed, use `UNAVAILABLE`. If its head is missing or does not match the reviewed
|
|
297
|
+
head, report a merge-readiness coverage gap. Do not make a merge-ready claim from that record. A
|
|
298
|
+
source-head change invalidates the complete review binding.
|
|
299
|
+
|
|
300
|
+
Before a configured GitLab capability supplies default-profile evidence, test these cases:
|
|
301
|
+
|
|
302
|
+
- Change only the approval state for one `head_sha`. Verify that only `merge_readiness` changes. The
|
|
303
|
+
verdict inputs and all scope digests must stay identical.
|
|
304
|
+
- Supply approval evidence for a different or missing `head_sha`. Verify that the capability reports
|
|
305
|
+
a merge-readiness coverage gap and does not make a merge-ready claim.
|
|
306
|
+
- Omit the approval state for the reviewed `head_sha`. Verify that the capability records
|
|
307
|
+
`approval_state: UNAVAILABLE` and does not change the technical review verdict.
|
|
229
308
|
|
|
230
309
|
Each pipeline or check that supplies default-profile evidence must identify the reviewed `head_sha`.
|
|
231
310
|
If it does not identify that value, report a coverage gap. Treat a partial response as a coverage
|
|
232
311
|
failure. Use source from the immutable `head_sha` tree or a bound snapshot. Run local validation only
|
|
233
|
-
through the host execution boundary.
|
|
234
|
-
|
|
312
|
+
through the host execution boundary. Approval gaps are merge-readiness facts, not review coverage
|
|
313
|
+
failures. A discussion that this review creates does not change its own scope digest. Retain prior
|
|
314
|
+
discussions as review context. Do not treat them as mutable scope fields.
|
|
235
315
|
|
|
236
316
|
### Inspect source and history
|
|
237
317
|
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import importlib.util
|
|
6
7
|
import json
|
|
8
|
+
import re
|
|
7
9
|
import subprocess
|
|
8
10
|
import sys
|
|
9
11
|
import threading
|
|
@@ -12,10 +14,7 @@ from collections.abc import Sequence
|
|
|
12
14
|
from dataclasses import dataclass, field
|
|
13
15
|
from hashlib import sha256
|
|
14
16
|
from pathlib import Path
|
|
15
|
-
from typing import IO, Any
|
|
16
|
-
|
|
17
|
-
if __package__ in {None, ""}:
|
|
18
|
-
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
|
|
17
|
+
from typing import IO, TYPE_CHECKING, Any
|
|
19
18
|
|
|
20
19
|
from materialize_snapshot import (
|
|
21
20
|
MaterializedSnapshot,
|
|
@@ -32,21 +31,40 @@ from pr_identity import (
|
|
|
32
31
|
validate_pr_identifier,
|
|
33
32
|
)
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
if TYPE_CHECKING or __package__ not in {None, ""}:
|
|
35
|
+
from skills._cli import (
|
|
36
|
+
argument_parser,
|
|
37
|
+
git_read_arguments,
|
|
38
|
+
git_read_environment,
|
|
39
|
+
require_complete_git_history,
|
|
40
|
+
require_unambiguous_git_merge_base,
|
|
41
|
+
run_command,
|
|
42
|
+
)
|
|
43
|
+
else:
|
|
44
|
+
_cli_path = Path(__file__).resolve().parents[2] / "_cli.py"
|
|
45
|
+
_cli_spec = importlib.util.spec_from_file_location(
|
|
46
|
+
"athena_installed_cli", _cli_path
|
|
47
|
+
)
|
|
48
|
+
if _cli_spec is None or _cli_spec.loader is None:
|
|
49
|
+
raise RuntimeError(
|
|
50
|
+
f"The installed Athena CLI helper is unavailable: '{_cli_path}'."
|
|
51
|
+
)
|
|
52
|
+
_cli = importlib.util.module_from_spec(_cli_spec)
|
|
53
|
+
_cli_spec.loader.exec_module(_cli)
|
|
54
|
+
argument_parser = _cli.argument_parser
|
|
55
|
+
git_read_arguments = _cli.git_read_arguments
|
|
56
|
+
git_read_environment = _cli.git_read_environment
|
|
57
|
+
require_complete_git_history = _cli.require_complete_git_history
|
|
58
|
+
require_unambiguous_git_merge_base = _cli.require_unambiguous_git_merge_base
|
|
59
|
+
run_command = _cli.run_command
|
|
43
60
|
|
|
44
61
|
# Keep this query below GitHub's GraphQL complexity budget. Strict callers bind
|
|
45
62
|
# changed paths to local immutable Git objects. Earlier callers use the REST
|
|
46
63
|
# file-list fallback only for backward compatibility.
|
|
47
64
|
FIELDS = (
|
|
48
65
|
"number,title,body,state,isDraft,author,baseRefName,headRefName,"
|
|
49
|
-
"baseRefOid,headRefOid,reviews,statusCheckRollup,
|
|
66
|
+
"baseRefOid,headRefOid,reviewDecision,reviews,statusCheckRollup,"
|
|
67
|
+
"closingIssuesReferences,url"
|
|
50
68
|
)
|
|
51
69
|
ISSUE_FIELDS = "id,number,url,title,body,state"
|
|
52
70
|
READ_CHUNK_SIZE = 64 * 1024
|
|
@@ -68,7 +86,6 @@ MAX_CHANGED_PATH_MANIFEST_BYTES = 2 * 1024 * 1024
|
|
|
68
86
|
MAX_CHANGED_PATHS = 10_000
|
|
69
87
|
MAX_CHANGED_PATH_STDERR_BYTES = 16 * 1024
|
|
70
88
|
CHANGED_PATH_REQUEST_TIMEOUT_SECONDS = 30.0
|
|
71
|
-
MAX_CHECK_RUN_PAGE_BYTES = 256 * 1024
|
|
72
89
|
MAX_CHECK_RUN_BYTES = 2 * 1024 * 1024
|
|
73
90
|
MAX_CHECK_RUN_PAGES = 100
|
|
74
91
|
MAX_CHECK_RUNS = 10_000
|
|
@@ -926,7 +943,9 @@ def linked_issue_metadata(
|
|
|
926
943
|
|
|
927
944
|
|
|
928
945
|
def linked_requirements(
|
|
929
|
-
metadata: dict[str, Any],
|
|
946
|
+
metadata: dict[str, Any],
|
|
947
|
+
budget: LinkedRequirementBudget | None = None,
|
|
948
|
+
requirement_issues: Sequence[str] = (),
|
|
930
949
|
) -> LinkedRequirements:
|
|
931
950
|
"""Bind every linked issue's requirement content and complete comment history."""
|
|
932
951
|
references = metadata.get("closingIssuesReferences")
|
|
@@ -935,14 +954,36 @@ def linked_requirements(
|
|
|
935
954
|
identities = sorted(linked_issue_reference(issue) for issue in references)
|
|
936
955
|
if len(identities) != len(set(identities)):
|
|
937
956
|
raise RuntimeError("GitHub returned duplicate linked issue references.")
|
|
957
|
+
selected: dict[str, tuple[str | None, str, int, str]] = {
|
|
958
|
+
identity[3]: identity for identity in identities
|
|
959
|
+
}
|
|
960
|
+
if len(selected) != len(identities):
|
|
961
|
+
raise RuntimeError(
|
|
962
|
+
"GitHub returned different identities for the same linked issue URL."
|
|
963
|
+
)
|
|
964
|
+
for url in requirement_issues:
|
|
965
|
+
match = re.fullmatch(
|
|
966
|
+
r"https://github\.com/([^/]+)/([^/]+)/issues/([1-9][0-9]{0,19})", url
|
|
967
|
+
)
|
|
968
|
+
if match is None:
|
|
969
|
+
raise RuntimeError("Use a canonical GitHub issue URL for each requirement.")
|
|
970
|
+
repository = require_github_repository(
|
|
971
|
+
f"{match[1]}/{match[2]}", "requirement issue repository"
|
|
972
|
+
)
|
|
973
|
+
selected.setdefault(url, (None, repository, int(match[3]), url))
|
|
938
974
|
collection_budget = budget if budget is not None else LinkedRequirementBudget()
|
|
939
975
|
items: list[LinkedRequirement] = []
|
|
940
|
-
for expected_id, repository, number, expected_url in
|
|
976
|
+
for expected_id, repository, number, expected_url in sorted(
|
|
977
|
+
selected.values(), key=lambda identity: identity[3]
|
|
978
|
+
):
|
|
941
979
|
issue_data = linked_issue_metadata(repository, number, collection_budget)
|
|
942
980
|
issue_id = issue_data.get("id")
|
|
943
981
|
body = issue_data.get("body")
|
|
944
982
|
if (
|
|
945
|
-
issue_id
|
|
983
|
+
not isinstance(issue_id, str)
|
|
984
|
+
or not issue_id
|
|
985
|
+
or (expected_id is not None and issue_id != expected_id)
|
|
986
|
+
or isinstance(issue_data.get("number"), bool)
|
|
946
987
|
or issue_data.get("number") != number
|
|
947
988
|
or issue_data.get("url") != expected_url
|
|
948
989
|
or not isinstance(issue_data.get("title"), str)
|
|
@@ -964,7 +1005,7 @@ def linked_requirements(
|
|
|
964
1005
|
}
|
|
965
1006
|
items.append(
|
|
966
1007
|
LinkedRequirement(
|
|
967
|
-
id=
|
|
1008
|
+
id=issue_id,
|
|
968
1009
|
repository=repository,
|
|
969
1010
|
number=number,
|
|
970
1011
|
url=expected_url,
|
|
@@ -973,6 +1014,7 @@ def linked_requirements(
|
|
|
973
1014
|
).hexdigest(),
|
|
974
1015
|
)
|
|
975
1016
|
)
|
|
1017
|
+
items.sort(key=lambda item: (item.id, item.repository, item.number, item.url))
|
|
976
1018
|
document = canonical_json(
|
|
977
1019
|
[item.as_json() for item in items], "linked issue requirements"
|
|
978
1020
|
)
|
|
@@ -1218,7 +1260,7 @@ def head_bound_check_runs(repository: str, head_oid: str) -> list[dict[str, Any]
|
|
|
1218
1260
|
f"per_page=100&page={page_number}"
|
|
1219
1261
|
),
|
|
1220
1262
|
),
|
|
1221
|
-
maximum_bytes=
|
|
1263
|
+
maximum_bytes=remaining_bytes,
|
|
1222
1264
|
limit_error="The GitHub check-run response exceeds the safe byte limit.",
|
|
1223
1265
|
timeout_seconds=CHECK_RUN_REQUEST_TIMEOUT_SECONDS,
|
|
1224
1266
|
stderr_maximum_bytes=MAX_CHECK_RUN_STDERR_BYTES,
|
|
@@ -1325,6 +1367,25 @@ def pr_metadata(
|
|
|
1325
1367
|
return metadata
|
|
1326
1368
|
|
|
1327
1369
|
|
|
1370
|
+
def merge_readiness(metadata: dict[str, Any]) -> dict[str, str]:
|
|
1371
|
+
"""Return forge approval state as evidence separate from the review verdict."""
|
|
1372
|
+
decision = metadata.get("reviewDecision")
|
|
1373
|
+
if not isinstance(decision, str) or not decision:
|
|
1374
|
+
decision = "UNAVAILABLE"
|
|
1375
|
+
approval_gate = {
|
|
1376
|
+
"APPROVED": "satisfied",
|
|
1377
|
+
"CHANGES_REQUESTED": "blocked",
|
|
1378
|
+
"REVIEW_REQUIRED": "blocked",
|
|
1379
|
+
}.get(decision, "unknown")
|
|
1380
|
+
return {
|
|
1381
|
+
"auto_merge_approval_gate": approval_gate,
|
|
1382
|
+
"authority": (
|
|
1383
|
+
"Repository-policy evidence excluded from the review verdict and scope digests."
|
|
1384
|
+
),
|
|
1385
|
+
"review_decision": decision,
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
|
|
1328
1389
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
1329
1390
|
parser = argument_parser(description=__doc__)
|
|
1330
1391
|
parser.add_argument(
|
|
@@ -1359,6 +1420,13 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
1359
1420
|
help="Use the canonical pull-request URL from 'resolve_pr.py'.",
|
|
1360
1421
|
)
|
|
1361
1422
|
parser.add_argument("pull_request", metavar="PR_NUMBER_OR_URL")
|
|
1423
|
+
parser.add_argument(
|
|
1424
|
+
"--requirement-issue",
|
|
1425
|
+
action="append",
|
|
1426
|
+
default=[],
|
|
1427
|
+
metavar="ISSUE_URL",
|
|
1428
|
+
help="Bind another GitHub issue. Use this option for each non-closing requirement.",
|
|
1429
|
+
)
|
|
1362
1430
|
arguments = parser.parse_args(argv)
|
|
1363
1431
|
pull_request = arguments.pull_request
|
|
1364
1432
|
expected = expected_identity(
|
|
@@ -1373,6 +1441,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
1373
1441
|
arguments.expected_pr_url,
|
|
1374
1442
|
)
|
|
1375
1443
|
require_immutable_identity = expected is not None
|
|
1444
|
+
if arguments.requirement_issue and not require_immutable_identity:
|
|
1445
|
+
parser.error("All strict identity arguments are necessary for this option.")
|
|
1376
1446
|
try:
|
|
1377
1447
|
validate_pr_identifier(pull_request)
|
|
1378
1448
|
requested = pull_request_number(pull_request)
|
|
@@ -1430,7 +1500,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
1430
1500
|
LinkedRequirementBudget() if expected is not None else None
|
|
1431
1501
|
)
|
|
1432
1502
|
reviewed_linked_requirements = (
|
|
1433
|
-
linked_requirements(
|
|
1503
|
+
linked_requirements(
|
|
1504
|
+
metadata, linked_requirement_budget, arguments.requirement_issue
|
|
1505
|
+
)
|
|
1434
1506
|
if expected is not None
|
|
1435
1507
|
else None
|
|
1436
1508
|
)
|
|
@@ -1504,7 +1576,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
1504
1576
|
if final_scope != reviewed_scope:
|
|
1505
1577
|
raise RuntimeError("The review scope changed during evidence collection.")
|
|
1506
1578
|
final_linked_requirements = (
|
|
1507
|
-
linked_requirements(
|
|
1579
|
+
linked_requirements(
|
|
1580
|
+
final_metadata, linked_requirement_budget, arguments.requirement_issue
|
|
1581
|
+
)
|
|
1508
1582
|
if expected is not None
|
|
1509
1583
|
else None
|
|
1510
1584
|
)
|
|
@@ -1525,7 +1599,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
1525
1599
|
"changed_files": changed_files,
|
|
1526
1600
|
"changed_paths": changed_files,
|
|
1527
1601
|
"checks": checks,
|
|
1528
|
-
"pull_request":
|
|
1602
|
+
"pull_request": {
|
|
1603
|
+
key: value
|
|
1604
|
+
for key, value in final_metadata.items()
|
|
1605
|
+
if key != "reviewDecision"
|
|
1606
|
+
},
|
|
1607
|
+
"merge_readiness": merge_readiness(final_metadata),
|
|
1529
1608
|
}
|
|
1530
1609
|
if identity is not None:
|
|
1531
1610
|
evidence["reviewed_identity"] = identity.as_json()
|