{"_id":"@aman_asmuei/arules-core","_rev":"6-bebd17cabf258e89ea9e4d6e9a764953","name":"@aman_asmuei/arules-core","dist-tags":{"latest":"0.2.2"},"versions":{"0.1.0":{"name":"@aman_asmuei/arules-core","version":"0.1.0","keywords":["aman","arules","guardrails","permissions","multi-tenant","ai-companion","ecosystem"],"author":{"name":"Aman Asmuei"},"license":"MIT","_id":"@aman_asmuei/arules-core@0.1.0","maintainers":[{"name":"aman_asmuei","email":"amanasmuei@gmail.com"}],"homepage":"https://github.com/amanasmuei/arules-core#readme","bugs":{"url":"https://github.com/amanasmuei/arules-core/issues"},"dist":{"shasum":"137e9c7fcdba1ba10cac61d2394d6e95c5bd9b34","tarball":"https://registry.npmjs.org/@aman_asmuei/arules-core/-/arules-core-0.1.0.tgz","fileCount":31,"integrity":"sha512-QElimW9br6Qke9XP25lmgJUdPzU1ejpU3mtyQkfX1R2RzRPYAZsjzw3oYpHgUGSNCNgWtldK7d/2UdnajC1Qkg==","signatures":[{"sig":"MEYCIQCVYQ+18+yCtd0fXNhEhWNvry1w44hJW/Hyd7aFYGHUZgIhAPxuYEMVqkpk0TqWYZ404dZ087mDRdV4yGruDz4k7swB","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":88129},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"7fbe619cbebf6dba2183882ba81bf820906153a9","scripts":{"dev":"tsc --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepare":"npm run build","test:watch":"vitest"},"_npmUser":{"name":"aman_asmuei","email":"amanasmuei@gmail.com"},"repository":{"url":"git+https://github.com/amanasmuei/arules-core.git","type":"git"},"_npmVersion":"10.9.7","description":"The guardrails layer for the aman ecosystem — multi-tenant Ruleset records with runtime enforcement (checkAction, checkToolCall, getGuardrailsPrompt) upstreamed from aman-tg, built on @aman_asmuei/aman-core.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"better-sqlite3":"^12.0.0","@aman_asmuei/aman-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0","@types/better-sqlite3":"^7.6.0"},"_npmOperationalInternal":{"tmp":"tmp/arules-core_0.1.0_1775555329014_0.29698751436775983","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by the aman-agentic package (npx -p aman-agentic aman-mcp / aman). Source: github.com/amanasmuei/aman-substrate"},"0.1.1":{"name":"@aman_asmuei/arules-core","version":"0.1.1","keywords":["aman","arules","guardrails","permissions","multi-tenant","ai-companion","ecosystem"],"author":{"name":"Aman Asmuei"},"license":"MIT","_id":"@aman_asmuei/arules-core@0.1.1","maintainers":[{"name":"aman_asmuei","email":"amanasmuei@gmail.com"}],"homepage":"https://github.com/amanasmuei/arules-core#readme","bugs":{"url":"https://github.com/amanasmuei/arules-core/issues"},"dist":{"shasum":"c07f763029a87ce5d99cdddcba4ce3510d383b6d","tarball":"https://registry.npmjs.org/@aman_asmuei/arules-core/-/arules-core-0.1.1.tgz","fileCount":31,"integrity":"sha512-QGJwAigwkNIqYpPxGduLMwHqRKMJYFPZpRAiczg8FjVSM+k87Mb9i46BApwFzXaF1GZB4mjJn9IMTimMB7kRIw==","signatures":[{"sig":"MEUCIEqZ5r7VftgWRh+jghyP2mNbATAFrVfkSzo6RqOne0L9AiEAhVIYefyejAAFRs5CiozavlDVrgn+aOcKtFRS6FTGMqs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":88306},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"e456b2e1b0ad3b23ce820470d7c8d534187f03f4","scripts":{"dev":"tsc --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepare":"npm run build","test:watch":"vitest"},"_npmUser":{"name":"aman_asmuei","email":"amanasmuei@gmail.com"},"repository":{"url":"git+https://github.com/amanasmuei/arules-core.git","type":"git"},"_npmVersion":"10.9.7","description":"The guardrails layer for the aman ecosystem — multi-tenant Ruleset records with runtime enforcement (checkAction, checkToolCall, getGuardrailsPrompt) upstreamed from aman-tg, built on @aman_asmuei/aman-core.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"better-sqlite3":"^12.0.0","@aman_asmuei/aman-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0","@types/better-sqlite3":"^7.6.0"},"_npmOperationalInternal":{"tmp":"tmp/arules-core_0.1.1_1775577996583_0.8753662975936651","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by the aman-agentic package (npx -p aman-agentic aman-mcp / aman). Source: github.com/amanasmuei/aman-substrate"},"0.2.0":{"name":"@aman_asmuei/arules-core","version":"0.2.0","keywords":["aman","arules","guardrails","permissions","multi-tenant","ai-companion","ecosystem"],"author":{"name":"Aman Asmuei"},"license":"MIT","_id":"@aman_asmuei/arules-core@0.2.0","maintainers":[{"name":"aman_asmuei","email":"amanasmuei@gmail.com"}],"homepage":"https://github.com/amanasmuei/arules-core#readme","bugs":{"url":"https://github.com/amanasmuei/arules-core/issues"},"dist":{"shasum":"c4f6187856f568c686748eb1f7442194384b71cc","tarball":"https://registry.npmjs.org/@aman_asmuei/arules-core/-/arules-core-0.2.0.tgz","fileCount":31,"integrity":"sha512-FSa9Q+Kox6j6uxyUrnCx3R7dYdrRWpClatkMyqECicGzMcQVlvS/C3JVWLFcZb+5p3mGCoIECElZtt4LQtuZAQ==","signatures":[{"sig":"MEQCIEcfA1byvdu6qSeZcugx1thgxU4ogrCp0BC/2rk4Va2dAiBKVzdCaXrLuBSXwLHxAAcNGXUHRiMqHpQ5sGwM3ff4uA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":89374},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"83e26bed10451c6ce47da63f683304e4be31d07e","scripts":{"dev":"tsc --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepare":"npm run build","test:watch":"vitest"},"_npmUser":{"name":"aman_asmuei","email":"amanasmuei@gmail.com"},"repository":{"url":"git+https://github.com/amanasmuei/arules-core.git","type":"git"},"_npmVersion":"10.9.7","description":"The guardrails layer for the aman ecosystem — multi-tenant Ruleset records with runtime enforcement (checkAction, checkToolCall, getGuardrailsPrompt) upstreamed from aman-tg, built on @aman_asmuei/aman-core.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"better-sqlite3":"^12.0.0","@aman_asmuei/aman-core":"^0.3.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0","@types/better-sqlite3":"^7.6.0"},"_npmOperationalInternal":{"tmp":"tmp/arules-core_0.2.0_1775745282683_0.5138050629454762","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by the aman-agentic package (npx -p aman-agentic aman-mcp / aman). Source: github.com/amanasmuei/aman-substrate"},"0.2.1":{"name":"@aman_asmuei/arules-core","version":"0.2.1","keywords":["aman","arules","guardrails","permissions","multi-tenant","ai-companion","ecosystem"],"author":{"name":"Aman Asmuei"},"license":"MIT","_id":"@aman_asmuei/arules-core@0.2.1","maintainers":[{"name":"aman_asmuei","email":"amanasmuei@gmail.com"}],"homepage":"https://github.com/amanasmuei/arules-core#readme","bugs":{"url":"https://github.com/amanasmuei/arules-core/issues"},"dist":{"shasum":"3379963738147c941d864df57bc624e273ad1d7c","tarball":"https://registry.npmjs.org/@aman_asmuei/arules-core/-/arules-core-0.2.1.tgz","fileCount":31,"integrity":"sha512-oGgXTXIrEdRL66EAqoKJEpVNv+RS9tBYj6RzVWHowkJcDDcnEkqHeyy51rZV/7VtVNSuMpopA5vocfoMcghSfw==","signatures":[{"sig":"MEQCIDMr9hvoDT/XtyOzMSj74m0I2XRWdQyE87HWOGMzTJN3AiAwB1UXjga/Qu/2yeDELh0dtTQu5oQ8f5AAxYiYu3jbNQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":92882},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"65583f2c57157986c020d538befa6a1280c42f4c","scripts":{"dev":"tsc --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepare":"npm run build","test:watch":"vitest"},"_npmUser":{"name":"aman_asmuei","email":"amanasmuei@gmail.com"},"repository":{"url":"git+https://github.com/amanasmuei/arules-core.git","type":"git"},"_npmVersion":"10.9.7","description":"The guardrails layer for the aman ecosystem — multi-tenant Ruleset records with runtime enforcement (checkAction, checkToolCall, getGuardrailsPrompt) upstreamed from aman-tg, built on @aman_asmuei/aman-core.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"better-sqlite3":"^12.0.0","@aman_asmuei/aman-core":"^0.3.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0","@types/better-sqlite3":"^7.6.0"},"_npmOperationalInternal":{"tmp":"tmp/arules-core_0.2.1_1777163909293_0.1267347644367276","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by the aman-agentic package (npx -p aman-agentic aman-mcp / aman). Source: github.com/amanasmuei/aman-substrate"},"0.2.2":{"name":"@aman_asmuei/arules-core","version":"0.2.2","keywords":["aman","arules","guardrails","permissions","multi-tenant","ai-companion","ecosystem"],"author":{"name":"Aman Asmuei"},"license":"MIT","_id":"@aman_asmuei/arules-core@0.2.2","maintainers":[{"name":"aman_asmuei","email":"amanasmuei@gmail.com"}],"homepage":"https://github.com/amanasmuei/arules-core#readme","bugs":{"url":"https://github.com/amanasmuei/arules-core/issues"},"dist":{"shasum":"e0a1d0b52d7ebbda43c176131685aec4819e9c5c","tarball":"https://registry.npmjs.org/@aman_asmuei/arules-core/-/arules-core-0.2.2.tgz","fileCount":31,"integrity":"sha512-yuevO4rqPHKAFj0eoqnvyIQTEsp56i7lSP7vywFv0xhmwfjXphLEYyMTkcvGCWk6WeM6KogqMXUgvOWeKjJ1/w==","signatures":[{"sig":"MEYCIQDfu+knXIyz46LFJu7zkIf5ZIjay7M40B6v2Aq1HcuRXQIhAL3K1rHCR5gW5DV0GrSXjscExFanjl3/rLntWVk17Eg7","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94829},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"f9b464aadc7f1153e5ed446ac29e75e2be74f9d0","scripts":{"dev":"tsc --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepare":"npm run build","test:watch":"vitest"},"_npmUser":{"name":"aman_asmuei","email":"amanasmuei@gmail.com"},"repository":{"url":"git+https://github.com/amanasmuei/arules-core.git","type":"git"},"_npmVersion":"10.9.7","description":"The guardrails layer for the aman ecosystem — multi-tenant Ruleset records with runtime enforcement (checkAction, checkToolCall, getGuardrailsPrompt) upstreamed from aman-tg, built on @aman_asmuei/aman-core.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"better-sqlite3":"^12.0.0","@aman_asmuei/aman-core":"^0.3.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0","@types/better-sqlite3":"^7.6.0"},"_npmOperationalInternal":{"tmp":"tmp/arules-core_0.2.2_1777826332616_0.5462171641665652","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by the aman-agentic package (npx -p aman-agentic aman-mcp / aman). Source: github.com/amanasmuei/aman-substrate"}},"time":{"created":"2026-04-07T09:48:48.957Z","modified":"2026-06-10T08:37:29.418Z","0.1.0":"2026-04-07T09:48:49.168Z","0.1.1":"2026-04-07T16:06:36.742Z","0.2.0":"2026-04-09T14:34:42.817Z","0.2.1":"2026-04-26T00:38:29.419Z","0.2.2":"2026-05-03T16:38:52.748Z"},"bugs":{"url":"https://github.com/amanasmuei/arules-core/issues"},"author":{"name":"Aman Asmuei"},"license":"MIT","homepage":"https://github.com/amanasmuei/arules-core#readme","keywords":["aman","arules","guardrails","permissions","multi-tenant","ai-companion","ecosystem"],"repository":{"url":"git+https://github.com/amanasmuei/arules-core.git","type":"git"},"description":"The guardrails layer for the aman ecosystem — multi-tenant Ruleset records with runtime enforcement (checkAction, checkToolCall, getGuardrailsPrompt) upstreamed from aman-tg, built on @aman_asmuei/aman-core.","maintainers":[{"name":"aman_asmuei","email":"amanasmuei@gmail.com"}],"readme":"<div align=\"center\">\n\n# @aman_asmuei/arules-core\n\n**The guardrails layer for the aman ecosystem.**\n\nMulti-tenant rule sets with a runtime enforcement engine — `checkAction`,\n`checkToolCall`, and `getGuardrailsPrompt` — extracted from `aman-tg`'s\nproduction guardrails and made multi-tenant. Same algorithm. Same\nbehavior. One source of truth across every aman frontend.\n\n[![npm version](https://img.shields.io/npm/v/@aman_asmuei/arules-core?style=for-the-badge&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/@aman_asmuei/arules-core)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)\n[![Node ≥18](https://img.shields.io/badge/node-%E2%89%A518-brightgreen?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org)\n[![Tests](https://img.shields.io/badge/tests-83_passing-brightgreen?style=for-the-badge)](#quality-signals)\n[![Part of aman](https://img.shields.io/badge/part_of-aman_ecosystem-ff6b35?style=for-the-badge)](https://github.com/amanasmuei/aman)\n\n[Install](#install) &middot;\n[Quick start](#quick-start) &middot;\n[The upstream story](#the-upstream-story) &middot;\n[Concepts](#concepts) &middot;\n[API reference](#api-reference) &middot;\n[The aman ecosystem](#the-aman-ecosystem)\n\n</div>\n\n---\n\n## What it is\n\n`arules-core` is the guardrails layer of the aman engine. It manages \"what\nthe AI must NOT do\" as a flexible markdown ruleset, with runtime enforcement\nhelpers that any host can call before executing a tool, generating a\nresponse, or taking a destructive action.\n\n**One ruleset per scope.** The same package serves your local dev rules\n(`dev:default`), Claude Code plugin rules (`dev:plugin`), per-agent rules\n(`agent:jiran`), and per-user rules for thousands of Telegram users\n(`tg:12345`). Complete state isolation. Same algorithm everywhere.\n\nThis package extracts a programmable library API out of the existing\n`@aman_asmuei/arules` CLI **and** upstreams the runtime enforcement engine\nthat has been running in production inside `aman-tg`'s\n`apps/api/src/guardrails.ts` for months. Both code paths now share one\nimplementation.\n\n---\n\n## The upstream story\n\nMost aman engine layers were built by extracting a clean library out of an\nexisting CLI. `arules-core` is different: **its enforcement algorithm\nalready existed in production**, deployed inside `aman-tg`'s API server,\nrunning per-Telegram-user safety checks for the Jiran agent and 13 others.\n\nThis package takes that production code, makes it multi-tenant, exposes it\nas a library, and lets every aman frontend share the same enforcement.\nAfter v1 ships, `aman-tg`'s `guardrails.ts` becomes a thin wrapper that\nimports from this package — closing the loop on what was originally a\none-way migration.\n\nThe result: a bug fix in `arules-core`'s `checkAction` improves Claude\nCode's `/rules` slash command, the CLI runtime, the MCP `rules_check` tool,\nand `aman-tg`'s per-Telegram-user enforcement — **all from one library,\nall from one set of tests**.\n\n---\n\n## Install\n\n```bash\nnpm install @aman_asmuei/arules-core\n```\n\n`arules-core` depends on `@aman_asmuei/aman-core` for the scope substrate\nand `Storage<T>` interface. `better-sqlite3` is required at runtime if you\nuse the `DatabaseStorage` backend (i.e. for non-`dev:*` scopes).\n\n---\n\n## Quick start\n\n```typescript\nimport {\n  getOrCreateRuleset,\n  addRule,\n  checkAction,\n  checkToolCall,\n  getGuardrailsPrompt,\n  type CheckActionResult,\n} from \"@aman_asmuei/arules-core\";\n\n// Bootstrap a default ruleset for the dev side\nawait getOrCreateRuleset(\"dev:default\");\n// → Creates ~/.arules/dev/default/rules.md with sensible defaults\n//   (## Always, ## Never, ## Safety, ## Privacy)\n\n// Add a tenant-specific rule\nawait addRule(\"Never\", \"Never deploy on Friday afternoons\", \"dev:default\");\n\n// Runtime check before letting the LLM take an action\nconst result: CheckActionResult = await checkAction(\n  \"deploy production database changes right now\",\n  \"dev:default\",\n);\n\nif (!result.safe) {\n  console.log(\"Blocked by:\", result.violations);\n  // → [\"Never deploy on Friday afternoons\", ...]\n}\n\n// Per-Telegram-user rulesets — production pattern\nawait addRule(\"Privacy\", \"Never store user 12345's location\", \"tg:12345\");\n\n// Tool call check before execution\nconst blockReason = await checkToolCall(\n  \"Storing user 12345's location\",\n  \"tg:12345\",\n);\nif (blockReason) {\n  // → \"Action blocked by guardrails: Never store user 12345's location\"\n  return blockReason; // refuse the tool call\n}\n\n// System prompt injection — slot the rules into the LLM's context\nconst guardPrompt = await getGuardrailsPrompt({ scope: \"tg:12345\" });\nsystemPrompt += guardPrompt;\n// → \"## GUARDRAILS — You MUST follow these rules:\\n### Always\\n- ...\\n### Never\\n- ...\"\n```\n\nThat's the full runtime enforcement loop, in 30 seconds.\n\n---\n\n## Concepts\n\n### Ruleset — a markdown blob\n\n`Ruleset` is a markdown string with the same shape `arules` already writes:\n\n```typescript\ninterface Ruleset {\n  content: string;\n}\n```\n\nExample content:\n\n```markdown\n# Guardrails\n\n## Always\n- Be honest about what you don't know\n- Confirm before destructive actions\n- Cite sources when stating facts\n\n## Never\n- Never push to main without explicit approval\n- Never commit secrets or credentials\n- Never delete production data without confirmation\n- ~~Old disabled rule~~\n\n## Safety\n- Refuse to create weapons or malware\n- Refuse harmful or illegal advice\n\n## Privacy\n- Never store personally identifiable information without consent\n```\n\nThe `## Category` headings are the only structural convention. Any\ncategory name works; the `Always`, `Never`, `Safety`, `Privacy` set is just\nthe default-template choice. Disabled rules wrap in `~~strikethrough~~`\nmarkers and are filtered out by the parser.\n\n### parseRules — active vs disabled\n\n```typescript\nimport { parseRules, parseRulesetFull } from \"@aman_asmuei/arules-core\";\n\nconst ruleset = { content: rawMarkdown };\n\n// Get active rules only (strikethrough filtered out)\nconst active = parseRules(ruleset);\n// → [\n//     { name: \"Always\", rules: [\"Be honest...\", \"Confirm before...\"] },\n//     { name: \"Never\", rules: [\"Never push to main...\", \"Never commit...\"] },\n//   ]\n\n// Get all rules with their disabled state preserved (for editing UIs)\nconst full = parseRulesetFull(ruleset);\n// → [\n//     { name: \"Never\", rules: [\n//         { text: \"Never push to main...\", disabled: false },\n//         { text: \"Old disabled rule\",     disabled: true  },\n//       ] },\n//   ]\n```\n\n### checkAction — the enforcement algorithm\n\nThe same naive-but-effective keyword-overlap algorithm that's been running\nin `aman-tg` production:\n\n1. Collect all \"prohibition\" rules: everything in the `Never` category, plus\n   rules in any other category containing prohibition keywords like `never`,\n   `don't`, `must not`, `forbidden`, `prohibited`, `refuse`, `decline`.\n2. For each prohibition rule, extract its meaningful keywords:\n   lowercase, length > 3, with stopwords filtered out\n   (`that`, `this`, `with`, `from`, `about`, ...).\n3. Lowercase the action description and check if it contains at least 2\n   of the rule's keywords. If yes, flag it as a potential violation.\n\n```typescript\nimport { checkActionPure, type Ruleset } from \"@aman_asmuei/arules-core\";\n\nconst ruleset: Ruleset = {\n  content: \"## Never\\n- Never delete production data without confirmation\\n\",\n};\n\nconst result = checkActionPure(\n  \"delete production database records permanently\",\n  ruleset,\n);\n// → { safe: false, violations: [\"Never delete production data without confirmation\"] }\n```\n\nThis is intentionally naive. False positives and false negatives both happen.\nA future v0.2 may layer in semantic matching, but the keyword approach is\n**preserved exactly** because it's the algorithm rule authors have been\nwriting against. Stability of the enforcement contract matters more than\nalgorithmic perfection.\n\n### getGuardrailsPrompt — LLM context injection\n\nGenerates a system-prompt block listing the safety-critical rules,\nformatted so the LLM treats them as hard constraints:\n\n```typescript\nconst prompt = await getGuardrailsPrompt({ scope: \"dev:default\" });\n// → \"\n//\n// ## GUARDRAILS — You MUST follow these rules:\n//\n// ### Always\n// - Be honest about what you don't know\n// - Confirm before destructive actions\n//\n// ### Never\n// - Never push to main without explicit approval\n// - Never commit secrets or credentials\n//\n// ### Safety\n// - Refuse to create weapons or malware\n//\n// ### Privacy\n// - Never store personally identifiable information without consent\n//\n// Violating these rules is NOT allowed under any circumstances.\"\n\nsystemPrompt += prompt;\n```\n\nBy default it includes the `Always`, `Never`, `Safety`, and `Privacy`\ncategories. Override via `{ includeCategories: [\"...\", \"...\"] }` if you want\na different selection.\n\n### checkToolCall — runtime tool gating\n\nSame as `checkAction`, but designed for the moment a tool is about to fire.\nReturns `null` if safe, an error message string if blocked:\n\n```typescript\nconst result = await checkToolCall(\n  \"Fetching URL: https://internal.example.com/admin\",\n  \"dev:default\",\n);\n// → null  if safe\n// → \"Action blocked by guardrails: Never fetch internal URLs\"  if blocked\n```\n\nThe pattern: build a human-readable description of what the tool is about\nto do, pass it to `checkToolCall`. If you get a string back, refuse the\ntool call and surface the message to the user.\n\n### Auto-routing storage — convention over configuration\n\nSame pattern as the rest of the aman engine: scope prefix picks the backend.\n\n| Scope prefix | Backend                | Where it persists                                  |\n|-------------|------------------------|----------------------------------------------------|\n| `dev:*`     | `MarkdownFileStorage`  | `~/.arules/{scope.replace(':','/')}/rules.md`      |\n| `tg:*`      | `DatabaseStorage`      | `~/.aman/engine.db` table `arules_rulesets`        |\n| `agent:*`   | `DatabaseStorage`      | same                                                |\n| (other)     | `DatabaseStorage`      | same                                                |\n\nOverride the home directory via `$ARULES_HOME`. The engine DB location is\nshared with the rest of the aman engine via `$AMAN_ENGINE_DB`.\n\n### Pure helpers — when you have your own loader\n\nIf you load rules from a custom location (e.g. a long-running server with\nits own `rules.md` file and mtime caching), bypass the storage layer and\nuse the pure helpers:\n\n```typescript\nimport {\n  parseRules,\n  checkActionPure,\n  checkToolCallPure,\n  getGuardrailsPromptPure,\n} from \"@aman_asmuei/arules-core\";\n\nconst ruleset = { content: fs.readFileSync(\"./my-rules.md\", \"utf-8\") };\n\nconst safe = checkActionPure(\"delete user records\", ruleset);\nconst prompt = getGuardrailsPromptPure(ruleset);\nconst block = checkToolCallPure(\"Deleting user 12345 records\", ruleset);\n```\n\nThis is exactly how `aman-tg`'s `guardrails.ts` consumes the library — it\nkeeps its own deployment-local `rules.md` and its own mtime caching, and\ndelegates only the parsing + matching to `arules-core`. Best of both worlds.\n\n---\n\n## API reference\n\n### Async (storage-backed) API\n\n#### Read\n\n| Symbol                               | Returns                          | Purpose                                          |\n|-------------------------------------|----------------------------------|--------------------------------------------------|\n| `getRuleset(scope?)`                | `Promise<Ruleset \\| null>`       | Read ruleset; null if missing                    |\n| `getOrCreateRuleset(scope?)`        | `Promise<Ruleset>`               | Read or bootstrap from default template          |\n| `listRuleCategories(scope?)`        | `Promise<RuleCategory[]>`        | Active categories only                           |\n| `listRuleCategoriesFull(scope?)`    | `Promise<FullRuleCategory[]>`    | Categories with active+disabled rules            |\n| `getCategoryRulesForScope(name, scope?)` | `Promise<string[] \\| null>`  | Active rules in one category                     |\n| `listCategoryNames(scope?)`         | `Promise<string[]>`              | All category names in document order             |\n| `listRulesetScopes()`               | `Promise<{markdown, database}>`  | All scopes with stored rulesets                  |\n\n#### Write\n\n| Symbol                               | Returns        | Purpose                                          |\n|-------------------------------------|----------------|--------------------------------------------------|\n| `putRuleset(ruleset, scope?)`       | `Promise<void>` | Replace the entire ruleset                       |\n| `addRule(category, rule, scope?)`   | `Promise<void>` | Add a rule; bootstraps + creates category if missing |\n| `removeRule(category, idx, scope?)` | `Promise<void>` | Remove rule by 1-based index                     |\n| `toggleRuleAt(category, idx, scope?)` | `Promise<void>` | Toggle disabled state by 1-based index         |\n| `deleteRuleset(scope?)`             | `Promise<void>` | Remove the ruleset for a scope                   |\n\n#### Enforcement (the runtime hot path)\n\n| Symbol                              | Returns                  | Purpose                                          |\n|------------------------------------|--------------------------|--------------------------------------------------|\n| `checkAction(action, scope?)`      | `Promise<CheckActionResult>` | Returns `{violations, safe}`                |\n| `checkToolCall(description, scope?)` | `Promise<string \\| null>` | null if safe, error message if blocked         |\n| `getGuardrailsPrompt(opts?)`       | `Promise<string>`        | System prompt block for LLM injection            |\n\n### Pure helpers (no storage)\n\n| Symbol                                | Returns                  | Purpose                                          |\n|--------------------------------------|--------------------------|--------------------------------------------------|\n| `parseRules(ruleset)`                | `RuleCategory[]`         | Active categories only                           |\n| `parseRulesetFull(ruleset)`          | `FullRuleCategory[]`     | Categories with disabled state                   |\n| `getCategoryRules(rs, name)`         | `string[] \\| null`       | Active rules in one category                     |\n| `listCategories(ruleset)`            | `string[]`               | All category names                                |\n| `addRuleToCategory(rs, cat, rule)`   | `Ruleset`                | Pure add — returns new Ruleset                   |\n| `removeRuleFromCategory(rs, cat, i)` | `Ruleset`                | Pure remove                                       |\n| `toggleRule(rs, cat, i)`             | `Ruleset`                | Pure toggle                                       |\n| `checkActionPure(action, rs)`        | `CheckActionResult`      | Sync rule check                                  |\n| `checkToolCallPure(desc, rs)`        | `string \\| null`         | Sync tool call check                             |\n| `getGuardrailsPromptPure(rs, opts?)` | `string`                 | Sync prompt builder                              |\n| `DEFAULT_PROMPT_CATEGORIES`          | `readonly string[]`      | `[\"always\", \"never\", \"safety\", \"privacy\"]`       |\n\n### Storage routing & migration\n\n| Symbol                              | Returns                       | Purpose                                          |\n|------------------------------------|-------------------------------|--------------------------------------------------|\n| `getStorageForScope(scope)`        | `Storage<Ruleset>`            | Pick the right backend for a scope              |\n| `getMarkdownStorage()`             | `MarkdownFileStorage<Ruleset>` | Cached singleton for `dev:*`                    |\n| `getDatabaseStorage()`             | `DatabaseStorage<Ruleset>`    | Cached singleton for everything else             |\n| `getArulesHome()`                  | `string`                      | Root directory (`$ARULES_HOME` or `~/.arules`)  |\n| `migrateLegacyArulesFile()`        | `ArulesMigrationReport`       | Copy `~/.arules/rules.md` → `~/.arules/dev/default/rules.md` |\n| `defaultRulesetTemplate(scope)`    | `Ruleset`                     | Default markdown template for a new scope        |\n\nThe legacy migration is idempotent and **never deletes** the legacy file.\n\n---\n\n## Architecture\n\n`arules-core` is one of three \"essential\" layer libraries in the aman engine v1:\n\n```\n                    ┌──────────────────────────┐\n                    │     aman engine v1       │\n                    │                          │\n                    │  ┌────────────────────┐  │\n                    │  │   aman-core        │  │ ← shared substrate\n                    │  │   Scope, Storage   │  │\n                    │  └─────────┬──────────┘  │\n                    │            │             │\n                    │       ┌────┴─────┐       │\n                    │       │          │       │\n                    │       ▼          ▼       │\n                    │  ┌─────────┐ ┌─────────┐ │\n                    │  │ acore-  │ │ arules- │ │\n                    │  │ core    │ │ core    │ │\n                    │  │         │ │ ←YOU    │ │\n                    │  │ identity│ │ rules   │ │\n                    │  └─────────┘ └─────────┘ │\n                    └──────────────────────────┘\n                              ▲\n                              │ consumed by\n        ┌─────────────────────┼─────────────────────┐\n        ▼                     ▼                     ▼\n   aman-mcp             aman-agent              aman-tg\n   (MCP server         (CLI runtime)         (Telegram backend)\n    aggregator)                              ← code originated HERE\n```\n\n**Where consumers use it**:\n\n- `aman-mcp` exposes `arules-core` via MCP tools (`rules_list`, `rules_check`,\n  `rules_add`, `rules_remove`, `rules_toggle`) — all scope-aware\n- `aman-agent` calls `arules-core` directly from its `/rules` slash command\n- `aman-tg` consumes `arules-core` from its `apps/api/src/guardrails.ts`,\n  closing the loop on the upstream migration. Same algorithm, now multi-tenant,\n  shared with every other consumer.\n\n---\n\n## What this is NOT\n\nTo stay focused, `arules-core` deliberately does not provide:\n\n- **Tool-specific guardrails.** Things like \"block private IP fetches in\n  `fetch_url`\" are security invariants that should be hardcoded in the\n  calling layer, not expressed as rules. `arules-core` handles the\n  rule-driven part; the layer wraps it with whatever else it needs.\n- **A semantic / LLM-based rule matcher.** Deferred to v0.2. The naive\n  keyword approach is preserved because it's the in-production algorithm\n  rule authors have been writing against. Stability > algorithmic perfection\n  (for now).\n- **Authentication or authorization.** This is \"what won't the AI do,\" not\n  \"who is allowed to ask.\" Use your auth system; pass the user ID as scope.\n- **A CLI.** That's `@aman_asmuei/arules`. This package is the library\n  the CLI will eventually wrap.\n\n---\n\n## Quality signals\n\n- **83 unit tests, all passing**, across 4 test files:\n  - `ruleset.test.ts` — 27 tests covering parsing, strikethrough handling,\n    add/remove/toggle, special section names, case-insensitive lookups\n  - `enforce.test.ts` — 19 tests covering `checkAction` keyword overlap,\n    prohibition keyword detection, prompt generation with custom categories,\n    case insensitivity\n  - `api.test.ts` — 30 tests covering scope routing, multi-tenant isolation,\n    `withScope` propagation, an end-to-end \"Jiran-pattern\" test simulating\n    aman-tg's per-user enforcement flow\n  - `migrate.test.ts` — 7 tests covering idempotent legacy migration with\n    byte-exact preservation\n- **`tsc --noEmit` clean** with `strict` mode\n- **Algorithm-equivalent with the production version** in `aman-tg`'s\n  `guardrails.ts`. After Phase 7 of the engine v1 build, `aman-tg` consumes\n  this library and its existing 26 tests still pass — proof of behavior equivalence.\n\n---\n\n## The aman ecosystem\n\n`arules-core` is one of several packages in the aman AI companion ecosystem:\n\n| Layer                                                                   | Role                                                |\n|------------------------------------------------------------------------|-----------------------------------------------------|\n| [@aman_asmuei/aman-core](https://github.com/amanasmuei/aman-core)       | Substrate — Scope, Storage, withScope               |\n| [@aman_asmuei/acore-core](https://github.com/amanasmuei/acore-core)     | Identity layer — multi-tenant Identity records      |\n| **[@aman_asmuei/arules-core](https://github.com/amanasmuei/arules-core)** | **Guardrails layer (this package)**               |\n| [@aman_asmuei/amem-core](https://github.com/amanasmuei/amem)            | Memory layer — semantic recall, embeddings          |\n| [@aman_asmuei/aman-mcp](https://github.com/amanasmuei/aman-mcp)         | MCP server aggregating all layers for any host      |\n| [@aman_asmuei/aman-agent](https://github.com/amanasmuei/aman-agent)     | Standalone CLI runtime, multi-LLM, scope-aware      |\n| [@aman_asmuei/arules](https://github.com/amanasmuei/arules)             | Single-user CLI — predates this library             |\n| [aman-claude-code](https://github.com/amanasmuei/aman-claude-code)                | Claude Code plugin (hooks + skills + MCP installer) |\n| [@aman_asmuei/aman](https://github.com/amanasmuei/aman)                 | Umbrella installer — one command for the ecosystem  |\n\n---\n\n## License\n\n[MIT](LICENSE) © Aman Asmuei\n\n---\n\n<div align=\"center\">\n  <sub>Built with ❤️ in 🇲🇾 <strong>Malaysia</strong> · Part of the <a href=\"https://github.com/amanasmuei\">aman ecosystem</a></sub>\n</div>\n","readmeFilename":"README.md"}