@boyingliu01/opencode-plugin 0.8.15 → 0.8.17
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/index.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { tool } from "@opencode-ai/plugin"
|
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import { exec } from "child_process"
|
|
4
4
|
import { promisify } from "util"
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"
|
|
6
|
+
import { join } from "node:path"
|
|
7
|
+
import { homedir } from "node:os"
|
|
5
8
|
|
|
6
9
|
const execAsync = promisify(exec)
|
|
7
10
|
|
|
@@ -10,10 +13,6 @@ interface OpenCodePluginInput {
|
|
|
10
13
|
$: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<{ text(): Promise<string> }>
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
/**
|
|
14
|
-
* Run a shell command via async exec, returning stdout or error message.
|
|
15
|
-
* Never throws — returns error string on failure.
|
|
16
|
-
*/
|
|
17
16
|
async function runCmd(cmd: string, cwd: string): Promise<string> {
|
|
18
17
|
try {
|
|
19
18
|
const { stdout } = await execAsync(cmd, { cwd, timeout: 30000 })
|
|
@@ -26,22 +25,15 @@ async function runCmd(cmd: string, cwd: string): Promise<string> {
|
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
/**
|
|
30
|
-
* Check for xp-gate CLI availability and run a command via exec.
|
|
31
|
-
*/
|
|
32
28
|
async function runXpGate(subcommand: string, cwd: string): Promise<string> {
|
|
33
|
-
// Check if xp-gate is on PATH
|
|
34
29
|
try {
|
|
35
30
|
await execAsync("command -v xp-gate", { cwd })
|
|
36
31
|
} catch {
|
|
37
|
-
return ""
|
|
32
|
+
return ""
|
|
38
33
|
}
|
|
39
34
|
return runCmd(`xp-gate ${subcommand}`, cwd)
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
/**
|
|
43
|
-
* Check for a newer xp-gate version (non-blocking, advisory only).
|
|
44
|
-
*/
|
|
45
37
|
async function getUpgradeSuggestion(cwd: string): Promise<string> {
|
|
46
38
|
try {
|
|
47
39
|
const result = await runXpGate("upgrade --preview", cwd)
|
|
@@ -57,6 +49,79 @@ async function getUpgradeSuggestion(cwd: string): Promise<string> {
|
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
|
|
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
|
+
// ── Plugin definition ──
|
|
124
|
+
|
|
60
125
|
export const XpGatePlugin = async (input: OpenCodePluginInput) => {
|
|
61
126
|
const { directory } = input
|
|
62
127
|
|
|
@@ -77,7 +142,6 @@ export const XpGatePlugin = async (input: OpenCodePluginInput) => {
|
|
|
77
142
|
const upgrade = await getUpgradeSuggestion(cwd)
|
|
78
143
|
return upgrade ? `${result}\n${upgrade}` : result
|
|
79
144
|
}
|
|
80
|
-
// Fallback: invoke xp-gate source directly via node
|
|
81
145
|
const cmd = `node ${directory}/src/npm-package/bin/xp-gate.js check "${target}"${gatesFlag}`
|
|
82
146
|
return runCmd(cmd, cwd)
|
|
83
147
|
},
|
|
@@ -95,7 +159,6 @@ export const XpGatePlugin = async (input: OpenCodePluginInput) => {
|
|
|
95
159
|
const upgrade = await getUpgradeSuggestion(cwd)
|
|
96
160
|
return upgrade ? `${result}\n${upgrade}` : result
|
|
97
161
|
}
|
|
98
|
-
// Fallback: npx tsx on the principles source
|
|
99
162
|
const cmd = `npx -y tsx ${directory}/src/principles/index.ts --files "${target}" --format console`
|
|
100
163
|
return runCmd(cmd, cwd)
|
|
101
164
|
},
|
|
@@ -113,12 +176,18 @@ export const XpGatePlugin = async (input: OpenCodePluginInput) => {
|
|
|
113
176
|
const upgrade = await getUpgradeSuggestion(cwd)
|
|
114
177
|
return upgrade ? `${result}\n${upgrade}` : result
|
|
115
178
|
}
|
|
116
|
-
// Fallback: @archlinter/cli directly
|
|
117
179
|
const cmd = `npx -y @archlinter/cli scan . --config ${config}`
|
|
118
180
|
return runCmd(cmd, cwd)
|
|
119
181
|
},
|
|
120
182
|
}),
|
|
121
183
|
},
|
|
184
|
+
"chat.message": async (_input: { message: string }) => {
|
|
185
|
+
if (!checked) {
|
|
186
|
+
checked = true
|
|
187
|
+
checkPluginUpdate(directory).catch((_err) => { /* silent: non-critical background check */ })
|
|
188
|
+
}
|
|
189
|
+
return { action: "continue" }
|
|
190
|
+
},
|
|
122
191
|
}
|
|
123
192
|
}
|
|
124
193
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-06-16
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** d3a0242
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.8.
|
|
6
|
+
**Version:** 0.8.17.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-16
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** d3a0242
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.8.
|
|
6
|
+
**Version:** 0.8.17.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.
|
|
@@ -406,7 +406,7 @@ Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW
|
|
|
406
406
|
- Phase 2 BUILD 仍然执行完整 TDD + 盲评 + 验证
|
|
407
407
|
|
|
408
408
|
### Phase 0: THINK(需求探索与设计)
|
|
409
|
-
- **Subagent dispatch**: orchestrator 通过 `task(category="
|
|
409
|
+
- **Subagent dispatch**: orchestrator 通过 `task(category="unspecified-high", load_skills=[])` 启动独立 session
|
|
410
410
|
- 输入: Phase -1 summary(worktree 路径)+ 用户原始需求
|
|
411
411
|
- 输出: 结构化设计文档 → 直接作为 Phase 1 PLAN 的输入
|
|
412
412
|
- **HARD-GATE**: 设计未批准 → 不可进入实现
|
|
@@ -655,7 +655,7 @@ Phase 2 第一步必须执行 DELPHI-GATE 检查。没有 delphi-review APPROVED
|
|
|
655
655
|
|-------|------|:---------:|----------|-------------|--------|
|
|
656
656
|
| -1 | ISOLATE | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
657
657
|
| -0.5 | AUTO-ESTIMATE | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
658
|
-
| 0 | THINK | ✅ | `
|
|
658
|
+
| 0 | THINK | ✅ | `unspecified-high` | `[]` | subagent |
|
|
659
659
|
| 1 | PLAN | ✅ | `deep` | `["autoplan", "delphi-review", "to-issues"]` | subagent |
|
|
660
660
|
| 2 | BUILD | ✅(已有) | ralph-loop | `["test-driven-development"]` | subagent |
|
|
661
661
|
| 3 | REVIEW | ✅ | `deep` | `["delphi-review", "test-specification-alignment"]` | subagent |
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-06-16
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** d3a0242
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.8.
|
|
6
|
+
**Version:** 0.8.17.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|