mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:35:21 +00:00
Workflows nativos em .forgejo/workflows/ (ci, ci-pr, agent-pool, automerge, issue-transition, plan-issue, vercel-promote, archive), removendo .github/workflows e .github/actions. Runner Forgejo v13 com services Postgres por porta + DB_HOST via gateway do bridge. Libs de agente sobre o novo cliente forgejo-api.mjs (zero dependências, GITHUB_SERVER_URL/FORGEJO_API_URL), helpers de workflow plain-Node. pnpm issue corrigido (await em função sync) e validado ao vivo. Testes: forgejoApi.unit.spec.ts novo, pin do matrix do ci.yml atualizado. Closes #1 (parte 1 — docs/issues/pool seguem na parte 2)
107 lines
3.6 KiB
JavaScript
107 lines
3.6 KiB
JavaScript
/**
|
|
* `pnpm issue` — Issues da fila de claim, determinístico e read-only (nunca
|
|
* altera o GitHub; claim continua `pnpm agent:claim`).
|
|
*
|
|
* pnpm issue next imprime a próxima Issue claimável (topo da fila do claim)
|
|
* pnpm issue all overview compacto das Issues abertas (contadores + lista)
|
|
*/
|
|
|
|
import {
|
|
dieAgent,
|
|
labelNames,
|
|
nextClaimableIssue,
|
|
parseArgs,
|
|
parseFrontmatter,
|
|
priorityRank,
|
|
} from './lib/agent-forgejo.mjs'
|
|
import { forgejoApi as api } from './lib/forgejo-api.mjs'
|
|
|
|
const die = dieAgent('issue')
|
|
|
|
const cmdNext = async () => {
|
|
const pick = await nextClaimableIssue()
|
|
if (!pick) {
|
|
die('Fila vazia — nada `ready` desbloqueado. Rode `pnpm issue all` para a overview.')
|
|
}
|
|
const { issue, meta, priority, satisfiedWithoutIssue } = pick
|
|
const id = typeof meta.id === 'string' ? meta.id : null
|
|
const prefix = id ? `${id} — ` : null
|
|
const subject =
|
|
prefix && issue.title.startsWith(prefix) ? issue.title.slice(prefix.length) : issue.title
|
|
|
|
console.log(`#${issue.number} ${id ?? ''} [${priority}]`.trim())
|
|
console.log(subject)
|
|
if (meta.model) console.log(`model: ${meta.model}`)
|
|
if (satisfiedWithoutIssue.length > 0) {
|
|
console.log(`deps satisfeitas (roadmap, sem Issue): ${satisfiedWithoutIssue.join(', ')}`)
|
|
}
|
|
console.log(`url: https://git.solla.dev/fsolla/teqo/issues/${issue.number}`)
|
|
}
|
|
|
|
const count = (map, key) => map.set(key, (map.get(key) ?? 0) + 1)
|
|
|
|
const cmdAll = async () => {
|
|
const issues = await api.listIssues({ state: 'open', limit: 200 })
|
|
|
|
const rows = []
|
|
for (const issue of issues) {
|
|
const { meta } = parseFrontmatter(issue.body)
|
|
if (typeof meta.id !== 'string' || meta.id.length === 0) continue
|
|
const labels = labelNames(issue)
|
|
rows.push({
|
|
issue,
|
|
id: meta.id,
|
|
state: labels.find((label) => ['ready', 'in-progress', 'blocked'].includes(label)) ?? 'open',
|
|
priority: labels.find((label) => /^prio:P[0-3]$/.test(label))?.replace('prio:', '') ?? 'P2',
|
|
kind: labels.find((label) => label.startsWith('kind:'))?.replace('kind:', '') ?? 'feature',
|
|
})
|
|
}
|
|
|
|
const byState = new Map()
|
|
const byPrio = new Map()
|
|
const byKind = new Map()
|
|
for (const row of rows) {
|
|
count(byState, row.state)
|
|
count(byPrio, row.priority)
|
|
count(byKind, row.kind)
|
|
}
|
|
const fmt = (map) =>
|
|
[...map.entries()]
|
|
.sort(([a], [b]) => String(a).localeCompare(String(b)))
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(' ')
|
|
|
|
console.log(`Issues abertas: ${rows.length}`)
|
|
console.log(` por estado: ${fmt(byState) || '(—)'}`)
|
|
console.log(` por prio: ${fmt(byPrio) || '(—)'}`)
|
|
console.log(` por kind: ${fmt(byKind) || '(—)'}`)
|
|
console.log('')
|
|
const sorted = [...rows].sort((a, b) => {
|
|
const rank = priorityRank(a.priority) - priorityRank(b.priority)
|
|
return rank !== 0 ? rank : a.issue.number - b.issue.number
|
|
})
|
|
for (const row of sorted) {
|
|
console.log(
|
|
` #${row.issue.number} ${row.id} [${row.state}/${row.priority}] ${row.issue.title}`,
|
|
)
|
|
}
|
|
}
|
|
|
|
const { positional } = parseArgs(process.argv.slice(2), new Set())
|
|
const subcommand = positional[0]
|
|
|
|
if (!subcommand) {
|
|
console.log('Uso: pnpm issue next | all')
|
|
console.log(' next imprime a próxima Issue claimável (topo da fila do claim)')
|
|
console.log(' all overview compacto das Issues abertas')
|
|
process.exit(1)
|
|
}
|
|
|
|
try {
|
|
if (subcommand === 'next') await cmdNext()
|
|
else if (subcommand === 'all') await cmdAll()
|
|
else die(`subcomando desconhecido: ${subcommand} (esperado: next | all)`)
|
|
} catch (error) {
|
|
if (error?.stderr) die(error.stderr.toString().trim())
|
|
die(error?.message ?? String(error))
|
|
}
|