@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.
Files changed (3) hide show
  1. package/README.md +52 -14
  2. package/package.json +21 -2
  3. 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
- Part of the [lythoskill](https://github.com/lythos-labs/lythoskill) meta-skill ecosystem.
5
+ ## Why
6
6
 
7
- ## What it does
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
- Manages your agent's working set of skills. You declare which skills you want in `skill-deck.toml`; `deck link` creates symlinks from the cold pool to `.claude/skills/`. Supports deny-by-default isolation, max_cards budgeting, transient expiry, and managed directory overlap detection.
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
- ## Commands
19
+ ## Quick Start
20
20
 
21
21
  ```bash
22
- # Link declared skills to working set
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
- # Link with custom deck file
26
- bunx @lythos/skill-deck link --deck ./my-deck.toml
40
+ Usage: lythoskill-deck link | lythoskill-deck validate [deck.toml]
27
41
 
28
- # Show current deck status
29
- bunx @lythos/skill-deck status
42
+ Commands:
43
+ link Sync working set with skill-deck.toml
44
+ validate [deck.toml] Validate deck configuration
30
45
 
31
- # Migrate from old deck format
32
- bunx @lythos/skill-deck migrate
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
- This is the **Starter** layer of the thin-skill pattern. The agent-visible **Skill** layer lives in `packages/lythoskill-deck/skill/` and is built to `skills/lythoskill-deck/`.
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.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 getFrontMatter(skillMdPath: string): string {
42
+ function parseSkillFrontmatter(skillMdPath: string): Record<string, any> {
43
43
  try {
44
44
  const c = readFileSync(skillMdPath, "utf-8");
45
- if (!c.startsWith("---")) return "";
46
- const parts = c.split("---");
47
- return parts.length >= 3 ? parts[1] : "";
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 = getFrontMatter(skillMdPath);
257
- const niche = extractField(fm, "deck_niche");
258
- const managedDirs = extractArrayField(fm, "deck_managed_dirs");
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"));