pantheon-opencode 1.6.0-beta.10 → 1.6.0-beta.12
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/AGENTS.global.md +42 -18
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +46 -0
- package/README.md +122 -38
- package/README.pt-BR.md +5 -4
- package/bin/pantheon-init.mjs +322 -101
- package/package.json +7 -6
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/build-agents-md.mjs +128 -10
- package/scripts/check-plugin-v2-coverage.mjs +7 -7
- package/scripts/doctor.mjs +242 -19
- package/scripts/install/backup.mjs +648 -0
- package/scripts/install/cli-ui.mjs +79 -1
- package/scripts/install/exit-codes.mjs +26 -0
- package/scripts/install/file-inventory.mjs +555 -0
- package/scripts/install/interactive.mjs +15 -1
- package/scripts/install/migrate.mjs +32 -6
- package/scripts/install/model-picker.mjs +63 -22
- package/scripts/install/opencode-version.mjs +9 -1
- package/scripts/install/opencode.mjs +761 -63
- package/scripts/install/plan-artifact.mjs +517 -0
- package/scripts/install/plan.mjs +449 -0
- package/scripts/install/plugin.mjs +136 -18
- package/scripts/install/shared.mjs +249 -94
- package/scripts/install/state.mjs +63 -8
- package/scripts/install/strings.mjs +20 -0
- package/scripts/install/venv.mjs +25 -6
- package/scripts/install/verify.mjs +128 -0
- package/scripts/install/vision-prompt.mjs +13 -1
- package/scripts/install-mcp.mjs +1 -1
- package/scripts/manifest-inventory.mjs +49 -3
- package/scripts/mcp_persistence.py +50 -15
- package/scripts/node-engine-policy.mjs +74 -0
- package/scripts/package-evidence.mjs +6 -0
- package/scripts/postinstall.mjs +3 -4
- package/scripts/prune.mjs +9 -3
- package/scripts/reconcile.mjs +325 -0
- package/scripts/restore.mjs +222 -0
- package/scripts/uninstall.mjs +258 -34
- package/scripts/update.mjs +164 -0
- package/scripts/validate-node-engine-policy.mjs +13 -0
- package/scripts/version-check.mjs +34 -1
- package/scripts/versioning.mjs +9 -1
- package/skills-lock.json +4 -4
- package/src/agents/iris.md +6 -0
- package/src/agents/mnemosyne.md +7 -2
- package/src/agents/zeus.md +19 -62
- package/src/instructions/agent-return-format.instructions.md +17 -15
- package/src/instructions/memory-protocol.instructions.md +3 -2
- package/src/instructions/test-economy.instructions.md +21 -0
- package/src/instructions/zeus-anti-stall.instructions.md +6 -6
- package/src/instructions/zeus-council-synthesis.instructions.md +10 -11
- package/src/instructions/zeus-memory-operations.instructions.md +11 -6
- package/src/instructions/zeus-timeout-retry.instructions.md +28 -9
- package/src/mcp/mcp_persistence.py +50 -15
- package/src/pantheon/cost-command.ts +5 -2
- package/src/pantheon/model-agents.ts +19 -0
- package/src/pantheon/model-command.ts +218 -56
- package/src/pantheon/v2-model-overrides.ts +275 -0
- package/src/pantheon/v2-tools.ts +18 -29
- package/src/pantheon/v2-unsupported.mjs +2 -3
- package/src/plugin-v2.ts +13 -0
- package/src/plugins/tui/dist/tui-v2.js +2 -2
- package/src/plugins/tui/dist/tui.js +1 -1
- package/src/plugins/tui/package-lock.json +3 -3
- package/src/plugins/tui/package.json +2 -2
- package/src/skills/artifact-management/SKILL.md +5 -1
- package/src/skills/auto-continue/SKILL.md +14 -6
- package/src/skills/memory-bank/SKILL.md +5 -1
- package/src/skills/worktrees/SKILL.md +30 -11
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import { execSync } from 'node:child_process'
|
|
6
6
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs'
|
|
7
|
-
import { isAbsolute, join, resolve } from 'node:path'
|
|
7
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path'
|
|
8
|
+
import { ACTIONS, recordChange, recordRemovalTree, removeReviewedTree, snapshot } from './plan.mjs'
|
|
8
9
|
import { ROOT, writeIfChanged } from './shared.mjs'
|
|
10
|
+
import {
|
|
11
|
+
assertNoSymlinkAncestors,
|
|
12
|
+
assertPathWithin,
|
|
13
|
+
assertSafeRemoval,
|
|
14
|
+
verifyPathRemoved,
|
|
15
|
+
} from './verify.mjs'
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Copy a plugin's runtime payload (the root TUI loader, bundled dist/*,
|
|
@@ -17,18 +24,38 @@ import { ROOT, writeIfChanged } from './shared.mjs'
|
|
|
17
24
|
* @param {object} [options]
|
|
18
25
|
* @param {boolean} [options.dryRun=false] - If true, don't write files
|
|
19
26
|
* @param {'v1'|'v2'} [options.version='v1'] - Host plugin generation
|
|
27
|
+
* @param {(change: object) => void} [options.recorder] - optional change recorder for preview
|
|
28
|
+
* @param {Set<string>|null} [options.reviewedRemovals] - paths approved by the current plan
|
|
29
|
+
* @param {Map<string, object>|null} [options.reviewedSnapshots] - pre-apply path snapshots
|
|
20
30
|
* @returns {{ created: number, skipped: number }}
|
|
21
31
|
*/
|
|
22
|
-
export function copyPluginFiles(
|
|
32
|
+
export function copyPluginFiles(
|
|
33
|
+
srcDir,
|
|
34
|
+
dstDir,
|
|
35
|
+
{
|
|
36
|
+
dryRun = false,
|
|
37
|
+
version = 'v1',
|
|
38
|
+
recorder = null,
|
|
39
|
+
reviewedRemovals = null,
|
|
40
|
+
reviewedSnapshots = null,
|
|
41
|
+
} = {},
|
|
42
|
+
) {
|
|
23
43
|
const result = { created: 0, skipped: 0 }
|
|
44
|
+
const distDir = join(dstDir, 'dist')
|
|
24
45
|
|
|
25
|
-
if (!dryRun)
|
|
26
|
-
|
|
46
|
+
if (!dryRun) {
|
|
47
|
+
assertNoSymlinkAncestors(dstDir)
|
|
48
|
+
mkdirSync(dstDir, { recursive: true })
|
|
49
|
+
assertNoSymlinkAncestors(dstDir)
|
|
50
|
+
assertPathWithin(dstDir, distDir)
|
|
51
|
+
assertNoSymlinkAncestors(distDir)
|
|
52
|
+
mkdirSync(distDir, { recursive: true })
|
|
53
|
+
assertNoSymlinkAncestors(distDir)
|
|
54
|
+
}
|
|
27
55
|
|
|
28
56
|
// Drop legacy raw copies from previous installs (root index.tsx + dist/tui.tsx).
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
rmSync(join(dstDir, 'dist', 'tui.tsx'), { force: true })
|
|
57
|
+
for (const legacyPath of [join(dstDir, 'index.tsx'), join(distDir, 'tui.tsx')]) {
|
|
58
|
+
removeLegacyFile(legacyPath, dstDir, { dryRun, recorder, reviewedRemovals, reviewedSnapshots })
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
// Copy dist/* files
|
|
@@ -36,7 +63,9 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
36
63
|
if (existsSync(distSrc)) {
|
|
37
64
|
for (const f of readdirSync(distSrc)) {
|
|
38
65
|
const c = readFileSync(join(distSrc, f), 'utf8')
|
|
39
|
-
const
|
|
66
|
+
const destination = join(distDir, f)
|
|
67
|
+
assertPathWithin(dstDir, destination)
|
|
68
|
+
const status = writeIfChanged(destination, c, dryRun, { recorder })
|
|
40
69
|
if (status === 'created') result.created++
|
|
41
70
|
else result.skipped++
|
|
42
71
|
}
|
|
@@ -53,6 +82,7 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
53
82
|
join(dstDir, 'package.json'),
|
|
54
83
|
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
55
84
|
dryRun,
|
|
85
|
+
{ recorder },
|
|
56
86
|
)
|
|
57
87
|
if (pkgStatus === 'created') result.created++
|
|
58
88
|
else result.skipped++
|
|
@@ -65,6 +95,7 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
65
95
|
join(dstDir, 'tui.ts'),
|
|
66
96
|
readFileSync(tuiEntrySrc, 'utf8'),
|
|
67
97
|
dryRun,
|
|
98
|
+
{ recorder },
|
|
68
99
|
)
|
|
69
100
|
if (entryStatus === 'created') result.created++
|
|
70
101
|
else result.skipped++
|
|
@@ -77,11 +108,17 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
77
108
|
v2ServerDestination,
|
|
78
109
|
readFileSync(v2ServerEntry, 'utf8'),
|
|
79
110
|
dryRun,
|
|
111
|
+
{ recorder },
|
|
80
112
|
)
|
|
81
113
|
if (entryStatus === 'created') result.created++
|
|
82
114
|
else result.skipped++
|
|
83
|
-
} else
|
|
84
|
-
|
|
115
|
+
} else {
|
|
116
|
+
removeLegacyFile(v2ServerDestination, dstDir, {
|
|
117
|
+
dryRun,
|
|
118
|
+
recorder,
|
|
119
|
+
reviewedRemovals,
|
|
120
|
+
reviewedSnapshots,
|
|
121
|
+
})
|
|
85
122
|
}
|
|
86
123
|
|
|
87
124
|
// Copy the lockfile so dependency installation is reproducible. A missing
|
|
@@ -92,6 +129,7 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
92
129
|
join(dstDir, 'package-lock.json'),
|
|
93
130
|
readFileSync(lockSrc, 'utf8'),
|
|
94
131
|
dryRun,
|
|
132
|
+
{ recorder },
|
|
95
133
|
)
|
|
96
134
|
if (lockStatus === 'created') result.created++
|
|
97
135
|
else result.skipped++
|
|
@@ -100,6 +138,49 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
100
138
|
return result
|
|
101
139
|
}
|
|
102
140
|
|
|
141
|
+
function removeLegacyFile(
|
|
142
|
+
filePath,
|
|
143
|
+
rootDir,
|
|
144
|
+
{ dryRun, recorder, reviewedRemovals, reviewedSnapshots },
|
|
145
|
+
) {
|
|
146
|
+
assertSafeRemoval(rootDir, filePath)
|
|
147
|
+
const present = existsSync(filePath)
|
|
148
|
+
if (dryRun) {
|
|
149
|
+
recordChange(
|
|
150
|
+
{
|
|
151
|
+
path: filePath,
|
|
152
|
+
action: present ? ACTIONS.REMOVE : ACTIONS.UNCHANGED,
|
|
153
|
+
bytes: 0,
|
|
154
|
+
scored: false,
|
|
155
|
+
},
|
|
156
|
+
recorder,
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
if (!present) return
|
|
161
|
+
|
|
162
|
+
const normalizedPath = resolve(filePath)
|
|
163
|
+
if (reviewedRemovals && !reviewedRemovals.has(normalizedPath)) {
|
|
164
|
+
throw new Error(`Refusing unreviewed plugin removal: ${filePath}`)
|
|
165
|
+
}
|
|
166
|
+
const expected = reviewedSnapshots?.get(normalizedPath)
|
|
167
|
+
if (reviewedSnapshots && (!expected || !sameSnapshot(snapshot(filePath), expected))) {
|
|
168
|
+
throw new Error(`Plugin removal target drifted after review: ${filePath}`)
|
|
169
|
+
}
|
|
170
|
+
rmSync(filePath, { force: true })
|
|
171
|
+
verifyPathRemoved(filePath)
|
|
172
|
+
recordChange({ path: filePath, action: ACTIONS.REMOVE, bytes: 0, scored: false }, recorder)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sameSnapshot(left, right) {
|
|
176
|
+
return (
|
|
177
|
+
left.exists === right.exists &&
|
|
178
|
+
left.sha256 === right.sha256 &&
|
|
179
|
+
left.size === right.size &&
|
|
180
|
+
left.mode === right.mode
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
103
184
|
/**
|
|
104
185
|
* Install a plugin from source to destination.
|
|
105
186
|
* @param {string} srcDir - Source plugin directory (e.g. ROOT/src/plugins/tui)
|
|
@@ -107,22 +188,55 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1'
|
|
|
107
188
|
* @param {object} [options]
|
|
108
189
|
* @param {boolean} [options.dryRun=false] - If true, don't write files
|
|
109
190
|
* @param {boolean} [options.clean=false] - If true, remove existing before install
|
|
191
|
+
* @param {(change: object) => void} [options.recorder] - optional change recorder for preview
|
|
110
192
|
* @returns {{ created: number, skipped: number, errors: number }}
|
|
111
193
|
*/
|
|
112
194
|
export function installPlugin(
|
|
113
195
|
srcDir,
|
|
114
196
|
dstDir,
|
|
115
|
-
{
|
|
197
|
+
{
|
|
198
|
+
dryRun = false,
|
|
199
|
+
clean = false,
|
|
200
|
+
version = 'v1',
|
|
201
|
+
recorder = null,
|
|
202
|
+
reviewedRemovals = null,
|
|
203
|
+
reviewedSnapshots = null,
|
|
204
|
+
} = {},
|
|
116
205
|
) {
|
|
117
206
|
const result = { created: 0, skipped: 0, errors: 0 }
|
|
207
|
+
const cleanTargetExisted = clean && existsSync(dstDir)
|
|
118
208
|
|
|
119
|
-
|
|
120
|
-
|
|
209
|
+
assertNoSymlinkAncestors(dstDir)
|
|
210
|
+
if (cleanTargetExisted) {
|
|
211
|
+
assertSafeRemoval(dirname(dstDir), dstDir)
|
|
212
|
+
if (dryRun) {
|
|
213
|
+
recordRemovalTree(dstDir, recorder)
|
|
214
|
+
} else {
|
|
215
|
+
removeReviewedTree(dstDir, reviewedRemovals, reviewedSnapshots)
|
|
216
|
+
}
|
|
121
217
|
}
|
|
122
218
|
|
|
123
|
-
const copyResult = copyPluginFiles(srcDir, dstDir, {
|
|
219
|
+
const copyResult = copyPluginFiles(srcDir, dstDir, {
|
|
220
|
+
dryRun,
|
|
221
|
+
version,
|
|
222
|
+
recorder,
|
|
223
|
+
reviewedRemovals,
|
|
224
|
+
reviewedSnapshots,
|
|
225
|
+
})
|
|
124
226
|
result.created += copyResult.created
|
|
125
227
|
result.skipped += copyResult.skipped
|
|
228
|
+
if (cleanTargetExisted) {
|
|
229
|
+
// The clean pass removes these directories before rebuilding their files;
|
|
230
|
+
// keep the net plan truthful while the separate removal review still lists
|
|
231
|
+
// every path actually cleared.
|
|
232
|
+
recordChange({ path: dstDir, action: ACTIONS.UNCHANGED, bytes: 0, scored: false }, recorder)
|
|
233
|
+
if (existsSync(join(srcDir, 'dist'))) {
|
|
234
|
+
recordChange(
|
|
235
|
+
{ path: join(dstDir, 'dist'), action: ACTIONS.UNCHANGED, bytes: 0, scored: false },
|
|
236
|
+
recorder,
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
126
240
|
|
|
127
241
|
// Install plugin dependencies (@opentui/core, @opentui/solid, solid-js).
|
|
128
242
|
// Lockfile installation is the only accepted dependency path.
|
|
@@ -150,9 +264,10 @@ export function installPluginDependencies(pluginDir, { dryRun = false } = {}) {
|
|
|
150
264
|
* @param {string} pluginId - Plugin identifier string
|
|
151
265
|
* @param {object} [options]
|
|
152
266
|
* @param {boolean} [options.dryRun=false]
|
|
267
|
+
* @param {(change: object) => void} [options.recorder] - optional change recorder for preview
|
|
153
268
|
* @returns {'created'|'skipped'}
|
|
154
269
|
*/
|
|
155
|
-
export function registerPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
|
|
270
|
+
export function registerPlugin(tuiJsonPath, pluginId, { dryRun = false, recorder = null } = {}) {
|
|
156
271
|
let tuiConfig = { plugin: [] }
|
|
157
272
|
if (existsSync(tuiJsonPath)) {
|
|
158
273
|
try {
|
|
@@ -168,7 +283,7 @@ export function registerPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
|
|
|
168
283
|
tuiConfig.plugin.push(pluginId)
|
|
169
284
|
}
|
|
170
285
|
const tuiContent = `${JSON.stringify(tuiConfig, null, 2)}\n`
|
|
171
|
-
return writeIfChanged(tuiJsonPath, tuiContent, dryRun)
|
|
286
|
+
return writeIfChanged(tuiJsonPath, tuiContent, dryRun, { recorder })
|
|
172
287
|
}
|
|
173
288
|
|
|
174
289
|
/**
|
|
@@ -256,12 +371,14 @@ export function staleTuiRefs(pluginArray, knownPantheonPaths = []) {
|
|
|
256
371
|
* @param {string} pluginId - Plugin to unregister
|
|
257
372
|
* @param {object} [options]
|
|
258
373
|
* @param {boolean} [options.dryRun=false]
|
|
374
|
+
* @param {string[]} [options.knownPantheonPaths=[]]
|
|
375
|
+
* @param {(change: object) => void} [options.recorder] - optional change recorder for preview
|
|
259
376
|
* @returns {'created'|'skipped'}
|
|
260
377
|
*/
|
|
261
378
|
export function unregisterPlugin(
|
|
262
379
|
tuiJsonPath,
|
|
263
380
|
pluginId,
|
|
264
|
-
{ dryRun = false, knownPantheonPaths = [] } = {},
|
|
381
|
+
{ dryRun = false, knownPantheonPaths = [], recorder = null } = {},
|
|
265
382
|
) {
|
|
266
383
|
if (!existsSync(tuiJsonPath)) {
|
|
267
384
|
// Nothing to clean — and never create an empty tui.json in a location
|
|
@@ -292,5 +409,6 @@ export function unregisterPlugin(
|
|
|
292
409
|
return !isPantheonTuiRef(ref, knownPantheonPaths)
|
|
293
410
|
})
|
|
294
411
|
const tuiContent = `${JSON.stringify(tuiConfig, null, 2)}\n`
|
|
295
|
-
|
|
412
|
+
// Stale-ref cleanup is not part of the install summary, so record it unscored.
|
|
413
|
+
return writeIfChanged(tuiJsonPath, tuiContent, dryRun, { recorder, scored: false })
|
|
296
414
|
}
|