agent-skill-installer 0.1.4 → 0.1.6

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/bin/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require("node:fs/promises");
4
4
  const path = require("node:path");
5
5
  const os = require("node:os");
6
+ const readline = require("node:readline/promises");
6
7
 
7
8
  const REPO_ROOT = path.resolve(__dirname, "..");
8
9
  const SKILLS_SOURCE_DIR = path.join(REPO_ROOT, "skills");
@@ -21,7 +22,7 @@ Commands:
21
22
 
22
23
  Options:
23
24
  --dest <path> Custom destination directory.
24
- --force Overwrite destination if a skill already exists.
25
+ --force Overwrite destination if a skill already exists (no prompt).
25
26
  -h, --help Show this help.
26
27
  `);
27
28
  }
@@ -112,6 +113,33 @@ async function listSkills() {
112
113
  }
113
114
  }
114
115
 
116
+ async function confirmOverwrite(name, targetPath) {
117
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
118
+ throw new Error(
119
+ `Destination already exists for "${name}": ${targetPath} (non-interactive mode, use --force to overwrite)`
120
+ );
121
+ }
122
+
123
+ const rl = readline.createInterface({
124
+ input: process.stdin,
125
+ output: process.stdout
126
+ });
127
+
128
+ try {
129
+ const answer = (
130
+ await rl.question(
131
+ `Skill "${name}" already exists at ${targetPath}. Overwrite it? [y/N] `
132
+ )
133
+ )
134
+ .trim()
135
+ .toLowerCase();
136
+
137
+ return answer === "y" || answer === "yes";
138
+ } finally {
139
+ rl.close();
140
+ }
141
+ }
142
+
115
143
  async function installSkills(rawArgs) {
116
144
  const parsed = parseInstallArgs(rawArgs);
117
145
  if (parsed.help) {
@@ -137,13 +165,16 @@ async function installSkills(rawArgs) {
137
165
  const to = path.join(parsed.dest, name);
138
166
  const exists = await dirExists(to);
139
167
 
140
- if (exists && !parsed.force) {
141
- throw new Error(
142
- `Destination already exists for "${name}": ${to} (use --force to overwrite)`
143
- );
168
+ if (exists && parsed.force) {
169
+ await fs.rm(to, { recursive: true, force: true });
144
170
  }
145
171
 
146
- if (exists && parsed.force) {
172
+ if (exists && !parsed.force) {
173
+ const shouldOverwrite = await confirmOverwrite(name, to);
174
+ if (!shouldOverwrite) {
175
+ console.log(`Skipped "${name}" (kept existing skill).`);
176
+ continue;
177
+ }
147
178
  await fs.rm(to, { recursive: true, force: true });
148
179
  }
149
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skill-installer",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Install one or more Codex skills from this package via npx.",
5
5
  "bin": {
6
6
  "agent-skill-installer": "bin/cli.js"
@@ -3,44 +3,46 @@ name: git-commit-helper
3
3
  description: "严格生成与校验中文 Git 提交信息,固定格式为 type(scope): summary,type 仅允许 feat/fix/refactor/perf/style/test/docs/chore,summary 必须用祈使句、不超过 50 字符、不得以句号结尾且不得包含 and/&/multiple changes。使用该技能于编写提交说明、检查提交文本是否合规,或执行使用 codex (codex-ice@gmail.com) 身份的提交时。"
4
4
  ---
5
5
 
6
- 按固定规范生成中文提交信息,并在提交前进行格式校验。
7
- 统一使用作者身份:`codex <codex-ice@gmail.com>`。
6
+ 生成中文提交信息,先校验再提交。统一使用作者身份:`codex <codex-ice@gmail.com>`。
8
7
 
9
8
  ## 工作流
10
9
 
11
- 1. 先查看暂存变更,确认本次提交只表达一个意图:`git diff --cached --name-status`。
12
- 2. 选择 `type`,必须是以下之一:`feat` `fix` `refactor` `perf` `style` `test` `docs` `chore`。
13
- 3. 写首行:`<type>(<scope>): <summary>`。
14
- 4. 首行后可选正文;若 `type=perf`,必须写正文说明优化原因。
15
- 5. 使用脚本校验:`python3 scripts/validate_commit_message.py --file <msg-file>`。
16
- 6. 通过校验后提交,并固定作者身份:
17
- `git -c user.name='codex' -c user.email='codex-ice@gmail.com' commit -F <msg-file>`。
10
+ 1. 检查暂存区,只允许一个明确意图:
11
+ `git diff --cached --name-status`
12
+ 2. 选择 `type`:`feat` `fix` `refactor` `perf` `style` `test` `docs` `chore`。
13
+ 3. 写提交消息文件,固定首行格式:
14
+ `<type>(<scope>): <summary>`
15
+ 4. 若 `type=perf`,在空行后写正文并说明优化原因。
16
+ 5. 执行校验:
17
+ `python3 skills/git-commit-helper/scripts/validate_commit_message.py --file <msg-file>`
18
+ 6. 校验通过后提交:
19
+ `git -c user.name='codex' -c user.email='codex-ice@gmail.com' commit -F <msg-file>`
18
20
 
19
21
  ## 强制规则
20
22
 
21
- 1. 格式必须严格为:`<type>(<scope>): <summary>`。
22
- 2. `type` 只能从这 8 个值中选择:`feat` `fix` `refactor` `perf` `style` `test` `docs` `chore`。
23
- 3. `summary` 必须满足:
24
- - 使用祈使句。
25
- - 不超过 50 字符。
26
- - 不要句号(`。` 或 `.`)。
27
- - 不要出现 `and`、`&`、`multiple changes`。
28
- - 使用中文表达。
29
- 4. 提交文本(`summary` 与可选正文)使用中文。
30
- 5. `perf` 类型必须在正文说明优化原因。
23
+ 1. 首行必须是:`<type>(<scope>): <summary>`。
24
+ 2. `scope` 只允许小写字母、数字、连字符(例如 `album`、`build-cache`)。
25
+ 3. `summary` 必须使用中文祈使句,且不超过 50 字符。
26
+ 4. `summary` 不能以 `。` 或 `.` 结尾。
27
+ 5. `summary` 不能包含 `and`、`&`、`multiple changes`。
28
+ 6. `summary` 与正文必须使用中文表达。
29
+ 7. 若包含正文,第 2 行必须是空行。
30
+ 8. `perf` 必须包含正文说明优化原因。
31
31
 
32
- ## 示例
32
+ ## 快速模板
33
33
 
34
- 正确:
35
- - `fix(album): 避免滚动时重复渲染`
36
- - `docs(api): 补充鉴权参数说明`
34
+ ```text
35
+ fix(scope): 优化某个单一行为
37
36
 
38
- 错误:
39
- - `update code`
40
- - `fix bug and refactor logic`
41
- - `optimize`
37
+ 仅在需要时写正文,说明原因或背景
38
+ ```
42
39
 
43
- ## 脚本资源
40
+ ## 失败处理
44
41
 
45
- - `scripts/validate_commit_message.py`
46
- 校验提交文本是否符合本技能的强制规则。
42
+ 1. 阅读脚本返回的逐条错误。
43
+ 2. 仅修改被指出的问题,避免一次改多个意图。
44
+ 3. 重新执行校验,直到输出“校验通过”。
45
+
46
+ ## 资源
47
+
48
+ - `skills/git-commit-helper/scripts/validate_commit_message.py`:提交信息规则校验器
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "git-commit-helper"
3
- short_description: "Generate Chinese Git commit messages with strict checks"
4
- default_prompt: "Use $git-commit-helper to draft a Chinese commit message and validate it before committing as codex <codex-ice@gmail.com>."
3
+ short_description: "严格生成并校验中文 Git 提交信息"
4
+ default_prompt: "Use $git-commit-helper to draft a Chinese commit message in `type(scope): summary`, validate it with the bundled script, then commit as codex <codex-ice@gmail.com>."
@@ -19,6 +19,18 @@ FORBIDDEN_SUMMARY_PATTERNS = (
19
19
  re.compile(r"&"),
20
20
  re.compile(r"multiple changes", re.IGNORECASE),
21
21
  )
22
+ PAST_TENSE_PREFIXES = (
23
+ "已",
24
+ "已经",
25
+ "完成",
26
+ "修复了",
27
+ "新增了",
28
+ "优化了",
29
+ "更新了",
30
+ "重构了",
31
+ "添加了",
32
+ "改了",
33
+ )
22
34
  FORBIDDEN_ENDINGS = (".", "。")
23
35
  MAX_SUMMARY_LEN = 50
24
36
 
@@ -49,36 +61,50 @@ def validate_commit_message(message: str) -> list[str]:
49
61
 
50
62
  match = HEADER_RE.match(header)
51
63
  commit_type = None
64
+ raw_summary = ""
52
65
  summary = ""
53
66
  if not match:
54
67
  errors.append("首行格式必须为 <type>(<scope>): <summary>")
55
68
  else:
56
69
  commit_type = match.group("type")
57
- summary = match.group("summary").strip()
70
+ raw_summary = match.group("summary")
71
+ summary = raw_summary.strip()
58
72
 
59
73
  if len(lines) > 1 and lines[1] != "":
60
74
  errors.append("若包含正文,第 2 行必须为空行")
61
75
 
62
- body = "\n".join(lines[2:]).strip() if len(lines) > 2 else ""
76
+ body_lines = lines[2:] if len(lines) > 2 else []
77
+ body = "\n".join(body_lines).strip()
63
78
 
64
79
  if commit_type and commit_type not in ALLOWED_TYPES:
65
80
  errors.append("type 必须是 feat/fix/refactor/perf/style/test/docs/chore 之一")
66
81
 
82
+ if not summary and match:
83
+ errors.append("summary 不能为空")
84
+
67
85
  if summary:
86
+ if raw_summary != summary:
87
+ errors.append("summary 前后不能有空格")
68
88
  if len(summary) > MAX_SUMMARY_LEN:
69
89
  errors.append("summary 长度不能超过 50 字符")
70
90
  if summary.endswith(FORBIDDEN_ENDINGS):
71
91
  errors.append("summary 结尾不能使用句号")
72
92
  if not CJK_RE.search(summary):
73
93
  errors.append("summary 必须使用中文")
94
+ if summary.startswith(PAST_TENSE_PREFIXES):
95
+ errors.append("summary 必须使用祈使句,避免使用“已/完成/xxx了”开头")
74
96
 
75
97
  for pattern in FORBIDDEN_SUMMARY_PATTERNS:
76
98
  if pattern.search(summary):
77
99
  errors.append("summary 不能包含 and / & / multiple changes")
78
100
  break
79
101
 
80
- if body and not CJK_RE.search(body):
81
- errors.append("正文需使用中文")
102
+ for line_no, line in enumerate(body_lines, start=3):
103
+ stripped = line.strip()
104
+ if not stripped:
105
+ continue
106
+ if not CJK_RE.search(stripped):
107
+ errors.append(f"正文第 {line_no} 行需使用中文")
82
108
 
83
109
  if commit_type == "perf" and not body:
84
110
  errors.append("perf 类型必须提供正文说明优化原因")