@lythos/skill-deck 0.1.1 → 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 +45 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lythos/skill-deck",
3
- "version": "0.1.1",
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
@@ -23,14 +23,8 @@ import {
23
23
  // ── 路径工具 ────────────────────────────────────────────────
24
24
 
25
25
  function findDeckToml(from: string): string | null {
26
- let dir = from;
27
- for (let i = 0; i < 10; i++) {
28
- const p = join(dir, "skill-deck.toml");
29
- if (existsSync(p)) return p;
30
- const parent = dirname(dir);
31
- if (parent === dir) break;
32
- dir = parent;
33
- }
26
+ const p = join(from, "skill-deck.toml");
27
+ if (existsSync(p)) return p;
34
28
  return null;
35
29
  }
36
30
 
@@ -89,6 +83,26 @@ function extractArrayField(fm: string, field: string): string[] {
89
83
  // ── 冷池查找 ────────────────────────────────────────────────
90
84
 
91
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
+
92
106
  // 1. 直接路径
93
107
  const direct = resolve(coldPool, name);
94
108
  if (existsSync(join(direct, "SKILL.md"))) return direct;
@@ -100,10 +114,17 @@ function findSource(name: string, coldPool: string, projectDir: string): string
100
114
  if (existsSync(join(mono, "SKILL.md"))) return mono;
101
115
  }
102
116
 
103
- // 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']);
104
124
  try {
105
125
  for (const entry of readdirSync(coldPool, { withFileTypes: true })) {
106
126
  if (!entry.isDirectory()) continue;
127
+ if (skipDirs.has(entry.name)) continue;
107
128
  const base = join(coldPool, entry.name);
108
129
  for (const sub of [join(base, name), join(base, "skills", name)]) {
109
130
  if (existsSync(join(sub, "SKILL.md"))) return sub;
@@ -111,27 +132,31 @@ function findSource(name: string, coldPool: string, projectDir: string): string
111
132
  }
112
133
  } catch {}
113
134
 
114
- // 4. 项目本地: <project>/skills/<name>
115
- const local = resolve(projectDir, "skills", name);
116
- if (existsSync(join(local, "SKILL.md"))) return local;
117
-
118
135
  return null;
119
136
  }
120
137
 
121
138
  // ── 主流程 ──────────────────────────────────────────────────
122
139
 
123
- export function linkDeck(cliDeckPath?: string): void {
140
+ export function linkDeck(cliDeckPath?: string, cliWorkdir?: string): void {
124
141
  const cliDeck = cliDeckPath || process.argv.find((_, i, a) => a[i - 1] === "--deck");
125
142
  const DECK_PATH = cliDeck
126
143
  ? resolve(cliDeck)
127
144
  : findDeckToml(process.cwd()) || resolve("skill-deck.toml");
128
145
 
129
146
  if (!existsSync(DECK_PATH)) {
130
- console.error(`❌ ${DECK_PATH} 不存在`);
147
+ console.error(`❌ skill-deck.toml not found in ${process.cwd()}`);
148
+ console.error(`\nCreate one:`);
149
+ console.error(` cat > skill-deck.toml <<'EOF'`);
150
+ console.error(` [deck]`);
151
+ console.error(` max_cards = 10`);
152
+ console.error(` \n [tool]`);
153
+ console.error(` skills = ["lythoskill-deck"]`);
154
+ console.error(` EOF`);
155
+ console.error(`\nOr specify a path: bunx @lythos/skill-deck link --deck /path/to/deck.toml`);
131
156
  process.exit(1);
132
157
  }
133
158
 
134
- const PROJECT_DIR = dirname(DECK_PATH);
159
+ const PROJECT_DIR = cliWorkdir ? resolve(cliWorkdir) : dirname(DECK_PATH);
135
160
  const deckRaw = readFileSync(DECK_PATH, "utf-8");
136
161
  const deckHash = hashContent(deckRaw);
137
162
  const deck = parseToml(deckRaw) as any;
@@ -211,10 +236,11 @@ const linkedSkills: LinkedSkill[] = [];
211
236
  for (const item of declared) {
212
237
  const dest = join(WORKING_SET, item.name);
213
238
 
214
- // 幂等:已存在则删除重建
215
- if (existsSync(dest)) {
239
+ // 幂等:已存在则删除重建(lstat 不跟随 symlink,能处理断链/自引用 symlink)
240
+ try {
241
+ lstatSync(dest);
216
242
  rmSync(dest, { recursive: true, force: true });
217
- }
243
+ } catch {}
218
244
 
219
245
  try {
220
246
  mkdirSync(dirname(dest), { recursive: true });