feat: v2-native pocock, pocock-worker and pocock-reviewer agents with drift tests
This commit is contained in:
1 parent
c81bc91e03
commit
f10b885d1c
5 files changed
+521
No files matched your search
@@ -0,0 +1,97 @@
|
||||
// Computes the next package version and prepends the CHANGELOG entry for an upstream sync.
|
||||
// Idempotent: when the changelog already names the target upstream commit, nothing changes.
|
||||
//
|
||||
// node scripts/version.ts [--dry]
|
||||
//
|
||||
// Env/inputs: upstream/UPSTREAM.json (target revision), CHANGELOG.md, .opencode/state/upstream.json.
|
||||
// Rule (decision D4): upstream content change => minor bump; humans write their own entries for
|
||||
// behavior changes; majors are never automatic.
|
||||
import { readFileSync, writeFileSync } from "node:fs"
|
||||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
||||
|
||||
export function latestVersion(changelog: string): string {
|
||||
const match = changelog.match(/^## \[(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\]/m)
|
||||
if (!match) throw new Error("no release heading like `## [1.0.0]` found in CHANGELOG.md")
|
||||
return match[1]
|
||||
}
|
||||
|
||||
export function bumpMinor(version: string): string {
|
||||
const [major, minor, patch] = version.split(".")
|
||||
if (patch.includes("-")) return `${major}.${minor}.${patch}` // prerelease: keep as-is
|
||||
return `${major}.${Number(minor) + 1}.0`
|
||||
}
|
||||
|
||||
export type SyncEntry = { version: string; date: string; body: string }
|
||||
|
||||
export function buildEntry(opts: {
|
||||
changelog: string
|
||||
fromCommit: string | null
|
||||
toCommit: string
|
||||
toVersion: string
|
||||
date: string
|
||||
diff: { added: string[]; removed: string[]; changed: string[] }
|
||||
}): SyncEntry | null {
|
||||
const short = opts.toCommit.slice(0, 9)
|
||||
if (opts.changelog.includes(short)) return null // already released this upstream revision
|
||||
const version = bumpMinor(latestVersion(opts.changelog))
|
||||
const lines = [
|
||||
`## [${version}] - ${opts.date}`,
|
||||
"",
|
||||
"### Changed",
|
||||
"",
|
||||
`- Upstream sync: mattpocock/skills \`${opts.fromCommit ? opts.fromCommit.slice(0, 9) : "initial"}\` -> \`${short}\` (upstream v${opts.toVersion}).`,
|
||||
]
|
||||
if (opts.diff.added.length) lines.push(` - added: ${opts.diff.added.join(", ")}`)
|
||||
if (opts.diff.removed.length) lines.push(` - removed: ${opts.diff.removed.join(", ")}`)
|
||||
if (opts.diff.changed.length) lines.push(` - changed: ${opts.diff.changed.join(", ")}`)
|
||||
return { version, date: opts.date, body: lines.join("\n") }
|
||||
}
|
||||
|
||||
/** Inserts the entry directly below the `## [Unreleased]` block heading area. */
|
||||
export function insertEntry(changelog: string, entry: SyncEntry): string {
|
||||
const marker = "## [Unreleased]"
|
||||
const i = changelog.indexOf(marker)
|
||||
if (i === -1) throw new Error("CHANGELOG.md has no `## [Unreleased]` heading")
|
||||
const after = i + marker.length
|
||||
return `${changelog.slice(0, after)}\n\n${entry.body}\n${changelog.slice(after)}`
|
||||
}
|
||||
|
||||
function main() {
|
||||
const changelogPath = path.join(root, "CHANGELOG.md")
|
||||
const changelog = readFileSync(changelogPath, "utf8")
|
||||
const target = JSON.parse(readFileSync(path.join(root, "upstream/UPSTREAM.json"), "utf8"))
|
||||
const state = JSON.parse(readFileSync(path.join(root, ".opencode/state/upstream.json"), "utf8"))
|
||||
// The diff for the entry comes from the last released sync recorded in state, if any.
|
||||
const entry = buildEntry({
|
||||
changelog,
|
||||
fromCommit: state.previous_commit ?? null,
|
||||
toCommit: target.commit,
|
||||
toVersion: target.upstream_version,
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
diff: state.last_diff ?? { added: [], removed: [], changed: [] },
|
||||
})
|
||||
if (!entry) {
|
||||
console.log(`changelog already covers upstream ${target.commit.slice(0, 9)}; no version change`)
|
||||
return
|
||||
}
|
||||
if (process.argv.includes("--dry")) {
|
||||
console.log(`would release ${entry.version}:\n\n${entry.body}`)
|
||||
return
|
||||
}
|
||||
writeFileSync(changelogPath, insertEntry(changelog, entry))
|
||||
state.released_commit = target.commit
|
||||
writeFileSync(path.join(root, ".opencode/state/upstream.json"), JSON.stringify(state, null, 2) + "\n")
|
||||
console.log(`prepared release ${entry.version} for upstream ${target.commit.slice(0, 9)}`)
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
try {
|
||||
main()
|
||||
} catch (err) {
|
||||
console.error((err as Error).message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user