@boyingliu01/xp-gate 0.9.1 → 0.9.3

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.
Files changed (35) hide show
  1. package/adapters/cpp.sh +3 -3
  2. package/adapters/dart.sh +2 -2
  3. package/adapters/flutter.sh +2 -2
  4. package/adapters/java.sh +4 -4
  5. package/adapters/kotlin.sh +4 -4
  6. package/adapters/objectivec.sh +2 -2
  7. package/adapters/plugins/p3c-java/scripts/install-maven-p3c.sh +3 -3
  8. package/adapters/plugins/whalecloud-java/scripts/install-maven-whalecloud.sh +3 -3
  9. package/adapters/powershell.sh +3 -3
  10. package/adapters/python.sh +2 -2
  11. package/adapters/shell.sh +3 -3
  12. package/adapters/swift.sh +2 -2
  13. package/mock-policy/AGENTS.md +2 -2
  14. package/mutation/AGENTS.md +2 -2
  15. package/package.json +1 -1
  16. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  17. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  18. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  19. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  20. package/plugins/opencode/__tests__/tui-plugin.test.ts +306 -0
  21. package/plugins/opencode/__tests__/xp-gate-update.test.ts +255 -0
  22. package/plugins/opencode/index.ts +158 -75
  23. package/plugins/opencode/package.json +14 -3
  24. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  25. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  26. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  27. package/plugins/opencode/tsconfig.json +1 -1
  28. package/plugins/opencode/tui-plugin.ts +188 -0
  29. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  30. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  31. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  32. package/principles/AGENTS.md +2 -2
  33. package/skills/delphi-review/AGENTS.md +2 -2
  34. package/skills/sprint-flow/AGENTS.md +2 -2
  35. package/skills/test-specification-alignment/AGENTS.md +2 -2
@@ -0,0 +1,255 @@
1
+ /**
2
+ * XP-Gate auto-update tests
3
+ *
4
+ * Tests for:
5
+ * - semverLt: version comparison
6
+ * - checkXpGateUpdate: xp-gate npm registry check + cache + auto-upgrade
7
+ * - chat.message integration
8
+ * - Legacy checkPluginUpdate modified to detect BOTH packages
9
+ */
10
+
11
+ import { describe, it, before, after } from "node:test"
12
+ import assert from "node:assert/strict"
13
+ import { randomUUID } from "node:crypto"
14
+ import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs"
15
+ import { join } from "node:path"
16
+ import { tmpdir, homedir } from "node:os"
17
+ import { execSync } from "node:child_process"
18
+
19
+ // ── Pure function: semverLt ──
20
+
21
+ function semverLt(a: string, b: string): boolean {
22
+ const pa = a.replace(/^v/, "").split(".").map(Number)
23
+ const pb = b.replace(/^v/, "").split(".").map(Number)
24
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
25
+ const na = pa[i] ?? 0
26
+ const nb = pb[i] ?? 0
27
+ if (na !== nb) return na < nb
28
+ }
29
+ return false
30
+ }
31
+
32
+ // ── XP-GATE specific: these are the new functions we need to implement ──
33
+
34
+ const XP_GATE_CACHE_FILE = join(homedir(), ".xp-gate", "xp-gate-version-check.json")
35
+ const XP_GATE_NPM_PKG = "@boyingliu01/xp-gate"
36
+ const XP_GATE_REGISTRY_URL = `https://registry.npmjs.org/-/package/${encodeURIComponent(XP_GATE_NPM_PKG)}/dist-tags`
37
+ const CACHE_TTL_MS = 86_400_000 // 24h
38
+
39
+ type XpGateCache = {
40
+ ts: number
41
+ localVersion: string
42
+ remoteVersion: string
43
+ status?: "current" | "upgraded"
44
+ }
45
+
46
+ /**
47
+ * Read version from installed xp-gate npm package.
48
+ * Returns null if not installed.
49
+ */
50
+ function getLocalXpGateVersion(): string | null {
51
+ try {
52
+ const pkgPath = join(
53
+ execSync("npm root -g", { encoding: "utf8" }).trim(),
54
+ XP_GATE_NPM_PKG,
55
+ "package.json"
56
+ )
57
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"))
58
+ return pkg.version || null
59
+ } catch {
60
+ return null
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Read cache file, returns null if absent / stale.
66
+ */
67
+ function readXpGateCache(): XpGateCache | null {
68
+ try {
69
+ if (!existsSync(XP_GATE_CACHE_FILE)) return null
70
+ const raw = readFileSync(XP_GATE_CACHE_FILE, "utf8")
71
+ const data: XpGateCache = JSON.parse(raw)
72
+ if (Date.now() - data.ts < CACHE_TTL_MS && data.remoteVersion) {
73
+ return data
74
+ }
75
+ return null // stale
76
+ } catch {
77
+ return null
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Write cache file.
83
+ */
84
+ function writeXpGateCache(data: XpGateCache): void {
85
+ try {
86
+ mkdirSync(join(homedir(), ".xp-gate"), { recursive: true })
87
+ const tmp = XP_GATE_CACHE_FILE + ".tmp." + process.pid
88
+ writeFileSync(tmp, JSON.stringify(data), "utf8")
89
+ try { rmSync(XP_GATE_CACHE_FILE) } catch {}
90
+ const orig = readFileSync(tmp, "utf8")
91
+ writeFileSync(XP_GATE_CACHE_FILE, orig, "utf8")
92
+ rmSync(tmp)
93
+ } catch {
94
+ // silent
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Fetch latest version from npm registry. Returns null on failure.
100
+ */
101
+ async function fetchNpmLatestVersion(url: string, timeoutMs = 5000): Promise<string | null> {
102
+ const controller = new AbortController()
103
+ const timer = setTimeout(() => controller.abort(), timeoutMs)
104
+ try {
105
+ const response = await fetch(url, { signal: controller.signal })
106
+ if (!response.ok) return null
107
+ const data: Record<string, unknown> = await response.json()
108
+ const latest = data.latest
109
+ return typeof latest === "string" ? latest : null
110
+ } catch {
111
+ return null
112
+ } finally {
113
+ clearTimeout(timer)
114
+ }
115
+ }
116
+
117
+ type UpgradeResult = {
118
+ action: "noop" | "upgraded" | "error"
119
+ localVersion: string | null
120
+ remoteVersion: string | null
121
+ error?: string
122
+ }
123
+
124
+ /**
125
+ * Check xp-gate version and auto-upgrade if outdated.
126
+ *
127
+ * - Respects daily cache
128
+ * - Fires npm install -g in background
129
+ * - Returns result for user notification
130
+ */
131
+ async function checkXpGateUpdate(): Promise<UpgradeResult> {
132
+ // 1. Check cache
133
+ const cached = readXpGateCache()
134
+ if (cached && cached.status === "current") {
135
+ return { action: "noop", localVersion: cached.localVersion, remoteVersion: cached.remoteVersion }
136
+ }
137
+
138
+ // 2. Get local version
139
+ const localVersion = getLocalXpGateVersion()
140
+ if (!localVersion) {
141
+ return { action: "noop", localVersion: null, remoteVersion: null }
142
+ }
143
+
144
+ // 3. Fetch remote
145
+ const remoteVersion = await fetchNpmLatestVersion(XP_GATE_REGISTRY_URL)
146
+ if (!remoteVersion) {
147
+ return { action: "noop", localVersion, remoteVersion: null }
148
+ }
149
+
150
+ // 4. Compare
151
+ if (!semverLt(localVersion, remoteVersion)) {
152
+ writeXpGateCache({ ts: Date.now(), localVersion, remoteVersion, status: "current" })
153
+ return { action: "noop", localVersion, remoteVersion }
154
+ }
155
+
156
+ // 5. Outdated — auto upgrade
157
+ writeXpGateCache({ ts: Date.now(), localVersion, remoteVersion })
158
+ try {
159
+ execSync(`npm install -g ${XP_GATE_NPM_PKG}@${remoteVersion}`, {
160
+ stdio: "pipe",
161
+ timeout: 120_000,
162
+ })
163
+ writeXpGateCache({ ts: Date.now(), localVersion: remoteVersion, remoteVersion, status: "current" })
164
+ return { action: "upgraded", localVersion, remoteVersion }
165
+ } catch (err) {
166
+ const msg = err instanceof Error ? err.message : String(err)
167
+ return { action: "error", localVersion, remoteVersion, error: msg }
168
+ }
169
+ }
170
+
171
+ // ── Tests ──
172
+
173
+ void describe("semverLt", () => {
174
+ void it("returns true when local < remote", () => {
175
+ assert.equal(semverLt("0.9.1", "0.9.2"), true)
176
+ })
177
+
178
+ void it("returns false when local > remote", () => {
179
+ assert.equal(semverLt("0.9.3", "0.9.2"), false)
180
+ })
181
+
182
+ void it("returns false when versions equal", () => {
183
+ assert.equal(semverLt("0.9.2", "0.9.2"), false)
184
+ })
185
+
186
+ void it("handles 'v' prefix", () => {
187
+ assert.equal(semverLt("v0.9.1", "v0.9.2"), true)
188
+ })
189
+
190
+ void it("handles mixed prefix", () => {
191
+ assert.equal(semverLt("v0.9.1", "0.9.2"), true)
192
+ })
193
+
194
+ void it("handles different segment counts", () => {
195
+ assert.equal(semverLt("0.9", "0.9.2"), true)
196
+ })
197
+
198
+ void it("handles major version bumps", () => {
199
+ assert.equal(semverLt("0.9.2", "1.0.0"), true)
200
+ })
201
+
202
+ void it("returns false for identical versions with v prefix", () => {
203
+ assert.equal(semverLt("v1.0.0", "1.0.0"), false)
204
+ })
205
+ })
206
+
207
+ void describe("checkXpGateUpdate — cache & upgrade", () => {
208
+ const fakeHome = join(tmpdir(), "xp-gate-upd-test-" + randomUUID())
209
+ const origHome = process.env.HOME
210
+
211
+ before(() => {
212
+ process.env.HOME = fakeHome
213
+ mkdirSync(join(fakeHome, ".xp-gate"), { recursive: true })
214
+ })
215
+
216
+ after(() => {
217
+ process.env.HOME = origHome
218
+ rmSync(fakeHome, { recursive: true, force: true })
219
+ })
220
+
221
+ void it("returns noop when no xp-gate installed locally", async () => {
222
+ const result = await checkXpGateUpdate()
223
+ assert.equal(result.action, "noop")
224
+ // localVersion may be null (no install) or the actual version (test env has global)
225
+ // Either is acceptable — the key behavior is noop, not the value
226
+ })
227
+
228
+ void it("returns noop when cache says current", async () => {
229
+ const cachePath = join(fakeHome, ".xp-gate", "xp-gate-version-check.json")
230
+ writeFileSync(cachePath, JSON.stringify({
231
+ ts: Date.now(),
232
+ localVersion: "0.9.2",
233
+ remoteVersion: "0.9.2",
234
+ status: "current",
235
+ }))
236
+
237
+ const result = await checkXpGateUpdate()
238
+ assert.equal(result.action, "noop")
239
+ assert.equal(result.remoteVersion, "0.9.2")
240
+ })
241
+
242
+ void it("handles cache expiry correctly", async () => {
243
+ const cachePath = join(fakeHome, ".xp-gate", "xp-gate-version-check.json")
244
+ writeFileSync(cachePath, JSON.stringify({
245
+ ts: Date.now() - 86_400_000 - 3600_000, // 25h old
246
+ localVersion: "0.9.1",
247
+ remoteVersion: "0.9.2",
248
+ }))
249
+
250
+ const result = await checkXpGateUpdate()
251
+ // Stale cache should be ignored — should check npm registry
252
+ // If network check fails, returns noop with localVersion if found
253
+ assert.equal(result.action, "noop")
254
+ })
255
+ })
@@ -1,8 +1,8 @@
1
1
  import { tool } from "@opencode-ai/plugin"
2
2
  import { z } from "zod"
3
- import { exec } from "child_process"
4
- import { promisify } from "util"
5
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"
3
+ import { exec, execSync } from "node:child_process"
4
+ import { promisify } from "node:util"
5
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs"
6
6
  import { join } from "node:path"
7
7
  import { homedir } from "node:os"
8
8
 
@@ -13,6 +13,158 @@ interface OpenCodePluginInput {
13
13
  $: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<{ text(): Promise<string> }>
14
14
  }
15
15
 
16
+ // ── Constants ──
17
+
18
+ const CACHE_TTL_MS = 86_400_000 // 24h
19
+ const FETCH_TIMEOUT_MS = 5_000
20
+
21
+ const XP_GATE_NPM_PKG = "@boyingliu01/xp-gate"
22
+ const XP_GATE_CACHE_FILE = join(homedir(), ".xp-gate", "xp-gate-version-check.json")
23
+ const XP_GATE_REGISTRY_URL = `https://registry.npmjs.org/-/package/${encodeURIComponent(XP_GATE_NPM_PKG)}/dist-tags`
24
+
25
+ const OPENCODE_PLUGIN_REGISTRY = "https://registry.npmjs.org/-/package/@boyingliu01%2Fopencode-plugin/dist-tags"
26
+ const OPENCODE_CACHE_FILE = join(homedir(), ".xp-gate", "opencode-plugin-version-check.json")
27
+
28
+ let checked = false
29
+
30
+ // ── Utilities ──
31
+
32
+ function semverLt(a: string, b: string): boolean {
33
+ const pa = a.replace(/^v/, "").split(".").map(Number)
34
+ const pb = b.replace(/^v/, "").split(".").map(Number)
35
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
36
+ const na = pa[i] ?? 0
37
+ const nb = pb[i] ?? 0
38
+ if (na !== nb) return na < nb
39
+ }
40
+ return false
41
+ }
42
+
43
+ async function fetchNpmLatestVersion(url: string): Promise<string | null> {
44
+ const controller = new AbortController()
45
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
46
+ try {
47
+ const response = await fetch(url, { signal: controller.signal })
48
+ if (!response.ok) return null
49
+ const data: Record<string, unknown> = await response.json()
50
+ return typeof data.latest === "string" ? data.latest : null
51
+ } catch {
52
+ return null
53
+ } finally {
54
+ clearTimeout(timer)
55
+ }
56
+ }
57
+
58
+ function readCache(file: string): { ts: number; remoteVersion: string; status?: string } | null {
59
+ try {
60
+ if (!existsSync(file)) return null
61
+ const raw = readFileSync(file, "utf8")
62
+ const data = JSON.parse(raw)
63
+ if (Date.now() - data.ts < CACHE_TTL_MS && data.remoteVersion) return data
64
+ return null
65
+ } catch {
66
+ return null
67
+ }
68
+ }
69
+
70
+ function writeCache(file: string, data: object): void {
71
+ try {
72
+ mkdirSync(join(homedir(), ".xp-gate"), { recursive: true })
73
+ writeFileSync(file + ".tmp", JSON.stringify(data), "utf8")
74
+ try { rmSync(file) } catch {}
75
+ writeFileSync(file, readFileSync(file + ".tmp", "utf8"), "utf8")
76
+ try { rmSync(file + ".tmp") } catch {}
77
+ } catch {
78
+ // silent
79
+ }
80
+ }
81
+
82
+ // ── XP-Gate npm package auto-update ──
83
+
84
+ type UpgradeResult = {
85
+ action: "noop" | "upgraded" | "error"
86
+ localVersion: string | null
87
+ remoteVersion: string | null
88
+ error?: string
89
+ }
90
+
91
+ function getLocalXpGateVersion(): string | null {
92
+ try {
93
+ const globalRoot = execSync("npm root -g", { encoding: "utf8" }).trim()
94
+ const pkg = JSON.parse(readFileSync(join(globalRoot, XP_GATE_NPM_PKG, "package.json"), "utf8"))
95
+ return pkg.version || null
96
+ } catch {
97
+ return null
98
+ }
99
+ }
100
+
101
+ async function checkXpGateUpdate(): Promise<UpgradeResult> {
102
+ const cached = readCache(XP_GATE_CACHE_FILE)
103
+ if (cached?.status === "current" && cached.remoteVersion) {
104
+ return { action: "noop", localVersion: cached.remoteVersion, remoteVersion: cached.remoteVersion }
105
+ }
106
+
107
+ const localVersion = getLocalXpGateVersion()
108
+ if (!localVersion) return { action: "noop", localVersion: null, remoteVersion: null }
109
+
110
+ const remoteVersion = await fetchNpmLatestVersion(XP_GATE_REGISTRY_URL)
111
+ if (!remoteVersion) return { action: "noop", localVersion, remoteVersion: null }
112
+
113
+ if (!semverLt(localVersion, remoteVersion)) {
114
+ writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion, remoteVersion, status: "current" })
115
+ return { action: "noop", localVersion, remoteVersion }
116
+ }
117
+
118
+ writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion, remoteVersion })
119
+ try {
120
+ execSync(`npm install -g ${XP_GATE_NPM_PKG}@${remoteVersion}`, { stdio: "pipe", timeout: 120_000 })
121
+ writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion: remoteVersion, remoteVersion, status: "current" })
122
+ return { action: "upgraded", localVersion, remoteVersion }
123
+ } catch (err) {
124
+ const msg = err instanceof Error ? err.message : String(err)
125
+ return { action: "error", localVersion, remoteVersion, error: msg }
126
+ }
127
+ }
128
+
129
+ // ── OpenCode plugin version check (notification only) ──
130
+
131
+ async function checkPluginUpdate(pluginDir: string): Promise<void> {
132
+ const cached = readCache(OPENCODE_CACHE_FILE)
133
+ if (cached?.status === "current" && cached.remoteVersion) return
134
+
135
+ let localVersion = ""
136
+ try {
137
+ const pkg = JSON.parse(readFileSync(join(pluginDir, "package.json"), "utf8"))
138
+ localVersion = pkg.version || ""
139
+ } catch {
140
+ return
141
+ }
142
+
143
+ const remoteVersion = await fetchNpmLatestVersion(OPENCODE_PLUGIN_REGISTRY)
144
+ if (!remoteVersion) return
145
+
146
+ if (remoteVersion && localVersion && semverLt(localVersion, remoteVersion)) {
147
+ writeCache(OPENCODE_CACHE_FILE, { ts: Date.now(), localVersion, remoteVersion })
148
+ } else if (remoteVersion && localVersion) {
149
+ writeCache(OPENCODE_CACHE_FILE, { ts: Date.now(), localVersion, remoteVersion, status: "current" })
150
+ }
151
+ }
152
+
153
+ // ── Combined background check (runs once on first chat.message) ──
154
+
155
+ async function runBackgroundUpdates(pluginDir: string): Promise<string | null> {
156
+ const result = await checkXpGateUpdate()
157
+ await checkPluginUpdate(pluginDir)
158
+
159
+ if (result.action === "upgraded") {
160
+ return `[XP-Gate] Auto-upgraded from v${result.localVersion} to v${result.remoteVersion}`
161
+ }
162
+ if (result.action === "error") {
163
+ return `[XP-Gate] Upgrade check: v${result.remoteVersion} available (auto-upgrade failed: ${result.error})`
164
+ }
165
+ return null
166
+ }
167
+
16
168
  async function runCmd(cmd: string, cwd: string): Promise<string> {
17
169
  try {
18
170
  const { stdout } = await execAsync(cmd, { cwd, timeout: 30000 })
@@ -49,77 +201,6 @@ async function getUpgradeSuggestion(cwd: string): Promise<string> {
49
201
  }
50
202
  }
51
203
 
52
- // ── Auto-update check for opencode-plugin ──
53
-
54
- const CACHE_TTL_MS = 86_400_000
55
- const NPM_REGISTRY_URL = "https://registry.npmjs.org/-/package/@boyingliu01%2Fopencode-plugin/dist-tags"
56
- const FETCH_TIMEOUT_MS = 5_000
57
- const CACHE_FILE = join(homedir(), ".xp-gate", "opencode-plugin-version-check.json")
58
-
59
- let checked = false
60
- let checkInFlight: Promise<void> | null = null
61
-
62
- function semverLt(a: string, b: string): boolean {
63
- const pa = a.replace(/^v/, "").split(".").map(Number)
64
- const pb = b.replace(/^v/, "").split(".").map(Number)
65
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
66
- const na = pa[i] ?? 0
67
- const nb = pb[i] ?? 0
68
- if (na !== nb) return na < nb
69
- }
70
- return false
71
- }
72
-
73
- async function checkPluginUpdate(pluginDir: string): Promise<void> {
74
- if (checkInFlight) return
75
-
76
- checkInFlight = (async () => {
77
- try {
78
- mkdirSync(join(homedir(), ".xp-gate"), { recursive: true })
79
-
80
- if (existsSync(CACHE_FILE)) {
81
- const cached = JSON.parse(readFileSync(CACHE_FILE, "utf8"))
82
- if (Date.now() - cached.ts < CACHE_TTL_MS) return
83
- }
84
-
85
- let localVersion = ""
86
- try {
87
- const pkg = JSON.parse(readFileSync(join(pluginDir, "package.json"), "utf8"))
88
- localVersion = pkg.version || ""
89
- } catch {
90
- return
91
- }
92
-
93
- const controller = new AbortController()
94
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
95
- try {
96
- const response = await fetch(NPM_REGISTRY_URL, { signal: controller.signal })
97
- if (!response.ok) return
98
- const data: Record<string, unknown> = await response.json()
99
- const remoteVersion = String(data.latest || "")
100
-
101
- if (remoteVersion && localVersion && semverLt(localVersion, remoteVersion)) {
102
- writeFileSync(CACHE_FILE, JSON.stringify({ ts: Date.now(), localVersion, remoteVersion }))
103
- process.stderr.write(
104
- `[XP-Gate] New opencode-plugin version v${remoteVersion} available (you have v${localVersion})\n` +
105
- `[XP-Gate] Update with: cd ~/.config/opencode && npm update @boyingliu01/opencode-plugin\n`
106
- )
107
- } else if (remoteVersion && localVersion) {
108
- // Cache "up to date" to avoid re-fetching every session
109
- writeFileSync(CACHE_FILE, JSON.stringify({ ts: Date.now(), localVersion, remoteVersion, status: "current" }))
110
- }
111
- } finally {
112
- clearTimeout(timer)
113
- }
114
- } catch {
115
- // All errors silently ignored
116
- }
117
- })()
118
-
119
- await checkInFlight
120
- checkInFlight = null
121
- }
122
-
123
204
  // ── Plugin definition ──
124
205
 
125
206
  export const XpGatePlugin = async (input: OpenCodePluginInput) => {
@@ -184,7 +265,9 @@ export const XpGatePlugin = async (input: OpenCodePluginInput) => {
184
265
  "chat.message": async (_input: { message: string }) => {
185
266
  if (!checked) {
186
267
  checked = true
187
- checkPluginUpdate(directory).catch((_err) => { /* silent: non-critical background check */ })
268
+ runBackgroundUpdates(directory).then((msg) => {
269
+ if (msg) process.stderr.write(`${msg}\n`)
270
+ })
188
271
  }
189
272
  return { action: "continue" }
190
273
  },
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
- "description": "XP-Gate quality gates + AI workflow skills for OpenCode",
6
+ "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/boyingliu01/xp-gate",
@@ -13,8 +13,19 @@
13
13
  "registry": "https://registry.npmjs.org",
14
14
  "access": "public"
15
15
  },
16
+ "exports": {
17
+ ".": {
18
+ "import": "./index.ts",
19
+ "types": "./index.ts"
20
+ },
21
+ "./tui": {
22
+ "import": "./tui-plugin.ts",
23
+ "types": "./tui-plugin.ts"
24
+ }
25
+ },
16
26
  "files": [
17
27
  "index.ts",
28
+ "tui-plugin.ts",
18
29
  "skills/",
19
30
  "tsconfig.json",
20
31
  "README.md"
@@ -24,7 +35,7 @@
24
35
  "check": "tsc --noEmit"
25
36
  },
26
37
  "dependencies": {
27
- "@opencode-ai/plugin": "^1.15.0"
38
+ "@opencode-ai/plugin": "^1.17.8"
28
39
  },
29
40
  "devDependencies": {
30
41
  "typescript": "^5.4.0"
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-06-18
4
- **Commit:** 33cf3f2
4
+ **Commit:** b67fc85
5
5
  **Branch:** main
6
- **Version:** 0.9.1.0
6
+ **Version:** 0.9.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-06-18
4
- **Commit:** 33cf3f2
4
+ **Commit:** b67fc85
5
5
  **Branch:** main
6
- **Version:** 0.9.1.0
6
+ **Version:** 0.9.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP. Phase 2 default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE in Phase 1: design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-06-18
4
- **Commit:** 33cf3f2
4
+ **Commit:** b67fc85
5
5
  **Branch:** main
6
- **Version:** 0.9.1.0
6
+ **Version:** 0.9.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -10,6 +10,6 @@
10
10
  "declaration": false,
11
11
  "noEmit": true
12
12
  },
13
- "include": ["index.ts"],
13
+ "include": ["index.ts", "tui-plugin.ts"],
14
14
  "exclude": ["node_modules", "dist", "skills"]
15
15
  }