team-skills 1.3.6 → 1.3.7

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
@@ -7,6 +7,12 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.7] - 2026-06-26
11
+
12
+ ### 修复
13
+
14
+ - 安装时自检:目标与源为同一文件时跳过删除/复制,防止误删源文件(`createSymlinkSafe` + `installSkillsProject` + `verifyGlobalSymlinks`)
15
+
10
16
  ## [1.3.6] - 2026-06-26
11
17
 
12
18
  ### 修复
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-skills",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback and quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mkdirSync, symlinkSync, unlinkSync, readlinkSync,
3
3
  lstatSync, existsSync, readdirSync, statSync,
4
- copyFileSync, chmodSync, rmSync,
4
+ copyFileSync, chmodSync, rmSync, realpathSync,
5
5
  } from 'node:fs';
6
6
  import { join, dirname } from 'node:path';
7
7
 
@@ -25,6 +25,12 @@ export function createSymlinkSafe(source, target, { force = false, dryRun = fals
25
25
  if (!force) return 'conflict';
26
26
  unlinkSync(target);
27
27
  } else if (existsSync(target)) {
28
+ // 目标存在但不是软连接 — 解析真实路径,如果是同一文件则视为已安装
29
+ try {
30
+ if (realpathSync(target) === realpathSync(source)) return 'exists';
31
+ } catch {
32
+ // 任一无法解析,继续走 force 逻辑
33
+ }
28
34
  if (!force) return 'conflict';
29
35
  rmSync(target, { recursive: true });
30
36
  }
@@ -1,5 +1,5 @@
1
1
  import { join } from 'node:path';
2
- import { existsSync, readdirSync, rmSync, copyFileSync } from 'node:fs';
2
+ import { existsSync, readdirSync, rmSync, copyFileSync, realpathSync } from 'node:fs';
3
3
  import { createSymlinkSafe, ensureDir, isSymlink, copyRecursive } from './fs-utils.js';
4
4
  import { GLOBAL_TARGETS, PROJECT_IDE_DIRS } from './constants.js';
5
5
  import * as log from './logger.js';
@@ -56,21 +56,44 @@ export function verifyGlobalSymlinks(targets, skills, rules) {
56
56
  log.heading('验证安装');
57
57
  let errors = 0;
58
58
 
59
- const verify = (label, dest) => {
60
- if (isSymlink(dest)) {
61
- log.success(label);
62
- } else {
63
- log.error(`${label} 未正确安装`);
64
- errors++;
65
- }
66
- };
67
-
68
59
  for (const t of targets) {
69
60
  for (const skill of skills) {
70
- verify(`${t.label} Skill: ${skill.name}`, join(t.dir, skill.name));
61
+ const dest = join(t.dir, skill.name);
62
+ const label = `${t.label} Skill: ${skill.name}`;
63
+ if (isSymlink(dest)) {
64
+ log.success(label);
65
+ } else if (existsSync(dest)) {
66
+ try {
67
+ if (realpathSync(dest) === realpathSync(skill.path)) {
68
+ log.skip(`${label}(已存在,跳过)`);
69
+ continue;
70
+ }
71
+ } catch { /* 忽略解析失败 */ }
72
+ log.error(`${label} 未正确安装`);
73
+ errors++;
74
+ } else {
75
+ log.error(`${label} 未正确安装`);
76
+ errors++;
77
+ }
71
78
  }
72
79
  for (const rule of rules) {
73
- verify(`${t.label} Rule: ${rule.name}`, join(t.dir, '_team-rules', rule.name));
80
+ const dest = join(t.dir, '_team-rules', rule.name);
81
+ const label = `${t.label} Rule: ${rule.name}`;
82
+ if (isSymlink(dest)) {
83
+ log.success(label);
84
+ } else if (existsSync(dest)) {
85
+ try {
86
+ if (realpathSync(dest) === realpathSync(rule.path)) {
87
+ log.skip(`${label}(已存在,跳过)`);
88
+ continue;
89
+ }
90
+ } catch { /* 忽略解析失败 */ }
91
+ log.error(`${label} 未正确安装`);
92
+ errors++;
93
+ } else {
94
+ log.error(`${label} 未正确安装`);
95
+ errors++;
96
+ }
74
97
  }
75
98
  }
76
99
 
@@ -98,7 +121,16 @@ export function installSkillsProject(projectDir, ides, skills, rules, { dryRun,
98
121
  for (const skill of skills) {
99
122
  const dest = join(skillsDst, skill.name);
100
123
  if (!dryRun) {
101
- if (existsSync(dest)) rmSync(dest, { recursive: true });
124
+ // 如果目标就是源文件本身,跳过删除+复制(否则删了就复制不了)
125
+ if (existsSync(dest)) {
126
+ try {
127
+ if (realpathSync(dest) === realpathSync(skill.path)) {
128
+ log.skip(`${tag}Skill: ${skill.name}(自身,跳过)`);
129
+ continue;
130
+ }
131
+ } catch { /* 忽略解析失败 */ }
132
+ rmSync(dest, { recursive: true });
133
+ }
102
134
  copyRecursive(skill.path, dest);
103
135
  }
104
136
  log.success(`${tag}Skill: ${skill.name}`);
@@ -108,7 +140,19 @@ export function installSkillsProject(projectDir, ides, skills, rules, { dryRun,
108
140
  const rulesDst = join(skillsDst, '_team-rules');
109
141
  if (!dryRun) ensureDir(rulesDst);
110
142
  for (const r of rules) {
111
- if (!dryRun) copyFileSync(r.path, join(rulesDst, r.name));
143
+ if (!dryRun) {
144
+ const ruleDest = join(rulesDst, r.name);
145
+ // 如果目标就是源文件本身,跳过复制
146
+ if (existsSync(ruleDest)) {
147
+ try {
148
+ if (realpathSync(ruleDest) === realpathSync(r.path)) {
149
+ log.skip(`${tag}Rule: ${r.name}(自身,跳过)`);
150
+ continue;
151
+ }
152
+ } catch { /* 忽略解析失败 */ }
153
+ }
154
+ copyFileSync(r.path, ruleDest);
155
+ }
112
156
  log.success(`${tag}Rule: ${r.name}`);
113
157
  count++;
114
158
  }