Skip to content

Commit 3d65ff5

Browse files
committed
release: v0.3.0
Private-Snapshot-RevId: d22d9f18f5d9712921097d5829c28d056e3ffd31
1 parent 3305dfa commit 3d65ff5

33 files changed

Lines changed: 1696 additions & 196 deletions

.gitattributes

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Check out all text files with LF on every platform. Tests compare bytes
2+
# from checked-out files (skill templates, snapshots); a CRLF working tree
3+
# (core.autocrlf on Windows) broke those comparisons.
4+
* text=auto eol=lf
5+
6+
*.png binary

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ agree on the approach before you invest time — see CONTRIBUTING.md.
1212

1313
## Related issue
1414

15-
<!-- e.g. "Fixes #123" or "Refs #123". Link an issue for non-trivial changes. -->
15+
<!-- e.g. "Closes #123". Required for features and behavior changes — open an
16+
issue first and get it assigned (comment `/assign` there); see
17+
CONTRIBUTING.md → Contribution model. Docs and small fixes don't need one. -->
1618

1719
## Type of change
1820

.github/workflows/ci-nudge.yml

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# "Fix this and we merge" nudge (InsForge `agent-zhang-beihai` pattern, part 3).
2+
# When a PR's CI finishes red, posts ONE sticky comment listing exactly which
3+
# jobs failed and the local one-liner that reproduces/fixes each (the automated
4+
# version of the hand-written "run `npm run format` and you're green" review
5+
# comments). The comment flips to a green confirmation once all checks pass.
6+
#
7+
# Runs in base-repo context via workflow_run — no PR code is checked out, so
8+
# fork PRs are safe. State is recomputed from check-runs each time, so the two
9+
# CI workflows (CI + Test Coverage) can complete in any order.
10+
#
11+
# Bot identity: same App-token-first / GITHUB_TOKEN-fallback pattern as
12+
# pr-triage.yml (the App needs the "Pull requests: Read & write" permission to
13+
# post as testsprite-hob[bot]; until then comments come from github-actions[bot]).
14+
name: CI failure nudge
15+
16+
on:
17+
workflow_run:
18+
workflows: ['CI', 'Test Coverage']
19+
types: [completed]
20+
21+
permissions:
22+
checks: read # read the head SHA's check-run state
23+
pull-requests: write
24+
issues: write # PR comments ride the issues API
25+
26+
env:
27+
MARKER: '<!-- hob:ci-nudge -->'
28+
29+
jobs:
30+
nudge:
31+
# Public repo only; PR-triggered runs only (pushes to main have no PR to nudge).
32+
if: >-
33+
github.repository == 'TestSprite/testsprite-cli' &&
34+
github.event.workflow_run.event == 'pull_request'
35+
runs-on: ubuntu-latest
36+
steps:
37+
- id: app-token
38+
continue-on-error: true
39+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
40+
with:
41+
app-id: ${{ secrets.TESTSPRITE_HOB_APP_ID || secrets.ALFHEIM_AGENT_APP_ID }}
42+
private-key: ${{ secrets.TESTSPRITE_HOB_PRIVATE_KEY || secrets.ALFHEIM_AGENT_PRIVATE_KEY }}
43+
44+
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.0.1
45+
env:
46+
APP_TOKEN: ${{ steps.app-token.outputs.token }}
47+
with:
48+
script: |
49+
const { owner, repo } = context.repo;
50+
const run = context.payload.workflow_run;
51+
const marker = process.env.MARKER;
52+
53+
const appClient = process.env.APP_TOKEN
54+
? require('@actions/github').getOctokit(process.env.APP_TOKEN)
55+
: null;
56+
async function write(fn) {
57+
if (appClient) {
58+
try { return await fn(appClient.rest); }
59+
catch (e) { if (e.status !== 403 && e.status !== 404) throw e; }
60+
}
61+
return await fn(github.rest);
62+
}
63+
64+
// Resolve the PR for this run. `workflow_run.pull_requests` is empty
65+
// for fork PRs, so fall back to the commit→PRs lookup.
66+
let pr = (run.pull_requests || [])[0];
67+
if (!pr) {
68+
const { data } = await github.rest.repos.listPullRequestsAssociatedWithCommit(
69+
{ owner, repo, commit_sha: run.head_sha });
70+
pr = data.find(p => p.state === 'open');
71+
} else {
72+
pr = (await github.rest.pulls.get({ owner, repo, pull_number: pr.number })).data;
73+
}
74+
if (!pr || pr.state !== 'open' || pr.user.type === 'Bot') return;
75+
76+
// Job name → the local command that reproduces/fixes it. Also the
77+
// allowlist of CI jobs this nudge watches — keep in sync with ci.yml.
78+
const FIX = {
79+
'Lint & Format': 'run `npm run lint:fix && npm run format`, then commit',
80+
'Typecheck': 'run `npm run typecheck` and fix the reported type errors',
81+
'Unit Tests': 'run `npm test` and fix the failing tests',
82+
'Build': 'run `npm run build` and fix the compile errors',
83+
'Local E2E Tests': 'run `npm run test:e2e` (it builds first)',
84+
'Coverage (>= 80%)': 'run `npm run test:coverage` — new code needs tests until every metric is back at 80%',
85+
};
86+
87+
// Recompute full CI state from this SHA's check runs, narrowed to the
88+
// CI jobs above — other workflows (e.g. pr-triage) also attach
89+
// github-actions check runs to the PR head and must not count here.
90+
const checks = await github.paginate(github.rest.checks.listForRef,
91+
{ owner, repo, ref: run.head_sha, per_page: 100 });
92+
const ours = checks.filter(c => c.app && c.app.slug === 'github-actions' && FIX[c.name]);
93+
const failing = ours.filter(c => ['failure', 'timed_out'].includes(c.conclusion));
94+
const pending = ours.filter(c => c.status !== 'completed');
95+
96+
const comments = await github.paginate(github.rest.issues.listComments,
97+
{ owner, repo, issue_number: pr.number, per_page: 100 });
98+
const sticky = comments.find(c => (c.body || '').includes(marker));
99+
100+
if (failing.length === 0) {
101+
// Only speak up on success if we previously flagged a failure, and
102+
// only once everything has actually finished.
103+
if (sticky && pending.length === 0 && !sticky.body.includes('all green')) {
104+
await write(rest => rest.issues.updateComment({ owner, repo, comment_id: sticky.id,
105+
body: `${marker}\n✅ CI is **all green** now — thanks, @${pr.user.login}!` }));
106+
}
107+
return;
108+
}
109+
110+
const lines = failing
111+
.sort((a, b) => a.name.localeCompare(b.name))
112+
.map(c => {
113+
const fix = FIX[c.name] || 'see the logs for details';
114+
return `- **${c.name}** — ${fix} ([logs](${c.html_url}))`;
115+
});
116+
const body = `${marker}\nThanks, @${pr.user.login}! CI is red on this PR — `
117+
+ `here's what failed and how to reproduce it locally:\n\n${lines.join('\n')}\n\n`
118+
+ `Everything runs on Node 22 after \`npm ci\`. Push a fix and this comment `
119+
+ `flips green automatically once all checks pass.`;
120+
121+
if (sticky) {
122+
if (sticky.body !== body) {
123+
await write(rest => rest.issues.updateComment(
124+
{ owner, repo, comment_id: sticky.id, body }));
125+
}
126+
} else {
127+
await write(rest => rest.issues.createComment(
128+
{ owner, repo, issue_number: pr.number, body }));
129+
}

.github/workflows/issue-triage.yml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# P1-2 issue auto-assign + 3-slot-cap bot (InsForge `agent-zhang-beihai` pattern).
2-
# Posts as the `alfheim-agent` GitHub App ⇒ `alfheim-agent[bot]`.
2+
# Posts as the `testsprite-hob` GitHub App ⇒ `testsprite-hob[bot]`.
33
#
44
# Setup (one-time, by an org admin):
5-
# 1. Create a GitHub App named `alfheim-agent` (org Settings → Developer settings →
5+
# 1. Create a GitHub App named `testsprite-hob` (org Settings → Developer settings →
66
# GitHub Apps → New). Permissions: Issues = Read & write, Metadata = Read. No webhook.
77
# Generate a private key; install the App on the public testsprite-cli repo.
88
# 2. Add two repo (or org) secrets:
9-
# ALFHEIM_AGENT_APP_ID = the App's numeric App ID
10-
# ALFHEIM_AGENT_PRIVATE_KEY = the App's .pem private key (full contents)
9+
# TESTSPRITE_HOB_APP_ID = the App's numeric App ID
10+
# TESTSPRITE_HOB_PRIVATE_KEY = the App's .pem private key (full contents)
11+
# (The ALFHEIM_AGENT_* fallbacks are this App's original secret names from
12+
# before it was renamed — same App ID + key; either naming works.)
1113
# Until those exist, the bot gracefully falls back to github-actions[bot] (still works).
1214
#
1315
# Fires on each new issue comment; assigns the commenter when they claim an issue
@@ -30,23 +32,26 @@ env:
3032
jobs:
3133
triage:
3234
# issues only (issue_comment also fires on PRs), and never react to a bot's own
33-
# comment — incl. our own alfheim-agent[bot], which (unlike GITHUB_TOKEN) would
35+
# comment — incl. our own testsprite-hob[bot], which (unlike GITHUB_TOKEN) would
3436
# otherwise re-trigger this workflow. `type == 'Bot'` covers both bot identities.
3537
if: ${{ !github.event.issue.pull_request && github.event.comment.user.type != 'Bot' }}
3638
runs-on: ubuntu-latest
3739
steps:
38-
# Mint a token for the alfheim-agent App so comments post as alfheim-agent[bot].
40+
# Mint a token for the testsprite-hob App so comments post as testsprite-hob[bot].
3941
# continue-on-error: until the App + secrets exist this no-ops and we fall back below.
4042
- id: app-token
4143
continue-on-error: true
4244
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
4345
with:
44-
app-id: ${{ secrets.ALFHEIM_AGENT_APP_ID }}
45-
private-key: ${{ secrets.ALFHEIM_AGENT_PRIVATE_KEY }}
46+
app-id: ${{ secrets.TESTSPRITE_HOB_APP_ID || secrets.ALFHEIM_AGENT_APP_ID }}
47+
private-key: ${{ secrets.TESTSPRITE_HOB_PRIVATE_KEY || secrets.ALFHEIM_AGENT_PRIVATE_KEY }}
48+
# Scope the minted token to what this job uses (issues API only) instead
49+
# of inheriting the App's full installation permissions.
50+
permission-issues: write
4651

4752
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.0.1
4853
with:
49-
# App token when available (→ alfheim-agent[bot]); else the default
54+
# App token when available (→ testsprite-hob[bot]); else the default
5055
# GITHUB_TOKEN (→ github-actions[bot]). Either way the logic is identical.
5156
github-token: ${{ steps.app-token.outputs.token || github.token }}
5257
script: |

0 commit comments

Comments
 (0)