import { describe, expect, it } from 'vitest'; import { autoRecipeSchema } from '../auto-recipe.ts'; describe('auto 配方契约', () => { it('收下一份用全了五个动词的配方', () => { const parsed = autoRecipeSchema.safeParse({ let: { evalRef: { cat: ['W05-', { fmt: { in: 'case:openedAt' }, as: 'digits' }] } }, do: [ { kind: 'project', into: 'sub_eval.part', prune: true, from: { rows: [{ pn: { in: 'originalInternalPn' } }] }, fields: { internal_pn: { col: 'pn' } } }, { kind: 'analyze', model: 'deepseek-v4-flash', into: 'items', instruction: '逐条判四态', input: { from: { each: 'parts.type_param' }, fields: { name: { col: 'param_name' } } }, output: { type: 'object' } }, { kind: 'card', as: 'execution', variant: 'data', title: '补数据', basis: [{ k: '原 PN', v: { in: 'originalInternalPn' } }], gateLedgers: ['parts.profile'] }, { kind: 'submit', rationale: '结论已备好' }, { kind: 'say', text: '这一步办完了' }, ], }); expect(parsed.success).toBe(true); }); it('不认识的动词当场拒 —— 没实现的形状不许装进来', () => { const parsed = autoRecipeSchema.safeParse({ do: [{ kind: 'drop', into: 'a.b' }] }); expect(parsed.success).toBe(false); }); it('嵌套只许两层:第三层当场拒', () => { const parsed = autoRecipeSchema.safeParse({ do: [{ kind: 'project', into: 'a.b', from: { each: 'a.b', then: { each: 'c.d', then: { each: 'e.f' } } }, fields: {} }], }); expect(parsed.success).toBe(false); }); it("prune 收 'live' 档 —— quiet 拍也撤,只给人不会在件上加行的表用(其余字符串拒)", () => { const live = autoRecipeSchema.safeParse({ do: [{ kind: 'project', into: 'a.b', prune: 'live', from: { once: true }, fields: { k: 'v' } }] }); expect(live.success).toBe(true); const junk = autoRecipeSchema.safeParse({ do: [{ kind: 'project', into: 'a.b', prune: 'always', from: { once: true }, fields: { k: 'v' } }] }); expect(junk.success).toBe(false); }); it('card 收确认闸条件档 gateWhen/gateWhenSay(2026-08-31,类型不一致不许确认)', () => { const ok = autoRecipeSchema.safeParse({ do: [{ kind: 'card', as: 'execution', title: 't', gateWhen: 'a_b_distinct_c <= 1', gateWhenSay: '先改对再确认' }], }); expect(ok.success).toBe(true); }); it('prune 只许挂在 project 上', () => { const parsed = autoRecipeSchema.safeParse({ do: [{ kind: 'say', text: 'x', prune: true }] }); expect(parsed.success).toBe(false); }); }); import { planStepSchema } from '../manifest.ts'; describe('全 auto 的步可以没有 guide', () => { /* brief 原样缺了 intent(required,非空)—— 缺了它 safeParse 恒失败,前两条测试就都验不到「guide 允不允许空」这件事本身 */ const base = { item: 'S1', intent: '这一步交出什么', guide: '', writes: [], reads: [], tools: [] }; it('声明了 auto 就允许 guide 为空 —— 那一步压根不问模型', () => { const parsed = planStepSchema.safeParse({ ...base, auto: { do: [{ kind: 'say', text: '办完了' }] }, }); expect(parsed.success).toBe(true); }); it('没有 auto 又不写 guide → 拒 —— 「作者忘了写做法」这个老错照旧要拦', () => { const parsed = planStepSchema.safeParse(base); expect(parsed.success).toBe(false); }); it('有 auto 但 guide 写了内容 → 照收(半 auto:确定的先做完,剩下的仍问模型)', () => { const parsed = planStepSchema.safeParse({ ...base, guide: '把结论写实', auto: { do: [{ kind: 'say', text: 'x' }] }, }); expect(parsed.success).toBe(true); }); });