import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import frontmatterRules from "./index.ts"; // ---- Tiny mock harness ----------------------------------------------------- type Handler = (event: any, ctx: any) => Promise; function makePi() { const handlers: Record = {}; const pi: any = { on: (name: string, h: Handler) => (handlers[name] = h) }; return { pi, handlers }; } const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "fmr-test-")); const ctx = { cwd: tmp, hasUI: false }; function rule(rel: string, content: string) { const fp = path.join(tmp, rel); fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, content); } function doc(rel: string, content: string) { const fp = path.join(tmp, rel); fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, content); return fp; } // Rules at project root .pi/rules rule(".pi/rules/security.md", `---\nname: Security review\nwhen:\n tags: { contains: security }\n---\nDo a threat model.`); rule(".pi/rules/spec.md", `---\nwhen:\n type: spec\n---\nFollow the spec checklist.`); rule(".pi/rules/high-prio-api.md", `---\nname: High prio API\nwhen:\n all:\n - type: spec\n - priority: { gte: 3 }\n any:\n - { area: api }\n - { tags: { containsAny: [api, http] } }\n---\nGet API review.`); rule(".pi/rules/inert.md", `---\nname: No condition\n---\nThis should never auto-load.`); rule(".pi/rules/nested-field.md", `---\nwhen:\n meta.owner: alice\n---\nPing alice.`); rule(".pi/rules/regex.md", `---\nwhen:\n title: { imatches: "^api[- ]" }\n---\nName matched regex.`); // A deeper rules dir that should NOT apply to a file above it rule("sub/.pi/rules/deep.md", `---\nwhen:\n type: spec\n---\nDeep rule.`); const { pi, handlers } = makePi(); frontmatterRules(pi); let pass = 0; let fail = 0; async function check(name: string, filePath: string, expectRules: string[], notExpect: string[] = [], freshSeen = true) { if (freshSeen) await handlers["session_start"]({}, ctx); // reset dedup const event = { toolName: "read", isError: false, input: { path: filePath }, content: [{ type: "text", text: "FILE_BODY" }], }; const res = await handlers["tool_result"](event, ctx); const text: string = res?.content?.[0]?.text ?? ""; const ok = expectRules.every((r) => text.includes(r)) && notExpect.every((r) => !text.includes(r)) && (expectRules.length === 0 ? res === undefined : true); if (ok) { pass++; console.log(` ok ${name}`); } else { fail++; console.log(`FAIL ${name}`); console.log(` expected: ${JSON.stringify(expectRules)} absent: ${JSON.stringify(notExpect)}`); console.log(` got:\n${text.split("\n").map((l) => " " + l).join("\n")}`); } } (async () => { // 1. array `contains` on tags await check("tags contains security", doc("a.md", `---\ntags: [security, infra]\n---\nbody`), ["Security review"], ["spec", "High prio"]); // 2. scalar equality on type await check("type spec equality", doc("b.md", `---\ntype: spec\n---\nbody`), ["Follow the spec checklist"], ["Security review"]); // 3. combinator all+any with gte and containsAny await check("complex all/any/gte", doc("c.md", `---\ntype: spec\npriority: 4\ntags: [http]\n---\nbody`), ["High prio API", "Follow the spec checklist"]); // 4. complex should fail when priority too low await check("complex fails low priority", doc("d.md", `---\ntype: spec\npriority: 1\narea: api\n---\nbody`), ["Follow the spec checklist"], ["High prio API"]); // 5. inert rule never loads; no frontmatter match at all -> undefined result await check("no matching frontmatter -> no injection", doc("e.md", `---\nrandom: value\n---\nbody`), []); // 6. nested dotted field await check("nested dotted field", doc("f.md", `---\nmeta:\n owner: alice\n---\nbody`), ["Ping alice"]); // 7. case-insensitive regex await check("imatches regex", doc("g.md", `---\ntitle: "API Gateway"\n---\nbody`), ["Name matched regex"]); // 8. file with NO frontmatter -> skip entirely await check("no frontmatter -> skip", doc("h.md", `# just a heading\nno frontmatter`), []); // 9. deep rule applies to files in its subtree await check("deep rule in subtree", doc("sub/deep-doc.md", `---\ntype: spec\n---\nbody`), ["Deep rule", "Follow the spec checklist"]); // 10. deep rule does NOT apply to files above it (file at root) await check("deep rule not above", doc("top.md", `---\ntype: spec\n---\nbody`), ["Follow the spec checklist"], ["Deep rule"]); // 11. dedup across reads within a session (no fresh seen): second matching read injects nothing await handlers["session_start"]({}, ctx); await check("dedup first read", doc("dq1.md", `---\ntype: spec\n---\nbody`), ["Follow the spec checklist"], [], false); await check("dedup second read (already seen)", doc("dq2.md", `---\ntype: spec\n---\nbody`), [], [], false); // 12. non-markdown read ignored const tsEvent = { toolName: "read", isError: false, input: { path: doc("x.ts", "const a=1;") }, content: [{ type: "text", text: "x" }] }; await handlers["session_start"]({}, ctx); const tsRes = await handlers["tool_result"](tsEvent, ctx); if (tsRes === undefined) { pass++; console.log(" ok non-markdown ignored"); } else { fail++; console.log("FAIL non-markdown ignored"); } console.log(`\n${pass} passed, ${fail} failed`); fs.rmSync(tmp, { recursive: true, force: true }); process.exit(fail === 0 ? 0 : 1); })();