@lythos/skill-deck 0.1.2 → 0.1.4

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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/cli.ts +4 -2
  3. package/src/link.ts +35 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lythos/skill-deck",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Declarative skill deck governance — cold pool, working set, deny-by-default",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -3,13 +3,15 @@ import { linkDeck } from './link.js'
3
3
 
4
4
  const command = process.argv[2]
5
5
  const deckFlagIdx = process.argv.indexOf('--deck')
6
+ const workdirFlagIdx = process.argv.indexOf('--workdir')
6
7
  const deckPath = deckFlagIdx >= 0 ? process.argv[deckFlagIdx + 1] : undefined
8
+ const workdir = workdirFlagIdx >= 0 ? process.argv[workdirFlagIdx + 1] : undefined
7
9
 
8
10
  switch (command) {
9
11
  case 'link':
10
- linkDeck(deckPath)
12
+ linkDeck(deckPath, workdir)
11
13
  break
12
14
  default:
13
- console.error('Usage: lythoskill-deck link [--deck <path>]')
15
+ console.error('Usage: lythoskill-deck link [--deck <path>] [--workdir <dir>]')
14
16
  process.exit(1)
15
17
  }
package/src/link.ts CHANGED
@@ -83,6 +83,26 @@ function extractArrayField(fm: string, field: string): string[] {
83
83
  // ── 冷池查找 ────────────────────────────────────────────────
84
84
 
85
85
  function findSource(name: string, coldPool: string, projectDir: string): string | null {
86
+ // 0. Fully-qualified path: host.tld/owner/repo/skill
87
+ // → cold_pool/host.tld/owner/repo/skills/skill
88
+ // Also handles host.tld/owner/repo (standalone skill without skills/ subdir)
89
+ const fqMatch = name.match(/^[a-z0-9-]+\.[a-z0-9-]+\//);
90
+ if (fqMatch) {
91
+ const parts = name.split("/");
92
+ const host = parts[0]; // github.com
93
+ const owner = parts[1]; // lythos-labs
94
+ const repo = parts[2]; // lythoskill
95
+ const skill = parts.slice(3).join("/"); // lythoskill-deck
96
+
97
+ if (skill) {
98
+ const fqPath = join(coldPool, host, owner, repo, "skills", skill);
99
+ if (existsSync(join(fqPath, "SKILL.md"))) return fqPath;
100
+ }
101
+ // fallback: standalone skill at repo root
102
+ const directPath = join(coldPool, host, owner, repo);
103
+ if (existsSync(join(directPath, "SKILL.md"))) return directPath;
104
+ }
105
+
86
106
  // 1. 直接路径
87
107
  const direct = resolve(coldPool, name);
88
108
  if (existsSync(join(direct, "SKILL.md"))) return direct;
@@ -94,10 +114,18 @@ function findSource(name: string, coldPool: string, projectDir: string): string
94
114
  if (existsSync(join(mono, "SKILL.md"))) return mono;
95
115
  }
96
116
 
97
- // 3. 扁平扫描: cold_pool/<any-repo>/<name> 或 <any-repo>/skills/<name>
117
+ // 3. 项目本地: <project>/skills/<name>(build 输出目录,优先级高于扁平扫描)
118
+ const local = resolve(projectDir, "skills", name);
119
+ if (existsSync(join(local, "SKILL.md"))) return local;
120
+
121
+ // 4. 扁平扫描: cold_pool/<any-repo>/<name> 或 <any-repo>/skills/<name>
122
+ // 跳过隐藏目录(agent working set、git、配置等)和 node_modules,
123
+ // 避免把 .claude/skills/ 里的 symlink 误判为有效 cold-pool 源
98
124
  try {
99
125
  for (const entry of readdirSync(coldPool, { withFileTypes: true })) {
100
126
  if (!entry.isDirectory()) continue;
127
+ if (entry.name.startsWith('.')) continue;
128
+ if (entry.name === 'node_modules') continue;
101
129
  const base = join(coldPool, entry.name);
102
130
  for (const sub of [join(base, name), join(base, "skills", name)]) {
103
131
  if (existsSync(join(sub, "SKILL.md"))) return sub;
@@ -105,16 +133,12 @@ function findSource(name: string, coldPool: string, projectDir: string): string
105
133
  }
106
134
  } catch {}
107
135
 
108
- // 4. 项目本地: <project>/skills/<name>
109
- const local = resolve(projectDir, "skills", name);
110
- if (existsSync(join(local, "SKILL.md"))) return local;
111
-
112
136
  return null;
113
137
  }
114
138
 
115
139
  // ── 主流程 ──────────────────────────────────────────────────
116
140
 
117
- export function linkDeck(cliDeckPath?: string): void {
141
+ export function linkDeck(cliDeckPath?: string, cliWorkdir?: string): void {
118
142
  const cliDeck = cliDeckPath || process.argv.find((_, i, a) => a[i - 1] === "--deck");
119
143
  const DECK_PATH = cliDeck
120
144
  ? resolve(cliDeck)
@@ -133,7 +157,7 @@ if (!existsSync(DECK_PATH)) {
133
157
  process.exit(1);
134
158
  }
135
159
 
136
- const PROJECT_DIR = dirname(DECK_PATH);
160
+ const PROJECT_DIR = cliWorkdir ? resolve(cliWorkdir) : dirname(DECK_PATH);
137
161
  const deckRaw = readFileSync(DECK_PATH, "utf-8");
138
162
  const deckHash = hashContent(deckRaw);
139
163
  const deck = parseToml(deckRaw) as any;
@@ -213,10 +237,11 @@ const linkedSkills: LinkedSkill[] = [];
213
237
  for (const item of declared) {
214
238
  const dest = join(WORKING_SET, item.name);
215
239
 
216
- // 幂等:已存在则删除重建
217
- if (existsSync(dest)) {
240
+ // 幂等:已存在则删除重建(lstat 不跟随 symlink,能处理断链/自引用 symlink)
241
+ try {
242
+ lstatSync(dest);
218
243
  rmSync(dest, { recursive: true, force: true });
219
- }
244
+ } catch {}
220
245
 
221
246
  try {
222
247
  mkdirSync(dirname(dest), { recursive: true });