pantheon-opencode 1.4.3 → 1.5.0-beta.10
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/.pantheon/code-mode/README.md +93 -0
- package/.pantheon/code-mode/checkpoint-session.py +7 -0
- package/.pantheon/code-mode/checkpoint-session.sh +11 -0
- package/.pantheon/code-mode/checkpoint_session.py +481 -0
- package/.pantheon/code-mode/compress-inline.py +450 -0
- package/.pantheon/code-mode/eval-llm-judge.py +255 -0
- package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
- package/.pantheon/code-mode/eval-run.py +197 -0
- package/.pantheon/code-mode/eval-static.py +321 -0
- package/.pantheon/code-mode/example-sync.sh +5 -0
- package/.pantheon/code-mode/manifest.json +21 -0
- package/.pantheon/code-mode/session-end-save.py +186 -0
- package/.pantheon/code-mode/session-end-save.sh +18 -0
- package/.pantheon/code-mode/test-allowed.sh +5 -0
- package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
- package/.pantheon/code-mode/test-env.sh +7 -0
- package/.pantheon/code-mode/test-prlimit.sh +9 -0
- package/.pantheon/code-mode/test-timeout.sh +6 -0
- package/CHANGELOG.md +1026 -0
- package/README.md +292 -1080
- package/README.pt-BR.md +308 -0
- package/bin/pantheon-init.mjs +207 -31
- package/opencode.json +1 -0
- package/package.json +34 -14
- package/plugin.json +15 -0
- package/pyproject.toml +65 -0
- package/scripts/_pantheon_paths.py +48 -7
- package/scripts/ci-validate-yaml.py +7 -2
- package/scripts/code_mode_server.py +451 -44
- package/scripts/cost.mjs +30 -14
- package/scripts/doctor.mjs +306 -129
- package/scripts/generate-preset-docs.mjs +127 -66
- package/scripts/generate-prompts.sh +2 -2
- package/scripts/install/cli-ui.mjs +23 -10
- package/scripts/install/config-migration.mjs +399 -0
- package/scripts/install/health-check.mjs +3 -3
- package/scripts/install/model-picker.mjs +72 -23
- package/scripts/install/opencode-version.mjs +47 -0
- package/scripts/install/opencode.mjs +396 -121
- package/scripts/install/plugin.mjs +89 -49
- package/scripts/install/shared.mjs +55 -159
- package/scripts/install/strings.mjs +129 -0
- package/scripts/install-mcp.mjs +45 -232
- package/scripts/manifest-inventory.mjs +410 -0
- package/scripts/mcp_persistence_server.py +798 -60
- package/scripts/mcp_resources_server.py +144 -40
- package/scripts/memory_mcp_server.py +2 -2
- package/scripts/package-evidence.mjs +242 -0
- package/scripts/probe-context-rehydrate.mjs +240 -0
- package/scripts/probe-pantheon-cost.mjs +313 -0
- package/scripts/recover-zenodo-deposition.mjs +146 -0
- package/scripts/release-beta-version.mjs +56 -60
- package/scripts/sync-artifacts.mjs +169 -0
- package/scripts/sync-tui.mjs +135 -42
- package/scripts/test-opencode-v1-v2-sandbox.sh +862 -0
- package/scripts/uninstall.mjs +298 -954
- package/scripts/validate-zenodo-release.mjs +154 -0
- package/scripts/validation-policy.mjs +7 -7
- package/scripts/version-check.mjs +113 -108
- package/scripts/versioning.mjs +230 -86
- package/scripts/zenodo-artifact.mjs +21 -0
- package/scripts/zenodo-release-state.mjs +53 -0
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +27 -16
- package/src/mcp/_pantheon_paths.py +48 -7
- package/src/mcp/code_mode_server.py +451 -44
- package/src/mcp/eval_store.py +1 -1
- package/src/mcp/mcp_persistence_server.py +798 -60
- package/src/mcp/mcp_resources_server.py +144 -40
- package/src/mcp/pantheon_vision_server.py +1 -1
- package/src/mcp/toon_codec.py +491 -0
- package/src/pantheon/auto-wake.ts +1 -5
- package/src/pantheon/background-job-board.ts +110 -2
- package/src/pantheon/compaction-handoff.ts +304 -0
- package/src/pantheon/cost-command.ts +356 -69
- package/src/pantheon/delegate-manager.ts +766 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +13 -12
- package/src/pantheon/delegation-finalize.ts +406 -0
- package/src/pantheon/delegation-notify.ts +212 -0
- package/src/pantheon/delegation-toast.ts +73 -0
- package/src/pantheon/delegation.ts +1337 -0
- package/src/pantheon/file-persistence-salvage.ts +64 -0
- package/src/pantheon/file-persistence.ts +194 -45
- package/src/pantheon/goal-loop.ts +2 -2
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/hashline/tool.ts +2 -2
- package/src/pantheon/model-command.ts +2 -1
- package/src/pantheon/native-probe.ts +234 -0
- package/src/pantheon/native-task-status.ts +139 -0
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/provider-capabilities.ts +221 -0
- package/src/pantheon/provider-stream-tracker.ts +182 -0
- package/src/pantheon/shared-board.ts +37 -0
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/task-result-guard.ts +107 -0
- package/src/pantheon/token-opt.ts +689 -0
- package/src/pantheon/tool-ceiling.ts +159 -0
- package/src/pantheon/toon-codec.ts +481 -0
- package/src/pantheon/tui-version-check.ts +1 -3
- package/src/pantheon/v2-bridge.ts +129 -0
- package/src/pantheon/v2-events.ts +148 -0
- package/src/pantheon/v2-hooks.ts +287 -0
- package/src/pantheon/v2-tool-definitions.ts +331 -0
- package/src/pantheon/zeus-delegate-with-retry.ts +406 -0
- package/src/plugin-v2/index.ts +21 -0
- package/src/plugin-v2.ts +824 -0
- package/src/plugin.ts +261 -51
- package/src/plugins/pantheon-hooks.ts +51 -0
- package/src/plugins/tui/dist/tui.d.ts +116 -24
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +416 -149
- package/src/plugins/tui/dist/tui.tsx +456 -87
- package/src/plugins/tui/package-lock.json +51 -51
- package/src/plugins/tui/package.json +7 -7
- package/src/plugins/tui/src/index.tsx +462 -95
- package/src/skills/README.md +0 -2
- package/src/pantheon/tool-context.ts +0 -41
package/scripts/versioning.mjs
CHANGED
|
@@ -6,45 +6,46 @@
|
|
|
6
6
|
* recommend Analyze commits and suggest next version bump type
|
|
7
7
|
* apply [type] Bump manifests + move [Unreleased] → [vX.Y.Z] in CHANGELOG
|
|
8
8
|
* type: patch | minor | major | auto (default: auto)
|
|
9
|
+
* --beta: bump the committed beta line instead
|
|
10
|
+
* beta Alias for `apply --beta`
|
|
9
11
|
* changelog [ver] (Internal) Insert a versioned section into CHANGELOG
|
|
10
12
|
* Normally called by `apply`; can be run standalone.
|
|
11
13
|
* status Show current version, latest tag, and pending bump type
|
|
12
14
|
*
|
|
13
|
-
* Design: the release signal is "package.json version > latest git tag"
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Design: the release signal is "package.json version > latest git tag" for
|
|
16
|
+
* stable, and "package.json version is a committed X.Y.Z-beta.N" for beta.
|
|
17
|
+
* Developers (or AI agents) call `apply` (or `apply --beta` / `beta`) to bump
|
|
18
|
+
* the manifests and promote CHANGELOG.md, then push. The dispatch-only release
|
|
19
|
+
* workflow reads the committed version and creates the release. No version
|
|
20
|
+
* bumping ever happens inside GitHub Actions.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { dirname, join } from 'node:path'
|
|
23
|
+
import { execFileSync } from 'node:child_process'
|
|
24
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
25
|
+
import { basename, dirname, join } from 'node:path'
|
|
22
26
|
import { fileURLToPath } from 'node:url'
|
|
27
|
+
import {
|
|
28
|
+
commitPreparedUpdates,
|
|
29
|
+
prepareInventoryVersion,
|
|
30
|
+
SEMVER_PATTERN,
|
|
31
|
+
validateInventory,
|
|
32
|
+
} from './manifest-inventory.mjs'
|
|
33
|
+
import { nextBetaVersion } from './release-beta-version.mjs'
|
|
23
34
|
import { collectEntries, groupCommits, parseCommitLine, renderChangelog } from './release-notes.mjs'
|
|
24
35
|
|
|
25
36
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
26
37
|
const ROOT = join(__dirname, '..')
|
|
27
38
|
|
|
28
|
-
const MANIFEST_FILES = [
|
|
29
|
-
// 'platform/forge.json' removed in v1.0 — directory no longer exists
|
|
30
|
-
'pyproject.toml',
|
|
31
|
-
'package.json',
|
|
32
|
-
'plugin.json',
|
|
33
|
-
'src/plugins/tui/package.json',
|
|
34
|
-
]
|
|
35
|
-
|
|
36
39
|
const CHANGELOG_PATH = join(ROOT, 'CHANGELOG.md')
|
|
40
|
+
const ZENODO_PATH = join(ROOT, '.zenodo.json')
|
|
41
|
+
const CITATION_PATH = join(ROOT, 'CITATION.cff')
|
|
37
42
|
|
|
38
43
|
// ---------------------------------------------------------------------------
|
|
39
44
|
// Git helpers
|
|
40
45
|
// ---------------------------------------------------------------------------
|
|
41
46
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
return execSync(cmd, { cwd: ROOT, encoding: 'utf-8' }).trim()
|
|
45
|
-
} catch {
|
|
46
|
-
return ''
|
|
47
|
-
}
|
|
47
|
+
function runGit(args) {
|
|
48
|
+
return execFileSync('git', args, { cwd: ROOT, encoding: 'utf8' }).trim()
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function getLatestTag() {
|
|
@@ -52,12 +53,15 @@ function getLatestTag() {
|
|
|
52
53
|
// The loose glob `v[0-9]*.[0-9]*.[0-9]*` ALSO matches v1.2.0-beta.9.*
|
|
53
54
|
// (the trailing `*` swallows the -beta suffix), which would poison the
|
|
54
55
|
// version gate — so filter strictly before sorting.
|
|
55
|
-
const
|
|
56
|
-
|
|
56
|
+
const tags = runGit(['tag', '-l', 'v*'])
|
|
57
|
+
.split('\n')
|
|
58
|
+
.filter((tag) => /^v\d+\.\d+\.\d+$/.test(tag))
|
|
59
|
+
tags.sort((left, right) => compareStable(left.slice(1), right.slice(1)))
|
|
60
|
+
return tags.at(-1) || 'v0.0.0'
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
function getCurrentVersion() {
|
|
60
|
-
return
|
|
64
|
+
return validateInventory(ROOT).sourceVersion
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// ---------------------------------------------------------------------------
|
|
@@ -65,7 +69,10 @@ function getCurrentVersion() {
|
|
|
65
69
|
// ---------------------------------------------------------------------------
|
|
66
70
|
|
|
67
71
|
function bumpVersion(version, type) {
|
|
68
|
-
const
|
|
72
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(version)
|
|
73
|
+
if (!match) throw new Error(`cannot bump invalid semver: ${version}`)
|
|
74
|
+
if (!['major', 'minor', 'patch'].includes(type)) throw new Error(`invalid bump type: ${type}`)
|
|
75
|
+
const [major, minor, patch] = match.slice(1).map(Number)
|
|
69
76
|
switch (type) {
|
|
70
77
|
case 'major':
|
|
71
78
|
return `${major + 1}.0.0`
|
|
@@ -76,8 +83,17 @@ function bumpVersion(version, type) {
|
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
function compareStable(left, right) {
|
|
87
|
+
const a = left.split('.').map(Number)
|
|
88
|
+
const b = right.split('.').map(Number)
|
|
89
|
+
for (let index = 0; index < 3; index += 1) {
|
|
90
|
+
if (a[index] !== b[index]) return a[index] - b[index]
|
|
91
|
+
}
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
function analyzeConventionalCommits(since) {
|
|
80
|
-
const log =
|
|
96
|
+
const log = runGit(['log', `${since}..HEAD`, '--format=%s'])
|
|
81
97
|
if (!log) return 'patch'
|
|
82
98
|
let bump = 'patch'
|
|
83
99
|
for (const msg of log.split('\n').filter(Boolean)) {
|
|
@@ -91,27 +107,94 @@ function analyzeConventionalCommits(since) {
|
|
|
91
107
|
// Manifest updater
|
|
92
108
|
// ---------------------------------------------------------------------------
|
|
93
109
|
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
function prepareManifests(newVersion) {
|
|
111
|
+
if (!SEMVER_PATTERN.test(newVersion)) throw new Error(`invalid version: ${newVersion}`)
|
|
112
|
+
const inventory = validateInventory(ROOT, { allowVersionDivergence: true })
|
|
113
|
+
return { inventory, updates: prepareInventoryVersion(inventory, newVersion) }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Serialize `.zenodo.json` the way Biome 2.x formats it: objects stay expanded
|
|
118
|
+
* (matching `JSON.stringify(value, null, 2)`), while arrays containing only
|
|
119
|
+
* primitives are collapsed onto one line when they fit within the line width.
|
|
120
|
+
* A plain `JSON.stringify` emits the `keywords` array multiline, which Biome
|
|
121
|
+
* collapses — reintroducing a lint failure on the next release bump.
|
|
122
|
+
*
|
|
123
|
+
* @param {unknown} value parsed JSON document
|
|
124
|
+
* @param {number} [lineWidth] formatter line width (matches biome.json)
|
|
125
|
+
* @returns {string} Biome-compatible JSON with a trailing newline
|
|
126
|
+
*/
|
|
127
|
+
function formatZenodoJson(value, lineWidth = 100) {
|
|
128
|
+
const indent = (depth) => ' '.repeat(depth)
|
|
129
|
+
const render = (node, depth, column) => {
|
|
130
|
+
if (Array.isArray(node)) {
|
|
131
|
+
if (node.length === 0) return '[]'
|
|
132
|
+
const primitiveOnly = node.every((item) => item === null || typeof item !== 'object')
|
|
133
|
+
if (primitiveOnly) {
|
|
134
|
+
const inline = `[${node.map((item) => JSON.stringify(item)).join(', ')}]`
|
|
135
|
+
if (column + inline.length <= lineWidth) return inline
|
|
110
136
|
}
|
|
111
|
-
|
|
112
|
-
|
|
137
|
+
const body = node
|
|
138
|
+
.map((item) => `${indent(depth + 1)}${render(item, depth + 1, indent(depth + 1).length)}`)
|
|
139
|
+
.join(',\n')
|
|
140
|
+
return `[\n${body}\n${indent(depth)}]`
|
|
141
|
+
}
|
|
142
|
+
if (node !== null && typeof node === 'object') {
|
|
143
|
+
const entries = Object.entries(node)
|
|
144
|
+
if (entries.length === 0) return '{}'
|
|
145
|
+
const body = entries
|
|
146
|
+
.map(([key, item]) => {
|
|
147
|
+
const keyPrefix = `${indent(depth + 1)}${JSON.stringify(key)}: `
|
|
148
|
+
return `${keyPrefix}${render(item, depth + 1, keyPrefix.length)}`
|
|
149
|
+
})
|
|
150
|
+
.join(',\n')
|
|
151
|
+
return `{\n${body}\n${indent(depth)}}`
|
|
113
152
|
}
|
|
153
|
+
return JSON.stringify(node)
|
|
114
154
|
}
|
|
155
|
+
return `${render(value, 0, 0)}\n`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Keep the Zenodo deposition metadata (.zenodo.json) and the citation file
|
|
160
|
+
* (CITATION.cff) aligned with the version and date being released. Zenodo's
|
|
161
|
+
* release validator hard-fails when CITATION.cff version differs from the
|
|
162
|
+
* release tag, so these must be bumped together with the manifests.
|
|
163
|
+
*/
|
|
164
|
+
function prepareReleaseMetadata(newVersion, dateStr) {
|
|
165
|
+
if (!SEMVER_PATTERN.test(newVersion)) throw new Error(`invalid version: ${newVersion}`)
|
|
166
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) throw new Error(`invalid date: ${dateStr}`)
|
|
167
|
+
const tagUrl = `https://github.com/ils15/pantheon-opencode/releases/tag/v${newVersion}`
|
|
168
|
+
const updates = []
|
|
169
|
+
|
|
170
|
+
if (existsSync(ZENODO_PATH)) {
|
|
171
|
+
const zenodo = JSON.parse(readFileSync(ZENODO_PATH, 'utf8'))
|
|
172
|
+
const zenodoPrevious = typeof zenodo.version === 'string' ? zenodo.version : null
|
|
173
|
+
if (zenodoPrevious && zenodoPrevious !== newVersion && typeof zenodo.description === 'string') {
|
|
174
|
+
zenodo.description = zenodo.description.replaceAll(zenodoPrevious, newVersion)
|
|
175
|
+
}
|
|
176
|
+
zenodo.version = newVersion
|
|
177
|
+
zenodo.publication_date = dateStr
|
|
178
|
+
zenodo.url = tagUrl
|
|
179
|
+
updates.push({ path: ZENODO_PATH, content: formatZenodoJson(zenodo) })
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (existsSync(CITATION_PATH)) {
|
|
183
|
+
const citationRaw = readFileSync(CITATION_PATH, 'utf8')
|
|
184
|
+
const citationMatch = /^version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(citationRaw)
|
|
185
|
+
const citationPrevious = citationMatch ? citationMatch[1] : null
|
|
186
|
+
let citation = citationRaw
|
|
187
|
+
if (citationPrevious && citationPrevious !== newVersion) {
|
|
188
|
+
citation = citation.replaceAll(`Pantheon v${citationPrevious}`, `Pantheon v${newVersion}`)
|
|
189
|
+
}
|
|
190
|
+
citation = citation
|
|
191
|
+
.replace(/^version:.*$/m, `version: ${newVersion}`)
|
|
192
|
+
.replace(/^date-released:.*$/m, `date-released: ${dateStr}`)
|
|
193
|
+
.replace(/^url:.*$/m, `url: ${tagUrl}`)
|
|
194
|
+
updates.push({ path: CITATION_PATH, content: citation })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return updates
|
|
115
198
|
}
|
|
116
199
|
|
|
117
200
|
// ---------------------------------------------------------------------------
|
|
@@ -181,15 +264,17 @@ function stripEmptySections(body) {
|
|
|
181
264
|
return keep.join('\n').replace(/\n{3,}/g, '\n\n')
|
|
182
265
|
}
|
|
183
266
|
|
|
184
|
-
function
|
|
267
|
+
function prepareUnreleased(newVersion, dateStr, notesBody = null) {
|
|
268
|
+
if (!SEMVER_PATTERN.test(newVersion)) throw new Error(`invalid changelog version: ${newVersion}`)
|
|
269
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) throw new Error(`invalid changelog date: ${dateStr}`)
|
|
270
|
+
if (notesBody !== null && typeof notesBody !== 'string') {
|
|
271
|
+
throw new Error('generated release notes must be text')
|
|
272
|
+
}
|
|
185
273
|
const content = readFileSync(CHANGELOG_PATH, 'utf-8')
|
|
186
274
|
|
|
187
275
|
const unreleasedHeader = '## [Unreleased]'
|
|
188
276
|
const idx = content.indexOf(unreleasedHeader)
|
|
189
|
-
if (idx === -1)
|
|
190
|
-
console.log(' ⚠ [Unreleased] section not found in CHANGELOG — skipping')
|
|
191
|
-
return false
|
|
192
|
-
}
|
|
277
|
+
if (idx === -1) throw new Error('CHANGELOG.md is missing the [Unreleased] section')
|
|
193
278
|
|
|
194
279
|
// Find the end of [Unreleased]: next ## header or end of file
|
|
195
280
|
const afterHeader = idx + unreleasedHeader.length
|
|
@@ -211,15 +296,10 @@ function promoteUnreleased(newVersion, dateStr, notesBody = null) {
|
|
|
211
296
|
.map((l) => l.trim())
|
|
212
297
|
.filter((l) => l && !l.startsWith('###') && !l.startsWith('## '))
|
|
213
298
|
|
|
214
|
-
if (notesBody === null && realLines.length === 0) {
|
|
215
|
-
console.log(' ℹ [Unreleased] is empty — CHANGELOG not modified')
|
|
216
|
-
return false
|
|
217
|
-
}
|
|
299
|
+
if (notesBody === null && realLines.length === 0) return { changed: false, content }
|
|
218
300
|
|
|
219
301
|
if (notesBody !== null && realLines.length > 0) {
|
|
220
|
-
console.log(
|
|
221
|
-
' ⚠ --notes replaces the manual [Unreleased] content — review the diff before committing',
|
|
222
|
-
)
|
|
302
|
+
console.log(' --notes replaces the manual [Unreleased] content')
|
|
223
303
|
}
|
|
224
304
|
|
|
225
305
|
// Strip lines that are just empty subsections (### X or ## 🆕/🐞/⚠️/✅
|
|
@@ -239,13 +319,35 @@ function promoteUnreleased(newVersion, dateStr, notesBody = null) {
|
|
|
239
319
|
|
|
240
320
|
const updated = `${before + unreleasedHeader + newTemplate}\n\n${newVersionHeader}${writtenBody}${after}`
|
|
241
321
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
322
|
+
if (!updated.includes('## [Unreleased]') || !updated.includes(newVersionHeader)) {
|
|
323
|
+
throw new Error('prepared CHANGELOG.md failed structural validation')
|
|
324
|
+
}
|
|
325
|
+
return { changed: true, content: updated, notesGenerated: notesBody !== null }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function contentHasUnreleased() {
|
|
329
|
+
return readFileSync(CHANGELOG_PATH, 'utf8').includes('## [Unreleased]')
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function commitPreparedRelease(manifestPlan, changelogPlan, metadataUpdates = []) {
|
|
333
|
+
const updates = [...manifestPlan.updates, ...metadataUpdates]
|
|
334
|
+
if (changelogPlan.changed) {
|
|
335
|
+
updates.push({ path: CHANGELOG_PATH, content: changelogPlan.content })
|
|
336
|
+
}
|
|
337
|
+
commitPreparedUpdates(updates)
|
|
338
|
+
for (const document of manifestPlan.inventory.documents) {
|
|
339
|
+
console.log(` ✓ ${document.entry.file} → ${manifestPlan.version}`)
|
|
340
|
+
}
|
|
341
|
+
for (const { path } of metadataUpdates) {
|
|
342
|
+
console.log(` ✓ ${basename(path)} → ${manifestPlan.version}`)
|
|
343
|
+
}
|
|
344
|
+
if (changelogPlan.changed) {
|
|
345
|
+
console.log(
|
|
346
|
+
changelogPlan.notesGenerated
|
|
347
|
+
? ` ✓ CHANGELOG: [Unreleased] → [v${manifestPlan.version}] (notes generated from commits)`
|
|
348
|
+
: ` ✓ CHANGELOG: [Unreleased] → [v${manifestPlan.version}]`,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
249
351
|
}
|
|
250
352
|
|
|
251
353
|
// ---------------------------------------------------------------------------
|
|
@@ -282,9 +384,35 @@ switch (command) {
|
|
|
282
384
|
break
|
|
283
385
|
}
|
|
284
386
|
|
|
387
|
+
case 'beta':
|
|
285
388
|
case 'apply': {
|
|
286
389
|
const args = process.argv.slice(3)
|
|
287
390
|
const useNotes = args.includes('--notes')
|
|
391
|
+
const useBeta = command === 'beta' || args.includes('--beta')
|
|
392
|
+
|
|
393
|
+
// Beta: advance the committed X.Y.Z-beta.N line and promote the
|
|
394
|
+
// [Unreleased] CHANGELOG section into [vX.Y.Z-beta.N], exactly like the
|
|
395
|
+
// stable path. The committed manifest is the single source of truth — no
|
|
396
|
+
// npm lookup, no git lookup, and no runtime version computation.
|
|
397
|
+
if (useBeta) {
|
|
398
|
+
if (!contentHasUnreleased())
|
|
399
|
+
throw new Error('CHANGELOG.md is missing the [Unreleased] section')
|
|
400
|
+
const current = getCurrentVersion()
|
|
401
|
+
const newVersion = nextBetaVersion(current)
|
|
402
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
403
|
+
console.log(`Bumping ${current} → ${newVersion} (beta)`)
|
|
404
|
+
const changelogPlan = prepareUnreleased(newVersion, date)
|
|
405
|
+
const manifestPlan = prepareManifests(newVersion)
|
|
406
|
+
commitPreparedRelease(
|
|
407
|
+
{ ...manifestPlan, version: newVersion },
|
|
408
|
+
changelogPlan,
|
|
409
|
+
prepareReleaseMetadata(newVersion, date),
|
|
410
|
+
)
|
|
411
|
+
console.log(`\nDone. Commit the version inventory and CHANGELOG as v${newVersion}.`)
|
|
412
|
+
console.log(`Tag v${newVersion} will be created by the release workflow after merge to main.`)
|
|
413
|
+
break
|
|
414
|
+
}
|
|
415
|
+
|
|
288
416
|
const type = args.find((a) => !a.startsWith('--')) || 'auto'
|
|
289
417
|
const latestTag = getLatestTag()
|
|
290
418
|
const current = getCurrentVersion()
|
|
@@ -298,23 +426,25 @@ switch (command) {
|
|
|
298
426
|
const since = latestTag === 'v0.0.0' ? null : latestTag
|
|
299
427
|
const raw = collectEntries({ since, draft: since === null })
|
|
300
428
|
const body = renderChangelog(groupCommits(raw.map(parseCommitLine)))
|
|
301
|
-
if (!body)
|
|
302
|
-
|
|
303
|
-
return null
|
|
304
|
-
}
|
|
305
|
-
console.log(` ℹ --notes: release notes generated from ${raw.length} commits`)
|
|
429
|
+
if (!body) return null
|
|
430
|
+
console.log(` --notes generated from ${raw.length} commits`)
|
|
306
431
|
return body
|
|
307
432
|
}
|
|
308
433
|
|
|
309
|
-
|
|
310
|
-
// without tagging — warn and use the current version as-is. Tags are
|
|
311
|
-
// workflow-owned, so there is nothing to create here.
|
|
434
|
+
if (!contentHasUnreleased()) throw new Error('CHANGELOG.md is missing the [Unreleased] section')
|
|
312
435
|
if (current !== latestVer) {
|
|
313
|
-
console.log(
|
|
314
|
-
|
|
315
|
-
|
|
436
|
+
console.log(
|
|
437
|
+
`package.json (${current}) is ahead of latest tag (${latestTag}); synchronizing inventory.`,
|
|
438
|
+
)
|
|
316
439
|
const date = new Date().toISOString().slice(0, 10)
|
|
317
|
-
|
|
440
|
+
const notesBody = generateNotes()
|
|
441
|
+
const changelogPlan = prepareUnreleased(current, date, notesBody)
|
|
442
|
+
const manifestPlan = prepareManifests(current)
|
|
443
|
+
commitPreparedRelease(
|
|
444
|
+
{ ...manifestPlan, version: current },
|
|
445
|
+
changelogPlan,
|
|
446
|
+
prepareReleaseMetadata(current, date),
|
|
447
|
+
)
|
|
318
448
|
console.log(`Tag v${current} will be created by the release workflow after merge to main.`)
|
|
319
449
|
break
|
|
320
450
|
}
|
|
@@ -324,13 +454,15 @@ switch (command) {
|
|
|
324
454
|
const date = new Date().toISOString().slice(0, 10)
|
|
325
455
|
|
|
326
456
|
console.log(`Bumping ${current} → ${newVersion} (${bumpType})`)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
457
|
+
const notesBody = generateNotes()
|
|
458
|
+
const changelogPlan = prepareUnreleased(newVersion, date, notesBody)
|
|
459
|
+
const manifestPlan = prepareManifests(newVersion)
|
|
460
|
+
commitPreparedRelease(
|
|
461
|
+
{ ...manifestPlan, version: newVersion },
|
|
462
|
+
changelogPlan,
|
|
463
|
+
prepareReleaseMetadata(newVersion, date),
|
|
464
|
+
)
|
|
465
|
+
console.log(`\nDone. Commit the version inventory and CHANGELOG as v${newVersion}.`)
|
|
334
466
|
console.log(`Tag v${newVersion} will be created by the release workflow after merge to main.`)
|
|
335
467
|
break
|
|
336
468
|
}
|
|
@@ -339,7 +471,11 @@ switch (command) {
|
|
|
339
471
|
case 'changelog': {
|
|
340
472
|
const version = arg || getCurrentVersion()
|
|
341
473
|
const date = new Date().toISOString().slice(0, 10)
|
|
342
|
-
|
|
474
|
+
const changelogPlan = prepareUnreleased(version, date)
|
|
475
|
+
if (changelogPlan.changed) {
|
|
476
|
+
commitPreparedUpdates([{ path: CHANGELOG_PATH, content: changelogPlan.content }])
|
|
477
|
+
console.log(` ✓ CHANGELOG: [Unreleased] → [v${version}]`)
|
|
478
|
+
}
|
|
343
479
|
break
|
|
344
480
|
}
|
|
345
481
|
|
|
@@ -353,11 +489,19 @@ Commands:
|
|
|
353
489
|
type: patch | minor | major | auto (default: auto)
|
|
354
490
|
--notes: pre-fill the promoted entry with release
|
|
355
491
|
notes generated from conventional commits
|
|
492
|
+
--beta: advance the committed X.Y.Z-beta.N line and
|
|
493
|
+
promote [Unreleased] → [vX.Y.Z-beta.N] in CHANGELOG
|
|
494
|
+
beta Alias for 'apply --beta'
|
|
356
495
|
changelog [version] Promote [Unreleased] → [vX.Y.Z] without bumping
|
|
357
496
|
|
|
358
|
-
Release flow:
|
|
497
|
+
Release flow (stable):
|
|
359
498
|
1. node scripts/versioning.mjs apply [minor]
|
|
360
|
-
2.
|
|
361
|
-
3.
|
|
499
|
+
2. Commit the version inventory and CHANGELOG.
|
|
500
|
+
3. Push after review; CI and the dispatch-only release workflow create the tag.
|
|
501
|
+
|
|
502
|
+
Release flow (beta):
|
|
503
|
+
1. node scripts/versioning.mjs apply --beta (or: node scripts/versioning.mjs beta)
|
|
504
|
+
2. Commit the version inventory and CHANGELOG.
|
|
505
|
+
3. Dispatch the release workflow with release_channel=beta.
|
|
362
506
|
`)
|
|
363
507
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
export function sha256File(path) {
|
|
5
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function findZenodoFile(record, filename, checksum) {
|
|
9
|
+
if (!record || typeof record !== 'object' || !Array.isArray(record.files)) {
|
|
10
|
+
throw new Error('Zenodo record did not contain a files array.')
|
|
11
|
+
}
|
|
12
|
+
const matches = record.files.filter((file) => file?.filename === filename)
|
|
13
|
+
if (matches.length > 1) throw new Error(`Zenodo record contains duplicate file: ${filename}.`)
|
|
14
|
+
if (matches.length === 0) return null
|
|
15
|
+
const file = matches[0]
|
|
16
|
+
const expected = `sha256:${checksum}`
|
|
17
|
+
if (file.checksum !== expected) {
|
|
18
|
+
throw new Error(`Zenodo checksum mismatch for ${filename}.`)
|
|
19
|
+
}
|
|
20
|
+
return file
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const DOI_PATTERN = /^10\.\d{4,9}\/\S+$/
|
|
2
|
+
const MARKER_PATTERN =
|
|
3
|
+
/<!-- zenodo-deposition:(?:id=(\d+);)?(?:doi=([^;\s]+);)?state=(pending|created|published)(?:;claim=([^\s]+))? -->/
|
|
4
|
+
|
|
5
|
+
export function extractZenodoDoi(response) {
|
|
6
|
+
if (!response || typeof response !== 'object') return null
|
|
7
|
+
const candidates = [response.doi, response.metadata?.doi, response.links?.doi]
|
|
8
|
+
for (const candidate of candidates) {
|
|
9
|
+
if (typeof candidate === 'string' && DOI_PATTERN.test(candidate.trim())) return candidate.trim()
|
|
10
|
+
}
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validateZenodoResponse(response, { requireDoi = false } = {}) {
|
|
15
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
16
|
+
throw new Error('Zenodo response must be a JSON object.')
|
|
17
|
+
}
|
|
18
|
+
if (!Number.isSafeInteger(response.id) || response.id <= 0) {
|
|
19
|
+
throw new Error('Zenodo response did not contain a valid deposition id.')
|
|
20
|
+
}
|
|
21
|
+
const doi = extractZenodoDoi(response)
|
|
22
|
+
if (requireDoi && !doi) throw new Error('Zenodo response did not contain a valid DOI.')
|
|
23
|
+
return { id: response.id, doi }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseZenodoMarker(body) {
|
|
27
|
+
const text = String(body ?? '')
|
|
28
|
+
const match = text.match(MARKER_PATTERN)
|
|
29
|
+
if (!match) {
|
|
30
|
+
const legacy = text.match(/<!-- zenodo-deposition-id:(\d+) -->/)
|
|
31
|
+
if (!legacy) return null
|
|
32
|
+
const id = Number(legacy[1])
|
|
33
|
+
if (!Number.isSafeInteger(id) || id <= 0)
|
|
34
|
+
throw new Error('Zenodo marker has an invalid deposition id.')
|
|
35
|
+
return { id, doi: null, state: 'created', claim: null }
|
|
36
|
+
}
|
|
37
|
+
const id = match[1] ? Number(match[1]) : null
|
|
38
|
+
if (match[1] && (!Number.isSafeInteger(id) || id <= 0))
|
|
39
|
+
throw new Error('Zenodo marker has an invalid deposition id.')
|
|
40
|
+
if (match[2] && !DOI_PATTERN.test(match[2])) throw new Error('Zenodo marker has an invalid DOI.')
|
|
41
|
+
return { id, doi: match[2] ?? null, state: match[3], claim: match[4] ?? null }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function zenodoMarker({ id = null, doi = null, state, claim = null }) {
|
|
45
|
+
if (!['pending', 'created', 'published'].includes(state))
|
|
46
|
+
throw new Error('Zenodo marker state is invalid.')
|
|
47
|
+
if (id !== null && (!Number.isSafeInteger(id) || id <= 0))
|
|
48
|
+
throw new Error('Zenodo marker id is invalid.')
|
|
49
|
+
if (doi !== null && !DOI_PATTERN.test(doi)) throw new Error('Zenodo marker DOI is invalid.')
|
|
50
|
+
if (state !== 'pending' && id === null)
|
|
51
|
+
throw new Error('Created or published Zenodo markers require an id.')
|
|
52
|
+
return `<!-- zenodo-deposition:${id === null ? '' : `id=${id};`}${doi === null ? '' : `doi=${doi};`}state=${state}${claim === null ? '' : `;claim=${claim}`} -->`
|
|
53
|
+
}
|
package/src/agents/mnemosyne.md
CHANGED
package/src/agents/zeus.md
CHANGED
|
@@ -144,23 +144,33 @@ task_status(task_id="ses_xxx", wait=true)
|
|
|
144
144
|
-> bloqueia ate completar: { state: "completed", task_result: "..." }
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
### Pantheon Delegation
|
|
147
|
+
### Pantheon Delegation Tools (plugin) — 3-tool API
|
|
148
148
|
|
|
149
|
-
O plugin Pantheon
|
|
150
|
-
(`.pantheon/board/state.json`) que rastreia trabalho em voo e o carrega para o
|
|
151
|
-
contexto de compactação. O toolset de delegação do plugin
|
|
152
|
-
(`pantheon_delegate`, `pantheon_delegation_read`, `pantheon_delegation_list`)
|
|
153
|
-
foi **removido em 1.4.3** — dispatch e coleta usam a API nativa
|
|
154
|
-
`task(background=true)` / `task_status(wait=true)`.
|
|
149
|
+
O plugin Pantheon adiciona 3 tools de delegacao gerenciada pelo BackgroundJobBoard:
|
|
155
150
|
|
|
151
|
+
```
|
|
152
|
+
pantheon_delegate({prompt, agent, description?, read_only?})
|
|
153
|
+
-> cria sessao filha, registra no board, retorna alias: [apo-1]
|
|
154
|
+
pantheon_delegation_read({id: "apo-1"})
|
|
155
|
+
-> BLOQUEIA ate o job terminar, retorna o report markdown, marca reconciled
|
|
156
|
+
-> durante a espera, mostra o que o agente esta fazendo: seccao final "## Agent Activity"
|
|
157
|
+
(amostra as messages do child a cada ~2s; tool calls + args truncados / texto)
|
|
158
|
+
pantheon_delegation_list()
|
|
159
|
+
-> lista jobs da sessao, com [unread] para jobs terminados nao lidos
|
|
160
|
+
-> jobs RUNNING mostram "last activity:" (ultima acao visivel do child)
|
|
161
|
+
-> fail-open: sem suporte a messages, formato atual inalterado
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Read-only enforcement:** agentes read-only (apollo, gaia — `read_only_agents` no routing.yml) tem `edit`, `write`, `bash`, `task` NEGADOS dentro da sessao delegada (o guard de `tool.execute.before` THROW com mensagem acionavel). Use `read_only: true` no delegate para qualquer agente de investigacao.
|
|
165
|
+
- **Depth-2 hard-enforced:** sessao read-only NAO cria subagentes (`task` bloqueado) — investigacao nunca vira arvore.
|
|
156
166
|
- **Compactacao:** jobs em voo (running + unread terminal) sao injetados no contexto de compactacao via `experimental.session.compacting` — delegacao em andamento nao se perde ao compactar.
|
|
157
167
|
|
|
158
|
-
### DELEGATION_RULES (
|
|
168
|
+
### DELEGATION_RULES (plugin path)
|
|
159
169
|
|
|
160
|
-
1. **NUNCA** polle `
|
|
161
|
-
2. Voce NAO recebe notificacao injetada no chat — politica do usuario: ZERO `task-notification` no transcript. Visibilidade de conclusao: marcador `[unread]`
|
|
162
|
-
3. Use `
|
|
163
|
-
4. `
|
|
170
|
+
1. **NUNCA** polle `pantheon_delegation_list` para checar se um job terminou.
|
|
171
|
+
2. Voce NAO recebe notificacao injetada no chat — politica do usuario: ZERO `task-notification` no transcript. Visibilidade de conclusao: marcador `[unread]` em `pantheon_delegation_list`, `pantheon_delegation_read({id})`, toasts TUI (pantheon-hooks) e carry-forward de compactacao.
|
|
172
|
+
3. Use `pantheon_delegation_read({id})` APENAS para fan-in explicito / recuperacao de resultado sob demanda (bloqueia ate o fim).
|
|
173
|
+
4. `pantheon_delegation_list` e para diagnostico, nao para polling.
|
|
164
174
|
|
|
165
175
|
### Background (sempre usar)
|
|
166
176
|
- **Apollo, Hermes, Aphrodite, Demeter, Hephaestus, Prometheus**
|
|
@@ -314,12 +324,13 @@ out = await guard.maybeRetry(result, async () => { ...task() de novo... })
|
|
|
314
324
|
Regra: retry 1x APENAS em `empty-mode1`/`empty-mode2`. Resultado com conteudo
|
|
315
325
|
nunca redispara. Apos 1 retry vazio → escalate (mesma regra do TODO Enforcer).
|
|
316
326
|
|
|
317
|
-
> **Waves DEVEM usar `
|
|
318
|
-
> Uso alto-nível: `import {
|
|
327
|
+
> **Waves DEVEM usar `zeusDelegateWithRetry`** (`src/pantheon/zeus-delegate-with-retry.ts`) — encapsula `delegate → waitForTerminal → classify → retry 1x → escalate` com cap duro 1; se ainda vazio throw `ZeusEscalationError` (escale: tente outro agente, simplifique, manual).
|
|
328
|
+
> Uso alto-nível: `import { zeusDelegateWithRetry } from './pantheon/zeus-delegate-with-retry.ts'` → `await zeusDelegateWithRetry({ board, client, sessionID, agent, prompt })`.
|
|
329
|
+
> Uso baixo-nível: `createZeusRetryHelper().executeWithRetry(first, () => secondRead())` — classifica empty e decide retry/escalate (mesmo cap 1).
|
|
319
330
|
|
|
320
331
|
### /cost — pantheon_cost tool (WIRED no plugin)
|
|
321
|
-
`pantheon_cost({ days?: number })` le o `opencode.db` READ-ONLY
|
|
322
|
-
|
|
332
|
+
`pantheon_cost({ days?: number })` le o `opencode.db` READ-ONLY usando o backend
|
|
333
|
+
unico `node:sqlite`; se indisponivel, retorna `UNSUPPORTED`, sem fallback. Devolve tabela markdown de custo + tokens por
|
|
323
334
|
agente nos ultimos N dias (default 7). Uso: quando Nyx pedir visibilidade de
|
|
324
335
|
custo, ou antes de escalar tier — decida se o batch valeu o preco.
|
|
325
336
|
|