@andyqiu/teamkit 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # teamkit
2
+
3
+ > **teamkit — Team-level shared OpenCode plugin for KH integration and beyond**
4
+
5
+ teamkit 是面向整个项目组的 [OpenCode](https://opencode.ai) 共享插件。它把团队知识库(KH)的深度集成能力从 CodeForge(程序员专属工具)中剥离出来,让策划、美术、数据等所有角色只需一行安装命令,就能在自己的 AI Agent 里自动获得:
6
+
7
+ - **KH 触发词检测 + 自动召回**:遇到"怎么部署 / 以前 / 为什么"等 30+ 触发词时自动搜索团队知识库
8
+ - **对话自动沉淀**:修完 bug / 做决策 / 踩到坑,自动写入 KH,全团队共享
9
+ - **长对话效果保证**:三层机制(working_memory 注入 / compaction 兜底 / AGENTS.md marker)确保上下文不丢失
10
+
11
+ > 📖 完整产品需求与设计决策见 **[docs/prd.md](docs/prd.md)**
12
+
13
+ ---
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ # 设计目标命令(包发布后生效)
19
+ npx @andyqiu/teamkit install --global
20
+ ```
21
+
22
+ 安装脚本做三件事:
23
+ 1. 把 teamkit plugin entry 注册到 OpenCode 全局配置
24
+ 2. 写入 `teamkit.json` 配置文件
25
+ 3. 在 `AGENTS.md` 注入 `<!-- knowledge-hub:start/end -->` 行为约束块
26
+
27
+ ---
28
+
29
+ ## 与 CodeForge 的关系
30
+
31
+ | | CodeForge | teamkit |
32
+ |---|---|---|
33
+ | 目标用户 | 程序员 | 全团队(程序 / 策划 / 美术 / 数据) |
34
+ | 形态 | agent + plugin + command + tool | **仅 plugin**(纯后台,无 agent) |
35
+ | KH 集成 | ✅(消费 teamkit 提供的能力) | ✅(提供方) |
36
+ | 安装依赖 | 包级零 import;Phase 2 后 KH 能力运行时依赖 teamkit | 不依赖 CodeForge |
37
+
38
+ 装了 teamkit 之后,CodeForge 通过标准 working_memory topic 协议无缝获得 KH 能力。**包级零 import 依赖**;Phase 2 切换后 CodeForge 的 KH 能力在运行时依赖 teamkit 提供。
39
+
40
+ ---
41
+
42
+ ## 目录结构
43
+
44
+ ```
45
+ teamkit/
46
+ ├── docs/ # PRD / 架构 / ADR / 兼容矩阵 / 验收套件
47
+ ├── plugins/ # OpenCode plugin .ts 文件(Phase 1 搬迁)
48
+ ├── lib/ # 共享工具库(Phase 1 搬迁)
49
+ ├── context-templates/# team-conventions.md 等注入模板(Phase 1)
50
+ ├── scripts/ # merge-agents-md.mjs / capability-detect / doctor
51
+ ├── tests/ # 单元测试 + 验收套件
52
+ ├── bin/ # teamkit / teamkit-doctor CLI
53
+ └── install.sh # 一键安装(全平台)
54
+ ```
55
+
56
+ > ⚠️ **当前为 Phase 0**:代码目录均为占位(`.gitkeep`),Phase 1 才正式搬迁代码。
57
+
58
+ ---
59
+
60
+ ## Roadmap
61
+
62
+ | 阶段 | 内容 |
63
+ |---|---|
64
+ | **Phase 0**(当前)| 仓库骨架 + 完整文档 + ADR 占位 |
65
+ | **Phase 1** | 整文件搬迁 **4 plugin**(kh-reminder / compaction-guard / kh-auto-context / auto-learning)+ 4 lib + capability-detect + doctor + install.sh |
66
+ | **Phase 2** | 与 CodeForge 仓库协同切换(CodeForge 退化为消费方) |
67
+ | **Phase 3** | CI matrix + verification suite 自动化 |
68
+
69
+ ---
70
+
71
+ ## License
72
+
73
+ MIT © Andy
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: teamkit-git
3
+ description: 团队 Git 操作专属子代理。任意角色看到 git 触发词时通过 Task tool 自动调用。
4
+ hidden: true
5
+ tools:
6
+ bash: true
7
+ read: true
8
+ write: false
9
+ edit: false
10
+ webfetch: false
11
+ ---
12
+
13
+ <!-- ADR: git-subagent-architecture -->
14
+
15
+ 你是 teamkit-git 子代理,专门负责安全地代用户操作 git。
16
+
17
+ ## 必须遵守的硬约束
18
+
19
+ 1. 永远先 `git rev-parse --show-toplevel` 确认仓库根
20
+ 2. 永远先 `git status` 和 `git branch --show-current` 采集现状
21
+ 3. 在 main 分支阻止 commit,引导新建分支
22
+ 4. git add 前 grep 敏感模式(AWS_KEY / PRIVATE_KEY / password= / token= / .env)
23
+ 5. git add 前列出大文件(>lfsThresholdMB),命中则拒绝直到用户确认
24
+ 6. **绝不**自动跑:reset --hard / push --force / branch -D / clean -fd / rebase main / 删远端分支
25
+ 7. **绝不**自动解决 merge 冲突——切冲突模式让用户拍板
26
+
27
+ ## 6 阶段流程
28
+
29
+ 1. 识别意图(提交/推送/分支/合并/撤销)
30
+ 2. 采集上下文(status / branch / diff --stat)
31
+ 3. 读规范(先读 `.teamkit/git-rules.json`,再 smart_search "本项目 git 规范")
32
+ 4. 生成方案(commit message 草稿 / 分支名建议)
33
+ 5. 用户确认
34
+ 6. 执行 + 返回结果摘要
35
+
36
+ ## 输出格式
37
+
38
+ 每次结束返回给主 agent:
39
+ - 执行的命令列表
40
+ - commit hash(如有)
41
+ - 当前分支 + 远端状态
42
+ - 用户后续建议(如"建议拉一下 main")
package/bin/.gitkeep ADDED
File without changes
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/teamkit-doctor.mjs — teamkit 诊断命令
4
+ *
5
+ * 诊断覆盖(PRD §4.5 R15):
6
+ * 1. opencode 版本
7
+ * 2. experimental.session.compacting hook 可用性
8
+ * 3. KH MCP 可达性(超时 3s)
9
+ * 4. AGENTS.md marker 状态(<!-- knowledge-hub:start/end --> + <!-- teamkit-git:start/end -->)
10
+ * 5. Git Agent 加载状态(agents/teamkit-git.md + hidden: true)
11
+ * 6. .teamkit/git-rules.json 合法性
12
+ * 7. KH 静默失效诊断(CodeForge 未装 teamkit 的场景)
13
+ *
14
+ * ADR: opencode-version-compatibility
15
+ */
16
+
17
+ import { readFileSync, existsSync } from "node:fs"
18
+ import { join, resolve } from "node:path"
19
+ import { homedir } from "node:os"
20
+ import { execSync } from "node:child_process"
21
+ import { createRequire } from "node:module"
22
+ import process from "node:process"
23
+
24
+ const HOME = homedir()
25
+ const OPENCODE_CONFIG_DIR = join(HOME, ".config", "opencode")
26
+ const OPENCODE_JSON_PATH = join(OPENCODE_CONFIG_DIR, "opencode.json")
27
+ const AGENTS_MD_PATH = join(OPENCODE_CONFIG_DIR, "AGENTS.md")
28
+
29
+ // 当前包根目录(从 bin/ 向上一级)
30
+ const PKG_ROOT = resolve(new URL(".", import.meta.url).pathname, "..")
31
+
32
+ const require = createRequire(import.meta.url)
33
+
34
+ // ─── 工具函数 ──────────────────────────────────────────────────────────────────
35
+
36
+ function ok(label, detail = "") {
37
+ process.stdout.write(` ✅ ${label}${detail ? " " + detail : ""}\n`)
38
+ }
39
+
40
+ function fail(label, detail = "") {
41
+ process.stdout.write(` ❌ ${label}${detail ? " " + detail : ""}\n`)
42
+ }
43
+
44
+ function warn(label, detail = "") {
45
+ process.stdout.write(` ⚠️ ${label}${detail ? " " + detail : ""}\n`)
46
+ }
47
+
48
+ function section(title) {
49
+ process.stdout.write(`\n── ${title} ──\n`)
50
+ }
51
+
52
+ // ─── 诊断 1:opencode 版本 ────────────────────────────────────────────────────
53
+ function diagOpenCodeVersion() {
54
+ section("opencode 版本")
55
+ try {
56
+ const ver = execSync("opencode --version", { stdio: "pipe", timeout: 5000 })
57
+ .toString()
58
+ .trim()
59
+ ok("opencode CLI 可用", ver)
60
+ return ver
61
+ } catch {
62
+ // 尝试读 package.json
63
+ const pkgPaths = [
64
+ join(OPENCODE_CONFIG_DIR, "package.json"),
65
+ join(HOME, ".local", "share", "opencode", "package.json"),
66
+ ]
67
+ for (const p of pkgPaths) {
68
+ if (existsSync(p)) {
69
+ try {
70
+ const pkg = JSON.parse(readFileSync(p, "utf8"))
71
+ ok("opencode 版本(package.json)", `v${pkg.version}`)
72
+ return pkg.version
73
+ } catch { /* 继续 */ }
74
+ }
75
+ }
76
+ fail("opencode CLI 不可用", "请安装 opencode:https://opencode.ai")
77
+ return null
78
+ }
79
+ }
80
+
81
+ // ─── 诊断 2:experimental.session.compacting hook ────────────────────────────
82
+ function diagSessionCompacting() {
83
+ section("experimental.session.compacting hook")
84
+ try {
85
+ const mod = require("@opencode-ai/plugin")
86
+
87
+ const hasHook = (
88
+ mod?.experimental?.session?.compacting !== undefined ||
89
+ (typeof mod?.hooks?.probe === "function" && (() => { try { mod.hooks.probe("experimental.session.compacting"); return true } catch { return false } })()) ||
90
+ (Array.isArray(mod?.HOOK_NAMES ?? mod?.SUPPORTED_HOOKS ?? mod?.hookNames) && (mod?.HOOK_NAMES ?? mod?.SUPPORTED_HOOKS ?? mod?.hookNames).includes("experimental.session.compacting"))
91
+ )
92
+
93
+ if (hasHook) {
94
+ ok("experimental.session.compacting hook 可用")
95
+ } else {
96
+ // 包存在但探测不到精确字段 → Phase 1 乐观
97
+ warn("experimental.session.compacting 无法精确探测", "Phase 1 乐观通过(见 ADR opencode-version-compatibility)")
98
+ }
99
+ } catch {
100
+ // 包不存在,用 CLI 版本推断
101
+ try {
102
+ execSync("opencode --version", { stdio: "pipe", timeout: 5000 })
103
+ warn("@opencode-ai/plugin 未安装", "opencode CLI 可用,推断 hook 可用(Phase 1)")
104
+ } catch {
105
+ fail(
106
+ "experimental.session.compacting hook 不可用",
107
+ "请升级 OpenCode 至支持 experimental.session.compacting hook 的版本"
108
+ )
109
+ }
110
+ }
111
+ }
112
+
113
+ // ─── 诊断 3:KH MCP 可达性 ────────────────────────────────────────────────────
114
+ async function diagKhMcp() {
115
+ section("KH MCP 可达性")
116
+ // 通过调用一次 smart_search 来探测 KH MCP(超时 3s)
117
+ // Phase 1:MCP 通过 opencode plugin API 暴露,独立进程中无法直接调用
118
+ // 改为检测 MCP 配置是否存在
119
+ try {
120
+ const cfg = existsSync(OPENCODE_JSON_PATH)
121
+ ? JSON.parse(readFileSync(OPENCODE_JSON_PATH, "utf8"))
122
+ : {}
123
+
124
+ const mcpServers = cfg?.mcp ?? cfg?.mcpServers ?? {}
125
+ const hasKhMcp = Object.keys(mcpServers).some(
126
+ (k) => k.toLowerCase().includes("knowledge") || k.toLowerCase().includes("kh")
127
+ )
128
+
129
+ if (hasKhMcp) {
130
+ ok("KH MCP 配置已找到", `(server: ${Object.keys(mcpServers).find((k) => k.toLowerCase().includes("knowledge") || k.toLowerCase().includes("kh"))})`)
131
+ } else {
132
+ warn("KH MCP 配置未找到", "如需 KH 功能,请配置 knowledge-hub MCP server")
133
+ }
134
+ } catch (err) {
135
+ fail("KH MCP 配置读取失败", err.message)
136
+ }
137
+ }
138
+
139
+ // ─── 诊断 4:AGENTS.md marker 状态 ───────────────────────────────────────────
140
+ function diagAgentsMdMarkers() {
141
+ section("AGENTS.md marker 状态")
142
+
143
+ if (!existsSync(AGENTS_MD_PATH)) {
144
+ fail("~/.config/opencode/AGENTS.md 不存在", "运行 install.sh 创建")
145
+ return
146
+ }
147
+
148
+ let content
149
+ try {
150
+ content = readFileSync(AGENTS_MD_PATH, "utf8")
151
+ } catch (err) {
152
+ fail("AGENTS.md 读取失败", err.message)
153
+ return
154
+ }
155
+
156
+ // 检测 knowledge-hub markers
157
+ const hasKhStart = content.includes("<!-- knowledge-hub:start -->")
158
+ const hasKhEnd = content.includes("<!-- knowledge-hub:end -->")
159
+ if (hasKhStart && hasKhEnd) {
160
+ ok("<!-- knowledge-hub:start/end --> 块存在")
161
+ } else if (hasKhStart || hasKhEnd) {
162
+ fail("<!-- knowledge-hub --> 块不完整", `start=${hasKhStart} end=${hasKhEnd},请重新运行 install.sh`)
163
+ } else {
164
+ fail("<!-- knowledge-hub:start/end --> 块缺失", "运行 install.sh 注入")
165
+ }
166
+
167
+ // 检测 teamkit-git markers
168
+ const hasGitStart = content.includes("<!-- teamkit-git:start -->")
169
+ const hasGitEnd = content.includes("<!-- teamkit-git:end -->")
170
+ if (hasGitStart && hasGitEnd) {
171
+ ok("<!-- teamkit-git:start/end --> 块存在")
172
+ } else if (hasGitStart || hasGitEnd) {
173
+ fail("<!-- teamkit-git --> 块不完整", `start=${hasGitStart} end=${hasGitEnd},请重新运行 install.sh`)
174
+ } else {
175
+ fail("<!-- teamkit-git:start/end --> 块缺失", "运行 install.sh 注入")
176
+ }
177
+ }
178
+
179
+ // ─── 诊断 5:Git Agent 加载状态 ──────────────────────────────────────────────
180
+ function diagGitAgent() {
181
+ section("Git Agent 加载状态")
182
+
183
+ const agentPath = join(PKG_ROOT, "agents", "teamkit-git.md")
184
+ if (!existsSync(agentPath)) {
185
+ fail("agents/teamkit-git.md 不存在")
186
+ return
187
+ }
188
+
189
+ let content
190
+ try {
191
+ content = readFileSync(agentPath, "utf8")
192
+ } catch (err) {
193
+ fail("agents/teamkit-git.md 读取失败", err.message)
194
+ return
195
+ }
196
+
197
+ ok("agents/teamkit-git.md 存在")
198
+
199
+ // 检测 hidden: true(限定多行匹配,防止内容区误匹配)
200
+ if (/^hidden:\s*true\s*$/m.test(content)) {
201
+ ok("hidden: true 已配置")
202
+ } else {
203
+ fail("hidden: true 缺失", "teamkit-git subagent 应设置 hidden: true")
204
+ }
205
+ }
206
+
207
+ // ─── 诊断 6:git-rules.json 合法性(全局 + 项目级) ─────────────────────────
208
+ function diagGitRules() {
209
+ section("git-rules.json 合法性")
210
+
211
+ // 6a:全局配置
212
+ const globalRulesPath = join(HOME, ".config", "teamkit", "git-rules.json")
213
+ if (!existsSync(globalRulesPath)) {
214
+ warn("全局 git-rules.json 不存在", "~/.config/teamkit/git-rules.json — 将使用内置默认值(运行 install.sh 生成)")
215
+ } else {
216
+ let globalRules
217
+ try {
218
+ globalRules = JSON.parse(readFileSync(globalRulesPath, "utf8"))
219
+ ok(
220
+ "全局 git-rules.json 合法",
221
+ `messageStyle=${globalRules.messageStyle} language=${globalRules.messageLanguage}`,
222
+ )
223
+ } catch (err) {
224
+ fail("全局 git-rules.json JSON 解析失败", err.message)
225
+ }
226
+ }
227
+
228
+ // 6b:项目级配置
229
+ const rulesPath = join(process.cwd(), ".teamkit", "git-rules.json")
230
+ if (!existsSync(rulesPath)) {
231
+ warn(".teamkit/git-rules.json 不存在", "(可选)项目级未配置,将使用全局或内置默认值")
232
+ return
233
+ }
234
+
235
+ let rules
236
+ try {
237
+ rules = JSON.parse(readFileSync(rulesPath, "utf8"))
238
+ } catch (err) {
239
+ fail(".teamkit/git-rules.json JSON 解析失败", err.message)
240
+ return
241
+ }
242
+
243
+ // 检测必填字段(PRD §4.3 R11)
244
+ const required = ["branchPrefixes", "messageStyle", "messageLanguage", "maxFilesPerCommit", "lfsThresholdMB"]
245
+ const missing = required.filter((k) => rules[k] === undefined)
246
+
247
+ if (missing.length === 0) {
248
+ ok(".teamkit/git-rules.json 合法", `messageStyle=${rules.messageStyle} language=${rules.messageLanguage}`)
249
+ } else {
250
+ fail(".teamkit/git-rules.json 缺少必填字段", missing.join(", "))
251
+ }
252
+ }
253
+
254
+ // ─── 诊断 7:KH 静默失效诊断 ─────────────────────────────────────────────────
255
+ // R15 增强(PRD §4.5 R15):诊断 CodeForge 升级后 KH 静默失效
256
+ function diagKhSilentFailure() {
257
+ section("KH 静默失效诊断")
258
+
259
+ if (!existsSync(OPENCODE_JSON_PATH)) {
260
+ warn("opencode.json 不存在,跳过静默失效检测")
261
+ return
262
+ }
263
+
264
+ let cfg
265
+ try {
266
+ cfg = JSON.parse(readFileSync(OPENCODE_JSON_PATH, "utf8"))
267
+ } catch {
268
+ warn("opencode.json 读取失败,跳过静默失效检测")
269
+ return
270
+ }
271
+
272
+ const plugins = cfg?.plugins ?? []
273
+ const pluginNames = plugins.map((p) => (typeof p === "string" ? p : p?.name ?? ""))
274
+
275
+ const hasCodeforge = pluginNames.some((n) => n.includes("codeforge"))
276
+ const hasTeamkit = pluginNames.some((n) => n.includes("teamkit"))
277
+
278
+ if (hasCodeforge && !hasTeamkit) {
279
+ // R15 增强:CodeForge 存在但 teamkit 未安装
280
+ warn(
281
+ "检测到 CodeForge 但未安装 @andyqiu/teamkit,KH 集成将静默失效。",
282
+ "运行:npm i -g @andyqiu/teamkit"
283
+ )
284
+ } else if (hasTeamkit) {
285
+ ok("@andyqiu/teamkit 已注册到 opencode.json plugins")
286
+ // 检查 teamkit 是否排在首位
287
+ const teamkitIdx = pluginNames.findIndex((n) => n.includes("teamkit"))
288
+ const codeforgeIdx = pluginNames.findIndex((n) => n.includes("codeforge"))
289
+ if (codeforgeIdx !== -1 && teamkitIdx > codeforgeIdx) {
290
+ warn("@andyqiu/teamkit 应排在 plugins 数组首位(当前在 codeforge 之后)", "运行 install.sh 修复顺序")
291
+ } else {
292
+ ok("plugin load order 正确(teamkit 在 codeforge 之前)")
293
+ }
294
+ } else {
295
+ warn("opencode.json plugins 中未找到 @andyqiu/teamkit", "运行 install.sh 注册")
296
+ }
297
+ }
298
+
299
+ // ─── 主流程 ───────────────────────────────────────────────────────────────────
300
+ async function main() {
301
+ process.stdout.write("╔══════════════════════════════════════╗\n")
302
+ process.stdout.write("║ teamkit doctor — 系统诊断 ║\n")
303
+ process.stdout.write("╚══════════════════════════════════════╝\n")
304
+
305
+ diagOpenCodeVersion()
306
+ diagSessionCompacting()
307
+ await diagKhMcp()
308
+ diagAgentsMdMarkers()
309
+ diagGitAgent()
310
+ diagGitRules()
311
+ diagKhSilentFailure()
312
+
313
+ process.stdout.write("\n诊断完成。如有 ❌ 项,请按提示修复后重新运行。\n")
314
+ process.stdout.write("如需详细帮助:https://github.com/andyqiu/teamkit\n\n")
315
+ }
316
+
317
+ main().catch((err) => {
318
+ process.stderr.write(`teamkit doctor 内部错误: ${err.message}\n`)
319
+ process.exit(1)
320
+ })
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/teamkit-upgrade.mjs — teamkit upgrade 命令
4
+ *
5
+ * 流程:
6
+ * 1. 读取当前版本(package.json)
7
+ * 2. 用 npm view @andyqiu/teamkit version 查询最新版本(超时 10s)
8
+ * 3. 若已是最新:提示并退出
9
+ * 4. 若有新版:显示 "当前 vX.X.X → 最新 vY.Y.Y,确认升级?[Y/n]"
10
+ * 5. 用户确认后:执行 npm install -g @andyqiu/teamkit@latest
11
+ * 6. 升级成功后:提示运行 teamkit install 以重新注入 AGENTS.md 配置
12
+ */
13
+ import { createRequire } from 'node:module'
14
+ import { dirname } from 'node:path'
15
+ import { fileURLToPath } from 'node:url'
16
+ import { execFileSync, spawnSync } from 'node:child_process'
17
+ import * as readline from 'node:readline'
18
+ import process from 'node:process'
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url))
21
+ const require = createRequire(import.meta.url)
22
+ const pkg = require('../package.json')
23
+
24
+ const currentVersion = pkg.version
25
+ const pkgName = pkg.name // '@andyqiu/teamkit'
26
+
27
+ function compareSemver(a, b) {
28
+ const pa = a.split('.').map(Number)
29
+ const pb = b.split('.').map(Number)
30
+ for (let i = 0; i < 3; i++) {
31
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return 1
32
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return -1
33
+ }
34
+ return 0
35
+ }
36
+
37
+ // ── 1. 查询最新版本 ────────────────────────────────────────────────────────────
38
+
39
+ let latestVersion
40
+ try {
41
+ latestVersion = execFileSync('npm', ['view', pkgName, 'version'], {
42
+ timeout: 10000,
43
+ encoding: 'utf8'
44
+ }).trim()
45
+ } catch (err) {
46
+ console.error(`teamkit: 无法查询最新版本(网络问题或 npm registry 不可用)`)
47
+ console.error(`请手动升级:npm install -g ${pkgName}@latest`)
48
+ process.exit(1)
49
+ }
50
+
51
+ // ── 2. 比较版本 ───────────────────────────────────────────────────────────────
52
+
53
+ if (compareSemver(currentVersion, latestVersion) >= 0) {
54
+ console.log(`✅ 已是最新版本 v${currentVersion},无需升级。`)
55
+ process.exit(0)
56
+ }
57
+
58
+ // ── 3. 交互确认 ───────────────────────────────────────────────────────────────
59
+
60
+ console.log(`发现新版本:当前 v${currentVersion} → 最新 v${latestVersion}`)
61
+
62
+ const rl = readline.createInterface({
63
+ input: process.stdin,
64
+ output: process.stdout
65
+ })
66
+
67
+ rl.question('确认升级?[Y/n] ', (answer) => {
68
+ rl.close()
69
+
70
+ const confirmed = answer.trim() === '' || /^y(es)?$/i.test(answer.trim())
71
+ if (!confirmed) {
72
+ console.log('已取消升级。')
73
+ process.exit(0)
74
+ }
75
+
76
+ // ── 4. 执行升级 ─────────────────────────────────────────────────────────────
77
+
78
+ console.log(`正在升级 ${pkgName}@latest ...`)
79
+ const result = spawnSync('npm', ['install', '-g', `${pkgName}@latest`], {
80
+ stdio: 'inherit'
81
+ })
82
+
83
+ if (result.status !== 0) {
84
+ console.error(`teamkit: 升级失败(npm exit code: ${result.status})`)
85
+ console.error(`请手动运行:npm install -g ${pkgName}@latest`)
86
+ process.exit(result.status ?? 1)
87
+ }
88
+
89
+ // ── 5. 升级后提示 ────────────────────────────────────────────────────────────
90
+
91
+ console.log(`✅ 升级完成!建议重新运行 teamkit install 以更新 AGENTS.md 配置。`)
92
+ process.exit(0)
93
+ })
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/teamkit.mjs — teamkit CLI 主入口
4
+ *
5
+ * 用法:
6
+ * teamkit version / -v / --version 显示版本
7
+ * teamkit doctor 运行系统诊断
8
+ * teamkit install [--dry-run] 安装/重新安装 teamkit 到 opencode
9
+ * teamkit uninstall 卸载 teamkit
10
+ * teamkit upgrade 升级到最新版本
11
+ * teamkit help / -h / --help 显示帮助
12
+ */
13
+ import { createRequire } from 'node:module'
14
+ import { resolve, dirname } from 'node:path'
15
+ import { fileURLToPath } from 'node:url'
16
+ import { spawnSync } from 'node:child_process'
17
+ import { existsSync } from 'node:fs'
18
+ import process from 'node:process'
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url))
21
+ const require = createRequire(import.meta.url)
22
+ const pkg = require('../package.json')
23
+
24
+ /** spawnSync 结果统一退出处理,避免 null status 被当成成功 */
25
+ function exitFromSpawn(result) {
26
+ if (result.error) {
27
+ console.error(`teamkit: 子命令启动失败:${result.error.message}`)
28
+ process.exit(1)
29
+ }
30
+ if (result.status !== null) process.exit(result.status)
31
+ process.exit(1)
32
+ }
33
+
34
+ const args = process.argv.slice(2)
35
+ const cmd = args[0]
36
+
37
+ // ── help ─────────────────────────────────────────────────────────────────────
38
+
39
+ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
40
+ console.log(`
41
+ teamkit — AI 原生团队协作底座 v${pkg.version}
42
+
43
+ 用法:
44
+ teamkit -v / --version / version 显示版本
45
+ teamkit doctor 运行系统诊断
46
+ teamkit install [--dry-run] 安装/重新安装 teamkit 到 opencode
47
+ teamkit uninstall 卸载 teamkit
48
+ teamkit upgrade 升级到最新版本
49
+ teamkit help 显示本帮助
50
+ `)
51
+ process.exit(0)
52
+ }
53
+
54
+ // ── version ───────────────────────────────────────────────────────────────────
55
+
56
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
57
+ console.log(`@andyqiu/teamkit v${pkg.version}`)
58
+ process.exit(0)
59
+ }
60
+
61
+ // ── doctor ────────────────────────────────────────────────────────────────────
62
+
63
+ if (cmd === 'doctor') {
64
+ const doctorPath = resolve(__dirname, 'teamkit-doctor.mjs')
65
+ const result = spawnSync(process.execPath, [doctorPath, ...args.slice(1)], {
66
+ stdio: 'inherit'
67
+ })
68
+ exitFromSpawn(result)
69
+ }
70
+
71
+ // ── install ───────────────────────────────────────────────────────────────────
72
+
73
+ if (cmd === 'install') {
74
+ const installSh = resolve(__dirname, '..', 'install.sh')
75
+ if (!existsSync(installSh)) {
76
+ console.error(`teamkit: install.sh 未找到(期望路径:${installSh})`)
77
+ process.exit(1)
78
+ }
79
+ const extraArgs = args.slice(1)
80
+ const result = spawnSync('bash', [installSh, ...extraArgs], {
81
+ stdio: 'inherit'
82
+ })
83
+ exitFromSpawn(result)
84
+ }
85
+
86
+ // ── uninstall ─────────────────────────────────────────────────────────────────
87
+
88
+ if (cmd === 'uninstall') {
89
+ const installSh = resolve(__dirname, '..', 'install.sh')
90
+ if (!existsSync(installSh)) {
91
+ console.error(`teamkit: install.sh 未找到(期望路径:${installSh})`)
92
+ console.error('请手动运行:bash <teamkit-pkg-root>/install.sh --uninstall')
93
+ process.exit(1)
94
+ }
95
+ const result = spawnSync('bash', [installSh, '--uninstall', ...args.slice(1)], {
96
+ stdio: 'inherit'
97
+ })
98
+ exitFromSpawn(result)
99
+ }
100
+
101
+ // ── upgrade ───────────────────────────────────────────────────────────────────
102
+
103
+ if (cmd === 'upgrade') {
104
+ const upgradePath = resolve(__dirname, 'teamkit-upgrade.mjs')
105
+ const result = spawnSync(process.execPath, [upgradePath, ...args.slice(1)], {
106
+ stdio: 'inherit'
107
+ })
108
+ exitFromSpawn(result)
109
+ }
110
+
111
+ // ── unknown ───────────────────────────────────────────────────────────────────
112
+
113
+ console.error(`teamkit: 未知命令 "${cmd}"`)
114
+ console.error('运行 teamkit help 查看可用命令')
115
+ process.exit(1)
File without changes