@staff0rd/assist 0.786.1 → 0.788.0
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/claude/skills/review-ci/SKILL.md +27 -8
- package/claude/skills/review-ci/review-ci.yml +14 -4
- package/claude/skills/review-ci/scripts/check.mjs +1 -1
- package/claude/skills/review-ci/scripts/init.mjs +32 -32
- package/claude/skills/review-ci/scripts/review.mjs +144 -144
- package/dist/commands/sessions/web/bundle.js +384 -384
- package/dist/index.js +53 -33
- package/package.json +1 -1
|
@@ -30,19 +30,38 @@ Never overwrite a differing target without the user's confirmation.
|
|
|
30
30
|
|
|
31
31
|
## 3. Run init
|
|
32
32
|
|
|
33
|
-
`init.mjs` asks for the
|
|
33
|
+
`init.mjs` asks for the config, checks every model can be reached, then sets the repo variables (and the secret, when an API key is used) with `gh`. It needs a terminal for its prompts, and the API key must never pass through this conversation.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
The review has three slots: two reviewers and the synthesis. Each slot is `claude:<model>` (run by Claude Code) or `codex:<model>` (run by Codex), so a provider with only GPT deployments can run every slot on Codex.
|
|
36
|
+
|
|
37
|
+
1. Ask the user for:
|
|
38
|
+
- the provider: `litellm` or `foundry`.
|
|
39
|
+
- the base URL:
|
|
40
|
+
- `litellm`: the proxy root (e.g. `https://litellm.example.com`); Claude uses it directly and Codex uses `<url>/v1`.
|
|
41
|
+
- `foundry`: the Azure AI Foundry resource root, `https://<resource>.services.ai.azure.com`, with no path; Claude uses `<url>/anthropic` and Codex uses `<url>/openai/v1`. The models are the resource's deployment names.
|
|
42
|
+
- each slot as `claude:<model>` or `codex:<model>`: reviewer 1, reviewer 2 and the synthesis.
|
|
43
|
+
- for `foundry`, the auth: an API key, or Entra. Entra is required when the resource has local auth disabled (`az cognitiveservices account show ... --query properties.disableLocalAuth`). For Entra, ask for the client ID and tenant ID of the identity with `Cognitive Services OpenAI User` on the resource, and optionally a GitHub environment to scope its federated credential to.
|
|
44
|
+
|
|
45
|
+
Do not ask for the API key.
|
|
46
|
+
|
|
47
|
+
2. Tell the user to run this in their own terminal from the repo root. Add `--azure-client-id <id> --azure-tenant-id <id> [--environment <name>]` for Entra. Without them, it prompts for the API key with masked input.
|
|
39
48
|
|
|
40
49
|
```
|
|
41
|
-
node .github/review-ci/init.mjs --provider <provider> --base-url <url> --
|
|
50
|
+
node .github/review-ci/init.mjs --provider <provider> --base-url <url> --reviewer-1 <harness:model> --reviewer-2 <harness:model> --synthesis <harness:model>
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
With Entra, init checks the models with the user's own `az login` token. Add `--skip-check` if the user's identity cannot reach the models but the CI identity can; the workflow's check step still verifies them.
|
|
54
|
+
|
|
55
|
+
3. For Entra, the identity needs a federated credential with issuer `https://token.actions.githubusercontent.com`, audience `api://AzureADTokenExchange`, and subject `repo:<owner>/<repo>:environment:<environment>` when an environment was given, otherwise `repo:<owner>/<repo>:pull_request`. Show the user the command, and do not run it yourself:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
az identity federated-credential create --name review-ci-<repo> --identity-name <identity> --resource-group <rg> --issuer https://token.actions.githubusercontent.com --subject <subject> --audiences api://AzureADTokenExchange
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
(`az ad app federated-credential create` for an app registration.) When an environment was given, it must exist in the repo (`gh api -X PUT repos/<owner>/<repo>/environments/<environment>`).
|
|
62
|
+
|
|
63
|
+
4. Once they report it finished, confirm with `gh variable list` (and `gh secret list` for an API key) that the variables are set. If init reported an unreachable model, relay its error and let the user re-run it.
|
|
45
64
|
|
|
46
65
|
## 4. Report
|
|
47
66
|
|
|
48
|
-
List the files written or skipped, and remind the user to commit `.github/review-ci/` and `.github/workflows/review-ci.yml`. The workflow runs on `pull_request: opened` and fails at its check step, naming the problem, when a variable or the secret is unset or a model cannot be reached. Its review step then posts the findings as a `COMMENT` review on the PR.
|
|
67
|
+
List the files written or skipped, and remind the user to commit `.github/review-ci/` and `.github/workflows/review-ci.yml`. The workflow runs on `pull_request: opened` and fails at its check step, naming the problem, when a variable or the secret is unset, the Entra token cannot be obtained, or a model cannot be reached. Its review step then posts the findings as a `COMMENT` review on the PR.
|
|
@@ -7,15 +7,20 @@ on:
|
|
|
7
7
|
permissions:
|
|
8
8
|
contents: read
|
|
9
9
|
pull-requests: write
|
|
10
|
+
id-token: write
|
|
10
11
|
|
|
11
12
|
jobs:
|
|
12
13
|
review:
|
|
13
14
|
runs-on: ubuntu-latest
|
|
15
|
+
environment: ${{ vars.ASSIST_REVIEW_ENVIRONMENT }}
|
|
14
16
|
env:
|
|
15
17
|
ASSIST_REVIEW_PROVIDER: ${{ vars.ASSIST_REVIEW_PROVIDER }}
|
|
16
18
|
ASSIST_REVIEW_BASE_URL: ${{ vars.ASSIST_REVIEW_BASE_URL }}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
ASSIST_REVIEW_REVIEWER_1: ${{ vars.ASSIST_REVIEW_REVIEWER_1 }}
|
|
20
|
+
ASSIST_REVIEW_REVIEWER_2: ${{ vars.ASSIST_REVIEW_REVIEWER_2 }}
|
|
21
|
+
ASSIST_REVIEW_SYNTHESIS: ${{ vars.ASSIST_REVIEW_SYNTHESIS }}
|
|
22
|
+
ASSIST_REVIEW_AZURE_CLIENT_ID: ${{ vars.ASSIST_REVIEW_AZURE_CLIENT_ID }}
|
|
23
|
+
ASSIST_REVIEW_AZURE_TENANT_ID: ${{ vars.ASSIST_REVIEW_AZURE_TENANT_ID }}
|
|
19
24
|
ASSIST_REVIEW_API_KEY: ${{ secrets.ASSIST_REVIEW_API_KEY }}
|
|
20
25
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
21
26
|
steps:
|
|
@@ -30,8 +35,13 @@ jobs:
|
|
|
30
35
|
with:
|
|
31
36
|
node-version: "lts/*"
|
|
32
37
|
|
|
33
|
-
- name: Install Claude Code
|
|
34
|
-
|
|
38
|
+
- name: Install Claude Code
|
|
39
|
+
if: startsWith(vars.ASSIST_REVIEW_REVIEWER_1, 'claude:') || startsWith(vars.ASSIST_REVIEW_REVIEWER_2, 'claude:') || startsWith(vars.ASSIST_REVIEW_SYNTHESIS, 'claude:')
|
|
40
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
41
|
+
|
|
42
|
+
- name: Install Codex
|
|
43
|
+
if: startsWith(vars.ASSIST_REVIEW_REVIEWER_1, 'codex:') || startsWith(vars.ASSIST_REVIEW_REVIEWER_2, 'codex:') || startsWith(vars.ASSIST_REVIEW_SYNTHESIS, 'codex:')
|
|
44
|
+
run: npm install -g @openai/codex
|
|
35
45
|
|
|
36
46
|
- name: Check review config
|
|
37
47
|
run: node .github/review-ci/check.mjs
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
|
|
2
|
-
var
|
|
2
|
+
var f=["ASSIST_REVIEW_PROVIDER","ASSIST_REVIEW_BASE_URL","ASSIST_REVIEW_REVIEWER_1","ASSIST_REVIEW_REVIEWER_2","ASSIST_REVIEW_SYNTHESIS"];var c="ASSIST_REVIEW_API_KEY",m=["litellm","foundry"],d=["claude","codex"];function E(e,r){if(e==="litellm")return{anthropic:r,responses:`${r}/v1`,anthropicKind:"gateway"};if(e==="foundry")return{anthropic:`${r}/anthropic`,responses:`${r}/openai/v1`,anthropicKind:"foundry"};throw new Error(`Unsupported ASSIST_REVIEW_PROVIDER "${e}" (expected one of: ${m.join(", ")})`)}async function l(e,r,t){try{let i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify(t),signal:AbortSignal.timeout(6e4)});if(i.ok)return;let n=(await i.text()).trim().slice(0,300);return`HTTP ${i.status}${n?`: ${n}`:""}`}catch(i){if(!(i instanceof Error))return String(i);let n=i.cause instanceof Error?i.cause.message:"";return n?`${i.message} (${n})`:i.message}}function A(e,r,t){let i={Authorization:`Bearer ${t}`};return e.auth.kind==="entra"?i:r.anthropicKind==="foundry"?{"x-api-key":t}:{...i,"x-api-key":t}}async function k(e,r,t,i){if(e.harness==="codex"){let s=`${t.responses}/responses`,a=await l(s,{Authorization:`Bearer ${i}`},{model:e.model,input:"ping",max_output_tokens:16});return a&&`Codex model "${e.model}" at ${s}: ${a}`}let n=`${t.anthropic}/v1/messages`,o=await l(n,{...A(r,t,i),"anthropic-version":"2023-06-01"},{model:e.model,max_tokens:1,messages:[{role:"user",content:"ping"}]});return o&&`Claude model "${e.model}" at ${n}: ${o}`}function x(e){let r=new Map;for(let t of Object.values(e.slots))r.set(`${t.harness}:${t.model}`,t);return[...r.values()]}async function S(e,r,t){return(await Promise.all(x(e).map(n=>k(n,e,r,t)))).filter(n=>!!n)}function $(e){return d.includes(e)}function R(e,r){let t=r.indexOf(":"),i=r.slice(0,t).trim(),n=r.slice(t+1).trim();return t===-1||!$(i)||!n?`${e} "${r}" must be ${d.map(o=>`${o}:<model>`).join(" or ")}`:{harness:i,model:n}}function p(e,r){return e==="foundry"&&r("ASSIST_REVIEW_AZURE_CLIENT_ID")?["ASSIST_REVIEW_AZURE_CLIENT_ID","ASSIST_REVIEW_AZURE_TENANT_ID"]:[c]}function w(e,r){return p(e,r).includes(c)?{kind:"key",apiKey:r(c)}:{kind:"entra",clientId:r("ASSIST_REVIEW_AZURE_CLIENT_ID"),tenantId:r("ASSIST_REVIEW_AZURE_TENANT_ID")}}var b={claude:"ASSIST_REVIEW_REVIEWER_1",codex:"ASSIST_REVIEW_REVIEWER_2",synthesis:"ASSIST_REVIEW_SYNTHESIS"};function g(e){let r=s=>e[s]?.trim()??"",t=r("ASSIST_REVIEW_PROVIDER"),i=[...f,...p(t,r)].filter(s=>!r(s));if(i.length>0)return{config:null,errors:i.map(s=>`${s} is not set`)};let n=Object.entries(b).map(([s,a])=>[s,R(a,r(a))]),o=n.flatMap(([,s])=>typeof s=="string"?[s]:[]);return o.length>0?{config:null,errors:o}:{config:{provider:t,baseUrl:r("ASSIST_REVIEW_BASE_URL").replace(/\/+$/,""),slots:Object.fromEntries(n),auth:w(t,r)},errors:o}}var u="https://cognitiveservices.azure.com";var v=3e4;async function C(e,r){if(!e.ok){let t=(await e.text()).trim().slice(0,300);throw new Error(`${r}: HTTP ${e.status}${t?`: ${t}`:""}`)}return await e.json()}async function V(e,r){let t=new URL(e);t.searchParams.set("audience","api://AzureADTokenExchange");let i=await fetch(t,{headers:{Authorization:`bearer ${r}`},signal:AbortSignal.timeout(v)}),n=await C(i,"GitHub OIDC token request failed");if(!n.value)throw new Error("GitHub OIDC token response had no value");return n.value}async function _(e,r,t){let i=await V(t.requestUrl,t.requestToken),n=await fetch(`https://login.microsoftonline.com/${encodeURIComponent(r)}/oauth2/v2.0/token`,{method:"POST",body:new URLSearchParams({grant_type:"client_credentials",client_id:e,scope:`${u}/.default`,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:i}),signal:AbortSignal.timeout(v)}),o=await C(n,`Entra token exchange for client ${e} failed`);if(!o.access_token)throw new Error("Entra token response had no access_token");return o.access_token}import{execFileSync as N}from"node:child_process";function y(e){try{return N("az",["account","get-access-token","--resource",u,"--tenant",e,"--query","accessToken","-o","tsv"],{encoding:"utf8",stdio:["ignore","pipe","pipe"],shell:process.platform==="win32"}).trim()}catch(r){let t=r.stderr?.trim();throw new Error(`az account get-access-token failed for tenant ${e}${t?`: ${t}`:""}; run az login --tenant ${e}`)}}async function I(e,r){if(e.kind==="key")return e.apiKey;let t=r.ACTIONS_ID_TOKEN_REQUEST_URL,i=r.ACTIONS_ID_TOKEN_REQUEST_TOKEN;if(t&&i){let n=await _(e.clientId,e.tenantId,{requestUrl:t,requestToken:i});return console.log(`::add-mask::${n}`),n}if(r.GITHUB_ACTIONS)throw new Error("No GitHub OIDC token endpoint; give the job the id-token: write permission");return y(e.tenantId)}function h(e){return e instanceof Error?e.message:String(e)}async function T(e){let{config:r,errors:t}=g(e);if(!r)return t;let i;try{i=E(r.provider,r.baseUrl)}catch(o){return[h(o)]}let n;try{n=await I(r.auth,e)}catch(o){return[h(o)]}return S(r,i,n)}async function O(){let e=await T(process.env);if(e.length>0){for(let r of e)console.error(`review-ci: ${r}`);process.exit(1)}console.log("review-ci: config is set and every model is reachable")}await O();export{O as reviewCiCheck};
|