adspecs 0.1.19 → 0.1.21

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.
Files changed (74) hide show
  1. package/.adspecs/feature.json +16 -16
  2. package/.adspecs/feature.yml +28 -28
  3. package/.adspecs/paths.json +17 -17
  4. package/.adspecs/templates/04-/345/211/215/347/253/257/345/212/237/350/203/275/350/256/276/350/256/241/346/250/241/346/235/277.md +1 -1
  5. package/.adspecs/templates/05-/345/220/216/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +613 -724
  6. package/.adspecs/templates/05b-/345/211/215/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +51 -51
  7. package/.claude-plugin/marketplace.json +23 -23
  8. package/.claude-plugin/plugin.json +18 -18
  9. package/.qoder-plugin/plugin.json +31 -31
  10. package/CLAUDE.md +1 -5
  11. package/INSTALL.md +3 -3
  12. package/README.md +395 -395
  13. package/bin/adspecs.js +129 -129
  14. package/hooks/commit-queue.js +245 -245
  15. package/hooks/hooks.json +63 -63
  16. package/hooks/session-start.js +44 -44
  17. package/hooks/wiki-queue.js +127 -127
  18. package/package.json +61 -61
  19. package/references/ant6-front-standard/index.md +99 -99
  20. package/references/antd-front-demo/public/mockServiceWorker.js +361 -361
  21. package/references/ecp-end-standard/index.md +63 -63
  22. package/references/python-end-standard/01-Python/345/220/216/347/253/257/347/274/226/347/240/201/350/247/204/350/214/203.md +372 -372
  23. package/references/python-end-standard/02-/346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/344/270/216/344/275/277/347/224/250/350/247/204/350/214/203.md +226 -226
  24. package/references/python-end-standard/03-Celery/345/274/202/346/255/245/344/273/273/345/212/241/350/247/204/350/214/203.md +237 -237
  25. package/references/python-end-standard/04-Redis/344/275/277/347/224/250/350/247/204/350/214/203.md +231 -231
  26. package/scripts/postinstall.js +107 -107
  27. package/scripts/sync-version.js +105 -105
  28. package/skills/.claude/.wiki-update-queue +26 -26
  29. package/skills/adspecs-constitution/SKILL.md +157 -0
  30. package/skills/adspecs-export-word/SKILL.md +498 -498
  31. package/skills/adspecs-export-word/references/md-to-docx.js +862 -862
  32. package/skills/adspecs-export-word/references/package-lock.json +220 -220
  33. package/skills/adspecs-export-word/references/package.json +10 -10
  34. package/skills/adspecs-front-prototype/SKILL.md +405 -405
  35. package/skills/adspecs-front-spec/SKILL.md +4 -4
  36. package/skills/adspecs-front-tasks/SKILL.md +213 -173
  37. package/skills/adspecs-plan/SKILL.md +59 -69
  38. package/skills/adspecs-prd/SKILL.md +13 -5
  39. package/skills/adspecs-prd-to-demo/SKILL.md +532 -0
  40. package/skills/adspecs-tasks/SKILL.md +175 -204
  41. package/skills/adspecs-update-status/SKILL.md +382 -382
  42. package/skills/adspecs-utest/SKILL.md +107 -116
  43. package/skills/grill-me/SKILL.md +7 -0
  44. package/skills/grill-me/agents/openai.yaml +5 -0
  45. package/skills/playwright-cli/SKILL.md +420 -0
  46. package/skills/playwright-cli/references/element-attributes.md +23 -0
  47. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  48. package/skills/playwright-cli/references/request-mocking.md +87 -0
  49. package/skills/playwright-cli/references/running-code.md +241 -0
  50. package/skills/playwright-cli/references/session-management.md +225 -0
  51. package/skills/playwright-cli/references/storage-state.md +275 -0
  52. package/skills/playwright-cli/references/test-generation.md +433 -0
  53. package/skills/playwright-cli/references/tracing.md +139 -0
  54. package/skills/playwright-cli/references/video-recording.md +143 -0
  55. package/skills/playwright-trace/SKILL.md +171 -0
  56. package/skills/project-init/SKILL.md +93 -22
  57. package/skills/project-init/references/front-demo/.claude/settings.local.json +9 -0
  58. package/skills/wiki-update/SKILL.md +232 -232
  59. package/src/commands/doctor.js +197 -197
  60. package/src/commands/init.js +83 -83
  61. package/src/commands/plugin.js +165 -165
  62. package/src/commands/update.js +87 -87
  63. package/src/lib/area-scanner.js +129 -129
  64. package/src/lib/copier.js +104 -104
  65. package/src/lib/dir-utils.js +161 -133
  66. package/src/lib/json-merge.js +114 -114
  67. package/src/lib/paths-defaults.js +37 -37
  68. package/src/lib/prompts.js +428 -347
  69. package/src/lib/readme-gen.js +143 -143
  70. package/src/lib/report.js +338 -327
  71. package/src/lib/scaffolder.js +551 -518
  72. package/src/lib/short-name.js +36 -36
  73. package/src/utils.js +80 -80
  74. package/references/antd-front-demo/.env +0 -15
@@ -1,231 +1,231 @@
1
- # Redis 使用规范
2
-
3
- > **版本**: v1.0 | **修订日期**: 2026-07-15
4
- > **技术栈**: Redis 7.0+ / redis-py (async)
5
-
6
- ---
7
-
8
- ## 一、连接管理
9
-
10
- ```python
11
- # app/core/redis.py
12
- import redis.asyncio as redis
13
- from app.core.config import settings
14
-
15
- redis_client = redis.from_url(
16
- settings.REDIS_URL,
17
- encoding="utf-8",
18
- decode_responses=True,
19
- max_connections=20,
20
- )
21
-
22
- async def get_redis() -> redis.Redis:
23
- return redis_client
24
-
25
- # 生命周期管理
26
- async def close_redis():
27
- await redis_client.close()
28
- ```
29
-
30
- ---
31
-
32
- ## 二、Key 命名规范
33
-
34
- ### 2.1 格式
35
-
36
- ```
37
- {app_prefix}:{module}:{entity}:{identifier}
38
- ```
39
-
40
- 冒号 `:` 分隔层级,便于 Redis CLI 和可视化工具浏览。
41
-
42
- ### 2.2 示例
43
-
44
- | 用途 | Key 示例 |
45
- |--------------|---------------------------------------------|
46
- | 用户缓存 | app:user:profile:123 |
47
- | 仓库列表 | app:warehouse:list:active |
48
- | 会话 | app:session:{token} |
49
- | 限流 | app:ratelimit:api:{user_id}:{endpoint} |
50
- | 分布式锁 | app:lock:inventory_sync:{warehouse_id} |
51
- | Celery 任务 | celery-task-meta-{task_id}(Celery 自动管理)|
52
-
53
- ### 2.3 规则
54
-
55
- | 规则 | 说明 |
56
- |--------------|------------------------------------------------|
57
- | 前缀 | 所有 key 必须以 `app:` 开头 |
58
- | 层级 | 冒号分隔,不超过 4 层 |
59
- | 长度 | 不超过 128 字节 |
60
- | 禁止空格 | 使用下划线或驼峰代替空格 |
61
- | TTL | 所有 key 必须设置过期时间 |
62
-
63
- ---
64
-
65
- ## 三、缓存使用
66
-
67
- ### 3.1 装饰器模式
68
-
69
- ```python
70
- import json
71
- import functools
72
-
73
- def cached(key_template: str, ttl: int = 300):
74
- """缓存装饰器"""
75
- def decorator(func):
76
- @functools.wraps(func)
77
- async def wrapper(*args, **kwargs):
78
- key = key_template.format(**kwargs)
79
- r = await get_redis()
80
- # 尝试读缓存
81
- data = await r.get(key)
82
- if data:
83
- return json.loads(data)
84
- # 执行函数
85
- result = await func(*args, **kwargs)
86
- # 写缓存
87
- await r.setex(key, ttl, json.dumps(result, default=str))
88
- return result
89
- return wrapper
90
- return decorator
91
-
92
- # 使用
93
- @cached(key_template="app:warehouse:detail:{warehouse_id}", ttl=600)
94
- async def get_warehouse_detail(warehouse_id: int):
95
- return await warehouse_service.get_by_id(db, warehouse_id)
96
- ```
97
-
98
- ### 3.2 缓存失效策略
99
-
100
- | 策略 | 适用场景 | 实现 |
101
- |--------------|--------------------|------------------------------------|
102
- | TTL 过期 | 所有缓存 | setex 设置过期时间 |
103
- | 主动删除 | 数据更新时 | 更新/删除操作后 delete key |
104
- | 模式删除 | 批量失效 | scan + delete(禁止 keys *) |
105
- | 版本号 | 配置类数据 | key 中嵌入版本号,更新时递增 |
106
-
107
- ```python
108
- # 缓存失效示例
109
- async def invalidate_warehouse_cache(warehouse_id: int):
110
- r = await get_redis()
111
- await r.delete(f"app:warehouse:detail:{warehouse_id}")
112
- await r.delete("app:warehouse:list:active")
113
- ```
114
-
115
- ---
116
-
117
- ## 四、分布式锁
118
-
119
- ```python
120
- from redis.asyncio.lock import Lock
121
-
122
- async def with_inventory_lock(warehouse_id: int, func, *args, **kwargs):
123
- """带分布式锁的库存操作"""
124
- r = await get_redis()
125
- lock_key = f"app:lock:inventory:{warehouse_id}"
126
- lock = Lock(r, lock_key, timeout=30, blocking_timeout=5)
127
-
128
- async with lock:
129
- return await func(*args, **kwargs)
130
-
131
- # 使用
132
- await with_inventory_lock(warehouse_id=123, func=sync_inventory)
133
- ```
134
-
135
- | 规则 | 说明 |
136
- |--------------|------------------------------------------------|
137
- | timeout | 锁的最大持有时间,防止死锁 |
138
- | blocking | 获取锁时是否阻塞等待 |
139
- | blocking_timeout | 阻塞等待的最大时间 |
140
- | 唯一标识 | Lock 自动使用 UUID,确保释放安全 |
141
-
142
- ---
143
-
144
- ## 五、限流
145
-
146
- ### 5.1 滑动窗口限流
147
-
148
- ```python
149
- import time
150
-
151
- async def is_rate_limited(user_id: int, endpoint: str, limit: int = 100, window: int = 60) -> bool:
152
- """滑动窗口限流"""
153
- r = await get_redis()
154
- key = f"app:ratelimit:{user_id}:{endpoint}"
155
- now = time.time()
156
-
157
- pipe = r.pipeline()
158
- pipe.zremrangebyscore(key, 0, now - window)
159
- pipe.zadd(key, {str(now): now})
160
- pipe.zcard(key)
161
- pipe.expire(key, window)
162
- results = await pipe.execute()
163
-
164
- return results[2] > limit
165
- ```
166
-
167
- ---
168
-
169
- ## 六、数据结构选择
170
-
171
- | 场景 | 数据结构 | 示例 |
172
- |----------------|----------------|---------------------------------------|
173
- | 简单缓存 | String | GET/SET user profile JSON |
174
- | 对象属性 | Hash | HSET user:123 name "Alice" |
175
- | 列表/队列 | List | LPUSH/RPOP 消息队列 |
176
- | 去重集合 | Set | SADD user:123:tags "vip" |
177
- | 排行榜/排序 | Sorted Set | ZADD leaderboard score member |
178
- | 位操作 | Bitmap | SETBIT user:active:20260715 user_id 1 |
179
- | 计数器 | String (INCR) | INCR page:views:home |
180
-
181
- ---
182
-
183
- ## 七、序列化规范
184
-
185
- | 规则 | 说明 |
186
- |--------------|------------------------------------------------|
187
- | 格式 | 统一使用 JSON |
188
- | 禁止 pickle | 安全风险,禁止序列化 Python 对象 |
189
- | datetime | 序列化为 ISO 8601 字符串 |
190
- | Decimal | 序列化为字符串 |
191
- | bytes | Base64 编码后存储 |
192
-
193
- ---
194
-
195
- ## 八、内存管理
196
-
197
- | 配置 | 推荐值 | 说明 |
198
- |----------------|-----------------|------------------------------------|
199
- | maxmemory | 根据服务器内存 | 设置最大内存限制 |
200
- | maxmemory-policy | allkeys-lru | 内存满时淘汰策略 |
201
- | 过期时间 | 必须设置 | 所有 key 必须有 TTL |
202
- | 大 key 监控 | redis-cli --bigkeys | 定期检查大 key |
203
-
204
- ---
205
-
206
- ## 九、测试规范
207
-
208
- ```python
209
- # tests/conftest.py
210
- import fakeredis.aioredis
211
-
212
- @pytest.fixture
213
- async def redis_client():
214
- """使用 fakeredis 隔离测试"""
215
- fake = fakeredis.aioredis.FakeRedis(decode_responses=True)
216
- yield fake
217
- await fake.flushall()
218
- await fake.close()
219
-
220
- # 测试示例
221
- async def test_cache_hit(redis_client):
222
- await redis_client.setex("app:test:key", 60, "value")
223
- result = await redis_client.get("app:test:key")
224
- assert result == "value"
225
- ```
226
-
227
- | 规则 | 说明 |
228
- |--------------|------------------------------------------------|
229
- | fakeredis | 单元测试使用 fakeredis,不依赖真实 Redis |
230
- | 隔离 | 每个测试前后 flushall |
231
- | 集成测试 | 使用独立 Redis DB(db=15) |
1
+ # Redis 使用规范
2
+
3
+ > **版本**: v1.0 | **修订日期**: 2026-07-15
4
+ > **技术栈**: Redis 7.0+ / redis-py (async)
5
+
6
+ ---
7
+
8
+ ## 一、连接管理
9
+
10
+ ```python
11
+ # app/core/redis.py
12
+ import redis.asyncio as redis
13
+ from app.core.config import settings
14
+
15
+ redis_client = redis.from_url(
16
+ settings.REDIS_URL,
17
+ encoding="utf-8",
18
+ decode_responses=True,
19
+ max_connections=20,
20
+ )
21
+
22
+ async def get_redis() -> redis.Redis:
23
+ return redis_client
24
+
25
+ # 生命周期管理
26
+ async def close_redis():
27
+ await redis_client.close()
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 二、Key 命名规范
33
+
34
+ ### 2.1 格式
35
+
36
+ ```
37
+ {app_prefix}:{module}:{entity}:{identifier}
38
+ ```
39
+
40
+ 冒号 `:` 分隔层级,便于 Redis CLI 和可视化工具浏览。
41
+
42
+ ### 2.2 示例
43
+
44
+ | 用途 | Key 示例 |
45
+ |--------------|---------------------------------------------|
46
+ | 用户缓存 | app:user:profile:123 |
47
+ | 仓库列表 | app:warehouse:list:active |
48
+ | 会话 | app:session:{token} |
49
+ | 限流 | app:ratelimit:api:{user_id}:{endpoint} |
50
+ | 分布式锁 | app:lock:inventory_sync:{warehouse_id} |
51
+ | Celery 任务 | celery-task-meta-{task_id}(Celery 自动管理)|
52
+
53
+ ### 2.3 规则
54
+
55
+ | 规则 | 说明 |
56
+ |--------------|------------------------------------------------|
57
+ | 前缀 | 所有 key 必须以 `app:` 开头 |
58
+ | 层级 | 冒号分隔,不超过 4 层 |
59
+ | 长度 | 不超过 128 字节 |
60
+ | 禁止空格 | 使用下划线或驼峰代替空格 |
61
+ | TTL | 所有 key 必须设置过期时间 |
62
+
63
+ ---
64
+
65
+ ## 三、缓存使用
66
+
67
+ ### 3.1 装饰器模式
68
+
69
+ ```python
70
+ import json
71
+ import functools
72
+
73
+ def cached(key_template: str, ttl: int = 300):
74
+ """缓存装饰器"""
75
+ def decorator(func):
76
+ @functools.wraps(func)
77
+ async def wrapper(*args, **kwargs):
78
+ key = key_template.format(**kwargs)
79
+ r = await get_redis()
80
+ # 尝试读缓存
81
+ data = await r.get(key)
82
+ if data:
83
+ return json.loads(data)
84
+ # 执行函数
85
+ result = await func(*args, **kwargs)
86
+ # 写缓存
87
+ await r.setex(key, ttl, json.dumps(result, default=str))
88
+ return result
89
+ return wrapper
90
+ return decorator
91
+
92
+ # 使用
93
+ @cached(key_template="app:warehouse:detail:{warehouse_id}", ttl=600)
94
+ async def get_warehouse_detail(warehouse_id: int):
95
+ return await warehouse_service.get_by_id(db, warehouse_id)
96
+ ```
97
+
98
+ ### 3.2 缓存失效策略
99
+
100
+ | 策略 | 适用场景 | 实现 |
101
+ |--------------|--------------------|------------------------------------|
102
+ | TTL 过期 | 所有缓存 | setex 设置过期时间 |
103
+ | 主动删除 | 数据更新时 | 更新/删除操作后 delete key |
104
+ | 模式删除 | 批量失效 | scan + delete(禁止 keys *) |
105
+ | 版本号 | 配置类数据 | key 中嵌入版本号,更新时递增 |
106
+
107
+ ```python
108
+ # 缓存失效示例
109
+ async def invalidate_warehouse_cache(warehouse_id: int):
110
+ r = await get_redis()
111
+ await r.delete(f"app:warehouse:detail:{warehouse_id}")
112
+ await r.delete("app:warehouse:list:active")
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 四、分布式锁
118
+
119
+ ```python
120
+ from redis.asyncio.lock import Lock
121
+
122
+ async def with_inventory_lock(warehouse_id: int, func, *args, **kwargs):
123
+ """带分布式锁的库存操作"""
124
+ r = await get_redis()
125
+ lock_key = f"app:lock:inventory:{warehouse_id}"
126
+ lock = Lock(r, lock_key, timeout=30, blocking_timeout=5)
127
+
128
+ async with lock:
129
+ return await func(*args, **kwargs)
130
+
131
+ # 使用
132
+ await with_inventory_lock(warehouse_id=123, func=sync_inventory)
133
+ ```
134
+
135
+ | 规则 | 说明 |
136
+ |--------------|------------------------------------------------|
137
+ | timeout | 锁的最大持有时间,防止死锁 |
138
+ | blocking | 获取锁时是否阻塞等待 |
139
+ | blocking_timeout | 阻塞等待的最大时间 |
140
+ | 唯一标识 | Lock 自动使用 UUID,确保释放安全 |
141
+
142
+ ---
143
+
144
+ ## 五、限流
145
+
146
+ ### 5.1 滑动窗口限流
147
+
148
+ ```python
149
+ import time
150
+
151
+ async def is_rate_limited(user_id: int, endpoint: str, limit: int = 100, window: int = 60) -> bool:
152
+ """滑动窗口限流"""
153
+ r = await get_redis()
154
+ key = f"app:ratelimit:{user_id}:{endpoint}"
155
+ now = time.time()
156
+
157
+ pipe = r.pipeline()
158
+ pipe.zremrangebyscore(key, 0, now - window)
159
+ pipe.zadd(key, {str(now): now})
160
+ pipe.zcard(key)
161
+ pipe.expire(key, window)
162
+ results = await pipe.execute()
163
+
164
+ return results[2] > limit
165
+ ```
166
+
167
+ ---
168
+
169
+ ## 六、数据结构选择
170
+
171
+ | 场景 | 数据结构 | 示例 |
172
+ |----------------|----------------|---------------------------------------|
173
+ | 简单缓存 | String | GET/SET user profile JSON |
174
+ | 对象属性 | Hash | HSET user:123 name "Alice" |
175
+ | 列表/队列 | List | LPUSH/RPOP 消息队列 |
176
+ | 去重集合 | Set | SADD user:123:tags "vip" |
177
+ | 排行榜/排序 | Sorted Set | ZADD leaderboard score member |
178
+ | 位操作 | Bitmap | SETBIT user:active:20260715 user_id 1 |
179
+ | 计数器 | String (INCR) | INCR page:views:home |
180
+
181
+ ---
182
+
183
+ ## 七、序列化规范
184
+
185
+ | 规则 | 说明 |
186
+ |--------------|------------------------------------------------|
187
+ | 格式 | 统一使用 JSON |
188
+ | 禁止 pickle | 安全风险,禁止序列化 Python 对象 |
189
+ | datetime | 序列化为 ISO 8601 字符串 |
190
+ | Decimal | 序列化为字符串 |
191
+ | bytes | Base64 编码后存储 |
192
+
193
+ ---
194
+
195
+ ## 八、内存管理
196
+
197
+ | 配置 | 推荐值 | 说明 |
198
+ |----------------|-----------------|------------------------------------|
199
+ | maxmemory | 根据服务器内存 | 设置最大内存限制 |
200
+ | maxmemory-policy | allkeys-lru | 内存满时淘汰策略 |
201
+ | 过期时间 | 必须设置 | 所有 key 必须有 TTL |
202
+ | 大 key 监控 | redis-cli --bigkeys | 定期检查大 key |
203
+
204
+ ---
205
+
206
+ ## 九、测试规范
207
+
208
+ ```python
209
+ # tests/conftest.py
210
+ import fakeredis.aioredis
211
+
212
+ @pytest.fixture
213
+ async def redis_client():
214
+ """使用 fakeredis 隔离测试"""
215
+ fake = fakeredis.aioredis.FakeRedis(decode_responses=True)
216
+ yield fake
217
+ await fake.flushall()
218
+ await fake.close()
219
+
220
+ # 测试示例
221
+ async def test_cache_hit(redis_client):
222
+ await redis_client.setex("app:test:key", 60, "value")
223
+ result = await redis_client.get("app:test:key")
224
+ assert result == "value"
225
+ ```
226
+
227
+ | 规则 | 说明 |
228
+ |--------------|------------------------------------------------|
229
+ | fakeredis | 单元测试使用 fakeredis,不依赖真实 Redis |
230
+ | 隔离 | 每个测试前后 flushall |
231
+ | 集成测试 | 使用独立 Redis DB(db=15) |
@@ -1,107 +1,107 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * npm 安装/更新后自动刷新 Claude Code 插件注册
6
- *
7
- * 触发时机:
8
- * - `npm install -g adspecs` → 首次安装
9
- * - `npm update -g adspecs` → 版本更新
10
- * - `npm install adspecs` (本地) → 作为项目依赖安装
11
- *
12
- * 行为:
13
- * - 检查 claude CLI 是否可用
14
- * - 检查 adspecs 插件是否已在 Claude Code 中注册
15
- * - 若已注册 → 静默重新安装以刷新版本(让 Claude Code 感知新版本)
16
- * - 若未注册 → 不做任何事(首次安装应由用户主动 `adspecs plugin install`)
17
- *
18
- * 关键约束:
19
- * - 绝不能阻断 npm install(所有异常静默吞掉)
20
- * - 必须在 2 秒内完成(超时自动退出)
21
- * - 不输出多余信息(仅在成功刷新时打印一行提示)
22
- */
23
-
24
- const { execSync, spawn } = require('child_process');
25
- const path = require('path');
26
- const fs = require('fs');
27
-
28
- // 2 秒超时保护
29
- const TIMEOUT_MS = 2000;
30
- const timer = setTimeout(() => {
31
- process.exit(0);
32
- }, TIMEOUT_MS);
33
- if (timer.unref) timer.unref();
34
-
35
- try {
36
- // 1. 检查 claude CLI 是否可用
37
- try {
38
- execSync('claude --version', { stdio: 'ignore', timeout: 1000 });
39
- } catch {
40
- // claude CLI 未安装 → 静默退出
41
- process.exit(0);
42
- }
43
-
44
- // 2. 判断是否是全局安装
45
- // npm 在全局安装时会设置 npm_config_global 或 initInstalled 等环境变量
46
- // 也可通过检查安装路径是否包含 node_modules/adspecs 判断
47
- const installDir = path.resolve(__dirname, '..');
48
- const isGlobal =
49
- process.env.npm_config_global === 'true' ||
50
- installDir.includes('npm/node_modules') ||
51
- installDir.includes('npm-global') ||
52
- installDir.includes(path.join('.npm-global', 'lib'));
53
-
54
- // 本地依赖安装 → 不做插件刷新(避免污染项目环境)
55
- if (!isGlobal) {
56
- process.exit(0);
57
- }
58
-
59
- // 3. 检查 adspecs 是否已在 Claude Code 中注册
60
- let installedPlugins = '';
61
- try {
62
- installedPlugins = execSync('claude plugins list 2>&1', {
63
- stdio: ['pipe', 'pipe', 'pipe'],
64
- timeout: 1000,
65
- encoding: 'utf8',
66
- });
67
- } catch (err) {
68
- // claude plugins list 命令失败 → 静默退出
69
- process.exit(0);
70
- }
71
-
72
- // 如果插件列表中未包含 adspecs → 首次安装,不自动注册
73
- if (!installedPlugins.toLowerCase().includes('adspecs')) {
74
- process.exit(0);
75
- }
76
-
77
- // 4. 已注册 → 重新安装以刷新版本
78
- // 使用异步 spawn 避免阻塞 npm install 流程
79
- const pluginDir = installDir;
80
- const child = spawn(
81
- 'claude',
82
- ['plugins', 'install', 'adspecs', '--scope', 'user'],
83
- {
84
- stdio: 'ignore',
85
- detached: true,
86
- windowsHide: true,
87
- }
88
- );
89
- child.on('error', () => {
90
- // 静默忽略
91
- });
92
- child.unref();
93
-
94
- // 输出一行提示(仅当 stdout 是 TTY)
95
- if (process.stdout.isTTY) {
96
- const pkg = JSON.parse(
97
- fs.readFileSync(path.join(installDir, 'package.json'), 'utf8')
98
- );
99
- console.log(
100
- `\n🔄 adspecs@${pkg.version} — Claude Code 插件注册已自动刷新\n`
101
- );
102
- }
103
- } catch {
104
- // 任何未预期的错误都静默吞掉,绝不阻断 npm install
105
- }
106
-
107
- process.exit(0);
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * npm 安装/更新后自动刷新 Claude Code 插件注册
6
+ *
7
+ * 触发时机:
8
+ * - `npm install -g adspecs` → 首次安装
9
+ * - `npm update -g adspecs` → 版本更新
10
+ * - `npm install adspecs` (本地) → 作为项目依赖安装
11
+ *
12
+ * 行为:
13
+ * - 检查 claude CLI 是否可用
14
+ * - 检查 adspecs 插件是否已在 Claude Code 中注册
15
+ * - 若已注册 → 静默重新安装以刷新版本(让 Claude Code 感知新版本)
16
+ * - 若未注册 → 不做任何事(首次安装应由用户主动 `adspecs plugin install`)
17
+ *
18
+ * 关键约束:
19
+ * - 绝不能阻断 npm install(所有异常静默吞掉)
20
+ * - 必须在 2 秒内完成(超时自动退出)
21
+ * - 不输出多余信息(仅在成功刷新时打印一行提示)
22
+ */
23
+
24
+ const { execSync, spawn } = require('child_process');
25
+ const path = require('path');
26
+ const fs = require('fs');
27
+
28
+ // 2 秒超时保护
29
+ const TIMEOUT_MS = 2000;
30
+ const timer = setTimeout(() => {
31
+ process.exit(0);
32
+ }, TIMEOUT_MS);
33
+ if (timer.unref) timer.unref();
34
+
35
+ try {
36
+ // 1. 检查 claude CLI 是否可用
37
+ try {
38
+ execSync('claude --version', { stdio: 'ignore', timeout: 1000 });
39
+ } catch {
40
+ // claude CLI 未安装 → 静默退出
41
+ process.exit(0);
42
+ }
43
+
44
+ // 2. 判断是否是全局安装
45
+ // npm 在全局安装时会设置 npm_config_global 或 initInstalled 等环境变量
46
+ // 也可通过检查安装路径是否包含 node_modules/adspecs 判断
47
+ const installDir = path.resolve(__dirname, '..');
48
+ const isGlobal =
49
+ process.env.npm_config_global === 'true' ||
50
+ installDir.includes('npm/node_modules') ||
51
+ installDir.includes('npm-global') ||
52
+ installDir.includes(path.join('.npm-global', 'lib'));
53
+
54
+ // 本地依赖安装 → 不做插件刷新(避免污染项目环境)
55
+ if (!isGlobal) {
56
+ process.exit(0);
57
+ }
58
+
59
+ // 3. 检查 adspecs 是否已在 Claude Code 中注册
60
+ let installedPlugins = '';
61
+ try {
62
+ installedPlugins = execSync('claude plugins list 2>&1', {
63
+ stdio: ['pipe', 'pipe', 'pipe'],
64
+ timeout: 1000,
65
+ encoding: 'utf8',
66
+ });
67
+ } catch (err) {
68
+ // claude plugins list 命令失败 → 静默退出
69
+ process.exit(0);
70
+ }
71
+
72
+ // 如果插件列表中未包含 adspecs → 首次安装,不自动注册
73
+ if (!installedPlugins.toLowerCase().includes('adspecs')) {
74
+ process.exit(0);
75
+ }
76
+
77
+ // 4. 已注册 → 重新安装以刷新版本
78
+ // 使用异步 spawn 避免阻塞 npm install 流程
79
+ const pluginDir = installDir;
80
+ const child = spawn(
81
+ 'claude',
82
+ ['plugins', 'install', 'adspecs', '--scope', 'user'],
83
+ {
84
+ stdio: 'ignore',
85
+ detached: true,
86
+ windowsHide: true,
87
+ }
88
+ );
89
+ child.on('error', () => {
90
+ // 静默忽略
91
+ });
92
+ child.unref();
93
+
94
+ // 输出一行提示(仅当 stdout 是 TTY)
95
+ if (process.stdout.isTTY) {
96
+ const pkg = JSON.parse(
97
+ fs.readFileSync(path.join(installDir, 'package.json'), 'utf8')
98
+ );
99
+ console.log(
100
+ `\n🔄 adspecs@${pkg.version} — Claude Code 插件注册已自动刷新\n`
101
+ );
102
+ }
103
+ } catch {
104
+ // 任何未预期的错误都静默吞掉,绝不阻断 npm install
105
+ }
106
+
107
+ process.exit(0);