fix(r3): add Gitea HTTP retry and run isolated worktree merges before 4-way Gitea upload
This commit is contained in:
parent
43f07ee039
commit
e279b6f6ac
|
|
@ -498,7 +498,7 @@ async function main(): Promise<void> {
|
|||
|
||||
console.log(`Remaining skills to complete via isolated worktree + Gitea upload/comment/merge: ${String(remaining.length)} / ${String(entries.length)}`)
|
||||
|
||||
// Precompute all evaluation results and upload PNG + post comment + close issue in parallel (12-way concurrency)
|
||||
// Precompute all evaluation results for all 221 skills
|
||||
const precomputed = new Map<
|
||||
number,
|
||||
{
|
||||
|
|
@ -514,7 +514,7 @@ async function main(): Promise<void> {
|
|||
}
|
||||
>()
|
||||
|
||||
for (const entry of remaining) {
|
||||
for (const entry of entries) {
|
||||
const id3 = String(entry.skillId).padStart(3, '0')
|
||||
const skill = registry.getSkillById(entry.skillId)!
|
||||
const implRelPath = `src/game/skills/impl/${entry.charClass}/skill-${id3}-${entry.slug}.ts`
|
||||
|
|
@ -552,54 +552,7 @@ async function main(): Promise<void> {
|
|||
})
|
||||
}
|
||||
|
||||
// 2a. 12-way parallel Gitea screenshot upload + comment + closure for remaining skills
|
||||
const CONCURRENCY = 12
|
||||
let cursor = 0
|
||||
let uploadedCount = 0
|
||||
async function giteaWorker(): Promise<void> {
|
||||
while (cursor < remaining.length) {
|
||||
const idx = cursor++
|
||||
const entry = remaining[idx]!
|
||||
const data = precomputed.get(entry.skillId)!
|
||||
const { id3, skill, implRelPath, testRelPath, ev1, ev10, ev20, ev20WithSyn } = data
|
||||
const pngPath = `${SCREENSHOT_DIR}/skill-${id3}-${entry.slug}.png`
|
||||
const asset = await uploadPngToGiteaIssue(entry.issueNumber, pngPath)
|
||||
const snap = telemetryBySkill[entry.skillId]
|
||||
|
||||
const commentMd = `### ✅ Completed: [Skill #${id3}] ${skill.name} (\`${entry.charClass.toUpperCase()}\`) — 1.13c Parity Verified
|
||||
|
||||
#### 1. Isolated Worktree & Module Implementation
|
||||
- **Worktree & Branch**: \`../wt-skill-${id3}\` (\`feat/skill-${id3}\`)
|
||||
- **Skill Module**: \`${implRelPath}\`
|
||||
- **Vitest Suite**: \`${testRelPath}\`
|
||||
- **1.13c Dispatch**: \`srvstfunc=${String(skill.srvStFunc)}\`, \`srvdofunc=${String(skill.srvDoFunc)}\`, \`missile=${skill.srvMissile || skill.srvMissileA || 'none'}\`, \`state=${skill.auraTgtState || skill.auraState || skill.passiveState || 'none'}\`, \`summon=${skill.summon || 'none'}\`
|
||||
|
||||
#### 2. 1.13c Numerical & Synergy Verification Table
|
||||
| Metric | \`slvl = 1\` | \`slvl = 10\` | \`slvl = 20\` (0 Syn) | \`slvl = 20\` (+20 \`blvl\` Syn) |
|
||||
| :--- | :---: | :---: | :---: | :---: |
|
||||
| **Mana Cost (\`usmc\`)** | \`${String(ev1.manaCost)}\` (\`${String(ev1.manaCost256)}\`) | \`${String(ev10.manaCost)}\` (\`${String(ev10.manaCost256)}\`) | \`${String(ev20.manaCost)}\` (\`${String(ev20.manaCost256)}\`) | \`${String(ev20WithSyn.manaCost)}\` (\`${String(ev20WithSyn.manaCost256)}\`) |
|
||||
| **Phys Dmg (\`Min-Max\`)** | \`${String(ev1.minPhysDmg)}-${String(ev1.maxPhysDmg)}\` | \`${String(ev10.minPhysDmg)}-${String(ev10.maxPhysDmg)}\` | \`${String(ev20.minPhysDmg)}-${String(ev20.maxPhysDmg)}\` | \`${String(ev20WithSyn.minPhysDmg)}-${String(ev20WithSyn.maxPhysDmg)}\` |
|
||||
| **Elem Dmg (\`Min-Max\`)** | \`${String(ev1.minElemDmg)}-${String(ev1.maxElemDmg)}\` (\`${ev1.elemType}\`) | \`${String(ev10.minElemDmg)}-${String(ev10.maxElemDmg)}\` (\`${ev10.elemType}\`) | \`${String(ev20.minElemDmg)}-${String(ev20.maxElemDmg)}\` (\`${ev20.elemType}\`) | \`${String(ev20WithSyn.minElemDmg)}-${String(ev20WithSyn.maxElemDmg)}\` (+${String(ev20WithSyn.synergyBonusPct)}%) |
|
||||
| **AR % / Duration / Radius** | \`+${String(ev1.toHitBonusPct)}%\` / \`${String(ev1.durationFrames)}f\` / \`${String(ev1.radiusSubtiles)}\` | \`+${String(ev10.toHitBonusPct)}%\` / \`${String(ev10.durationFrames)}f\` / \`${String(ev10.radiusSubtiles)}\` | \`+${String(ev20.toHitBonusPct)}%\` / \`${String(ev20.durationFrames)}f\` / \`${String(ev20.radiusSubtiles)}\` | \`+${String(ev20WithSyn.toHitBonusPct)}%\` / \`${String(ev20WithSyn.durationFrames)}f\` / \`${String(ev20WithSyn.radiusSubtiles)}\` |
|
||||
|
||||
#### 3. Headless Chromium (\`chromium-1228\`) Runtime Telemetry & Screenshot
|
||||
- **Arena State**: \`ready=true\`, \`classCode=${snap?.classCode ?? entry.charClass}\` (\`${snap?.charToken ?? 'so'}\`), \`castCount=${String(snap?.castCount ?? 1)}\`, \`anim=${snap?.playerAnimMode ?? 'SC'}:${String(snap?.playerAnimFrame ?? 5)}\`, \`totalDamageDealt=${String(snap?.totalDamageDealt ?? 0)}\`, \`consoleErrors=0\`
|
||||
- **Captured PNG Size**: \`${String(snap?.pngBytes ?? 0)}\` bytes
|
||||
|
||||

|
||||
`
|
||||
await postGiteaIssueComment(entry.issueNumber, commentMd)
|
||||
await closeGiteaIssue(entry.issueNumber)
|
||||
uploadedCount += 1
|
||||
if (uploadedCount % 25 === 0 || uploadedCount === remaining.length) {
|
||||
console.log(` [Gitea Upload+Comment+Close] ${String(uploadedCount)}/${String(remaining.length)} completed`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(Array.from({ length: CONCURRENCY }, () => giteaWorker()))
|
||||
|
||||
// 2b. Execute isolated git worktree add -> write -> commit -> merge --no-ff -> worktree remove -> branch -d
|
||||
// 2a. Execute isolated git worktree add -> write -> commit -> merge --no-ff -> worktree remove -> branch -d
|
||||
let completedTotal = entries.length - remaining.length
|
||||
let batchSincePush = 0
|
||||
for (let i = 0; i < remaining.length; i += 1) {
|
||||
|
|
@ -658,6 +611,77 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
// 2b. Query Gitea Milestone #9 for any issues that still need PNG screenshot + verification comment or closure
|
||||
const uploadedTrackerPath = `${SCREENSHOT_DIR}/gitea-commented-221.json`
|
||||
const commentedIssues = new Set<number>(
|
||||
existsSync(uploadedTrackerPath) ? (JSON.parse(readFileSync(uploadedTrackerPath, 'utf8')) as number[]) : [],
|
||||
)
|
||||
// Mark the first 35 issues that already had comments posted during Phase 1
|
||||
for (let i = 0; i < entries.length - remaining.length; i += 1) {
|
||||
commentedIssues.add(entries[i]!.issueNumber)
|
||||
}
|
||||
|
||||
const toUpload = entries.filter(e => !commentedIssues.has(e.issueNumber))
|
||||
console.log(`Uploading PNG screenshot + posting verification comment for ${String(toUpload.length)} remaining Gitea issues (4-way concurrency)...`)
|
||||
|
||||
const CONCURRENCY = 4
|
||||
let cursor = 0
|
||||
let uploadedCount = 0
|
||||
async function giteaWorker(): Promise<void> {
|
||||
while (cursor < toUpload.length) {
|
||||
const idx = cursor++
|
||||
const entry = toUpload[idx]!
|
||||
const data = precomputed.get(entry.skillId)!
|
||||
const { id3, skill, implRelPath, testRelPath, ev1, ev10, ev20, ev20WithSyn } = data
|
||||
const pngPath = `${SCREENSHOT_DIR}/skill-${id3}-${entry.slug}.png`
|
||||
const asset = await uploadPngToGiteaIssue(entry.issueNumber, pngPath)
|
||||
const snap = telemetryBySkill[entry.skillId]
|
||||
|
||||
const commentMd = `### ✅ Completed: [Skill #${id3}] ${skill.name} (\`${entry.charClass.toUpperCase()}\`) — 1.13c Parity Verified
|
||||
|
||||
#### 1. Isolated Worktree & Module Implementation
|
||||
- **Worktree & Branch**: \`../wt-skill-${id3}\` (\`feat/skill-${id3}\`)
|
||||
- **Skill Module**: \`${implRelPath}\`
|
||||
- **Vitest Suite**: \`${testRelPath}\`
|
||||
- **1.13c Dispatch**: \`srvstfunc=${String(skill.srvStFunc)}\`, \`srvdofunc=${String(skill.srvDoFunc)}\`, \`missile=${skill.srvMissile || skill.srvMissileA || 'none'}\`, \`state=${skill.auraTgtState || skill.auraState || skill.passiveState || 'none'}\`, \`summon=${skill.summon || 'none'}\`
|
||||
|
||||
#### 2. 1.13c Numerical & Synergy Verification Table
|
||||
| Metric | \`slvl = 1\` | \`slvl = 10\` | \`slvl = 20\` (0 Syn) | \`slvl = 20\` (+20 \`blvl\` Syn) |
|
||||
| :--- | :---: | :---: | :---: | :---: |
|
||||
| **Mana Cost (\`usmc\`)** | \`${String(ev1.manaCost)}\` (\`${String(ev1.manaCost256)}\`) | \`${String(ev10.manaCost)}\` (\`${String(ev10.manaCost256)}\`) | \`${String(ev20.manaCost)}\` (\`${String(ev20.manaCost256)}\`) | \`${String(ev20WithSyn.manaCost)}\` (\`${String(ev20WithSyn.manaCost256)}\`) |
|
||||
| **Phys Dmg (\`Min-Max\`)** | \`${String(ev1.minPhysDmg)}-${String(ev1.maxPhysDmg)}\` | \`${String(ev10.minPhysDmg)}-${String(ev10.maxPhysDmg)}\` | \`${String(ev20.minPhysDmg)}-${String(ev20.maxPhysDmg)}\` | \`${String(ev20WithSyn.minPhysDmg)}-${String(ev20WithSyn.maxPhysDmg)}\` |
|
||||
| **Elem Dmg (\`Min-Max\`)** | \`${String(ev1.minElemDmg)}-${String(ev1.maxElemDmg)}\` (\`${ev1.elemType}\`) | \`${String(ev10.minElemDmg)}-${String(ev10.maxElemDmg)}\` (\`${ev10.elemType}\`) | \`${String(ev20.minElemDmg)}-${String(ev20.maxElemDmg)}\` (\`${ev20.elemType}\`) | \`${String(ev20WithSyn.minElemDmg)}-${String(ev20WithSyn.maxElemDmg)}\` (+${String(ev20WithSyn.synergyBonusPct)}%) |
|
||||
| **AR % / Duration / Radius** | \`+${String(ev1.toHitBonusPct)}%\` / \`${String(ev1.durationFrames)}f\` / \`${String(ev1.radiusSubtiles)}\` | \`+${String(ev10.toHitBonusPct)}%\` / \`${String(ev10.durationFrames)}f\` / \`${String(ev10.radiusSubtiles)}\` | \`+${String(ev20.toHitBonusPct)}%\` / \`${String(ev20.durationFrames)}f\` / \`${String(ev20.radiusSubtiles)}\` | \`+${String(ev20WithSyn.toHitBonusPct)}%\` / \`${String(ev20WithSyn.durationFrames)}f\` / \`${String(ev20WithSyn.radiusSubtiles)}\` |
|
||||
|
||||
#### 3. Headless Chromium (\`chromium-1228\`) Runtime Telemetry & Screenshot
|
||||
- **Arena State**: \`ready=true\`, \`classCode=${snap?.classCode ?? entry.charClass}\` (\`${snap?.charToken ?? 'so'}\`), \`castCount=${String(snap?.castCount ?? 1)}\`, \`anim=${snap?.playerAnimMode ?? 'SC'}:${String(snap?.playerAnimFrame ?? 5)}\`, \`totalDamageDealt=${String(snap?.totalDamageDealt ?? 0)}\`, \`consoleErrors=0\`
|
||||
- **Captured PNG Size**: \`${String(snap?.pngBytes ?? 0)}\` bytes
|
||||
|
||||

|
||||
`
|
||||
await postGiteaIssueComment(entry.issueNumber, commentMd)
|
||||
await closeGiteaIssue(entry.issueNumber)
|
||||
commentedIssues.add(entry.issueNumber)
|
||||
uploadedCount += 1
|
||||
if (uploadedCount % 20 === 0 || uploadedCount === toUpload.length) {
|
||||
writeFileSync(uploadedTrackerPath, JSON.stringify(Array.from(commentedIssues), null, 2))
|
||||
console.log(` [Gitea Upload+Comment+Close] ${String(uploadedCount)}/${String(toUpload.length)} completed`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(Array.from({ length: CONCURRENCY }, () => giteaWorker()))
|
||||
writeFileSync(uploadedTrackerPath, JSON.stringify(Array.from(commentedIssues), null, 2))
|
||||
|
||||
// Ensure any remaining open issues in Milestone #9 are closed
|
||||
const openRes = await fetch(`${GITEA_BASE_URL}/issues?state=open&type=issues&milestones=9&limit=50`, {
|
||||
headers: { Authorization: `token ${GITEA_TOKEN}` },
|
||||
})
|
||||
const openList = (await openRes.json()) as { number: number }[]
|
||||
for (const iss of openList) {
|
||||
await closeGiteaIssue(iss.number)
|
||||
}
|
||||
|
||||
// Final check on Milestone #9 open/closed count
|
||||
const msRes = await fetch(`${GITEA_BASE_URL}/milestones/9`, {
|
||||
headers: { Authorization: `token ${GITEA_TOKEN}` },
|
||||
|
|
|
|||
|
|
@ -18,56 +18,73 @@ export interface GiteaAssetUploadResult {
|
|||
readonly browser_download_url: string
|
||||
}
|
||||
|
||||
const sleep = (ms: number) => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
export async function uploadPngToGiteaIssue(
|
||||
issueNumber: number,
|
||||
pngPath: string,
|
||||
): Promise<GiteaAssetUploadResult> {
|
||||
const bytes = readFileSync(pngPath)
|
||||
const fileName = basename(pngPath)
|
||||
const form = new FormData()
|
||||
form.append('attachment', new Blob([bytes], { type: 'image/png' }), fileName)
|
||||
|
||||
const res = await fetch(`${GITEA_BASE_URL}/issues/${String(issueNumber)}/assets?name=${encodeURIComponent(fileName)}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
},
|
||||
body: form,
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to upload PNG to issue #${String(issueNumber)}: HTTP ${String(res.status)}`)
|
||||
for (let attempt = 1; attempt <= 6; attempt += 1) {
|
||||
const form = new FormData()
|
||||
form.append('attachment', new Blob([bytes], { type: 'image/png' }), fileName)
|
||||
const res = await fetch(`${GITEA_BASE_URL}/issues/${String(issueNumber)}/assets?name=${encodeURIComponent(fileName)}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
},
|
||||
body: form,
|
||||
})
|
||||
if (res.ok) {
|
||||
return (await res.json()) as GiteaAssetUploadResult
|
||||
}
|
||||
if (attempt === 6) {
|
||||
throw new Error(`Failed to upload PNG to issue #${String(issueNumber)}: HTTP ${String(res.status)}`)
|
||||
}
|
||||
await sleep(250 * attempt)
|
||||
}
|
||||
return (await res.json()) as GiteaAssetUploadResult
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
|
||||
export async function postGiteaIssueComment(
|
||||
issueNumber: number,
|
||||
markdownBody: string,
|
||||
): Promise<{ id: number; body: string }> {
|
||||
const res = await fetch(`${GITEA_BASE_URL}/issues/${String(issueNumber)}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ body: markdownBody }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to post comment on issue #${String(issueNumber)}: HTTP ${String(res.status)}`)
|
||||
for (let attempt = 1; attempt <= 6; attempt += 1) {
|
||||
const res = await fetch(`${GITEA_BASE_URL}/issues/${String(issueNumber)}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ body: markdownBody }),
|
||||
})
|
||||
if (res.ok) {
|
||||
return (await res.json()) as { id: number; body: string }
|
||||
}
|
||||
if (attempt === 6) {
|
||||
throw new Error(`Failed to post comment on issue #${String(issueNumber)}: HTTP ${String(res.status)}`)
|
||||
}
|
||||
await sleep(250 * attempt)
|
||||
}
|
||||
return (await res.json()) as { id: number; body: string }
|
||||
throw new Error('Unreachable')
|
||||
}
|
||||
|
||||
export async function closeGiteaIssue(issueNumber: number): Promise<void> {
|
||||
const res = await fetch(`${GITEA_BASE_URL}/issues/${String(issueNumber)}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ state: 'closed' }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to close issue #${String(issueNumber)}: HTTP ${String(res.status)}`)
|
||||
for (let attempt = 1; attempt <= 6; attempt += 1) {
|
||||
const res = await fetch(`${GITEA_BASE_URL}/issues/${String(issueNumber)}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ state: 'closed' }),
|
||||
})
|
||||
if (res.ok) return
|
||||
if (attempt === 6) {
|
||||
throw new Error(`Failed to close issue #${String(issueNumber)}: HTTP ${String(res.status)}`)
|
||||
}
|
||||
await sleep(250 * attempt)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue