@ultime/codecraft 1.0.3 → 1.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 - 2026-10-27
4
+
5
+ - 新增根 `SKILL.md`,兼容 Agent Skills / `skills-npm`;安装 `@ultime/codecraft` 后可被自动发现。
6
+ - 新增 `codecraft install`:自动检测 Claude / Trae / Cursor,写入 `.agents/skills` 与对应目录;支持 `--agent` / `--global` / `--dest` / `--force` / `--dry-run`。
7
+ - README 增加三种安装方式:`skills-npm`、内置安装器、手动指针。
8
+ - `docs/07-ai-prompts.md` 与 adapters 补充安装入口。
9
+
3
10
  ## 1.0.3 - 2026-10-27
4
11
 
5
12
  - 文档从 v0 草稿固化为 v1 稳定版。
package/README.md CHANGED
@@ -19,29 +19,47 @@
19
19
 
20
20
  规则正文只在 [`docs/`](./docs/);其它位置只放入口与指针,避免第二份真源。
21
21
 
22
- > 第一次用:直接看 [5 分钟上手](#5-分钟上手)。
22
+ > 第一次用:直接看 [安装](#安装) 和 [第一次使用](#第一次使用)。
23
23
 
24
- ## 5 分钟上手
24
+ ## 安装
25
25
 
26
- Codecraft 有两种用法:**让 AI 按规范工作**(skill)和**在本地跑检查**(CLI)。先完成第 1、2 步就能用起来。
26
+ ### 方式 A:npm + skills-npm(推荐,多项目 / 团队)
27
27
 
28
- ### 第 1 步:把入口接到 agent
28
+ ```bash
29
+ npm i -D @ultime/codecraft skills-npm
30
+ npx skills-npm setup
31
+ ```
32
+
33
+ 之后每次 `npm install` 都会把 `node_modules/@ultime/codecraft` 里的 skill 同步到 agent 目录:`.agents/skills/codecraft`,以及检测到的 Claude / Trae / Cursor 目录。需要 Node.js >= 22.20(`skills-npm` 的要求)。
29
34
 
30
- Codecraft 不是装完 npm 包就自动出现在 agent 里。选一种方式接入:
35
+ ### 方式 B:Codecraft 自带安装器(单次,无额外依赖)
36
+
37
+ ```bash
38
+ npx @ultime/codecraft install
39
+ ```
40
+
41
+ 自动检测当前项目的 agent,把 `SKILL.md`、`docs/`、`scenarios/`、`prompts/` 复制到 `.agents/skills/codecraft` 和对应 agent 目录(`.claude/skills`、`.trae/skills`、`.cursor/skills`)。
42
+
43
+ ```bash
44
+ npx @ultime/codecraft install --agent claude # 指定 agent,可逗号分隔
45
+ npx @ultime/codecraft install --global --agent claude # 装到用户目录
46
+ npx @ultime/codecraft install --dry-run # 只看计划
47
+ npx @ultime/codecraft install --force # 覆盖已有目录
48
+ ```
31
49
 
32
- **方式 A:指针式(推荐)**
50
+ ### 方式 C:手动指针
33
51
 
34
- 在项目规则里留一行,让 agent 做代码任务时来读:
52
+ 不复制文件,只在项目规则里留一行:
35
53
 
36
- - Trae:项目 `.trae/rules/codecraft.md`
37
- - Claude:项目 `CLAUDE.md` 或 `~/.claude/CLAUDE.md`
38
- - 其他 agent:项目 `AGENTS.md`
54
+ - Trae:`.trae/rules/codecraft.md`
55
+ - Claude:`CLAUDE.md` 或 `~/.claude/CLAUDE.md`
56
+ - 其他:`AGENTS.md`
39
57
 
40
58
  ```md
41
59
  代码任务先读 <Codecraft 路径>/codecraft/adapters/trae/SKILL.md
42
60
  ```
43
61
 
44
- 平台对应的入口文件:
62
+ 平台入口:
45
63
 
46
64
  | 平台 | 入口文件 |
47
65
  |---|---|
@@ -49,11 +67,7 @@ Codecraft 不是装完 npm 包就自动出现在 agent 里。选一种方式接
49
67
  | Trae | [`adapters/trae/SKILL.md`](./adapters/trae/SKILL.md) |
50
68
  | Claude | [`adapters/claude/SKILL.md`](./adapters/claude/SKILL.md) |
51
69
 
52
- **方式 B:复制到 skills 目录**
53
-
54
- 把对应平台的入口文件放进 agent 的 skills 目录。必须让 `docs/`、`scenarios/`、`prompts/` 与该文件保持 `../../` 的相对位置(符号链接即可),否则入口里的路径会失效。
55
-
56
- ### 第 2 步:对 AI 说第一句话
70
+ ## 第一次使用
57
71
 
58
72
  ```text
59
73
  用 Codecraft 审计本项目,只读,不改文件。
@@ -63,18 +77,11 @@ Codecraft 不是装完 npm 包就自动出现在 agent 里。选一种方式接
63
77
 
64
78
  更多场景(只出报告、新项目、重构评审、架构设计、模式选型、脚手架、Design System)见 [docs/07-ai-prompts.md](./docs/07-ai-prompts.md)。
65
79
 
66
- ### 第 3 步:需要本地检查时再装 CLI
80
+ 需要本地检查时再装 CLI:
67
81
 
68
82
  ```bash
69
83
  npx @ultime/codecraft check --root .
70
- npx @ultime/codecraft@latest check --root .
71
- ```
72
-
73
- 安装为项目依赖:
74
-
75
- ```bash
76
- npm i -D @ultime/codecraft
77
- npx codecraft check --root .
84
+ npx codecraft check --root . # 已安装为项目依赖时
78
85
  ```
79
86
 
80
87
  本地开发 / 未发布时用 tarball:
@@ -85,8 +92,6 @@ npm i -D /path/to/ultime-codecraft-<version>.tgz
85
92
  npx codecraft check --root .
86
93
  ```
87
94
 
88
- 也可以 `pnpm link --global /path/to/codecraft`,之后在任意目录使用 `codecraft`。
89
-
90
95
  ## 模式
91
96
 
92
97
  | 模式 | 是否改文件 | 对应命令 |
@@ -107,6 +112,7 @@ codecraft <command> [options]
107
112
  | `audit` | 内置规则 + `codecraft.config.json` 里的 gates,出只读报告 |
108
113
  | `plan` | 把 findings 转成只读升级计划;默认不跑 gates |
109
114
  | `init` | 生成 app / library 骨架;默认 dry-run |
115
+ | `install` | 把 skill 安装到 agent skills 目录;默认写入,`--dry-run` 只看计划 |
110
116
  | `version` | 输出版本号 |
111
117
 
112
118
  ### 常用
@@ -121,6 +127,9 @@ codecraft audit --config codecraft.config.json --fail-fast
121
127
  codecraft plan --root . --with-gates
122
128
  codecraft init --root . --name demo # dry-run
123
129
  codecraft init --root . --name demo --apply # 写盘
130
+ codecraft install # 自动检测并安装
131
+ codecraft install --agent trae,claude --force
132
+ codecraft install --dry-run
124
133
  ```
125
134
 
126
135
  ### 参数
@@ -138,13 +147,18 @@ codecraft init --root . --name demo --apply # 写盘
138
147
  | `--with-gates` | — | 关 | `plan` 执行 gates |
139
148
  | `--name <name>` | 字符串 | — | `init` 的 app / package 名称;`--apply` 时必填 |
140
149
  | `--apply` | — | 关 | `init` 写盘;已存在路径跳过,不覆盖 |
150
+ | `--agent <list>` | `auto` / `claude` / `trae` / `cursor` / `generic` | `auto` | `install` 目标,可逗号分隔 |
151
+ | `--global` | — | 关 | `install` 写入用户目录 |
152
+ | `--dest <path>` | 目录 | — | `install` 的显式目标 |
153
+ | `--force` | — | 关 | `install` 覆盖已有 codecraft 目录 |
154
+ | `--dry-run` | — | 关 | `install` 只输出计划,不写盘 |
141
155
 
142
156
  ### 退出码
143
157
 
144
158
  | 码 | 含义 |
145
159
  |---|---|
146
160
  | `0` | 通过;warning 不阻断 |
147
- | `1` | 有 error findings,或有 gate 失败 |
161
+ | `1` | 有 error findings、install 失败,或有 gate 失败 |
148
162
  | `2` | 用法错误、root 不存在、配置非法 |
149
163
 
150
164
  ### 输出
@@ -194,14 +208,16 @@ A4(单一收口)属于结构判断,由 review 与项目门禁负责,不
194
208
  ## 目录
195
209
 
196
210
  ```text
211
+ SKILL.md 标准 agent skill 入口(skills-npm / install 使用)
197
212
  docs/ 宪法、族卡、AI 契约、风格总图、模式、架构、AI 提示词、门禁
198
213
  scenarios/ 新项目 / 旧项目审计 / 只出报告 / 重构评审
199
- adapters/ generic / Trae / Claude 入口
214
+ adapters/ generic / Trae / Claude 手动指针
200
215
  prompts/ 输出格式
201
- src/ + bin/ CLI
216
+ src/ + bin/ CLI(含 install)
202
217
  tests/ node:test 用例
203
218
  templates/ init --apply 的 app / library 骨架
204
219
  evals/ golden tasks(人工回归)
220
+ ROADMAP.md 后续版本计划与交接说明
205
221
  ```
206
222
 
207
223
  ## 版本与稳定承诺
@@ -209,6 +225,7 @@ evals/ golden tasks(人工回归)
209
225
  - semver;当前版本见 [VERSION](./VERSION)。
210
226
  - 规则 ID `A1–A21` 稳定;增删规则属于 minor 以上的变更。
211
227
  - 报告始终携带版本号;跨版本对比以 JSON 为准。
228
+ - 后续计划:[ROADMAP.md](https://github.com/ultime-space/skills/blob/main/codecraft/ROADMAP.md)。
212
229
 
213
230
  ## 边界
214
231
 
package/SKILL.md ADDED
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: codecraft
3
+ description: 语言无关的代码规范(Codecraft)。用于项目代码审计与分析、架构设计、重构评审、代码规范检查和脚手架。Language-agnostic code standard for auditing, reviewing, designing, and scaffolding code.
4
+ ---
5
+
6
+ # Codecraft(代码手艺)
7
+
8
+ ## 先读
9
+
10
+ 1. 项目 `AGENTS.md` / `rules/`
11
+ 2. `docs/00-constitution.md`
12
+ 3. 只读相关族卡:`docs/01-family-cards.md`
13
+ 4. 设计架构时:`docs/06-architecture.md`
14
+ 5. 需要选型时:`docs/05-patterns.md`
15
+ 6. 选一个场景:`scenarios/`
16
+ 7. 按 `docs/02-ai-contract.md` 执行
17
+ 8. 按 `prompts/output.md` 输出
18
+ 9. 不知道怎么说时:`docs/07-ai-prompts.md`
19
+
20
+ ## 模式
21
+
22
+ - **Audit(审计)**:只读,出报告。
23
+ - **Plan(计划)**:只读,出架构 / 升级计划。
24
+ - **Apply(执行)**:改代码 + 跑门禁。
25
+
26
+ ## 工作流
27
+
28
+ 1. 判定任务属于哪个场景。
29
+ 2. 只读相关族卡,不要一次加载全部。
30
+ 3. 先出计划;Apply 前确认范围。
31
+ 4. 跑项目验证命令;不能跑要说明原因。
32
+ 5. 按 `prompts/output.md` 输出。
33
+
34
+ ## 禁止
35
+
36
+ - Audit / Plan 模式改文件。
37
+ - 把业务逻辑放进 Transport 或 Page。
38
+ - 内部代码 import `external/` 或生成类型。
39
+ - 手改生成物。
40
+ - 写敏感信息;发现即停止、脱敏、提示轮换。
41
+ - 安全红线登记例外。
42
+
43
+ 规则正文只在 `docs/`;本文件只做入口。
package/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.3
1
+ 1.1.0
@@ -9,4 +9,6 @@ Core 是 `docs/` + `scenarios/` + `scripts/`;adapters 只做入口,不复制
9
9
 
10
10
  规则:adapter 只写“读什么、按什么流程、输出什么”,规则正文留在 `docs/`。
11
11
 
12
+ 包根的 [`SKILL.md`](../SKILL.md) 是标准 agent skill 入口(`skills-npm` / `codecraft install` 使用);本目录的平台文件用于手动指针接入。两者二选一,不要同时维护两套正文。
13
+
12
14
  使用者提示词(怎么对 AI 说):[`../docs/07-ai-prompts.md`](../docs/07-ai-prompts.md)。
@@ -6,7 +6,22 @@
6
6
 
7
7
  ## 1. 先让 AI 找到 Codecraft
8
8
 
9
- 在项目的 agent 入口里留一条指针,不要复制规则:
9
+ 最快的方式:
10
+
11
+ ```bash
12
+ npx @ultime/codecraft install
13
+ ```
14
+
15
+ 或走 npm 同步:
16
+
17
+ ```bash
18
+ npm i -D @ultime/codecraft skills-npm
19
+ npx skills-npm setup
20
+ ```
21
+
22
+ 装好后项目里会有 `.agents/skills/codecraft/SKILL.md`(以及检测到的 agent 目录)。
23
+
24
+ 也可以手动在 agent 入口里留一条指针,不要复制规则:
10
25
 
11
26
  - Trae:项目 `.trae/rules/codecraft.md`
12
27
  - Claude:`CLAUDE.md`
@@ -107,6 +107,13 @@
107
107
  - 期望:命中 A21;只报文件与行号,不回显密钥;提示轮换。
108
108
  - 失败信号:漏报;把密钥原文打印到报告;把普通变量名误报为密钥。
109
109
 
110
+ ## 16. 安装到 agent
111
+
112
+ - 输入:空项目,无 agent 配置目录。
113
+ - 命令:`codecraft install --root . --agent generic`;再执行一次;最后 `--force`。
114
+ - 期望:生成 `.agents/skills/codecraft/SKILL.md`、`docs/`、`scenarios/`、`prompts/`;第二次 skip;`--force` 覆盖。
115
+ - 失败信号:复制到错误目录;覆盖用户已有内容;`SKILL.md` 引用断链。
116
+
110
117
  ## 评分
111
118
 
112
119
  - 每条任务:pass / fail。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultime/codecraft",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Language-agnostic code standard as an installable skill package.",
5
5
  "keywords": [
6
6
  "ai",
@@ -10,7 +10,8 @@
10
10
  "audit",
11
11
  "architecture",
12
12
  "review",
13
- "scaffold"
13
+ "scaffold",
14
+ "agent-skills"
14
15
  ],
15
16
  "author": "UltimeLam",
16
17
  "license": "MIT",
@@ -30,6 +31,7 @@
30
31
  "codecraft": "bin/codecraft.mjs"
31
32
  },
32
33
  "files": [
34
+ "SKILL.md",
33
35
  "bin",
34
36
  "src",
35
37
  "docs",
package/src/cli.mjs CHANGED
@@ -4,6 +4,7 @@ import { check } from './check.mjs'
4
4
  import { loadConfig, runGates } from './gates.mjs'
5
5
  import { buildPlan, summarizePlan } from './plan.mjs'
6
6
  import { applyInitPlan, buildInitPlan } from './init.mjs'
7
+ import { applyInstallPlan, buildInstallPlan, parseAgents } from './install.mjs'
7
8
 
8
9
  const PACKAGE = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
9
10
  const VERSION = PACKAGE.version
@@ -15,12 +16,18 @@ Commands:
15
16
  audit check + configured gates; read-only report
16
17
  plan Read-only upgrade plan from findings
17
18
  init Scaffold an app or package (dry-run unless --apply)
19
+ install Install the skill into agent skills directories
18
20
  version Print version
19
21
 
20
22
  Options:
21
23
  --root <path> Project root (default: cwd)
22
24
  --pack app|library|design-system|auto Rule pack (default: auto)
23
25
  --config <path> Config file (default: codecraft.config.json)
26
+ --agent auto|claude|trae|cursor|generic Install target, comma-separated (default: auto)
27
+ --global Install into the home directory
28
+ --dest <path> Explicit destination (relative to root / home)
29
+ --force Replace an existing codecraft skill directory
30
+ --dry-run Show the install plan without writing
24
31
  --no-gates Skip gates in audit / plan
25
32
  --with-gates Run configured gates in plan
26
33
  --fail-fast Stop gates after the first failure
@@ -51,7 +58,7 @@ export function main(argv) {
51
58
  }
52
59
 
53
60
  const command = args.command || 'check'
54
- if (!['check', 'audit', 'plan', 'init'].includes(command)) {
61
+ if (!['check', 'audit', 'plan', 'init', 'install'].includes(command)) {
55
62
  console.error(`codecraft: unknown command "${command}"`)
56
63
  return 2
57
64
  }
@@ -62,6 +69,42 @@ export function main(argv) {
62
69
  return 2
63
70
  }
64
71
 
72
+ if (command === 'install') {
73
+ let agents
74
+ try {
75
+ agents = parseAgents(args.agent)
76
+ } catch (error) {
77
+ console.error(`codecraft: ${error.message}`)
78
+ return 2
79
+ }
80
+ try {
81
+ const plan = buildInstallPlan(root, { agents, global: args.global, dest: args.dest })
82
+ const applied = args.dryRun ? null : applyInstallPlan(plan, { force: args.force })
83
+ const meta = {
84
+ version: VERSION,
85
+ mode: 'install',
86
+ root,
87
+ scope: args.global ? 'global' : 'project',
88
+ agent: args.agent,
89
+ dest: args.dest || null,
90
+ dryRun: args.dryRun,
91
+ targets: plan.length,
92
+ created: applied?.created.length ?? 0,
93
+ replaced: applied?.replaced.length ?? 0,
94
+ skipped: applied?.skipped.length ?? 0,
95
+ }
96
+ if (args.format === 'json') {
97
+ console.log(JSON.stringify({ ...meta, plan, applied }, null, 2))
98
+ } else {
99
+ console.log(formatInstall(meta, plan, applied))
100
+ }
101
+ return plan.length ? 0 : 1
102
+ } catch (error) {
103
+ console.error(`codecraft: ${error.message}`)
104
+ return 1
105
+ }
106
+ }
107
+
65
108
  let config
66
109
  try {
67
110
  config = loadConfig(root, args.config)
@@ -180,6 +223,11 @@ function parseArgs(argv) {
180
223
  failFast: false,
181
224
  name: '',
182
225
  apply: false,
226
+ agent: 'auto',
227
+ global: false,
228
+ dest: '',
229
+ force: false,
230
+ dryRun: false,
183
231
  format: 'md',
184
232
  severity: 'warn',
185
233
  ci: false,
@@ -196,7 +244,12 @@ function parseArgs(argv) {
196
244
  else if (arg === '--with-gates') args.withGates = true
197
245
  else if (arg === '--fail-fast') args.failFast = true
198
246
  else if (arg === '--apply') args.apply = true
247
+ else if (arg === '--global') args.global = true
248
+ else if (arg === '--force') args.force = true
249
+ else if (arg === '--dry-run') args.dryRun = true
199
250
  else if (arg === '--name') args.name = argv[++i] ?? ''
251
+ else if (arg === '--agent') args.agent = argv[++i] ?? 'auto'
252
+ else if (arg === '--dest') args.dest = argv[++i] ?? ''
200
253
  else if (arg === '--root') args.root = argv[++i] ?? '.'
201
254
  else if (arg === '--pack') args.pack = argv[++i] ?? 'auto'
202
255
  else if (arg === '--config') args.config = argv[++i] ?? ''
@@ -329,6 +382,49 @@ function formatInit(meta, plan, applied) {
329
382
  return lines.filter((line, index) => !(line === '' && lines[index - 1] === '')).join('\n')
330
383
  }
331
384
 
385
+ function formatInstall(meta, plan, applied) {
386
+ const lines = [
387
+ '# Codecraft Install',
388
+ '',
389
+ `- Version: ${meta.version}`,
390
+ `- Mode: install`,
391
+ `- Scope: ${meta.scope}`,
392
+ `- Agent: ${meta.agent}`,
393
+ `- Root: ${meta.root}`,
394
+ `- Dest: ${meta.dest ?? '(auto)'}`,
395
+ `- Dry run: ${meta.dryRun}`,
396
+ `- Targets: ${meta.targets}`,
397
+ ]
398
+
399
+ if (applied) {
400
+ lines.push(
401
+ `- Created: ${meta.created}`,
402
+ `- Replaced: ${meta.replaced}`,
403
+ `- Skipped: ${meta.skipped}`,
404
+ )
405
+ }
406
+
407
+ if (!plan.length) {
408
+ lines.push('', '未找到可安装的目标;请用 `--agent` 指定,或先创建 agent 配置目录。')
409
+ } else {
410
+ lines.push('', '## Plan', '', '| Agent | Target | Action |', '|---|---|---|')
411
+ for (const entry of plan) {
412
+ lines.push(`| ${entry.agent} | ${entry.target} | ${entry.action} |`)
413
+ }
414
+ }
415
+
416
+ lines.push(
417
+ '',
418
+ '## Next',
419
+ '',
420
+ meta.dryRun
421
+ ? '- Re-run without `--dry-run` to write the skill.'
422
+ : '- Restart the agent or reload skills;然后说:`用 Codecraft 审计本项目,只读,不改文件。`',
423
+ )
424
+
425
+ return lines.join('\n')
426
+ }
427
+
332
428
  function header(meta) {
333
429
  return [
334
430
  `# Codecraft ${title(meta.mode)}`,
@@ -0,0 +1,100 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url))
7
+ const SKILL_FILES = ['SKILL.md']
8
+ const SKILL_DIRS = ['docs', 'scenarios', 'prompts']
9
+
10
+ const AGENT_DIRS = {
11
+ claude: '.claude/skills',
12
+ trae: '.trae/skills',
13
+ cursor: '.cursor/skills',
14
+ generic: '.agents/skills',
15
+ }
16
+
17
+ const AGENT_MARKERS = {
18
+ claude: ['.claude', 'CLAUDE.md'],
19
+ trae: ['.trae'],
20
+ cursor: ['.cursor'],
21
+ generic: ['.agents'],
22
+ }
23
+
24
+ export const AGENTS = Object.keys(AGENT_DIRS)
25
+
26
+ export function parseAgents(value) {
27
+ if (!value || value === 'auto') return []
28
+ const agents = [...new Set(value.split(',').map((item) => item.trim()).filter(Boolean))]
29
+ const invalid = agents.filter((agent) => !AGENTS.includes(agent))
30
+ if (invalid.length) {
31
+ throw new Error(`unknown agent: ${invalid.join(', ')}(可选:auto, ${AGENTS.join(', ')})`)
32
+ }
33
+ return agents
34
+ }
35
+
36
+ export function buildInstallPlan(root, options = {}) {
37
+ const base = options.global ? os.homedir() : path.resolve(root)
38
+ const explicit = options.agents ?? []
39
+ const invalid = explicit.filter((agent) => !AGENTS.includes(agent))
40
+ if (invalid.length) {
41
+ throw new Error(`unknown agent: ${invalid.join(', ')}(可选:${AGENTS.join(', ')})`)
42
+ }
43
+
44
+ const agents = explicit.length ? explicit : detectAgents(base, options.global)
45
+ const destinations = options.dest
46
+ ? [{ agent: 'custom', dir: path.resolve(base, options.dest) }]
47
+ : agents.map((agent) => ({ agent, dir: path.join(base, AGENT_DIRS[agent]) }))
48
+
49
+ assertSource()
50
+
51
+ return destinations.map(({ agent, dir }) => {
52
+ const target = path.join(dir, 'codecraft')
53
+ const exists = fs.existsSync(target)
54
+ return { agent, target, exists, action: exists ? 'exists' : 'create' }
55
+ })
56
+ }
57
+
58
+ export function applyInstallPlan(plan, options = {}) {
59
+ const created = []
60
+ const skipped = []
61
+ const replaced = []
62
+
63
+ for (const entry of plan) {
64
+ if (entry.exists && !options.force) {
65
+ skipped.push(entry.target)
66
+ continue
67
+ }
68
+ if (entry.exists) {
69
+ fs.rmSync(entry.target, { recursive: true, force: true })
70
+ replaced.push(entry.target)
71
+ }
72
+
73
+ fs.mkdirSync(entry.target, { recursive: true })
74
+ for (const file of SKILL_FILES) {
75
+ fs.copyFileSync(path.join(PACKAGE_ROOT, file), path.join(entry.target, file))
76
+ }
77
+ for (const dir of SKILL_DIRS) {
78
+ fs.cpSync(path.join(PACKAGE_ROOT, dir), path.join(entry.target, dir), { recursive: true })
79
+ }
80
+ created.push(entry.target)
81
+ }
82
+
83
+ return { created, skipped, replaced }
84
+ }
85
+
86
+ function detectAgents(base, global) {
87
+ const found = AGENTS.filter((agent) =>
88
+ AGENT_MARKERS[agent].some((marker) => fs.existsSync(path.join(base, marker))),
89
+ )
90
+ if (global) return found.length ? found : ['generic']
91
+ return [...new Set([...found, 'generic'])]
92
+ }
93
+
94
+ function assertSource() {
95
+ for (const file of SKILL_FILES) {
96
+ if (!fs.existsSync(path.join(PACKAGE_ROOT, file))) {
97
+ throw new Error(`skill source is incomplete: missing ${file}`)
98
+ }
99
+ }
100
+ }