team-skills 1.0.0 → 1.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.
@@ -11,7 +11,7 @@ argument-hint: [target-dir]
11
11
 
12
12
  安装内容:
13
13
 
14
- - `skills/*` → `{target-dir}/`(Agent Skills,含 team-score 评分 Skill 和 using-team-skills meta-skill)
14
+ - `skills/*` → `{target-dir}/`(Agent Skills,含 using-team-skills meta-skill)
15
15
  - `skills/_team-rules/*` → `{target-dir}/_team-rules/`(共享规则文件,被所有 Skill 引用)
16
16
  - `.claude/commands/*` → `{target-dir}/{name}/SKILL.md`(Commands 也作为 Skill 安装,Cursor 可发现)
17
17
  - `.claude/commands/*` → `~/.claude/commands/`(兼容 Claude Code 斜杠命令)
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](CONTRIBUTING.md)
13
13
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow?style=flat-square)](https://conventionalcommits.org)
14
14
 
15
- **Spec-Driven · 有向图回退 · 质量门禁 · 100 分制评分**
15
+ **Spec-Driven · 有向图回退 · 质量门禁**
16
16
 
17
17
  </div>
18
18
 
@@ -60,104 +60,87 @@ reviewAgent 发现 spec 遗漏 ──→ 自动回退 specAgent
60
60
  - **反规避条款**:预判 6 种常见借口并逐一反驳
61
61
  - **三视角对抗审查**:攻击者/怀疑者/用户视角反向验证
62
62
 
63
- ### 📊 100 分制评分,不是凭感觉
64
-
65
- ```
66
- 7 项硬门槛(任一不通过则整体不通过)
67
- 5 个维度 × 25 子项
68
- 每个子项有可检查的证据要求
69
- ```
70
-
71
63
  ---
72
64
 
73
- ## 🚀 安装与使用
65
+ ## 🚀 安装
74
66
 
75
67
  ### 前置条件
76
68
 
77
69
  - **Node.js** >= 18
78
70
  - **Claude Code** 或 **Cursor**:至少安装其中之一
79
71
 
80
- ### 方式一:npx 安装(推荐)
81
-
82
- 无需 clone 仓库,直接全局安装 Skills:
72
+ ### 一键安装(推荐)
83
73
 
84
74
  ```bash
85
- npx team-skills setup
75
+ npx team-skills@latest setup
86
76
  ```
87
77
 
88
- ### 方式二:clone 后安装
78
+ 自动将 Skills、斜杠命令和 Hooks 以 symlink 方式安装到全局目录。
79
+
80
+ 启用可选的项目评分功能(`team-score`):
89
81
 
90
82
  ```bash
91
- git clone https://github.com/andeya/team-skills.git
92
- cd team-skills
93
- npm install
94
- npm run setup
83
+ npx team-skills@latest setup --with-score
95
84
  ```
96
85
 
97
- ### 方式三:初始化到项目(版本控制)
98
-
99
- 将 Skills 复制到你的项目中,可以版本控制和自定义:
86
+ 如需频繁使用 CLI,可全局安装:
100
87
 
101
88
  ```bash
102
- npx team-skills init
103
- # 或在已 clone 的仓库中
104
- node bin/team-skills.js init /path/to/your-project
89
+ npm i -g team-skills
90
+ team-skills setup
105
91
  ```
106
92
 
107
- 这会在项目中创建 `.team-skills/` 目录,包含所有 Skills、Hooks 和命令文件。
93
+ ### 初始化到项目(可选)
108
94
 
109
- ### 安装内容
95
+ Skills 复制到项目中,支持版本控制和自定义:
110
96
 
111
- | 安装内容 | 说明 |
112
- |----------|------|
113
- | 12 个 Agent Skills | 安装到 `~/.agents/skills/`,Cursor 自动发现 |
114
- | 斜杠命令 | 安装到 `~/.claude/commands/`,Claude Code 可用 `/team-{name}` |
115
- | 共享规则 | `_team-rules/` 被所有 Skill 引用 |
116
- | Hooks(可选) | session-start 钩子,每次新 session 自动加载 |
97
+ ```bash
98
+ npx team-skills@latest init
99
+ # 含评分功能
100
+ npx team-skills@latest init --with-score
101
+ ```
117
102
 
118
- ### CLI 命令
103
+ 自动检测项目中的 `.claude/` 和 `.cursor/` 目录,将对应文件复制到 IDE 能发现的位置。后续更新:
119
104
 
120
- | 命令 | 说明 |
121
- |------|------|
122
- | `team-skills setup [target]` | symlink 安装到全局目录(开发者模式) |
123
- | `team-skills init [dir]` | 复制到用户项目(消费者模式) |
124
- | `team-skills update [dir]` | 增量更新 init 的副本 |
125
- | `team-skills uninstall [target]` | 移除所有 symlink |
126
- | `team-skills list` | 列出已安装 Skills 及状态 |
127
- | `team-skills --version` | 显示版本 |
105
+ ```bash
106
+ npx team-skills@latest update
107
+ ```
108
+
109
+ > **提示**:Hooks 仅在全局安装(`setup`)模式下生效,`init` 不安装 hooks。
128
110
 
129
- 所有命令支持 `--dry-run` 查看将执行的操作。
111
+ ### 安装内容
130
112
 
131
- ### 验证安装
113
+ | 内容 | 位置 | 说明 |
114
+ |------|------|------|
115
+ | 11 个 Agent Skills | `~/.agents/skills/` | Cursor 自动发现 |
116
+ | 斜杠命令 | `~/.claude/commands/` | Claude Code `/team-{name}` |
117
+ | 共享规则 | `~/.agents/skills/_team-rules/` | 被所有 Skill 引用 |
118
+ | Hooks(可选) | `~/.cursor/hooks/` | session-start 自动加载 |
132
119
 
133
- 安装后,在聊天框中输入 `/` 查看是否出现 `team-` 开头的命令列表。或使用 CLI 检查:
120
+ ### 验证
134
121
 
135
122
  ```bash
136
- team-skills list
137
-
138
- # 或在 Claude Code / Cursor 中
123
+ # 在 Claude Code / Cursor 中输入 / 查看 team- 开头的命令
139
124
  /using-team-skills
140
-
141
- # 评估项目协作成熟度
142
- /team-score
143
125
  ```
144
126
 
145
- ### 更新 Skills
127
+ ### CLI 参考
146
128
 
147
- ```bash
148
- # npx 安装用户
149
- npx team-skills setup
129
+ | 命令 | 说明 | 关键选项 |
130
+ |------|------|----------|
131
+ | `team-skills setup` | symlink 安装到全局目录 | `--with-score` `--no-hooks` `--force` |
132
+ | `team-skills init [dir]` | 复制到项目 IDE 目录 | `--ide <claude\|cursor\|both>` `--with-score` |
133
+ | `team-skills update [dir]` | 升级包 + 更新项目副本 | `--skip-self` `--ide` `--with-score` |
134
+ | `team-skills uninstall` | 移除所有全局 symlink | `--no-hooks` `--no-commands` |
135
+ | `team-skills list` | 查看全局安装状态 | `--json` |
150
136
 
151
- # 项目内 init 用户
152
- npx team-skills update
137
+ 所有命令支持 `--dry-run`。
153
138
 
154
- # clone 安装用户
155
- git pull && npm run setup
156
- ```
139
+ ---
157
140
 
158
- ### 使用方式
141
+ ## 📖 使用方式
159
142
 
160
- #### 方式一:全自动编排(推荐)
143
+ ### 全自动编排(推荐)
161
144
 
162
145
  一条命令启动完整流水线,适合从零开始的功能开发:
163
146
 
@@ -165,29 +148,15 @@ git pull && npm run setup
165
148
  /team-orchestrator 实现用户登录功能
166
149
  ```
167
150
 
168
- 编排器会自动完成 7 个步骤:
169
-
170
- ```
171
-
172
- 1. H1: 向你确认目标理解
173
- 2. specAgent: 产出 SDD 规格
174
- 3. H2: 向你确认规格方案
175
- 4. implAgent: TDD 实现
176
- 5. testAgent: 四维测试
177
- 6. reviewAgent: 五维审查
178
- 7. H4: 向你交付验收
151
+ 编排器自动完成:H1 确认目标 → specAgent 产出 SDD → H2 确认规格 → implAgent TDD 实现 → testAgent 四维测试 → reviewAgent 五维审查 → H4 验收交付
179
152
 
180
- ```
181
-
182
- 简单任务可用精简模式(H1 简化为单句确认,跳过 H2,H4 保留):
153
+ 简单任务可用精简模式:
183
154
 
184
155
  ```bash
185
156
  /team-orchestrator --compact 修复登录页按钮样式
186
157
  ```
187
158
 
188
- #### 方式二:按需调用单个 Skill
189
-
190
- 你可以在任意阶段单独调用某个 Skill,适合只想做其中一步的场景:
159
+ ### 按需调用单个 Skill
191
160
 
192
161
  | 场景 | 命令 |
193
162
  |------|------|
@@ -202,13 +171,6 @@ git pull && npm run setup
202
171
  | 代码写完了 | `/team-finish` |
203
172
  | 不知道用哪个 | `/using-team-skills` |
204
173
 
205
- #### 方式三:评估项目成熟度
206
-
207
- ```bash
208
- /team-score
209
- # 输出 100 分制评分报告 + 改进建议
210
- ```
211
-
212
174
  ---
213
175
 
214
176
  ## 🏗️ 核心架构
@@ -280,7 +242,6 @@ graph TD
280
242
  Q -->|"遇到 Bug"| DEBUG[🐛 team-debug<br/>→ 根因分析 + 修复]:::skill
281
243
  Q -->|"实现完成,准备合并"| FINISH[🏁 team-finish<br/>→ 合并/PR/清理]:::skill
282
244
  Q -->|"声称完成"| VERIFY[✅ team-verify<br/>→ 验证证据链]:::skill
283
- Q -->|"评估项目成熟度"| SCORE[📊 team-score<br/>→ 100 分制评分]:::skill
284
245
  Q -->|"不知道用哪个"| USING[🧭 using-team-skills<br/>→ Skill 推荐]:::skill
285
246
  Q -->|"需要完整交付流水线"| ORCH[⚙️ team-orchestrator<br/>→ 全自动编排]:::orch
286
247
 
@@ -301,7 +262,7 @@ graph TD
301
262
 
302
263
  ---
303
264
 
304
- ## 📦 包含 12 个可独立使用的 Skill
265
+ ## 📦 包含 11 个可独立使用的 Skill
305
266
 
306
267
  | Skill | 一句话说明 | 使用场景 |
307
268
  |-------|-----------|----------|
@@ -315,7 +276,6 @@ graph TD
315
276
  | `team-debug` | 四阶段根因分析 + 修复 | "这个 bug 怎么回事?" |
316
277
  | `team-feedback` | 先验证再实施,非表演性同意 | "Review 反馈来了" |
317
278
  | `team-finish` | 分支完成处理(合并/PR/保留/丢弃) | "代码写完了" |
318
- | `team-score` | 100 分制扫描评估 | "项目协作成熟度如何?" |
319
279
  | `using-team-skills` | Meta-skill,自动引导你选正确的 Skill | "我该用哪个?" |
320
280
 
321
281
  > 每个 Skill 可独立使用,也可通过 `team-orchestrator` 串联成完整流水线。
@@ -366,19 +326,15 @@ Team Skills 融合了业界多个 AI 协作框架的精华:
366
326
  | **OpenSpec** (Fission AI) | Delta Spec 增量规格、RFC 2119 + Given/When/Then |
367
327
  | **Karpathy Skills** | 过度抽象防御、死代码清理、困惑管理 |
368
328
  | **Agent-Style** | 5 条 LLM 输出质量约束 |
369
- | **独创** | 有向图回退、评分追溯矩阵、消费方契约、H1-H4 人类介入点 |
329
+ | **独创** | 有向图回退、质量追溯矩阵、消费方契约、H1-H4 人类介入点 |
370
330
 
371
331
  ---
372
332
 
373
333
  ## 🔧 本地开发
374
334
 
375
- ### 前置要求
376
-
377
- - Node.js >= 18
378
-
379
- ### 安装依赖
380
-
381
335
  ```bash
336
+ git clone https://github.com/andeya/team-skills.git
337
+ cd team-skills
382
338
  npm install
383
339
  ```
384
340
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "team-skills",
3
- "version": "1.0.0",
4
- "description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback, quality gates, and 100-point scoring",
3
+ "version": "1.1.1",
4
+ "description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback and quality gates",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "team-skills": "./bin/team-skills.js"
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "lint": "markdownlint-cli2 '**/*.md' '#node_modules' --config .markdownlint.json && node scripts/check-skill-structure.js",
22
22
  "format": "markdownlint-cli2 '**/*.md' '#node_modules' --config .markdownlint.json --fix",
23
- "cli-test": "node bin/team-skills.js --version && node bin/team-skills.js list && node bin/team-skills.js setup --dry-run",
23
+ "cli-test": "node bin/team-skills.js --version && node bin/team-skills.js list && node bin/team-skills.js setup --dry-run && node bin/team-skills.js init --dry-run --ide claude /tmp/test-project && node bin/team-skills.js update --dry-run --skip-self --ide claude /tmp/test-project",
24
24
  "prepare": "git config core.hooksPath .githooks 2>/dev/null || true",
25
25
  "setup": "node bin/team-skills.js setup",
26
26
  "uninstall": "node bin/team-skills.js uninstall"
@@ -56,7 +56,7 @@ NO AGENT DISPATCH WITHOUT H1 HUMAN CONFIRMATION FIRST
56
56
  - Claude Code:通过 `/team-orchestrator`、`/team-spec`、`/team-impl`、`/team-test`、`/team-review` 调用
57
57
  - Cursor:通过 `~/.agents/skills/` 下的 Skill 机制自动发现
58
58
 
59
- <!-- 评分追溯矩阵(内部参考,不产出到文件)
59
+ <!-- 质量检查追溯矩阵(内部参考,不产出到文件)
60
60
  硬门槛:
61
61
  G1 任务规划 → 01-plan.md
62
62
  G2 修改边界 → 04-boundary.md
@@ -65,7 +65,7 @@ NO AGENT DISPATCH WITHOUT H1 HUMAN CONFIRMATION FIRST
65
65
  G5 资产可执行 → 12-asset-update.md(消费方契约)+ CLAUDE.md
66
66
  G6 风险说明 → 05-risk.md + 11-review.md §四
67
67
  G7 决策解释 → 08-ai-decisions.md + 15-brief.md
68
- 评分维度:
68
+ 质量维度:
69
69
  D1.1 分层组织 → CLAUDE.md + {module}/CLAUDE.md + task-rules.md
70
70
  D1.2 内容8类 → 02-context.md + CLAUDE.md + review-checklist + delivery-checklist
71
71
  D1.3 规则可执行 → 12-asset-update.md(触发条件+可执行指令+示例)
@@ -231,8 +231,6 @@ NO AGENT DISPATCH WITHOUT H1 HUMAN CONFIRMATION FIRST
231
231
 
232
232
  判断标准:预计修改文件数 ≤ 3 且无跨模块影响 → Small;修改文件 4-15 → Medium;修改文件 > 15 或跨 2+ 模块 → Large。
233
233
 
234
- > **评分注意**:精简模式跳过 01-plan、02-context、05-risk、14-team、15-brief,无法通过 team-score 满分评估。如需满分评估,使用完整模式。
235
-
236
234
  **精简模式 vs 完整模式对比**:
237
235
 
238
236
  | 环节 | 完整模式 | 精简模式 |
@@ -479,9 +477,9 @@ NO AGENT DISPATCH WITHOUT H1 HUMAN CONFIRMATION FIRST
479
477
  | ---- | ---- | ---- | ----------- | ---- |
480
478
  ```
481
479
 
482
- ### Step 8:最终质量检查(评分对齐验证)
480
+ ### Step 8:最终质量检查
483
481
 
484
- 逐条核验,确保每个评分维度都有明确证据。以下清单对齐 team-score 全部评分子项。
482
+ 逐条核验,确保每个维度都有明确证据。
485
483
 
486
484
  **硬门槛(7 项全部必须通过):**
487
485
 
@@ -560,7 +558,7 @@ NO AGENT DISPATCH WITHOUT H1 HUMAN CONFIRMATION FIRST
560
558
  Team 全流程完成 ✅
561
559
  产出目录:docs/tasks/{slug}/
562
560
  文件总数:17 个文档(01-15 + prompt-template + task-rules)+ 代码 + 测试 + 资产更新
563
- 全部质量检查通过(对齐 team-score 全部评分子项)
561
+ 全部质量检查通过
564
562
  ```
565
563
 
566
564
  ## 集成关系
@@ -576,9 +574,7 @@ Team 全流程完成 ✅
576
574
  - `team-test` — REQUIRED:编排流程中必须调度测试审计
577
575
  - `team-review` — REQUIRED:编排流程中必须调度代码审查
578
576
  - `team-finish` — 分支完成处理
579
- - `team-score` — 评估项目协作成熟度
580
577
 
581
578
  ## 下一步
582
579
 
583
- - 交付完成后,推荐使用 `team-score` 评估项目协作成熟度
584
580
  - 如果发现流程问题,更新 `CLAUDE.md` 和 `skills/_team-rules/` 中的规则
@@ -317,7 +317,7 @@ NO COMPLETION CLAIMS WITHOUT CONSTITUTIONAL COMPLIANCE CHECK
317
317
  3. **新规则沉淀**(§二.5):列出本次发现的可固化规则,注明写入位置和理由。对每条新规则,必须同时执行写入——追加到目标文件(CLAUDE.md / 模块 CLAUDE.md / task-rules.md),并在 12-asset-update.md 中记录变更
318
318
  4. **改进承诺**(§三):具体行动 + 预期效果
319
319
 
320
- > 重点:§二.5 的新规则沉淀是评分项 D4.4 的关键证据,不可省略。"发现规则但未写入目标文件"视为未完成。
320
+ > 重点:§二.5 的新规则沉淀是质量检查 D4.4 的关键证据,不可省略。"发现规则但未写入目标文件"视为未完成。
321
321
 
322
322
  ## 产出文件
323
323
 
@@ -56,7 +56,6 @@ NO SKILL RECOMMENDATION WITHOUT SCENE ANALYSIS FIRST
56
56
  | 声明完成,需验证门禁 | team-verify |
57
57
  | 实现完成,需处理分支 | team-finish |
58
58
  | 需完整交付流水线 | team-orchestrator |
59
- | 评估项目协作成熟度 | team-score |
60
59
 
61
60
  ## 执行步骤
62
61
 
@@ -73,7 +72,6 @@ NO SKILL RECOMMENDATION WITHOUT SCENE ANALYSIS FIRST
73
72
  - 收到审查反馈 → 推荐 `team-feedback`
74
73
  - 实现完成 → 推荐 `team-finish`
75
74
  - 需要完整流水线 → 推荐 `team-orchestrator`
76
- - 评估成熟度 → 推荐 `team-score`
77
75
 
78
76
  ### Step 2:推荐并说明理由
79
77
 
@@ -1,115 +1,80 @@
1
1
  import { join } from 'node:path';
2
- import { existsSync } from 'node:fs';
3
- import {
4
- PACKAGE_ROOT, LOCAL_INSTALL_DIR, SKILLS_DIR, HOOKS_DIR, COMMANDS_DIR,
5
- } from '../lib/constants.js';
6
- import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks, discoverSkillsModuleClaude } from '../lib/inventory.js';
7
- import { copyRecursive, computeDirectoryHashes, ensureDir } from '../lib/fs-utils.js';
8
- import { createManifest, writeManifest, readManifest, getPackageVersion } from '../lib/manifest.js';
9
- import * as log from '../lib/logger.js';
10
2
  import { copyFileSync as fsCopyFile } from 'node:fs';
3
+ import { PACKAGE_ROOT } from '../lib/constants.js';
4
+ import { discoverSkills, discoverSharedRules, discoverCommands, discoverSkillsModuleClaude } from '../lib/inventory.js';
5
+ import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
6
+ import { detectIDE } from '../lib/detect-ide.js';
7
+ import * as log from '../lib/logger.js';
11
8
 
12
9
  export function registerInit(program) {
13
10
  program
14
11
  .command('init')
15
- .description('Copy skills into your project for version-controlled local use')
12
+ .description('Copy skills into current project for the detected IDE(s)')
16
13
  .argument('[dir]', 'Project directory', '.')
17
- .option('--no-hooks', 'Skip hooks')
18
- .option('--no-commands', 'Skip command files')
14
+ .option('--ide <type>', 'Force IDE type: claude, cursor, or both')
15
+ .option('--with-score', 'Include team-score skill (hidden by default)', false)
19
16
  .option('--dry-run', 'Show what would be copied', false)
20
17
  .action(runInit);
21
18
  }
22
19
 
23
20
  function runInit(dir, opts) {
24
- const { hooks, commands, dryRun } = opts;
25
- const installDir = join(dir, LOCAL_INSTALL_DIR);
26
-
27
- const existing = readManifest(installDir);
28
- if (existing) {
29
- log.error(`${installDir} 已存在(v${existing.version})。使用 team-skills update 更新。`);
30
- process.exit(1);
31
- }
21
+ const { ide, withScore, dryRun } = opts;
22
+ const exclude = withScore ? [] : ['team-score'];
23
+ const ides = detectIDE(dir, ide, { strict: true });
32
24
 
33
25
  const tag = dryRun ? '[dry-run] ' : '';
34
- let fileCount = 0;
26
+ let count = 0;
35
27
 
36
28
  log.heading('初始化 team-skills 到项目');
37
- log.info(`目标目录: ${installDir}`);
29
+ log.info(`项目目录: ${dir}`);
30
+ log.info(`目标 IDE: ${ides.join(', ')}`);
38
31
 
39
- // Copy skills/
40
- log.heading('复制 Skills');
41
- const skillsSrc = join(PACKAGE_ROOT, SKILLS_DIR);
42
- const skillsDst = join(installDir, 'skills');
43
- if (dryRun) {
44
- log.info(`${tag}${skillsSrc} → ${skillsDst}`);
45
- } else {
46
- copyRecursive(skillsSrc, skillsDst);
47
- }
48
- const skills = discoverSkills();
32
+ const skills = discoverSkills(PACKAGE_ROOT, { exclude });
49
33
  const rules = discoverSharedRules();
50
- fileCount += skills.length + rules.length;
51
- for (const s of skills) log.success(`${tag}Skill: ${s.name}`);
52
- for (const r of rules) log.success(`${tag}Rule: ${r.name}`);
53
-
54
- // Copy skills/CLAUDE.md if exists
55
34
  const skillsClaude = discoverSkillsModuleClaude();
56
- if (skillsClaude) {
57
- if (!dryRun) {
58
- fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
35
+
36
+ // Cursor: skills → .cursor/skills/
37
+ if (ides.includes('cursor')) {
38
+ const skillsDst = join(dir, '.cursor', 'skills');
39
+ log.heading(`复制 Skills → ${skillsDst}`);
40
+
41
+ if (!dryRun) ensureDir(skillsDst);
42
+ for (const skill of skills) {
43
+ const dest = join(skillsDst, skill.name);
44
+ if (!dryRun) copyRecursive(skill.path, dest);
45
+ log.success(`${tag}Skill: ${skill.name}`);
46
+ count++;
59
47
  }
60
- log.success(`${tag}skills/CLAUDE.md`);
61
- fileCount++;
62
- }
63
48
 
64
- // Copy hooks/
65
- if (hooks !== false) {
66
- log.heading('复制 Hooks');
67
- const hookFiles = discoverHooks();
68
- const hooksDst = join(installDir, 'hooks');
69
- if (hookFiles.length > 0) {
70
- if (!dryRun) ensureDir(hooksDst);
71
- for (const h of hookFiles) {
72
- if (!dryRun) {
73
- fsCopyFile(h.path, join(hooksDst, h.name));
74
- }
75
- log.success(`${tag}Hook: ${h.name}`);
76
- fileCount++;
77
- }
49
+ const rulesDst = join(skillsDst, '_team-rules');
50
+ if (!dryRun) ensureDir(rulesDst);
51
+ for (const r of rules) {
52
+ if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
53
+ log.success(`${tag}Rule: ${r.name}`);
54
+ count++;
78
55
  }
79
- }
80
56
 
81
- // Copy commands/
82
- if (commands !== false) {
83
- log.heading('复制 Commands');
84
- const cmds = discoverCommands();
85
- const cmdsDst = join(installDir, 'commands');
86
- if (cmds.length > 0) {
87
- if (!dryRun) ensureDir(cmdsDst);
88
- for (const c of cmds) {
89
- if (!dryRun) {
90
- fsCopyFile(c.path, join(cmdsDst, c.filename));
91
- }
92
- log.success(`${tag}Command: ${c.filename}`);
93
- fileCount++;
94
- }
57
+ if (skillsClaude) {
58
+ if (!dryRun) fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
59
+ log.success(`${tag}skills/CLAUDE.md`);
60
+ count++;
95
61
  }
96
62
  }
97
63
 
98
- // Write manifest
99
- if (!dryRun) {
100
- const hashes = computeDirectoryHashes(installDir);
101
- const manifest = createManifest(getPackageVersion(), hashes);
102
- writeManifest(installDir, manifest);
103
- log.success('manifest.json 已生成');
104
- }
64
+ // Claude Code: commands → .claude/commands/
65
+ if (ides.includes('claude')) {
66
+ const cmdsDst = join(dir, '.claude', 'commands');
67
+ log.heading(`复制 Commands ${cmdsDst}`);
105
68
 
106
- log.done(`初始化完成${dryRun ? ' (dry-run)' : ''}!共 ${fileCount} 个组件。`);
69
+ const cmds = discoverCommands();
70
+ if (!dryRun) ensureDir(cmdsDst);
71
+ for (const c of cmds) {
72
+ if (!dryRun) fsCopyFile(c.path, join(cmdsDst, c.filename));
73
+ log.success(`${tag}Command: ${c.filename}`);
74
+ count++;
75
+ }
76
+ }
107
77
 
108
- console.log(`
109
- 集成说明:
110
- Cursor: 将 ${installDir}/skills 设为 agent skills 目录
111
- Claude Code: 将 ${installDir}/commands/*.md 链接到 .claude/commands/
112
- Hooks: 将 ${installDir}/hooks/ 链接到 ~/.cursor/hooks/ 或 ~/.claude/hooks/
113
- 更新: 运行 team-skills update
114
- `);
78
+ log.done(`初始化完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
79
+ log.info('提示:Hooks 仅在全局安装 (setup) 模式下生效,init 不安装 hooks。');
115
80
  }
@@ -2,11 +2,10 @@ import { join } from 'node:path';
2
2
  import { existsSync, readlinkSync } from 'node:fs';
3
3
  import {
4
4
  DEFAULT_SKILLS_TARGET, DEFAULT_COMMANDS_TARGET,
5
- CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR, LOCAL_INSTALL_DIR,
5
+ CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR,
6
6
  } from '../lib/constants.js';
7
7
  import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks } from '../lib/inventory.js';
8
8
  import { isSymlink } from '../lib/fs-utils.js';
9
- import { readManifest } from '../lib/manifest.js';
10
9
  import * as log from '../lib/logger.js';
11
10
 
12
11
  export function registerList(program) {
@@ -20,7 +19,7 @@ export function registerList(program) {
20
19
 
21
20
  function runList(opts) {
22
21
  const { target, json } = opts;
23
- const results = { skills: [], rules: [], commands: [], hooks: [], localInit: null };
22
+ const results = { skills: [], rules: [], commands: [], hooks: [] };
24
23
 
25
24
  // Check symlink-based install
26
25
  const skills = discoverSkills();
@@ -28,7 +27,6 @@ function runList(opts) {
28
27
  const dest = join(target, skill.name);
29
28
  results.skills.push({
30
29
  name: skill.name,
31
- type: 'symlink',
32
30
  status: getStatus(dest, skill.path),
33
31
  path: dest,
34
32
  });
@@ -67,17 +65,6 @@ function runList(opts) {
67
65
  }
68
66
  }
69
67
 
70
- // Check for local init
71
- const localManifest = readManifest(LOCAL_INSTALL_DIR);
72
- if (localManifest) {
73
- results.localInit = {
74
- version: localManifest.version,
75
- installedAt: localManifest.installedAt,
76
- sourceCommit: localManifest.sourceCommit,
77
- fileCount: Object.keys(localManifest.files).length,
78
- };
79
- }
80
-
81
68
  if (json) {
82
69
  console.log(JSON.stringify(results, null, 2));
83
70
  return;
@@ -96,16 +83,8 @@ function runList(opts) {
96
83
  log.heading('Hooks');
97
84
  printTable(results.hooks);
98
85
 
99
- if (results.localInit) {
100
- log.heading('项目内安装 (.team-skills/)');
101
- log.info(`版本: ${results.localInit.version}`);
102
- log.info(`安装时间: ${results.localInit.installedAt}`);
103
- log.info(`来源 commit: ${results.localInit.sourceCommit}`);
104
- log.info(`文件数: ${results.localInit.fileCount}`);
105
- }
106
-
107
86
  // Summary
108
- const installed = results.skills.filter(s => s.status === 'ok').length;
87
+ const installed = results.skills.filter(s => s.status === 'ok' || s.status === 'file').length;
109
88
  const total = results.skills.length;
110
89
  console.log(`\nSkills: ${installed}/${total} 已安装`);
111
90
  }
@@ -14,18 +14,20 @@ export function registerSetup(program) {
14
14
  .argument('[target]', 'Target skills directory', DEFAULT_SKILLS_TARGET)
15
15
  .option('--no-hooks', 'Skip hook installation')
16
16
  .option('--no-commands', 'Skip Claude Code command symlinks')
17
+ .option('--with-score', 'Include team-score skill (hidden by default)', false)
17
18
  .option('--force', 'Overwrite existing symlinks', false)
18
19
  .option('--dry-run', 'Show what would be done without doing it', false)
19
20
  .action(runSetup);
20
21
  }
21
22
 
22
23
  function runSetup(target, opts) {
23
- const { hooks, commands, force, dryRun } = opts;
24
+ const { hooks, commands, withScore, force, dryRun } = opts;
24
25
  const tag = dryRun ? '[dry-run] ' : '';
26
+ const exclude = withScore ? [] : ['team-score'];
25
27
  let count = 0;
26
28
 
27
29
  log.heading('安装 Agent Skills');
28
- const skills = discoverSkills();
30
+ const skills = discoverSkills(PACKAGE_ROOT, { exclude });
29
31
  for (const skill of skills) {
30
32
  const dest = join(target, skill.name);
31
33
  const result = createSymlinkSafe(skill.path, dest, { force, dryRun });
@@ -84,21 +86,28 @@ function runSetup(target, opts) {
84
86
  }
85
87
  }
86
88
 
87
- log.heading('验证安装');
88
- let errors = 0;
89
- for (const skill of skills) {
90
- const dest = join(target, skill.name);
91
- if (isSymlink(dest)) {
92
- log.success(`${skill.name}`);
93
- } else if (!dryRun) {
94
- log.error(`${skill.name} 未正确安装`);
95
- errors++;
89
+ if (!dryRun) {
90
+ log.heading('验证安装');
91
+ let errors = 0;
92
+ const verify = (label, dest) => {
93
+ if (isSymlink(dest)) {
94
+ log.success(label);
95
+ } else {
96
+ log.error(`${label} 未正确安装`);
97
+ errors++;
98
+ }
99
+ };
100
+ for (const skill of skills) verify(`Skill: ${skill.name}`, join(target, skill.name));
101
+ for (const rule of rules) verify(`Rule: ${rule.name}`, join(rulesTarget, rule.name));
102
+ if (commands !== false) {
103
+ for (const cmd of discoverCommands()) {
104
+ verify(`Command: ${cmd.filename}`, join(DEFAULT_COMMANDS_TARGET, cmd.filename));
105
+ }
106
+ }
107
+ if (errors > 0) {
108
+ log.error(`有 ${errors} 个组件安装异常,请检查。`);
109
+ process.exit(1);
96
110
  }
97
- }
98
-
99
- if (errors > 0) {
100
- log.error(`有 ${errors} 个组件安装异常,请检查。`);
101
- process.exit(1);
102
111
  }
103
112
 
104
113
  log.done(`安装完成!${dryRun ? '(dry-run)' : `共处理 ${count} 个组件。`}`);
@@ -13,14 +13,14 @@ export function registerUninstall(program) {
13
13
  .command('uninstall')
14
14
  .description('Remove all team-skills symlinks')
15
15
  .argument('[target]', 'Target skills directory', DEFAULT_SKILLS_TARGET)
16
- .option('--keep-hooks', 'Do not remove hooks')
17
- .option('--keep-commands', 'Do not remove Claude Code commands')
16
+ .option('--no-hooks', 'Skip removing hooks')
17
+ .option('--no-commands', 'Skip removing commands')
18
18
  .option('--dry-run', 'Show what would be removed', false)
19
19
  .action(runUninstall);
20
20
  }
21
21
 
22
22
  function runUninstall(target, opts) {
23
- const { keepHooks, keepCommands, dryRun } = opts;
23
+ const { hooks, commands, dryRun } = opts;
24
24
  let removed = 0;
25
25
 
26
26
  log.heading('移除 Agent Skills');
@@ -56,7 +56,7 @@ function runUninstall(target, opts) {
56
56
  }
57
57
  if (!dryRun) rmdirIfEmpty(join(target, '_team-rules'));
58
58
 
59
- if (!keepCommands) {
59
+ if (commands !== false) {
60
60
  log.heading('移除 Command Skills + Claude Code 命令');
61
61
  for (const cmd of discoverCommands()) {
62
62
  // Command Skill directory
@@ -88,7 +88,7 @@ function runUninstall(target, opts) {
88
88
  }
89
89
  }
90
90
 
91
- if (!keepHooks) {
91
+ if (hooks !== false) {
92
92
  log.heading('移除 Hooks');
93
93
  const hookFiles = discoverHooks();
94
94
  for (const dir of [CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR]) {
@@ -1,118 +1,136 @@
1
1
  import { join } from 'node:path';
2
- import { existsSync, readdirSync, statSync, copyFileSync as fsCopyFile } from 'node:fs';
3
- import { LOCAL_INSTALL_DIR, PACKAGE_ROOT, SKILLS_DIR, HOOKS_DIR, COMMANDS_DIR } from '../lib/constants.js';
4
- import { computeDirectoryHashes, computeFileHash, ensureDir } from '../lib/fs-utils.js';
5
- import { readManifest, writeManifest, createManifest, getPackageVersion } from '../lib/manifest.js';
2
+ import { existsSync, copyFileSync as fsCopyFile, rmSync, readdirSync } from 'node:fs';
3
+ import { execSync } from 'node:child_process';
4
+ import { PACKAGE_ROOT } from '../lib/constants.js';
5
+ import { discoverSkills, discoverSharedRules, discoverCommands, discoverSkillsModuleClaude } from '../lib/inventory.js';
6
+ import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
7
+ import { detectIDE } from '../lib/detect-ide.js';
6
8
  import * as log from '../lib/logger.js';
7
- import { dirname } from 'node:path';
8
9
 
9
10
  export function registerUpdate(program) {
10
11
  program
11
12
  .command('update')
12
- .description('Update previously init\'d skills to latest version')
13
+ .description('Upgrade team-skills package and update project installations')
13
14
  .argument('[dir]', 'Project directory', '.')
14
- .option('--force', 'Overwrite locally modified files (creates .bak backup)', false)
15
+ .option('--ide <type>', 'Force IDE type: claude, cursor, or both')
16
+ .option('--with-score', 'Include team-score skill (hidden by default)', false)
17
+ .option('--skip-self', 'Skip self-upgrade, only update project', false)
15
18
  .option('--dry-run', 'Show what would change', false)
16
19
  .action(runUpdate);
17
20
  }
18
21
 
19
- function runUpdate(dir, opts) {
20
- const { force, dryRun } = opts;
21
- const installDir = join(dir, LOCAL_INSTALL_DIR);
22
- const manifest = readManifest(installDir);
22
+ function upgradeSelf(dryRun) {
23
+ log.heading('升级 team-skills 包');
24
+ try {
25
+ const current = execSync(
26
+ 'npm view team-skills version --registry https://registry.npmjs.org 2>/dev/null',
27
+ { encoding: 'utf8' },
28
+ ).trim();
29
+ const local = execSync(
30
+ `node -e "console.log(require('${join(PACKAGE_ROOT, 'package.json')}').version)"`,
31
+ { encoding: 'utf8' },
32
+ ).trim();
33
+ if (current === local) {
34
+ log.skip(`已是最新版本 (${local})`);
35
+ return;
36
+ }
37
+ log.info(`${local} → ${current}`);
38
+ if (!dryRun) {
39
+ execSync('npm install -g team-skills@latest --registry https://registry.npmjs.org', { stdio: 'inherit' });
40
+ log.success(`已升级到 ${current}`);
41
+ } else {
42
+ log.success(`[dry-run] 将升级到 ${current}`);
43
+ }
44
+ } catch {
45
+ log.warn('自升级跳过(无法访问 npm registry 或非全局安装)');
46
+ }
47
+ }
23
48
 
24
- if (!manifest) {
25
- log.error(`未找到 ${installDir}/manifest.json。请先运行 team-skills init。`);
26
- process.exit(1);
49
+ function cleanStaleSkills(targetDir, currentNames, dryRun) {
50
+ if (!existsSync(targetDir)) return;
51
+ const existing = readdirSync(targetDir).filter(
52
+ name => !name.startsWith('_') && name !== 'CLAUDE.md',
53
+ );
54
+ for (const name of existing) {
55
+ if (!currentNames.has(name)) {
56
+ const tag = dryRun ? '[dry-run] ' : '';
57
+ if (!dryRun) rmSync(join(targetDir, name), { recursive: true });
58
+ log.warn(`${tag}移除旧 Skill: ${name}`);
59
+ }
27
60
  }
61
+ }
62
+
63
+ function runUpdate(dir, opts) {
64
+ const { ide, withScore, skipSelf, dryRun } = opts;
65
+
66
+ if (!skipSelf) upgradeSelf(dryRun);
28
67
 
29
- const currentVersion = getPackageVersion();
30
- log.info(`已安装版本: ${manifest.version} | 最新版本: ${currentVersion}`);
68
+ const exclude = withScore ? [] : ['team-score'];
69
+ const ides = detectIDE(dir, ide);
70
+
71
+ if (ides.length === 0) {
72
+ log.info('当前项目未检测到 IDE 配置(.claude/ 或 .cursor/),跳过项目更新。');
73
+ return;
74
+ }
31
75
 
32
76
  const tag = dryRun ? '[dry-run] ' : '';
33
- const sourceMap = buildSourceMap();
77
+ let count = 0;
34
78
 
35
- let updated = 0;
36
- let skipped = 0;
37
- let added = 0;
79
+ log.heading('更新项目中的 team-skills');
80
+ log.info(`项目目录: ${dir}`);
81
+ log.info(`目标 IDE: ${ides.join(', ')}`);
38
82
 
39
- log.heading('检查文件更新');
83
+ const skills = discoverSkills(PACKAGE_ROOT, { exclude });
84
+ const rules = discoverSharedRules();
85
+ const skillsClaude = discoverSkillsModuleClaude();
86
+ const currentSkillNames = new Set(skills.map(s => s.name));
40
87
 
41
- for (const [relPath, sourceFullPath] of Object.entries(sourceMap)) {
42
- const installedPath = join(installDir, relPath);
43
- const sourceHash = computeFileHash(sourceFullPath);
88
+ // Cursor skills .cursor/skills/
89
+ if (ides.includes('cursor')) {
90
+ const skillsDst = join(dir, '.cursor', 'skills');
91
+ log.heading(`更新 Skills → ${skillsDst}`);
44
92
 
45
- if (!existsSync(installedPath)) {
93
+ cleanStaleSkills(skillsDst, currentSkillNames, dryRun);
94
+
95
+ if (!dryRun) ensureDir(skillsDst);
96
+ for (const skill of skills) {
97
+ const dest = join(skillsDst, skill.name);
46
98
  if (!dryRun) {
47
- ensureDir(dirname(installedPath));
48
- fsCopyFile(sourceFullPath, installedPath);
99
+ if (existsSync(dest)) rmSync(dest, { recursive: true });
100
+ copyRecursive(skill.path, dest);
49
101
  }
50
- log.success(`${tag}新增: ${relPath}`);
51
- added++;
52
- continue;
102
+ log.success(`${tag}Skill: ${skill.name}`);
103
+ count++;
53
104
  }
54
105
 
55
- const installedHash = computeFileHash(installedPath);
56
- if (installedHash === sourceHash) continue;
57
-
58
- const manifestHash = manifest.files[relPath];
59
- if (manifestHash && installedHash !== manifestHash && !force) {
60
- log.warn(`跳过: ${relPath}(本地已修改,使用 --force 覆盖)`);
61
- skipped++;
62
- continue;
106
+ const rulesDst = join(skillsDst, '_team-rules');
107
+ if (!dryRun) ensureDir(rulesDst);
108
+ for (const r of rules) {
109
+ if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
110
+ log.success(`${tag}Rule: ${r.name}`);
111
+ count++;
63
112
  }
64
113
 
65
- if (!dryRun) {
66
- if (force && manifestHash && installedHash !== manifestHash) {
67
- fsCopyFile(installedPath, installedPath + '.bak');
68
- log.info(`备份: ${relPath}.bak`);
69
- }
70
- fsCopyFile(sourceFullPath, installedPath);
114
+ if (skillsClaude) {
115
+ if (!dryRun) fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
116
+ log.success(`${tag}skills/CLAUDE.md`);
117
+ count++;
71
118
  }
72
- log.success(`${tag}更新: ${relPath}`);
73
- updated++;
74
119
  }
75
120
 
76
- for (const relPath of Object.keys(manifest.files)) {
77
- if (!sourceMap[relPath]) {
78
- log.warn(`源文件已删除: ${relPath}(如不再需要请手动删除)`);
121
+ // Claude Code commands .claude/commands/
122
+ if (ides.includes('claude')) {
123
+ const cmdsDst = join(dir, '.claude', 'commands');
124
+ log.heading(`更新 Commands → ${cmdsDst}`);
125
+
126
+ const cmds = discoverCommands();
127
+ if (!dryRun) ensureDir(cmdsDst);
128
+ for (const c of cmds) {
129
+ if (!dryRun) fsCopyFile(c.path, join(cmdsDst, c.filename));
130
+ log.success(`${tag}Command: ${c.filename}`);
131
+ count++;
79
132
  }
80
133
  }
81
134
 
82
- if (!dryRun) {
83
- const newHashes = computeDirectoryHashes(installDir);
84
- delete newHashes['manifest.json'];
85
- const newManifest = createManifest(currentVersion, newHashes);
86
- writeManifest(installDir, newManifest);
87
- }
88
-
89
- log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!更新 ${updated},新增 ${added},跳过 ${skipped}。`);
90
- }
91
-
92
- function buildSourceMap() {
93
- const map = {};
94
-
95
- const skillsDir = join(PACKAGE_ROOT, SKILLS_DIR);
96
- if (existsSync(skillsDir)) scanRecursive(skillsDir, 'skills', map);
97
-
98
- const hooksDir = join(PACKAGE_ROOT, HOOKS_DIR);
99
- if (existsSync(hooksDir)) scanRecursive(hooksDir, 'hooks', map);
100
-
101
- const cmdsDir = join(PACKAGE_ROOT, COMMANDS_DIR);
102
- if (existsSync(cmdsDir)) scanRecursive(cmdsDir, 'commands', map);
103
-
104
- return map;
105
- }
106
-
107
- function scanRecursive(baseDir, prefix, map) {
108
- const entries = readdirSync(baseDir, { withFileTypes: true });
109
- for (const entry of entries) {
110
- const fullPath = join(baseDir, entry.name);
111
- const relPath = `${prefix}/${entry.name}`;
112
- if (entry.isDirectory()) {
113
- scanRecursive(fullPath, relPath, map);
114
- } else {
115
- map[relPath] = fullPath;
116
- }
117
- }
135
+ log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
118
136
  }
@@ -10,8 +10,6 @@ export const DEFAULT_SKILLS_TARGET = join(homedir(), '.agents', 'skills');
10
10
  export const DEFAULT_COMMANDS_TARGET = join(homedir(), '.claude', 'commands');
11
11
  export const CURSOR_HOOKS_DIR = join(homedir(), '.cursor', 'hooks');
12
12
  export const CLAUDE_HOOKS_DIR = join(homedir(), '.claude', 'hooks');
13
- export const LOCAL_INSTALL_DIR = '.team-skills';
14
- export const MANIFEST_FILE = 'manifest.json';
15
13
  export const SKILLS_DIR = 'skills';
16
14
  export const HOOKS_DIR = 'hooks';
17
15
  export const COMMANDS_DIR = join('.claude', 'commands');
@@ -0,0 +1,28 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import * as log from './logger.js';
4
+
5
+ export function detectIDE(projectDir, forceIDE, { strict = false } = {}) {
6
+ if (forceIDE) {
7
+ if (!['claude', 'cursor', 'both'].includes(forceIDE)) {
8
+ log.error(`不支持的 IDE 类型: ${forceIDE}。可选: claude, cursor, both`);
9
+ process.exit(1);
10
+ }
11
+ return forceIDE === 'both' ? ['claude', 'cursor'] : [forceIDE];
12
+ }
13
+
14
+ const detected = [];
15
+ if (existsSync(join(projectDir, '.claude'))) detected.push('claude');
16
+ if (existsSync(join(projectDir, '.cursor'))) detected.push('cursor');
17
+
18
+ if (detected.length === 0 && strict) {
19
+ log.error('未检测到项目级 IDE 配置(.claude/ 或 .cursor/ 目录)。');
20
+ log.info('请使用 --ide 指定目标 IDE:');
21
+ log.info(' --ide claude 仅安装 Claude Code 命令');
22
+ log.info(' --ide cursor 仅安装 Cursor skills');
23
+ log.info(' --ide both 同时安装两者');
24
+ process.exit(1);
25
+ }
26
+
27
+ return detected;
28
+ }
@@ -2,13 +2,14 @@ import { readdirSync, statSync, existsSync } from 'node:fs';
2
2
  import { join, basename } from 'node:path';
3
3
  import { PACKAGE_ROOT, SKILLS_DIR, HOOKS_DIR, COMMANDS_DIR } from './constants.js';
4
4
 
5
- export function discoverSkills(root = PACKAGE_ROOT) {
5
+ export function discoverSkills(root = PACKAGE_ROOT, { exclude = [] } = {}) {
6
6
  const skillsDir = join(root, SKILLS_DIR);
7
7
  if (!existsSync(skillsDir)) return [];
8
8
 
9
9
  return readdirSync(skillsDir)
10
10
  .filter(name => {
11
11
  if (name.startsWith('_') || name === 'CLAUDE.md') return false;
12
+ if (exclude.includes(name)) return false;
12
13
  const full = join(skillsDir, name);
13
14
  return statSync(full).isDirectory();
14
15
  })
@@ -1,45 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { execSync } from 'node:child_process';
4
- import { MANIFEST_FILE, PACKAGE_ROOT } from './constants.js';
5
-
6
- export function readManifest(dir) {
7
- const p = join(dir, MANIFEST_FILE);
8
- if (!existsSync(p)) return null;
9
- try {
10
- return JSON.parse(readFileSync(p, 'utf8'));
11
- } catch {
12
- return null;
13
- }
14
- }
15
-
16
- export function writeManifest(dir, data) {
17
- const p = join(dir, MANIFEST_FILE);
18
- writeFileSync(p, JSON.stringify(data, null, 2) + '\n', 'utf8');
19
- }
20
-
21
- export function createManifest(packageVersion, fileHashes) {
22
- return {
23
- version: packageVersion,
24
- installedAt: new Date().toISOString(),
25
- sourceCommit: getSourceCommit(),
26
- files: fileHashes,
27
- };
28
- }
29
-
30
- function getSourceCommit() {
31
- try {
32
- return execSync('git rev-parse --short HEAD', {
33
- cwd: PACKAGE_ROOT,
34
- encoding: 'utf8',
35
- stdio: ['pipe', 'pipe', 'pipe'],
36
- }).trim();
37
- } catch {
38
- return 'unknown';
39
- }
40
- }
41
-
42
- export function getPackageVersion() {
43
- const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
44
- return pkg.version;
45
- }