pantheon-opencode 1.6.0-beta.8 → 1.6.0-beta.9

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/CHANGELOG.md CHANGED
@@ -18,6 +18,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
18
18
 
19
19
  ## ✅ Closed Issues
20
20
 
21
+ ## [v1.6.0-beta.9] - 2026-10-12
22
+
23
+ <!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
24
+ move this section to a versioned entry and reset the template below. -->
25
+
26
+ ## 🆕 What's New
27
+
28
+ - **Entry point TUI dedicado ao OpenCode V2.** O pacote agora compila e instala
29
+ separadamente os loaders V1/V2, e adiciona `/pantheon-focus` para foco de
30
+ sessão sem depender das goal-tools ausentes no host V2.
31
+
32
+ ## 🐞 Fixed
33
+
34
+ - **Instalação V2:** agentes e slash commands de projeto são instalados nos
35
+ diretórios descobertos pelo host; o TUI seleciona o bundle da geração correta
36
+ e o estado dos componentes só é marcado como instalado após sucesso.
37
+ - **`/pantheon-audit`:** inclui o scanner heurístico no pacote e localiza a cópia
38
+ instalada antes de executar a primeira camada.
39
+ - **`/pantheon-consolidate`:** delega a consolidação ao agente Mnemosyne, dono
40
+ da qualidade do banco de memória.
41
+
42
+ ## ⚠️ Known Issues
43
+
44
+ - **Sidebar V2 na sessão inicial:** a captura do TUI recém-aberto ainda não
45
+ mostra o conteúdo Pantheon antes do primeiro prompt; validar o render inicial
46
+ interativo continua sendo um gate aberto para a beta.
47
+ - **Goal persistente no OpenCode V2:** as goal-tools não fazem parte da
48
+ superfície V2 suportada; `/pantheon-focus` não substitui uma goal-tool
49
+ persistente/removível do host.
50
+
21
51
  ## [v1.6.0-beta.8] - 2026-10-11
22
52
 
23
53
  <!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
package/README.md CHANGED
@@ -518,7 +518,9 @@ full `TARGET_SHA`; a second pack is not interchangeable.
518
518
 
519
519
  CI also rebuilds the TUI with `npm run build --prefix src/plugins/tui` and
520
520
  checks every packaged `dist` artifact byte-for-byte against its checked-in
521
- version, including final newlines.
521
+ version, including final newlines. The build includes separate V1 and native
522
+ OpenCode V2 TUI entrypoints; `init` selects the entrypoint and sidebar slot API
523
+ for the host generation in use.
522
524
 
523
525
  ## Sandbox validation (V2)
524
526
 
@@ -15,7 +15,13 @@ agent: "zeus"
15
15
  ## Layers
16
16
 
17
17
  ### Layer 1 — Heuristic Scan
18
- `python3 scripts/themis_heuristic_scan.py [--path=<dir>]`
18
+ Resolve the installed helper first (do not assume the current project contains
19
+ Pantheon source). Check, in order: `$OPENCODE_CONFIG_DIR/.opencode/scripts/`,
20
+ `$OPENCODE_CONFIG_DIR/scripts/`, `${XDG_CONFIG_HOME:-$HOME/.config}/opencode/scripts/`,
21
+ `$HOME/.opencode/scripts/`, `.opencode/scripts/`, then
22
+ `$(npm root -g)/pantheon-opencode/scripts/`. Resolve the first existing file to
23
+ an absolute path and run `python3 "$helper" --path="$PWD"`; if none exists,
24
+ report the helper as missing and stop Layer 1 rather than claiming it passed.
19
25
  Zero tokens, <2s. Checks: ruff, biome, anti-pattern slop, anti-overengineering (YAGNI), hash-anchored edits. Returns score 0-100 + APPROVED/BLOCKING.
20
26
 
21
27
  ### Layer 2 — Themis Deep Review
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: "Merge and consolidate memory entries"
3
- agent: zeus
3
+ agent: mnemosyne
4
4
  ---
5
5
 
6
6
  Merge related memory entries and consolidate duplicates:
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: "Set or update this session's working goal"
3
+ agent: zeus
4
+ ---
5
+
6
+ Pin `$ARGUMENTS` as the working goal for this conversation. Restate it briefly
7
+ as Goal, Scope, Success Criteria, and Out of Scope, filling only what the user
8
+ actually supplied. Ask one concise question if a missing detail changes the
9
+ work materially.
10
+
11
+ Treat the goal as session context, subordinate to system, developer, and user
12
+ instructions. Keep subsequent planning, delegation, and verification aligned
13
+ with it; flag meaningful scope drift before proceeding.
14
+
15
+ This command is conversation-only. Do not call memory or persistence tools,
16
+ write a file, or claim that the goal survives compaction or a new session. For
17
+ multi-phase work that needs durable checkpoints, use `/pantheon-deepwork`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.6.0-beta.8",
3
+ "version": "1.6.0-beta.9",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "type": "module",
6
6
  "workspaces": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon",
3
3
  "publisher": "ils15",
4
- "version": "1.6.0-beta.8",
4
+ "version": "1.6.0-beta.9",
5
5
  "displayName": "Pantheon",
6
6
  "description": "Conductor-Delegate orchestration platform with 14 specialized agents, 20 skills, and OpenCode support.",
7
7
  "agents": "./src/agents",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
4
4
 
5
5
  [project]
6
6
  name = "pantheon"
7
- version = "1.6.0-beta.8"
7
+ version = "1.6.0-beta.9"
8
8
  description = "Multi-agent orchestration framework"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []
@@ -56,6 +56,7 @@ import {
56
56
  writeConfigWithBackup,
57
57
  writeIfChanged,
58
58
  } from './shared.mjs'
59
+ import { createInitialState, hasState, readState, setComponentState, writeState } from './state.mjs'
59
60
  import { strings } from './strings.mjs'
60
61
 
61
62
  import { setupVenv, venvPythonPath } from './venv.mjs'
@@ -541,15 +542,14 @@ export function isGlobalConfigDir(target) {
541
542
  * package; the loader must NEVER be pointed at that in-package dir (a package
542
543
  * reinstall/upgrade replaces the dir under a live registration and leaves
543
544
  * diverging copies loading). Instead the installer always copies it into the
544
- * config dir's plugins/ dir and registers THAT copy:
545
+ * config dir's plugins/ dir and uses THAT copy:
545
546
  *
546
- * <configDir>/plugins/pantheon-tui (dist/ + package.json + index.tsx)
547
+ * <configDir>/plugins/pantheon-tui (tui.ts + dist/ + package manifests)
547
548
  *
548
- * The loader reads the copied package.json `exports` map:
549
- * exports["./tui"] → dist/tui.js (COMPILED Solid output)
550
- * exports["./server"] → dist/server.js (no-op server() stub)
549
+ * V1 registers that copy through tui.json. V2 discovers the local plugin's
550
+ * index.ts + tui.ts pair; the V2 shim re-exports dist/tui-v2.js.
551
551
  *
552
- * @param {string} configDir - directory holding tui.json (target for global
552
+ * @param {string} configDir - OpenCode config directory (target for global
553
553
  * installs, <target>/.opencode for project installs)
554
554
  * @returns {string} absolute path of the copied plugin directory
555
555
  */
@@ -575,23 +575,20 @@ export function tuiConfigLocations(target) {
575
575
  }
576
576
 
577
577
  /**
578
- * Sync the Pantheon TUI plugin registration across EVERY tui.json location
579
- * OpenCode reads.
578
+ * Sync the Pantheon TUI plugin payload and V1 registration. V2 discovers the
579
+ * copied local-plugin entrypoints and does not use a project tui.json.
580
580
  *
581
581
  * Contract (single source of truth):
582
- * 1. ALWAYS copy src/plugins/tui → <configDir>/plugins/pantheon-tui
583
- * (dist/* + package.json + index.tsx) so the registered reference is a
584
- * directory that exists and carries the loader contract (exports →
585
- * dist/tui.js, dist/server.js). The copy is idempotent: a second run is
586
- * byte-identical.
582
+ * 1. ALWAYS copy src/plugins/tui → <configDir>/plugins/pantheon-tui with the
583
+ * selected host entrypoints and compiled bundle. The copy is idempotent.
587
584
  * 2. Clean stale Pantheon TUI refs from ALL tui.json locations OpenCode
588
585
  * loads — ~/.opencode, $XDG_CONFIG_HOME/opencode and
589
586
  * <target>/.opencode — covering every old reference shape: dist file
590
587
  * paths, in-package src/plugins/tui dirs, bare plugins/pantheon-tui
591
588
  * copies, and npx paths. A stale ref left in the non-target location
592
589
  * would still be loaded and break the TUI.
593
- * 3. Register EXACTLY the copied directory in the selected target only —
594
- * exactly ONE Pantheon TUI reference exists system-wide after install.
590
+ * 3. V1 registers the copied directory in the selected target. V2 relies on
591
+ * local-plugin discovery and does not write a project tui.json entry.
595
592
  *
596
593
  * @param {string} target - install target directory
597
594
  * @param {object} [opts]
@@ -599,18 +596,19 @@ export function tuiConfigLocations(target) {
599
596
  * @param {boolean} [opts.dryRun=false]
600
597
  * @returns {'created'|'skipped'} status for the target registration
601
598
  */
602
- export function syncTuiRegistration(target, { isGlobal = false, dryRun = false } = {}) {
599
+ export function syncTuiRegistration(
600
+ target,
601
+ { isGlobal = false, dryRun = false, version = 'v1' } = {},
602
+ ) {
603
603
  const configDir = isGlobal ? target : join(target, '.opencode')
604
604
  const targetTuiConfigPath = join(configDir, 'tui.json')
605
605
 
606
- // 1. Always copy the single source of truth into the target config's
607
- // plugins/pantheon-tui. Registering this copied directory (never the
608
- // in-package src/plugins/tui) keeps installs hermetic and idempotent:
609
- // the ref points at a dir that exists and has dist/tui.js.
606
+ // 1. Copy the host-specific plugin into the target config's
607
+ // plugins/pantheon-tui. This keeps the installed entry hermetic.
610
608
  const tuiSrcDir = join(ROOT, 'src', 'plugins', 'tui')
611
609
  const tuiCopyDir = resolveTuiCopyTarget(configDir)
612
610
  if (existsSync(tuiSrcDir)) {
613
- copyPluginFiles(tuiSrcDir, tuiCopyDir, { dryRun })
611
+ copyPluginFiles(tuiSrcDir, tuiCopyDir, { dryRun, version })
614
612
  }
615
613
 
616
614
  // 2. Clean stale Pantheon TUI refs from every location OpenCode reads.
@@ -626,6 +624,11 @@ export function syncTuiRegistration(target, { isGlobal = false, dryRun = false }
626
624
  })
627
625
  }
628
626
 
627
+ // OpenCode V2 discovers local plugins from the config plugins directories;
628
+ // its TUI plugin API is exposed by the sibling tui.ts entrypoint. V1 still
629
+ // requires the explicit tui.json registration.
630
+ if (version === 'v2') return 'skipped'
631
+
629
632
  // 3. Register the copied directory in the selected target only.
630
633
  return registerPlugin(targetTuiConfigPath, tuiCopyDir, { dryRun })
631
634
  }
@@ -862,14 +865,13 @@ export async function installOpenCode(
862
865
  }
863
866
  }
864
867
 
865
- // Determine layout based on install scope.
866
- // Global config dir (~/.opencode or ~/.config/opencode) uses a flat layout:
867
- // agents/ skills/ commands/ plugins/
868
- // Project installs use the .opencode/ sub-directory layout:
869
- // .opencode/agents/ .opencode/skills/ .opencode/commands/
868
+ // V1 project installs use the legacy .opencode/agents layout. OpenCode V2's
869
+ // active config directory is the project root and its agent loader scans
870
+ // <config-dir>/{agent,agents}/**, so V2 project agents must be at ./agents.
871
+ // Global installs are flat for both versions.
870
872
  const isGlobal = isGlobalConfigDir(target)
871
873
  const _subDir = isGlobal ? '' : '.opencode'
872
- const agentPrefix = isGlobal ? 'agents' : '.opencode/agents'
874
+ const agentPrefix = isGlobal || version === 'v2' ? 'agents' : '.opencode/agents'
873
875
 
874
876
  configure({ dryRun })
875
877
 
@@ -888,7 +890,7 @@ export async function installOpenCode(
888
890
  stats.errors++
889
891
  } else {
890
892
  const agentStep = step(S.installingAgents)
891
- const dstDir = isGlobal ? join(target, 'agents') : join(target, '.opencode', 'agents')
893
+ const dstDir = join(target, agentPrefix)
892
894
  if (!dryRun) mkdirSync(dstDir, { recursive: true })
893
895
  if (clean && existsSync(dstDir) && !dryRun) {
894
896
  const existing = readdirSync(dstDir)
@@ -968,9 +970,7 @@ export async function installOpenCode(
968
970
  // already-installed Pantheon agents, but must not create agents the user
969
971
  // did not install. Full agent installs do this in phase 1 above.
970
972
  if (!componentSet.has('agents')) {
971
- const installedAgents = isGlobal
972
- ? join(target, 'agents')
973
- : join(target, '.opencode', 'agents')
973
+ const installedAgents = join(target, agentPrefix)
974
974
  const sourceAgents = join(ROOT, 'src', 'agents')
975
975
  if (existsSync(installedAgents) && existsSync(sourceAgents)) {
976
976
  const refreshed = copyAgentPrompts(sourceAgents, installedAgents, dryRun, true)
@@ -1000,7 +1000,11 @@ export async function installOpenCode(
1000
1000
  if (componentSet.has('commands')) {
1001
1001
  section('\u26A1 Commands')
1002
1002
  const srcCmds = join(ROOT, 'commands')
1003
- const dstCmds = isGlobal ? join(target, 'commands') : join(target, '.opencode', 'commands')
1003
+ // V2 project sandboxes use OPENCODE_CONFIG_DIR=<project root>, so OpenCode
1004
+ // discovers custom commands from <target>/commands. V1 project installs
1005
+ // keep the standard .opencode/commands layout; global installs are flat.
1006
+ const commandDir = isGlobal || version === 'v2' ? 'commands' : '.opencode/commands'
1007
+ const dstCmds = join(target, commandDir)
1004
1008
  if (existsSync(srcCmds)) {
1005
1009
  const cmdResult = syncDir(srcCmds, dstCmds, dryRun, clean, (f) => f.endsWith('.md'))
1006
1010
  stats.created += cmdResult.created
@@ -1025,7 +1029,7 @@ export async function installOpenCode(
1025
1029
  const dstPluginDir = isGlobal
1026
1030
  ? join(target, 'plugins', 'pantheon-tui')
1027
1031
  : join(target, '.opencode', 'plugins', 'pantheon-tui')
1028
- const pluginStats = installPlugin(srcPluginDir, dstPluginDir, { dryRun, clean })
1032
+ const pluginStats = installPlugin(srcPluginDir, dstPluginDir, { dryRun, clean, version })
1029
1033
  stats.created += pluginStats.created
1030
1034
  stats.skipped += pluginStats.skipped
1031
1035
  stats.errors += pluginStats.errors
@@ -1037,7 +1041,7 @@ export async function installOpenCode(
1037
1041
  if (componentSet.has('plugins')) {
1038
1042
  let tuiStatus
1039
1043
  try {
1040
- tuiStatus = syncTuiRegistration(target, { isGlobal, dryRun })
1044
+ tuiStatus = syncTuiRegistration(target, { isGlobal, dryRun, version })
1041
1045
  } catch (cause) {
1042
1046
  error(`TUI plugin setup failed: ${cause.message}`)
1043
1047
  stats.errors++
@@ -1066,6 +1070,7 @@ export async function installOpenCode(
1066
1070
  'mcp_persistence.py',
1067
1071
  'pantheon_vision.py',
1068
1072
  'eval_store.py',
1073
+ 'themis_heuristic_scan.py',
1069
1074
  ]
1070
1075
  const srcScriptsDir = join(ROOT, 'scripts')
1071
1076
  // Canonical MCP server sources live in src/mcp/. Map them explicitly so the
@@ -1706,4 +1711,25 @@ export async function installOpenCode(
1706
1711
  if (stats.errors > 0) {
1707
1712
  throw new Error(`installation completed with ${stats.errors} error(s); see output above`)
1708
1713
  }
1714
+
1715
+ // Record the component state only after the install has completed. The
1716
+ // postinstall marker can predate `init` and start with every component
1717
+ // pending; leaving it untouched made a healthy beta install look incomplete
1718
+ // to `doctor` forever. Unselected components retain their previous state.
1719
+ if (!dryRun) {
1720
+ const packageVersion = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version
1721
+ let state = readState(target)
1722
+ if (!state && hasState(target)) {
1723
+ throw new Error('Installation state exists but cannot be read; refusing to replace it')
1724
+ }
1725
+ state ??= createInitialState(packageVersion)
1726
+ state.pantheon_version = packageVersion
1727
+ for (const component of ['agents', 'skills', 'plugins', 'runtime']) {
1728
+ if (!componentSet.has(component)) continue
1729
+ const status = component === 'runtime' && !runtimeHealthy ? 'failed' : 'installed'
1730
+ const componentVersion = state.components?.[component]?.version ?? 1
1731
+ setComponentState(state, component, componentVersion, status)
1732
+ }
1733
+ writeState(target, state)
1734
+ }
1709
1735
  }
@@ -8,24 +8,24 @@ import { isAbsolute, join, resolve } from 'node:path'
8
8
  import { ROOT, writeIfChanged } from './shared.mjs'
9
9
 
10
10
  /**
11
- * Copy a plugin's runtime payload (the bundled dist/*, package.json and
12
- * package-lock.json) from source to destination. Only the bundle is copied —
13
- * the raw TSX source is never shipped to the destination, so the plugin can
14
- * be modularized without the loader needing relative imports.
11
+ * Copy a plugin's runtime payload (the root TUI loader, bundled dist/*,
12
+ * package.json and package-lock.json) from source to destination. The raw TSX
13
+ * implementation is never shipped to the destination; tui.ts only re-exports
14
+ * the host-specific bundle so the local plugin stays hermetic.
15
15
  * @param {string} srcDir - Source plugin directory (e.g. ROOT/src/plugins/tui)
16
16
  * @param {string} dstDir - Destination plugin directory
17
17
  * @param {object} [options]
18
18
  * @param {boolean} [options.dryRun=false] - If true, don't write files
19
+ * @param {'v1'|'v2'} [options.version='v1'] - Host plugin generation
19
20
  * @returns {{ created: number, skipped: number }}
20
21
  */
21
- export function copyPluginFiles(srcDir, dstDir, { dryRun = false } = {}) {
22
+ export function copyPluginFiles(srcDir, dstDir, { dryRun = false, version = 'v1' } = {}) {
22
23
  const result = { created: 0, skipped: 0 }
23
24
 
24
25
  if (!dryRun) mkdirSync(dstDir, { recursive: true })
25
26
  if (!dryRun) mkdirSync(join(dstDir, 'dist'), { recursive: true })
26
27
 
27
28
  // Drop legacy raw copies from previous installs (root index.tsx + dist/tui.tsx).
28
- // The loader consumes only the bundle, so stale raws must not linger.
29
29
  if (!dryRun) {
30
30
  rmSync(join(dstDir, 'index.tsx'), { force: true })
31
31
  rmSync(join(dstDir, 'dist', 'tui.tsx'), { force: true })
@@ -45,15 +45,45 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false } = {}) {
45
45
  // Copy package.json
46
46
  const pkgSrc = join(srcDir, 'package.json')
47
47
  if (existsSync(pkgSrc)) {
48
+ const packageJson = JSON.parse(readFileSync(pkgSrc, 'utf8'))
49
+ if (version === 'v2' && packageJson.exports?.['./tui-v2']) {
50
+ packageJson.exports['./tui'] = packageJson.exports['./tui-v2']
51
+ }
48
52
  const pkgStatus = writeIfChanged(
49
53
  join(dstDir, 'package.json'),
50
- readFileSync(pkgSrc, 'utf8'),
54
+ `${JSON.stringify(packageJson, null, 2)}\n`,
51
55
  dryRun,
52
56
  )
53
57
  if (pkgStatus === 'created') result.created++
54
58
  else result.skipped++
55
59
  }
56
60
 
61
+ // Each host generation receives its own root loader and compiled bundle.
62
+ const tuiEntrySrc = join(srcDir, version === 'v2' ? 'tui-v2.ts' : 'tui.ts')
63
+ if (existsSync(tuiEntrySrc)) {
64
+ const entryStatus = writeIfChanged(
65
+ join(dstDir, 'tui.ts'),
66
+ readFileSync(tuiEntrySrc, 'utf8'),
67
+ dryRun,
68
+ )
69
+ if (entryStatus === 'created') result.created++
70
+ else result.skipped++
71
+ }
72
+
73
+ const v2ServerEntry = join(srcDir, 'index-v2.ts')
74
+ const v2ServerDestination = join(dstDir, 'index.ts')
75
+ if (version === 'v2' && existsSync(v2ServerEntry)) {
76
+ const entryStatus = writeIfChanged(
77
+ v2ServerDestination,
78
+ readFileSync(v2ServerEntry, 'utf8'),
79
+ dryRun,
80
+ )
81
+ if (entryStatus === 'created') result.created++
82
+ else result.skipped++
83
+ } else if (!dryRun) {
84
+ rmSync(v2ServerDestination, { force: true })
85
+ }
86
+
57
87
  // Copy the lockfile so dependency installation is reproducible. A missing
58
88
  // lockfile is intentionally not synthesized by the installer.
59
89
  const lockSrc = join(srcDir, 'package-lock.json')
@@ -79,14 +109,18 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false } = {}) {
79
109
  * @param {boolean} [options.clean=false] - If true, remove existing before install
80
110
  * @returns {{ created: number, skipped: number, errors: number }}
81
111
  */
82
- export function installPlugin(srcDir, dstDir, { dryRun = false, clean = false } = {}) {
112
+ export function installPlugin(
113
+ srcDir,
114
+ dstDir,
115
+ { dryRun = false, clean = false, version = 'v1' } = {},
116
+ ) {
83
117
  const result = { created: 0, skipped: 0, errors: 0 }
84
118
 
85
119
  if (clean && existsSync(dstDir) && !dryRun) {
86
120
  rmSync(dstDir, { recursive: true, force: true })
87
121
  }
88
122
 
89
- const copyResult = copyPluginFiles(srcDir, dstDir, { dryRun })
123
+ const copyResult = copyPluginFiles(srcDir, dstDir, { dryRun, version })
90
124
  result.created += copyResult.created
91
125
  result.skipped += copyResult.skipped
92
126
 
@@ -40,6 +40,7 @@ const MCP_SCRIPTS = {
40
40
  'pantheon_vision.py': () => join(ROOT, 'src', 'mcp', 'pantheon_vision.py'),
41
41
  'eval_store.py': () => join(ROOT, 'src', 'mcp', 'eval_store.py'),
42
42
  'scrub-secrets.py': () => join(ROOT, 'scripts', 'scrub-secrets.py'),
43
+ 'themis_heuristic_scan.py': () => join(ROOT, 'scripts', 'themis_heuristic_scan.py'),
43
44
  }
44
45
 
45
46
  /**
@@ -14,6 +14,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync
14
14
  import { homedir } from 'node:os'
15
15
  import { join } from 'node:path'
16
16
  import { fileURLToPath, pathToFileURL } from 'node:url'
17
+ import { resolveOpenCodeVersion } from './install/opencode-version.mjs'
17
18
 
18
19
  const __dirname = fileURLToPath(new URL('.', import.meta.url))
19
20
  const ROOT = join(__dirname, '..')
@@ -63,7 +64,7 @@ function resolveConfigDir() {
63
64
  * The raw TSX source is never copied — the loader consumes only the bundle.
64
65
  * @returns {{ created: number, skipped: number }}
65
66
  */
66
- function copyPluginFiles(srcDir, dstDir) {
67
+ function copyPluginFiles(srcDir, dstDir, version) {
67
68
  const result = { created: 0, skipped: 0 }
68
69
 
69
70
  mkdirSync(dstDir, { recursive: true })
@@ -87,11 +88,30 @@ function copyPluginFiles(srcDir, dstDir) {
87
88
  // package.json
88
89
  const pkgSrc = join(srcDir, 'package.json')
89
90
  if (existsSync(pkgSrc)) {
90
- const content = readFileSync(pkgSrc, 'utf8')
91
+ const packageJson = JSON.parse(readFileSync(pkgSrc, 'utf8'))
92
+ if (version === 'v2' && packageJson.exports?.['./tui-v2']) {
93
+ packageJson.exports['./tui'] = packageJson.exports['./tui-v2']
94
+ }
95
+ const content = `${JSON.stringify(packageJson, null, 2)}\n`
91
96
  if (writeIfChanged(join(dstDir, 'package.json'), content)) result.created++
92
97
  else result.skipped++
93
98
  }
94
99
 
100
+ const tuiEntry = join(srcDir, version === 'v2' ? 'tui-v2.ts' : 'tui.ts')
101
+ if (existsSync(tuiEntry)) {
102
+ if (writeIfChanged(join(dstDir, 'tui.ts'), readFileSync(tuiEntry, 'utf8'))) result.created++
103
+ else result.skipped++
104
+ }
105
+
106
+ const v2ServerEntry = join(srcDir, 'index-v2.ts')
107
+ const v2ServerDestination = join(dstDir, 'index.ts')
108
+ if (version === 'v2' && existsSync(v2ServerEntry)) {
109
+ if (writeIfChanged(v2ServerDestination, readFileSync(v2ServerEntry, 'utf8'))) result.created++
110
+ else result.skipped++
111
+ } else if (existsSync(v2ServerDestination)) {
112
+ rmSync(v2ServerDestination, { force: true })
113
+ }
114
+
95
115
  // Keep the copied plugin installable with deterministic npm ci.
96
116
  const lockSrc = join(srcDir, 'package-lock.json')
97
117
  if (existsSync(lockSrc)) {
@@ -169,7 +189,8 @@ async function main() {
169
189
  const sourceVersion = readVersion(join(tuiSrcDir, 'package.json'))
170
190
 
171
191
  // Copy fresh files
172
- const { created } = copyPluginFiles(tuiSrcDir, tuiCopyDir)
192
+ const version = resolveOpenCodeVersion('auto', { warn: (message) => console.warn(message) })
193
+ const { created } = copyPluginFiles(tuiSrcDir, tuiCopyDir, version)
173
194
 
174
195
  // Lockfile installation is the only accepted dependency path. Strip
175
196
  // global-context npm vars so a global postinstall can't trigger ECIGLOBAL.
package/skills-lock.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "src/skills/memory-bank/SKILL.md": "4717cf18b43550c9f02cfab810968b97a5b2584f00a15516ecba643c80cdc110",
13
13
  "src/skills/reflect/SKILL.md": "4c7608c2a55cb8a50ac90fc0e2446bdf25e7914b1748cb7731c311d13c29dfdd",
14
14
  "src/skills/security-hardening/SKILL.md": "6d637e122421f7bc3b751f04854ad343a5a5d35249458bc8939233138fd3f20d",
15
- "src/skills/session-goal/SKILL.md": "793498265f70253c20524f6adc88bcc86db11d7160fe8232a26aedc6b1c5489f",
15
+ "src/skills/session-goal/SKILL.md": "3a708d15a59a6bff2dd32ff7ead6548ecccc7be8d39437fee3133ea6254b6134",
16
16
  "src/skills/simplify/SKILL.md": "aa739e9f75962a77d6574557d674ea01b5aea95e580b721d66c058753161b053",
17
17
  "src/skills/spec-driven-development/SKILL.md": "e93b9103d6cd1848ab01328efde82c1daf564d1cabbdc2278254b346fcf55d02",
18
18
  "src/skills/tdd-with-agents/SKILL.md": "9d20607ea34fc3e02c7e3ef5186b103d84196bd9a13aaadd55a44f41339125da",
@@ -12,17 +12,9 @@ mcp_tools:
12
12
  - memory_recall
13
13
  - memory_store
14
14
  - memory_search
15
- - memory_delete
16
- - memory_update
17
- - memory_export
18
- - memory_link
19
- - memory_traverse
20
- - memory_compress
21
- - memory_consolidate
22
- - memory_verify
23
- - memory_sessions
24
- - memory_expand
25
- - memory_cleanup
15
+ - memory_forget
16
+ - memory_list
17
+ - memory_stats
26
18
  pantheon-code-mode: [execute_code_script]
27
19
  skills:
28
20
  - artifact-management
@@ -64,6 +56,7 @@ If asked to edit code, refuse and redirect to the appropriate implementation age
64
56
  - Close sprints (wipe .tmp/)
65
57
  - Clean tmp without closing sprint
66
58
  - List artifacts
59
+ - Use `memory_forget` only for an explicitly requested durable-memory deletion; do not delete automatically.
67
60
 
68
61
  ### 2. Artifact Management
69
62
  - Create artifacts in .pantheon/memory-bank/.tmp/ (PLAN, IMPL, REVIEW, DISC)
@@ -0,0 +1,9 @@
1
+ //#region src/index-v2.d.ts
2
+ /** @jsxImportSource @opentui/solid */
3
+ declare const plugin: {
4
+ id: string;
5
+ setup(context: any): (() => void) | undefined;
6
+ };
7
+ //#endregion
8
+ export { plugin as default };
9
+ //# sourceMappingURL=tui-v2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-v2.d.ts","names":[],"sources":["../src/index-v2.tsx"],"mappings":";;cAuFM;;EAEW,MAAA"}