@lythos/skill-deck 0.1.7 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lythos/skill-deck",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Declarative skill deck governance — cold pool, working set, deny-by-default",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/link.ts CHANGED
@@ -17,6 +17,7 @@ import { resolve, dirname, join } from "path";
17
17
  import { homedir } from "os";
18
18
  import {
19
19
  SkillDeckLockSchema,
20
+ SkillDeckTomlSchema,
20
21
  type SkillDeckLock, type LinkedSkill, type ConstraintReport,
21
22
  } from "./schema.js";
22
23
 
@@ -160,11 +161,38 @@ if (!existsSync(DECK_PATH)) {
160
161
  const PROJECT_DIR = cliWorkdir ? resolve(cliWorkdir) : dirname(DECK_PATH);
161
162
  const deckRaw = readFileSync(DECK_PATH, "utf-8");
162
163
  const deckHash = hashContent(deckRaw);
163
- const deck = parseToml(deckRaw) as any;
164
+ const parsedToml = parseToml(deckRaw) as any;
165
+
166
+ // 预处理:TOML 解析器可能在空表上生成 Symbol 键,先清理
167
+ function stripSymbols(obj: any): any {
168
+ if (obj == null) return obj;
169
+ if (Array.isArray(obj)) return obj.map(stripSymbols);
170
+ if (typeof obj === 'object') {
171
+ const clean: Record<string, any> = {};
172
+ for (const key of Object.keys(obj)) {
173
+ clean[key] = stripSymbols(obj[key]);
174
+ }
175
+ return clean;
176
+ }
177
+ return obj;
178
+ }
179
+ const cleanToml = stripSymbols(parsedToml);
180
+
181
+ // Schema 校验:防止模板与运行时解析漂移
182
+ const schemaResult = SkillDeckTomlSchema.safeParse(cleanToml);
183
+ if (!schemaResult.success) {
184
+ console.error("❌ skill-deck.toml 格式错误:");
185
+ for (const issue of schemaResult.error.issues) {
186
+ const path = issue.path.map(String).filter(p => p !== "undefined").join(".") || "<root>";
187
+ console.error(` ${path}: ${issue.message}`);
188
+ }
189
+ process.exit(1);
190
+ }
191
+ const deck = schemaResult.data;
164
192
 
165
- const WORKING_SET = expandHome(deck.deck?.working_set || ".claude/skills", PROJECT_DIR);
166
- const COLD_POOL = expandHome(deck.deck?.cold_pool || "~/.agents/skill-repos", PROJECT_DIR);
167
- const MAX_CARDS = Number(deck.deck?.max_cards || 10);
193
+ const WORKING_SET = expandHome(deck.deck.working_set, PROJECT_DIR);
194
+ const COLD_POOL = expandHome(deck.deck.cold_pool, PROJECT_DIR);
195
+ const MAX_CARDS = deck.deck.max_cards;
168
196
 
169
197
  // ── 收集声明 ────────────────────────────────────────────────
170
198
 
package/src/schema.ts CHANGED
@@ -1,5 +1,30 @@
1
1
  import { z } from "zod";
2
2
 
3
+ // ── skill-deck.toml 声明文件 Schema ─────────────────────────
4
+ // 防止模板与运行时解析漂移(template drift)
5
+
6
+ export const SkillDeckTomlSchema = z.object({
7
+ deck: z.object({
8
+ working_set: z.string().default(".claude/skills"),
9
+ cold_pool: z.string().default("~/.agents/skill-repos"),
10
+ max_cards: z.number().int().min(1).default(10),
11
+ }),
12
+ innate: z.object({
13
+ skills: z.array(z.string()).default([]),
14
+ }).optional(),
15
+ tool: z.object({
16
+ skills: z.array(z.string()).default([]),
17
+ }).optional(),
18
+ combo: z.object({
19
+ skills: z.array(z.string()).default([]),
20
+ }).optional(),
21
+ // transient: 动态子表,key 是 skill 名,value 是任意配置
22
+ // 用 z.any() 避免空 [transient] 导致 record key 验证失败
23
+ transient: z.record(z.string(), z.any()).optional(),
24
+ });
25
+
26
+ export type SkillDeckToml = z.infer<typeof SkillDeckTomlSchema>;
27
+
3
28
  // ── 单个已链接 Skill ────────────────────────────────────────
4
29
  export const LinkedSkillSchema = z.object({
5
30
  name: z.string(),