Skill Wiki v0.1.0

文档 / implementation / checkers

本页目录

Checkers · L1 / L2 / L3

三层检查,各抓一类 bug。L1 加 L3 是纯逻辑、强制开;L2 可选,调一个小 LLM。

L1 · Schema(强制)

校验每个原子的结构是否符合所属 kind 的 schema。必填字段齐不齐?装饰器约束满不满足? atom id 长不长成 @scope/kind-kebab-name 的样子?跨原子引用在 Prime 内部能不能解析?

// packages/compiler/src/checker-l1.ts
export function checkL1(ast: PrimeAST): Diagnostic[] {
  const errors: Diagnostic[] = [];
  for (const atom of ast.atoms) {
    errors.push(...validateRequiredFields(atom));
    errors.push(...validateIdShape(atom));
    errors.push(...validateKindSchema(atom));
    errors.push(...validateEdgeShape(atom));
  }
  return errors;
}

开销:每个原子约 5 ms。没有 API key 也能跑。

L2 · 语义 LLM 检查(可选)

调一个小 LLM(默认 DeepSeek)抓语义漂移。问的是:body 跟 metadata 自相矛盾吗? 一条 rule 上写着 severity: low,可 remediation 描述的是关键无障碍阻断 —— 这就是 L2 要标的事。

// packages/compiler/src/checker-l2.ts (excerpt)
const prompt = `Atom ${atom.id} of kind ${atom.kind}.

Metadata says: severity=${atom.severity}, applies-to=${atom.appliesTo}.
Body says: "${atom.body}"

Does the body's tone, scope, or severity match the metadata?
Reply: { "ok": bool, "reason"?: string }
`;

const verdict = await aiClient.json(prompt);
if (!verdict.ok) {
  diagnostics.push({ atom: atom.id, message: verdict.reason });
}

开销:DeepSeek 上每个原子约 $0.0001;结果按内容 hash 缓存到 .l2-cache.json。 没 API key 的构建会跳过 L2,并在输出里加一条 notice。

L3 · 跨原子(强制)

在解析后的边图上走一遍。三类错:

  1. requires 链里的环 —— 加载顺序解不开。
  2. 矛盾 —— 带 contradicts 边的原子,又同时出现在某条组合 contract 里。
  3. kind 不匹配 —— validates-with 从一条 rule 指向另一条 rule(应该指向 source / metric / check)。
// packages/compiler/src/checker-l3-cross.ts (excerpt)
function detectCycles(graph: EdgeGraph): Cycle[] {
  // Tarjan's SCC over the requires-only subgraph
  const sccs = stronglyConnectedComponents(graph.subgraph('requires'));
  return sccs.filter((scc) => scc.size > 1).map(toCycle);
}

function checkContradicts(atoms: Atom[]): Diagnostic[] {
  const ds: Diagnostic[] = [];
  for (const atom of atoms) {
    for (const c of atom.contradicts) {
      const target = byId.get(c);
      if (!target) continue;
      const sharedScope = scopesContaining(atom).filter(
        (s) => scopesContaining(target).includes(s),
      );
      if (sharedScope.length > 0) {
        ds.push(scopeContradictsError(atom, target, sharedScope));
      }
    }
  }
  return ds;
}

诊断格式

三种 checker 输出统一的 Diagnostic 形状:

interface Diagnostic {
  level:    'error' | 'warning' | 'info';
  layer:    'L1' | 'L2' | 'L3';
  atom:     string;            // atom id
  message:  string;
  hint?:    string;            // suggested fix
  source?:  { file: string; line: number };
}

怎么跑

# All three on a whole sources tree
prime check --registry

# Only L1 + L3 (no API key needed)
prime check --registry --no-l2

# Single file
prime check primes/sources/@my/rule-x.prime