@lythos/skill-deck 0.3.0 → 0.4.0
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/README.md +52 -14
- package/package.json +21 -2
- package/src/link.ts +13 -40
package/README.md
CHANGED
|
@@ -1,40 +1,78 @@
|
|
|
1
1
|
# @lythos/skill-deck
|
|
2
2
|
|
|
3
|
-
> Declarative skill deck governance. Reconcile declared skills against your cold pool via symlinks.
|
|
3
|
+
> Declarative skill deck governance for AI agents. Reconcile declared skills against your cold pool via symlinks — deny-by-default, max-cards budgeting, transient expiry.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Why
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
When an AI agent has access to 50+ skills, context window pollution and silent conflicts become real problems. Two skills claiming the same niche, redundant descriptions, incompatible assumptions — all invisible until the agent hallucinates.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`skill-deck.toml` solves this by declaring *exactly* which skills the agent should see. `deck link` creates symlinks from the cold pool to `.claude/skills/` and **removes everything else**. Deny-by-default means undeclared skills physically do not exist in the agent's view.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
bun add -d @lythos/skill-deck
|
|
15
|
-
# or
|
|
15
|
+
# or use directly
|
|
16
16
|
bunx @lythos/skill-deck <command>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Quick Start
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
#
|
|
22
|
+
# 1. Create a skill-deck.toml
|
|
23
|
+
cat > skill-deck.toml << 'EOF'
|
|
24
|
+
[deck]
|
|
25
|
+
max_cards = 10
|
|
26
|
+
|
|
27
|
+
[tool]
|
|
28
|
+
skills = ["lythoskill-deck"]
|
|
29
|
+
EOF
|
|
30
|
+
|
|
31
|
+
# 2. Link — creates symlinks in .claude/skills/
|
|
23
32
|
bunx @lythos/skill-deck link
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
lythoskill-deck — Declarative skill deck governance — cold pool, working set, deny-by-default
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
bunx @lythos/skill-deck link --deck ./my-deck.toml
|
|
40
|
+
Usage: lythoskill-deck link | lythoskill-deck validate [deck.toml]
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
Commands:
|
|
43
|
+
link Sync working set with skill-deck.toml
|
|
44
|
+
validate [deck.toml] Validate deck configuration
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
Options:
|
|
47
|
+
--deck <path> Specify skill-deck.toml path
|
|
48
|
+
--workdir <dir> Specify working directory
|
|
33
49
|
```
|
|
34
50
|
|
|
51
|
+
## Key Concepts
|
|
52
|
+
|
|
53
|
+
| Concept | One-liner |
|
|
54
|
+
|---------|-----------|
|
|
55
|
+
| **Cold Pool** | All downloaded skills (`~/.agents/skill-repos/`). Agent cannot see here. |
|
|
56
|
+
| **skill-deck.toml** | Declares desired state: "this project uses these skills." |
|
|
57
|
+
| **`deck link`** | Reconciler. Makes `.claude/skills/` match the declaration. |
|
|
58
|
+
| **Working Set** | `.claude/skills/` — symlinks only. What the agent actually scans. |
|
|
59
|
+
| **deny-by-default** | Undeclared skills are physically absent from the working set. |
|
|
60
|
+
|
|
61
|
+
## Skill Documentation
|
|
62
|
+
|
|
63
|
+
This package is the **Starter** layer (CLI implementation).
|
|
64
|
+
The agent-visible **Skill** layer documentation is here:
|
|
65
|
+
[packages/lythoskill-deck/skill/SKILL.md](../../packages/lythoskill-deck/skill/SKILL.md)
|
|
66
|
+
|
|
35
67
|
## Architecture
|
|
36
68
|
|
|
37
|
-
|
|
69
|
+
Part of the [lythoskill](https://github.com/lythos-labs/lythoskill) ecosystem — the thin-skill pattern separates heavy logic (this npm package) from lightweight agent instructions (SKILL.md).
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Starter (this package) → npm publish → bunx @lythos/skill-deck ...
|
|
73
|
+
Skill (packages/<name>/skill/) → build → SKILL.md + thin scripts
|
|
74
|
+
Output (skills/<name>/) → git commit → agent-visible skill
|
|
75
|
+
```
|
|
38
76
|
|
|
39
77
|
## License
|
|
40
78
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lythos/skill-deck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Declarative skill deck governance — cold pool, working set, deny-by-default",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai-agent",
|
|
7
|
+
"skill",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"agent-skills",
|
|
10
|
+
"llm-tooling",
|
|
11
|
+
"lythoskill"
|
|
12
|
+
],
|
|
13
|
+
"author": "lythos-labs",
|
|
5
14
|
"license": "MIT",
|
|
6
15
|
"type": "module",
|
|
7
16
|
"bin": {
|
|
@@ -14,6 +23,16 @@
|
|
|
14
23
|
],
|
|
15
24
|
"dependencies": {
|
|
16
25
|
"@iarna/toml": "^2.2.5",
|
|
26
|
+
"yaml": "^2.8.3",
|
|
17
27
|
"zod": "^4.3.6"
|
|
18
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/lythos-labs/lythoskill.git",
|
|
32
|
+
"directory": "packages/lythoskill-deck"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/lythos-labs/lythoskill/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/lythos-labs/lythoskill/tree/main/packages/lythoskill-deck#readme"
|
|
19
38
|
}
|
package/src/link.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { parse as parseToml } from "@iarna/toml";
|
|
11
|
+
import YAML from "yaml";
|
|
11
12
|
import { createHash } from "crypto";
|
|
12
13
|
import {
|
|
13
14
|
existsSync, mkdirSync, readFileSync, readdirSync,
|
|
@@ -36,49 +37,17 @@ export function expandHome(p: string, base: string): string {
|
|
|
36
37
|
function hashContent(content: string): string {
|
|
37
38
|
return createHash("sha256").update(content).digest("hex");
|
|
38
39
|
}
|
|
39
|
-
|
|
40
40
|
// ── Front matter 提取 ───────────────────────────────────────
|
|
41
41
|
|
|
42
|
-
function
|
|
42
|
+
function parseSkillFrontmatter(skillMdPath: string): Record<string, any> {
|
|
43
43
|
try {
|
|
44
44
|
const c = readFileSync(skillMdPath, "utf-8");
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return
|
|
48
|
-
} catch { return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function extractField(fm: string, field: string): string {
|
|
52
|
-
const m = fm.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
|
|
53
|
-
return m ? m[1].trim() : "";
|
|
45
|
+
const match = c.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
46
|
+
if (!match) return {};
|
|
47
|
+
return YAML.parse(match[1]) || {};
|
|
48
|
+
} catch { return {}; }
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
function extractArrayField(fm: string, field: string): string[] {
|
|
57
|
-
const lines = fm.split("\n");
|
|
58
|
-
const results: string[] = [];
|
|
59
|
-
let collecting = false;
|
|
60
|
-
for (const line of lines) {
|
|
61
|
-
if (line.match(new RegExp(`^${field}:\\s*$`))) {
|
|
62
|
-
collecting = true;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (line.match(new RegExp(`^${field}:\\s*\\[`))) {
|
|
66
|
-
const inline = line.match(/\[(.+)\]/);
|
|
67
|
-
if (inline) return inline[1].split(",").map(s => s.trim().replace(/^["']|["']$/g, ""));
|
|
68
|
-
collecting = true;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (collecting) {
|
|
72
|
-
const item = line.match(/^\s+-\s+(.+)/);
|
|
73
|
-
if (item) {
|
|
74
|
-
results.push(item[1].trim().replace(/^["']|["']$/g, ""));
|
|
75
|
-
} else if (line.trim() !== "" && !line.match(/^\s*#/)) {
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return results;
|
|
81
|
-
}
|
|
82
51
|
|
|
83
52
|
// ── 冷池查找 ────────────────────────────────────────────────
|
|
84
53
|
|
|
@@ -253,9 +222,13 @@ for (const item of declared) {
|
|
|
253
222
|
|
|
254
223
|
// 提取元数据
|
|
255
224
|
const skillMdPath = join(item.sourcePath, "SKILL.md");
|
|
256
|
-
const fm =
|
|
257
|
-
const niche =
|
|
258
|
-
const managedDirs =
|
|
225
|
+
const fm = parseSkillFrontmatter(skillMdPath);
|
|
226
|
+
const niche = String(fm["deck_niche"] || "");
|
|
227
|
+
const managedDirs = Array.isArray(fm["deck_managed_dirs"])
|
|
228
|
+
? fm["deck_managed_dirs"].map(String)
|
|
229
|
+
: fm["deck_managed_dirs"]
|
|
230
|
+
? [String(fm["deck_managed_dirs"])]
|
|
231
|
+
: [];
|
|
259
232
|
let contentHash: string | undefined;
|
|
260
233
|
try {
|
|
261
234
|
contentHash = hashContent(readFileSync(skillMdPath, "utf-8"));
|