@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/install.sh ADDED
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh — teamkit 安装脚本
3
+ #
4
+ # 四件事:
5
+ # 1. node scripts/capability-detect.mjs(hard fail 门)
6
+ # 2. 把 @andyqiu/teamkit 注册到 ~/.config/opencode/opencode.json plugins 数组(排首位)
7
+ # 3. 把 context-templates/kh-instructions.md 注入 ~/.config/opencode/AGENTS.md <!-- knowledge-hub:start/end -->
8
+ # 把 context-templates/git-instructions.md 注入 ~/.config/opencode/AGENTS.md <!-- teamkit-git:start/end -->
9
+ # 4. 把 agents/teamkit-git.md 复制到 ~/.config/opencode/agents/teamkit-git.md
10
+ #
11
+ # 支持 --dry-run 参数:打印将执行的操作但不实际写文件
12
+ #
13
+ # ADR: install-sh-wsl-path-guard
14
+ # ADR: opencode-version-compatibility
15
+
16
+ set -euo pipefail
17
+
18
+ # ────────────────────────────────────────────────────────────────────────────
19
+ # 颜色 & 输出工具
20
+ # ────────────────────────────────────────────────────────────────────────────
21
+ RED='\033[0;31m'
22
+ YELLOW='\033[1;33m'
23
+ GREEN='\033[0;32m'
24
+ CYAN='\033[0;36m'
25
+ NC='\033[0m' # No Color
26
+
27
+ err() { printf "${RED}❌ %s${NC}\n" "$*" >&2; }
28
+ warn() { printf "${YELLOW}⚠️ %s${NC}\n" "$*"; }
29
+ info() { printf "${CYAN}ℹ️ %s${NC}\n" "$*"; }
30
+ ok() { printf "${GREEN}✅ %s${NC}\n" "$*"; }
31
+
32
+ # ────────────────────────────────────────────────────────────────────────────
33
+ # 参数解析
34
+ # ────────────────────────────────────────────────────────────────────────────
35
+ DRY_RUN=0
36
+
37
+ for arg in "$@"; do
38
+ case "$arg" in
39
+ --dry-run) DRY_RUN=1 ;;
40
+ *)
41
+ err "未知参数: $arg"
42
+ printf "用法: install.sh [--dry-run]\n" >&2
43
+ exit 1
44
+ ;;
45
+ esac
46
+ done
47
+
48
+ # ────────────────────────────────────────────────────────────────────────────
49
+ # 脚本所在目录(teamkit 包根)
50
+ # ────────────────────────────────────────────────────────────────────────────
51
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
52
+
53
+ # ────────────────────────────────────────────────────────────────────────────
54
+ # WSL 路径检查(ADR: install-sh-wsl-path-guard)
55
+ # 必须在所有路径解析之前执行
56
+ # ────────────────────────────────────────────────────────────────────────────
57
+ if command -v wslpath >/dev/null 2>&1 || [[ "${TEAMKIT_FORCE_WSL_CHECK:-}" == "1" ]]; then
58
+ case "$HOME" in
59
+ /mnt/c/*|/mnt/d/*|/mnt/e/*|/mnt/f/*|/mnt/g/*)
60
+ err "WSL 路径错位:\$HOME 落在 Windows NTFS 挂载路径 ($HOME)"
61
+ err "问题:teamkit 配置将写到 NTFS 文件系统,导致性能差 / 权限失效 / 路径混乱"
62
+ err "修复:在 /etc/wsl.conf 中设置 [user] default=<你的Linux用户名>,重启 WSL"
63
+ err "绕过:若你确实需要在 NTFS 路径运行,设置 TEAMKIT_ALLOW_WSL_WINDOWS_HOME=1"
64
+ if [[ "${TEAMKIT_ALLOW_WSL_WINDOWS_HOME:-}" != "1" ]]; then
65
+ exit 1
66
+ fi
67
+ warn "已通过 TEAMKIT_ALLOW_WSL_WINDOWS_HOME=1 跳过 WSL 路径检查"
68
+ ;;
69
+ esac
70
+ fi
71
+
72
+ # ────────────────────────────────────────────────────────────────────────────
73
+ # 路径配置
74
+ # ────────────────────────────────────────────────────────────────────────────
75
+ OPENCODE_CONFIG_DIR="${HOME}/.config/opencode"
76
+ OPENCODE_JSON="${OPENCODE_CONFIG_DIR}/opencode.json"
77
+ AGENTS_MD="${OPENCODE_CONFIG_DIR}/AGENTS.md"
78
+ KH_TEMPLATE="${SCRIPT_DIR}/context-templates/kh-instructions.md"
79
+ GIT_TEMPLATE="${SCRIPT_DIR}/context-templates/git-instructions.md"
80
+ MERGE_SCRIPT="${SCRIPT_DIR}/scripts/merge-agents-md.mjs"
81
+
82
+ # ────────────────────────────────────────────────────────────────────────────
83
+ # 前置检查
84
+ # ────────────────────────────────────────────────────────────────────────────
85
+ if ! command -v node >/dev/null 2>&1; then
86
+ err "Node.js 未安装,请先安装 Node.js >= 18"
87
+ exit 1
88
+ fi
89
+
90
+ if [[ ! -f "${KH_TEMPLATE}" ]]; then
91
+ err "模板文件不存在: ${KH_TEMPLATE}"
92
+ exit 1
93
+ fi
94
+
95
+ if [[ ! -f "${GIT_TEMPLATE}" ]]; then
96
+ err "模板文件不存在: ${GIT_TEMPLATE}"
97
+ exit 1
98
+ fi
99
+
100
+ # ────────────────────────────────────────────────────────────────────────────
101
+ # STEP 0:capability detection(hard fail 门)
102
+ # ADR: opencode-version-compatibility §Decision §4
103
+ # ────────────────────────────────────────────────────────────────────────────
104
+ info "Step 0/3: 运行能力探测..."
105
+
106
+ if [[ "${DRY_RUN}" == "1" ]]; then
107
+ info "[dry-run] 将运行: node ${SCRIPT_DIR}/scripts/capability-detect.mjs"
108
+ else
109
+ if ! node "${SCRIPT_DIR}/scripts/capability-detect.mjs"; then
110
+ err "capability detection 失败,install 中止"
111
+ err "运行 \`teamkit doctor\` 获取详细诊断信息"
112
+ exit 1
113
+ fi
114
+ ok "能力探测通过"
115
+ fi
116
+
117
+ # ────────────────────────────────────────────────────────────────────────────
118
+ # STEP 1:注册 @andyqiu/teamkit 到 opencode.json plugins 数组(排首位)
119
+ # ────────────────────────────────────────────────────────────────────────────
120
+ info "Step 1/3: 注册 plugin entry 到 opencode.json..."
121
+
122
+ register_plugin_entry() {
123
+ local config_file="${OPENCODE_JSON}"
124
+ local plugin_name="@andyqiu/teamkit"
125
+
126
+ if [[ "${DRY_RUN}" == "1" ]]; then
127
+ info "[dry-run] 将把 ${plugin_name} 注册到 ${config_file} plugin 数组首位"
128
+ return 0
129
+ fi
130
+
131
+ # 确保配置目录存在
132
+ mkdir -p "${OPENCODE_CONFIG_DIR}"
133
+
134
+ # 用 Node.js 做 JSON 操作(避免 shell 解析 JSON 的各种坑)
135
+ # 通过 argv 传路径,Node.js 内自己读文件;heredoc 用单引号防止变量展开
136
+ node - "${config_file}" <<'NODEJS_EOF'
137
+ const fs = require('fs');
138
+ const path = process.argv[2];
139
+ let cfg = {};
140
+ try {
141
+ if (fs.existsSync(path)) cfg = JSON.parse(fs.readFileSync(path, 'utf8'));
142
+ } catch { cfg = {}; }
143
+
144
+ // opencode 用 "plugin"(单数),兼容读取两种 key
145
+ const existing = Array.isArray(cfg.plugin) ? cfg.plugin
146
+ : Array.isArray(cfg.plugins) ? cfg.plugins
147
+ : [];
148
+
149
+ const pluginName = '@andyqiu/teamkit';
150
+ const filtered = existing.filter(p => {
151
+ const name = typeof p === 'string' ? p : (p?.name ?? p?.path ?? '');
152
+ return !name.includes('teamkit');
153
+ });
154
+ filtered.unshift(pluginName);
155
+
156
+ // 统一写到 "plugin" key
157
+ cfg.plugin = filtered;
158
+ // 如果原来有 "plugins" key,同步删除避免混淆
159
+ if (cfg.plugins !== undefined) delete cfg.plugins;
160
+
161
+ fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
162
+ process.stdout.write(' ✓ 已注册 ' + pluginName + ' 到 plugin[0]\n');
163
+ NODEJS_EOF
164
+ }
165
+
166
+ register_plugin_entry
167
+
168
+ # ────────────────────────────────────────────────────────────────────────────
169
+ # STEP 2:注入 AGENTS.md
170
+ # ────────────────────────────────────────────────────────────────────────────
171
+ info "Step 2/3: 注入 AGENTS.md..."
172
+
173
+ inject_agents_md() {
174
+ if [[ "${DRY_RUN}" == "1" ]]; then
175
+ node "${MERGE_SCRIPT}" \
176
+ --target "${AGENTS_MD}" \
177
+ --template "${KH_TEMPLATE}" \
178
+ --dry-run
179
+ # git-instructions 注入(dry-run)
180
+ info "[dry-run] 将注入 git-instructions.md 到 <!-- teamkit-git:start/end --> 块"
181
+ return 0
182
+ fi
183
+
184
+ # 确保配置目录存在
185
+ mkdir -p "${OPENCODE_CONFIG_DIR}"
186
+
187
+ # 注入 KH instructions(<!-- knowledge-hub:start/end -->)
188
+ node "${MERGE_SCRIPT}" \
189
+ --target "${AGENTS_MD}" \
190
+ --template "${KH_TEMPLATE}"
191
+
192
+ # 注入 git instructions(<!-- teamkit-git:start/end -->)
193
+ # merge-agents-md.mjs 只处理 knowledge-hub marker,
194
+ # git-instructions 需要单独处理 teamkit-git marker
195
+ # 通过 argv 传路径,heredoc 用单引号防止变量展开
196
+ node - "${AGENTS_MD}" "${GIT_TEMPLATE}" <<'NODEJS_EOF'
197
+ const fs = require('fs');
198
+ const agentsMdPath = process.argv[2];
199
+ const gitTemplatePath = process.argv[3];
200
+ const template = fs.readFileSync(gitTemplatePath, 'utf8').replace(/\n+$/, '');
201
+ const GIT_START = '<!-- teamkit-git:start -->';
202
+ const GIT_END = '<!-- teamkit-git:end -->';
203
+
204
+ let content = fs.existsSync(agentsMdPath) ? fs.readFileSync(agentsMdPath, 'utf8') : '';
205
+
206
+ const startIdx = content.indexOf(GIT_START);
207
+ const endIdx = content.indexOf(GIT_END);
208
+
209
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
210
+ const before = content.slice(0, startIdx + GIT_START.length);
211
+ const after = content.slice(endIdx);
212
+ content = before + '\n\n' + template + '\n\n' + after;
213
+ process.stdout.write(' ✓ AGENTS.md teamkit-git 块已更新\n');
214
+ } else {
215
+ content = content + '\n\n' + GIT_START + '\n\n' + template + '\n\n' + GIT_END + '\n';
216
+ process.stdout.write(' ✓ AGENTS.md teamkit-git 块已追加\n');
217
+ }
218
+
219
+ fs.writeFileSync(agentsMdPath, content, 'utf8');
220
+ NODEJS_EOF
221
+ }
222
+
223
+ inject_agents_md
224
+
225
+ # ────────────────────────────────────────────────────────────────────────────
226
+ # STEP 3:注册 Git Agent(teamkit-git subagent)到全局 agents 目录
227
+ # ────────────────────────────────────────────────────────────────────────────
228
+ info "Step 3/3: 注册 Git Agent 到 opencode agents 目录..."
229
+
230
+ register_git_agent() {
231
+ local agents_dir="${HOME}/.config/opencode/agents"
232
+ local src="${SCRIPT_DIR}/agents/teamkit-git.md"
233
+
234
+ if [[ ! -f "${src}" ]]; then
235
+ warn "agents/teamkit-git.md 不存在,跳过 Git Agent 注册"
236
+ return 0
237
+ fi
238
+
239
+ if [[ "${DRY_RUN}" == "1" ]]; then
240
+ info "[dry-run] 将复制 ${src} → ${agents_dir}/teamkit-git.md"
241
+ return 0
242
+ fi
243
+
244
+ mkdir -p "${agents_dir}"
245
+ cp "${src}" "${agents_dir}/teamkit-git.md"
246
+ echo " → Git Agent 已注册到 ${agents_dir}/teamkit-git.md"
247
+ }
248
+
249
+ register_git_agent
250
+
251
+ # ────────────────────────────────────────────────────────────────────────────
252
+ # STEP 4:初始化全局 git-rules.json(不存在时生成默认配置)
253
+ # 全局配置路径:~/.config/teamkit/git-rules.json
254
+ # 默认 narrative + zh,面向非程序员(程序员可在项目级覆盖)
255
+ # ────────────────────────────────────────────────────────────────────────────
256
+ info "Step 4/4: 初始化全局 git-rules.json..."
257
+
258
+ init_global_git_rules() {
259
+ local teamkit_config_dir="${HOME}/.config/teamkit"
260
+ local global_rules_file="${teamkit_config_dir}/git-rules.json"
261
+
262
+ if [[ "${DRY_RUN}" == "1" ]]; then
263
+ if [[ -f "${global_rules_file}" ]]; then
264
+ info "[dry-run] 全局 git-rules.json 已存在,将跳过(不覆盖): ${global_rules_file}"
265
+ else
266
+ info "[dry-run] 将创建全局 git-rules.json: ${global_rules_file}"
267
+ fi
268
+ return 0
269
+ fi
270
+
271
+ if [[ -f "${global_rules_file}" ]]; then
272
+ ok "全局 git-rules.json 已存在,跳过(保留用户配置)"
273
+ return 0
274
+ fi
275
+
276
+ mkdir -p "${teamkit_config_dir}"
277
+ cat > "${global_rules_file}" <<'JSON_EOF'
278
+ {
279
+ "branchPrefixes": ["feat/", "fix/", "docs/", "art/", "design/", "data/"],
280
+ "messageStyle": "narrative",
281
+ "messageLanguage": "zh",
282
+ "maxFilesPerCommit": 20,
283
+ "sensitivePatterns": [],
284
+ "lfsThresholdMB": 10
285
+ }
286
+ JSON_EOF
287
+ ok "已生成全局 git-rules.json(narrative + zh,适合非程序员): ${global_rules_file}"
288
+ }
289
+
290
+ init_global_git_rules
291
+
292
+ # ────────────────────────────────────────────────────────────────────────────
293
+ # 结尾输出
294
+ # ────────────────────────────────────────────────────────────────────────────
295
+ if [[ "${DRY_RUN}" == "1" ]]; then
296
+ info "[dry-run] 以上为模拟操作,未实际写文件"
297
+ printf "\n"
298
+ ok "dry-run 完成。使用不带 --dry-run 参数运行以实际安装。"
299
+ else
300
+ printf "\n"
301
+ ok "teamkit installed successfully. Run: teamkit doctor to verify."
302
+ fi
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@andyqiu/teamkit",
3
+ "version": "0.1.1",
4
+ "description": "Team-level shared OpenCode plugin: KH integration + future team-wide workflows",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "bin": {
8
+ "teamkit": "bin/teamkit.mjs",
9
+ "teamkit-doctor": "bin/teamkit-doctor.mjs"
10
+ },
11
+ "files": [
12
+ "dist/index.cjs",
13
+ "bin/",
14
+ "agents/",
15
+ "context-templates/",
16
+ "scripts/",
17
+ "install.sh",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "bun build src/index.ts --outfile dist/index.cjs --target=node --format=cjs",
23
+ "test": "node tests/compaction-guard.test.ts",
24
+ "test:verify": "echo 'TODO: vitest --run tests/'",
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "echo 'no lint configured (pre-publish TODO)'",
27
+ "prepublishOnly": "npm run build && npm run typecheck && npm test",
28
+ "adr:check": "node scripts/adr-check.mjs",
29
+ "adr:sync": "node scripts/adr-index-sync.mjs",
30
+ "adr:check-sync": "node scripts/adr-index-sync.mjs --check",
31
+ "publish:patch": "bash scripts/publish.sh --patch",
32
+ "publish:minor": "bash scripts/publish.sh --minor",
33
+ "publish:major": "bash scripts/publish.sh --major",
34
+ "publish:release": "bash scripts/publish.sh"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^25.9.1",
38
+ "typescript": "^5.0.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@opencode-ai/plugin": "*"
42
+ },
43
+ "author": "Andy",
44
+ "license": "MIT"
45
+ }