@lythos/skill-deck 0.1.2 → 0.1.3

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 +34 -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.3",
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,17 @@ 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 相关目录,避免把 symlink 误判为有效源
123
+ const skipDirs = new Set(['.claude', '.kimi', '.git', 'node_modules', '.lythos-curator']);
98
124
  try {
99
125
  for (const entry of readdirSync(coldPool, { withFileTypes: true })) {
100
126
  if (!entry.isDirectory()) continue;
127
+ if (skipDirs.has(entry.name)) continue;
101
128
  const base = join(coldPool, entry.name);
102
129
  for (const sub of [join(base, name), join(base, "skills", name)]) {
103
130
  if (existsSync(join(sub, "SKILL.md"))) return sub;
@@ -105,16 +132,12 @@ function findSource(name: string, coldPool: string, projectDir: string): string
105
132
  }
106
133
  } catch {}
107
134
 
108
- // 4. 项目本地: <project>/skills/<name>
109
- const local = resolve(projectDir, "skills", name);
110
- if (existsSync(join(local, "SKILL.md"))) return local;
111
-
112
135
  return null;
113
136
  }
114
137
 
115
138
  // ── 主流程 ──────────────────────────────────────────────────
116
139
 
117
- export function linkDeck(cliDeckPath?: string): void {
140
+ export function linkDeck(cliDeckPath?: string, cliWorkdir?: string): void {
118
141
  const cliDeck = cliDeckPath || process.argv.find((_, i, a) => a[i - 1] === "--deck");
119
142
  const DECK_PATH = cliDeck
120
143
  ? resolve(cliDeck)
@@ -133,7 +156,7 @@ if (!existsSync(DECK_PATH)) {
133
156
  process.exit(1);
134
157
  }
135
158
 
136
- const PROJECT_DIR = dirname(DECK_PATH);
159
+ const PROJECT_DIR = cliWorkdir ? resolve(cliWorkdir) : dirname(DECK_PATH);
137
160
  const deckRaw = readFileSync(DECK_PATH, "utf-8");
138
161
  const deckHash = hashContent(deckRaw);
139
162
  const deck = parseToml(deckRaw) as any;
@@ -213,10 +236,11 @@ const linkedSkills: LinkedSkill[] = [];
213
236
  for (const item of declared) {
214
237
  const dest = join(WORKING_SET, item.name);
215
238
 
216
- // 幂等:已存在则删除重建
217
- if (existsSync(dest)) {
239
+ // 幂等:已存在则删除重建(lstat 不跟随 symlink,能处理断链/自引用 symlink)
240
+ try {
241
+ lstatSync(dest);
218
242
  rmSync(dest, { recursive: true, force: true });
219
- }
243
+ } catch {}
220
244
 
221
245
  try {
222
246
  mkdirSync(dirname(dest), { recursive: true });