guardrails-ref 1.0.0

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/dist/add.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runAdd(name: string, projectPath?: string): boolean;
package/dist/add.js ADDED
@@ -0,0 +1,29 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { resolve } from "path";
3
+ import chalk from "chalk";
4
+ import { TEMPLATES, TEMPLATE_NAMES } from "./templates.js";
5
+ export function runAdd(name, projectPath = ".") {
6
+ const normalized = name.toLowerCase().replace(/\s+/g, "-");
7
+ const content = TEMPLATES[normalized];
8
+ if (!content) {
9
+ console.log(chalk.red("Unknown guardrail:") + " " + name);
10
+ console.log(chalk.gray("Available: " + TEMPLATE_NAMES.join(", ")));
11
+ return false;
12
+ }
13
+ const root = resolve(projectPath);
14
+ const guardrailsDir = resolve(root, ".agents", "guardrails");
15
+ const exampleDir = resolve(guardrailsDir, normalized);
16
+ const exampleFile = resolve(exampleDir, "GUARDRAIL.md");
17
+ if (existsSync(exampleFile)) {
18
+ console.log(chalk.yellow(".agents/guardrails/" + normalized + "/GUARDRAIL.md already exists"));
19
+ return true;
20
+ }
21
+ if (!existsSync(guardrailsDir)) {
22
+ mkdirSync(guardrailsDir, { recursive: true });
23
+ console.log(chalk.green("✓") + " Created .agents/guardrails/");
24
+ }
25
+ mkdirSync(exampleDir, { recursive: true });
26
+ writeFileSync(exampleFile, content);
27
+ console.log(chalk.green("✓") + " Added .agents/guardrails/" + normalized + "/GUARDRAIL.md");
28
+ return true;
29
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import chalk from "chalk";
4
+ import { validatePath, listGuardrails } from "./validate.js";
5
+ import { runSetup } from "./setup.js";
6
+ import { runInit } from "./init.js";
7
+ import { runAdd } from "./add.js";
8
+ program
9
+ .name("guardrails-ref")
10
+ .description("Validate and list Agent Guardrails (GUARDRAIL.md) files")
11
+ .version("1.0.0");
12
+ program
13
+ .command("validate [path]")
14
+ .description("Validate GUARDRAIL.md files in a directory or a single file")
15
+ .action((path = ".") => {
16
+ const result = validatePath(path);
17
+ let hasErrors = false;
18
+ for (const r of result.results) {
19
+ if (r.success) {
20
+ const warnStr = r.warnings.length > 0 ? chalk.yellow(` (${r.warnings.length} warnings)`) : "";
21
+ console.log(chalk.green("✓"), r.path, warnStr);
22
+ for (const w of r.warnings) {
23
+ console.log(chalk.yellow(" warning:"), w);
24
+ }
25
+ }
26
+ else {
27
+ hasErrors = true;
28
+ console.log(chalk.red("✗"), r.path);
29
+ for (const e of r.errors) {
30
+ console.log(chalk.red(" error:"), e);
31
+ }
32
+ for (const w of r.warnings) {
33
+ console.log(chalk.yellow(" warning:"), w);
34
+ }
35
+ }
36
+ }
37
+ if (result.total === 0) {
38
+ console.log(chalk.yellow("No GUARDRAIL.md or GUARDRAILS.md files found"));
39
+ }
40
+ else {
41
+ console.log();
42
+ console.log(`Valid: ${result.valid}/${result.total} Invalid: ${result.invalid}/${result.total}`);
43
+ }
44
+ process.exit(hasErrors ? 1 : 0);
45
+ });
46
+ program
47
+ .command("init [path]")
48
+ .description("Create .agents/guardrails/, add no-plaintext-secrets, and run setup (one command to get started)")
49
+ .action((path = ".") => {
50
+ runInit(path);
51
+ });
52
+ program
53
+ .command("add <name> [path]")
54
+ .description("Add an example guardrail by name (e.g. no-destructive-commands, no-new-deps-without-approval)")
55
+ .action((name, path = ".") => {
56
+ const ok = runAdd(name, path);
57
+ process.exit(ok ? 0 : 1);
58
+ });
59
+ program
60
+ .command("setup [path]")
61
+ .description("Add the guardrail one-liner to Cursor rules and Claude instructions (required until IDEs support guardrails natively)")
62
+ .action((path = ".") => {
63
+ const result = runSetup(path);
64
+ console.log(result.message);
65
+ });
66
+ program
67
+ .command("list [path]")
68
+ .description("List discovered guardrails")
69
+ .action((path = ".") => {
70
+ const guardrails = listGuardrails(path);
71
+ if (guardrails.length === 0) {
72
+ console.log(chalk.yellow("No guardrails found"));
73
+ return;
74
+ }
75
+ for (const g of guardrails) {
76
+ console.log(chalk.cyan(g.name));
77
+ console.log(" ", g.description.slice(0, 80) + (g.description.length > 80 ? "..." : ""));
78
+ console.log(" ", chalk.gray(g.path));
79
+ console.log();
80
+ }
81
+ console.log(`Total: ${guardrails.length} guardrail(s)`);
82
+ });
83
+ program.parse();
package/dist/init.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export interface InitResult {
2
+ guardrailsDir: string;
3
+ exampleCreated: boolean;
4
+ setupDone: string;
5
+ }
6
+ export declare function runInit(projectPath?: string): InitResult;
package/dist/init.js ADDED
@@ -0,0 +1,32 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { resolve } from "path";
3
+ import chalk from "chalk";
4
+ import { runSetup } from "./setup.js";
5
+ import { TEMPLATES } from "./templates.js";
6
+ export function runInit(projectPath = ".") {
7
+ const root = resolve(projectPath);
8
+ const guardrailsDir = resolve(root, ".agents", "guardrails");
9
+ const exampleDir = resolve(guardrailsDir, "no-plaintext-secrets");
10
+ const exampleFile = resolve(exampleDir, "GUARDRAIL.md");
11
+ let exampleCreated = false;
12
+ if (!existsSync(guardrailsDir)) {
13
+ mkdirSync(guardrailsDir, { recursive: true });
14
+ console.log(chalk.green("✓") + " Created .agents/guardrails/");
15
+ }
16
+ if (!existsSync(exampleFile)) {
17
+ mkdirSync(exampleDir, { recursive: true });
18
+ writeFileSync(exampleFile, TEMPLATES["no-plaintext-secrets"]);
19
+ exampleCreated = true;
20
+ console.log(chalk.green("✓") + " Created .agents/guardrails/no-plaintext-secrets/GUARDRAIL.md");
21
+ }
22
+ else {
23
+ console.log(chalk.yellow(" .agents/guardrails/no-plaintext-secrets/GUARDRAIL.md already exists"));
24
+ }
25
+ const setupResult = runSetup(projectPath);
26
+ console.log(setupResult.message);
27
+ return {
28
+ guardrailsDir,
29
+ exampleCreated,
30
+ setupDone: setupResult.message,
31
+ };
32
+ }
@@ -0,0 +1,23 @@
1
+ export interface GuardrailFrontmatter {
2
+ name: string;
3
+ description: string;
4
+ scope?: "global" | "project" | "session";
5
+ severity?: "critical" | "warning" | "advisory";
6
+ triggers?: string[];
7
+ license?: string;
8
+ metadata?: Record<string, string>;
9
+ }
10
+ export interface ParsedGuardrail {
11
+ path: string;
12
+ name: string;
13
+ description: string;
14
+ frontmatter: GuardrailFrontmatter;
15
+ body: string;
16
+ }
17
+ export interface ParseResult {
18
+ success: boolean;
19
+ guardrail?: ParsedGuardrail;
20
+ errors: string[];
21
+ warnings: string[];
22
+ }
23
+ export declare function parseGuardrailFile(filePath: string): ParseResult;
package/dist/parse.js ADDED
@@ -0,0 +1,121 @@
1
+ import matter from "gray-matter";
2
+ import { readFileSync } from "fs";
3
+ import { resolve } from "path";
4
+ const NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
5
+ const MAX_NAME_LENGTH = 64;
6
+ const MAX_DESCRIPTION_LENGTH = 1024;
7
+ const VALID_SCOPES = new Set(["global", "project", "session"]);
8
+ const VALID_SEVERITIES = new Set(["critical", "warning", "advisory"]);
9
+ export function parseGuardrailFile(filePath) {
10
+ const errors = [];
11
+ const warnings = [];
12
+ let content;
13
+ try {
14
+ content = readFileSync(filePath, "utf-8");
15
+ }
16
+ catch (err) {
17
+ return {
18
+ success: false,
19
+ errors: [`Cannot read file: ${filePath}`],
20
+ warnings: [],
21
+ };
22
+ }
23
+ let parsed;
24
+ try {
25
+ parsed = matter(content);
26
+ }
27
+ catch (err) {
28
+ return {
29
+ success: false,
30
+ errors: [`Invalid YAML frontmatter: ${err.message}`],
31
+ warnings: [],
32
+ };
33
+ }
34
+ const data = parsed.data;
35
+ const body = parsed.content.trim();
36
+ // Required: name
37
+ const name = data.name;
38
+ if (typeof name !== "string" || !name.trim()) {
39
+ errors.push("Missing or empty required field: name");
40
+ }
41
+ else {
42
+ if (name.length > MAX_NAME_LENGTH) {
43
+ warnings.push(`name exceeds ${MAX_NAME_LENGTH} characters`);
44
+ }
45
+ if (!NAME_REGEX.test(name)) {
46
+ errors.push("name must be lowercase letters, numbers, and hyphens only; no consecutive hyphens; cannot start or end with hyphen");
47
+ }
48
+ }
49
+ // Required: description
50
+ const description = data.description;
51
+ if (typeof description !== "string" || !description.trim()) {
52
+ errors.push("Missing or empty required field: description");
53
+ }
54
+ else if (description.length > MAX_DESCRIPTION_LENGTH) {
55
+ warnings.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters`);
56
+ }
57
+ // Optional: scope
58
+ const scope = data.scope;
59
+ if (scope !== undefined) {
60
+ if (typeof scope !== "string" || !VALID_SCOPES.has(scope)) {
61
+ warnings.push(`scope must be one of: global, project, session`);
62
+ }
63
+ }
64
+ // Optional: severity
65
+ const severity = data.severity;
66
+ if (severity !== undefined) {
67
+ if (typeof severity !== "string" || !VALID_SEVERITIES.has(severity)) {
68
+ warnings.push(`severity must be one of: critical, warning, advisory`);
69
+ }
70
+ }
71
+ // Optional: triggers
72
+ const triggers = data.triggers;
73
+ if (triggers !== undefined) {
74
+ if (!Array.isArray(triggers) || !triggers.every((t) => typeof t === "string")) {
75
+ warnings.push("triggers must be a list of strings");
76
+ }
77
+ }
78
+ // Check directory name matches (when in a directory)
79
+ const dirName = filePath.split(/[/\\]/).slice(-2)[0];
80
+ if (dirName && dirName !== "." && typeof name === "string" && name !== dirName) {
81
+ warnings.push(`name "${name}" does not match parent directory "${dirName}"`);
82
+ }
83
+ if (errors.length > 0) {
84
+ return {
85
+ success: false,
86
+ errors,
87
+ warnings,
88
+ };
89
+ }
90
+ const frontmatter = {
91
+ name: name.trim(),
92
+ description: description.trim(),
93
+ };
94
+ if (scope && typeof scope === "string" && VALID_SCOPES.has(scope)) {
95
+ frontmatter.scope = scope;
96
+ }
97
+ if (severity && typeof severity === "string" && VALID_SEVERITIES.has(severity)) {
98
+ frontmatter.severity = severity;
99
+ }
100
+ if (Array.isArray(triggers) && triggers.every((t) => typeof t === "string")) {
101
+ frontmatter.triggers = triggers;
102
+ }
103
+ if (data.license && typeof data.license === "string") {
104
+ frontmatter.license = data.license;
105
+ }
106
+ if (data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata)) {
107
+ frontmatter.metadata = data.metadata;
108
+ }
109
+ return {
110
+ success: true,
111
+ guardrail: {
112
+ path: resolve(filePath),
113
+ name: frontmatter.name,
114
+ description: frontmatter.description,
115
+ frontmatter,
116
+ body,
117
+ },
118
+ errors: [],
119
+ warnings,
120
+ };
121
+ }
@@ -0,0 +1,6 @@
1
+ export interface SetupResult {
2
+ cursor: boolean;
3
+ claude: boolean;
4
+ message: string;
5
+ }
6
+ export declare function runSetup(projectPath?: string): SetupResult;
package/dist/setup.js ADDED
@@ -0,0 +1,81 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { resolve } from "path";
3
+ import chalk from "chalk";
4
+ const GUARDRAIL_RULE = "You MUST read and follow all constraints in .agents/guardrails/. Never violate a guardrail without explicit human approval.";
5
+ const GUARDRAIL_RULE_MARKER = "read and follow all constraints in .agents/guardrails";
6
+ function hasRule(content) {
7
+ return content.includes(GUARDRAIL_RULE_MARKER);
8
+ }
9
+ export function runSetup(projectPath = ".") {
10
+ const root = resolve(projectPath);
11
+ let cursorDone = false;
12
+ let claudeDone = false;
13
+ // Cursor: .cursor/rules/agent-guardrails.md (preferred) or .cursorrules (legacy)
14
+ const cursorRulesDir = resolve(root, ".cursor", "rules");
15
+ const cursorRuleFile = resolve(cursorRulesDir, "agent-guardrails.md");
16
+ const cursorRuleContent = `---
17
+ description: Load and follow Agent Guardrails
18
+ alwaysApply: true
19
+ ---
20
+
21
+ ${GUARDRAIL_RULE}
22
+ `;
23
+ const cursorrulesPath = resolve(root, ".cursorrules");
24
+ if (existsSync(cursorRuleFile)) {
25
+ const existing = readFileSync(cursorRuleFile, "utf-8");
26
+ if (!hasRule(existing)) {
27
+ writeFileSync(cursorRuleFile, cursorRuleContent);
28
+ cursorDone = true;
29
+ }
30
+ }
31
+ else if (existsSync(cursorrulesPath)) {
32
+ // Legacy: append to .cursorrules
33
+ const existing = readFileSync(cursorrulesPath, "utf-8");
34
+ if (!hasRule(existing)) {
35
+ const appended = existing.trimEnd() + "\n\n" + GUARDRAIL_RULE + "\n";
36
+ writeFileSync(cursorrulesPath, appended);
37
+ cursorDone = true;
38
+ }
39
+ }
40
+ else {
41
+ // Create .cursor/rules/agent-guardrails.md
42
+ if (!existsSync(cursorRulesDir)) {
43
+ mkdirSync(cursorRulesDir, { recursive: true });
44
+ }
45
+ writeFileSync(cursorRuleFile, cursorRuleContent);
46
+ cursorDone = true;
47
+ }
48
+ // Claude Code: .claude/instructions.md
49
+ const claudeDir = resolve(root, ".claude");
50
+ const claudeInstructions = resolve(claudeDir, "instructions.md");
51
+ if (existsSync(claudeInstructions)) {
52
+ const existing = readFileSync(claudeInstructions, "utf-8");
53
+ if (!hasRule(existing)) {
54
+ const appended = existing.trimEnd() + "\n\n" + GUARDRAIL_RULE + "\n";
55
+ writeFileSync(claudeInstructions, appended);
56
+ claudeDone = true;
57
+ }
58
+ }
59
+ else {
60
+ if (!existsSync(claudeDir)) {
61
+ mkdirSync(claudeDir, { recursive: true });
62
+ }
63
+ writeFileSync(claudeInstructions, GUARDRAIL_RULE + "\n");
64
+ claudeDone = true;
65
+ }
66
+ const messages = [];
67
+ if (cursorDone) {
68
+ messages.push(chalk.green("✓") + " Added guardrail rule for Cursor");
69
+ }
70
+ if (claudeDone) {
71
+ messages.push(chalk.green("✓") + " Added guardrail rule for Claude Code");
72
+ }
73
+ if (messages.length === 0) {
74
+ messages.push(chalk.yellow("Guardrail rule already present in all config files."));
75
+ }
76
+ return {
77
+ cursor: cursorDone,
78
+ claude: claudeDone,
79
+ message: messages.join("\n"),
80
+ };
81
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Bundled example guardrails. Used by init and add commands.
3
+ */
4
+ export declare const TEMPLATES: Record<string, string>;
5
+ export declare const TEMPLATE_NAMES: string[];
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Bundled example guardrails. Used by init and add commands.
3
+ */
4
+ export const TEMPLATES = {
5
+ "no-plaintext-secrets": `---
6
+ name: no-plaintext-secrets
7
+ description: Never log, commit, or expose API keys, passwords, or tokens. Use environment variables or secrets managers. Apply when adding logging, implementing auth, or integrating third-party APIs.
8
+ scope: project
9
+ severity: critical
10
+ triggers:
11
+ - "Adding logging"
12
+ - "Implementing authentication"
13
+ - "API integration"
14
+ - "Handling credentials"
15
+ ---
16
+
17
+ # No Plaintext Secrets
18
+
19
+ ## Trigger
20
+ Implementing authentication endpoints, adding logging, integrating third-party APIs, or handling any user credentials or API keys.
21
+
22
+ ## Instruction
23
+ - Never store, log, or commit plaintext credentials (API keys, passwords, tokens, sessions)
24
+ - Always use environment variables or secrets managers (e.g. 1Password, Vault)
25
+ - Use bcrypt with 12 salt rounds for password hashing
26
+ - Always require HTTPS for authentication endpoints
27
+ - Use a \`redactSensitive()\` helper when logging objects that may contain secrets
28
+
29
+ ## Reason
30
+ API keys were exposed in git during a 2025 security audit. Plaintext credentials in logs led to emergency key rotation.
31
+ `,
32
+ "database-migrations": `---
33
+ name: database-migrations
34
+ description: Always use migration files for schema changes. Never modify production schema directly. Apply when modifying database schema, adding tables, or changing columns.
35
+ scope: project
36
+ severity: critical
37
+ triggers:
38
+ - "Modifying database schema"
39
+ - "Adding or changing tables"
40
+ - "Database migrations"
41
+ - "Prisma schema"
42
+ - "SQL migrations"
43
+ ---
44
+
45
+ # Database Migrations
46
+
47
+ ## Trigger
48
+ Modifying database schema, adding tables, changing columns, or optimizing queries that affect schema.
49
+
50
+ ## Instruction
51
+ - Always create a migration file (e.g. \`prisma migrate dev\`, \`rails db:migrate\`, or equivalent)
52
+ - Never modify production schema directly without a migration
53
+ - Test migrations in staging before applying to production
54
+ - Require explicit human approval before running migrations in production
55
+
56
+ ## Reason
57
+ Direct schema changes caused 4-hour downtime and data inconsistencies. Migrations provide rollback capability and audit trail.
58
+ `,
59
+ "no-destructive-commands": `---
60
+ name: no-destructive-commands
61
+ description: Never run destructive shell commands or SQL without explicit human approval. Apply when deleting files, dropping tables, truncating data, or modifying production.
62
+ scope: project
63
+ severity: critical
64
+ triggers:
65
+ - "Deleting files"
66
+ - "Cleaning up"
67
+ - "Dropping tables"
68
+ - "Truncating data"
69
+ - "Database cleanup"
70
+ - "rm -rf"
71
+ - "DROP TABLE"
72
+ - "TRUNCATE"
73
+ ---
74
+
75
+ # No Destructive Commands
76
+
77
+ ## Trigger
78
+ Running shell commands that delete files, database commands that drop or truncate data, or any operation that irreversibly removes content.
79
+
80
+ ## Instruction
81
+ - Never run \`rm -rf\`, \`rm -r\`, or recursive deletes without explicit human approval
82
+ - Never run \`DROP TABLE\`, \`DROP DATABASE\`, \`TRUNCATE\`, or \`DELETE\` without a WHERE clause on production data without explicit approval
83
+ - For cleanup tasks: propose the command, explain the impact, and wait for confirmation before executing
84
+ - Prefer moving to trash or using \`--dry-run\` when available
85
+ - If the user asks to "delete everything" or "clean slate": stop and confirm scope before proceeding
86
+
87
+ ## Reason
88
+ Agents have run \`rm -rf\` on wrong directories and \`DROP TABLE\` in production. Destructive operations must never execute without explicit human confirmation.
89
+ `,
90
+ "no-new-deps-without-approval": `---
91
+ name: no-new-deps-without-approval
92
+ description: Do not add new npm, pip, or other package dependencies without explicit human approval. Apply when installing packages, adding imports, or suggesting new libraries.
93
+ scope: project
94
+ severity: warning
95
+ triggers:
96
+ - "Installing packages"
97
+ - "Adding dependencies"
98
+ - "npm install"
99
+ - "pip install"
100
+ - "New library"
101
+ - "Use package X"
102
+ ---
103
+
104
+ # No New Dependencies Without Approval
105
+
106
+ ## Trigger
107
+ Adding a new package to package.json, requirements.txt, pyproject.toml, Cargo.toml, or any dependency manifest.
108
+
109
+ ## Instruction
110
+ - Before running \`npm install <pkg>\`, \`pip install <pkg>\`, or equivalent: list the package and why it's needed, then ask for approval
111
+ - Prefer using existing project dependencies over adding new ones
112
+ - If a built-in or stdlib solution exists, use it instead of a new dependency
113
+ - When suggesting a new dependency: include package name, purpose, and alternative (e.g. "or we could use the built-in X")
114
+ - Never add dependencies "to fix" a problem without confirming the user wants new packages in the project
115
+
116
+ ## Reason
117
+ Uncontrolled dependency growth leads to security risk, bundle bloat, and maintenance burden. Teams want to review what gets added to their lockfile.
118
+ `,
119
+ "rate-limiting": `---
120
+ name: rate-limiting
121
+ description: Limit tool calls and API requests to prevent runaway loops. Max 50 tool calls per session, max 10 per iteration. Stop after 3 consecutive errors.
122
+ scope: session
123
+ severity: warning
124
+ triggers:
125
+ - "Debugging API integrations"
126
+ - "Stripe or payment API calls"
127
+ - "External API calls in loops"
128
+ - "Context window approaching capacity"
129
+ ---
130
+
131
+ # Rate Limiting
132
+
133
+ ## Trigger
134
+ Debugging API integrations, making repeated external API calls, or when context exceeds 80% capacity or 10+ consecutive errors.
135
+
136
+ ## Instruction
137
+ - Max 50 tool calls per session
138
+ - Max 10 tool calls per iteration
139
+ - Stop after 3 consecutive errors and request context rotation
140
+ - For payment APIs (Stripe, etc.): always use test mode when debugging
141
+ - When context exceeds 80% capacity: re-inject GUARDRAILS.md + summary + objective, then reset context
142
+
143
+ ## Reason
144
+ Agent debugging Stripe entered an infinite loop of test calls, resulting in 2000+ requests in 30 minutes, $200 API costs, and account suspension.
145
+ `,
146
+ };
147
+ export const TEMPLATE_NAMES = Object.keys(TEMPLATES);
@@ -0,0 +1,17 @@
1
+ export interface ValidateResult {
2
+ valid: number;
3
+ invalid: number;
4
+ total: number;
5
+ results: Array<{
6
+ path: string;
7
+ success: boolean;
8
+ errors: string[];
9
+ warnings: string[];
10
+ }>;
11
+ }
12
+ export declare function validatePath(inputPath: string): ValidateResult;
13
+ export declare function listGuardrails(inputPath: string): Array<{
14
+ path: string;
15
+ name: string;
16
+ description: string;
17
+ }>;
@@ -0,0 +1,99 @@
1
+ import { readdirSync, statSync } from "fs";
2
+ import { join, resolve } from "path";
3
+ import { parseGuardrailFile } from "./parse.js";
4
+ const GUARDRAIL_FILENAMES = ["GUARDRAIL.md", "GUARDRAILS.md"];
5
+ const MAX_DEPTH = 6;
6
+ const MAX_DIRS = 2000;
7
+ function findGuardrailFiles(dir, depth = 0, count = { dirs: 0 }) {
8
+ if (depth > MAX_DEPTH || count.dirs > MAX_DIRS) {
9
+ return [];
10
+ }
11
+ const files = [];
12
+ try {
13
+ const entries = readdirSync(dir, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ if (entry.name === ".git" || entry.name === "node_modules") {
16
+ continue;
17
+ }
18
+ const fullPath = join(dir, entry.name);
19
+ if (entry.isFile()) {
20
+ if (GUARDRAIL_FILENAMES.includes(entry.name)) {
21
+ files.push(fullPath);
22
+ }
23
+ }
24
+ else if (entry.isDirectory()) {
25
+ count.dirs++;
26
+ files.push(...findGuardrailFiles(fullPath, depth + 1, count));
27
+ }
28
+ }
29
+ }
30
+ catch {
31
+ // Skip directories we can't read
32
+ }
33
+ return files;
34
+ }
35
+ export function validatePath(inputPath) {
36
+ const resolved = resolve(inputPath);
37
+ const results = [];
38
+ let valid = 0;
39
+ let invalid = 0;
40
+ let filesToValidate = [];
41
+ try {
42
+ const stat = statSync(resolved);
43
+ if (stat.isFile()) {
44
+ if (GUARDRAIL_FILENAMES.includes(resolved.split(/[/\\]/).pop() || "")) {
45
+ filesToValidate = [resolved];
46
+ }
47
+ }
48
+ else if (stat.isDirectory()) {
49
+ filesToValidate = findGuardrailFiles(resolved);
50
+ }
51
+ }
52
+ catch {
53
+ results.push({
54
+ path: resolved,
55
+ success: false,
56
+ errors: ["Path does not exist or is not accessible"],
57
+ warnings: [],
58
+ });
59
+ invalid++;
60
+ }
61
+ for (const filePath of filesToValidate) {
62
+ const parseResult = parseGuardrailFile(filePath);
63
+ results.push({
64
+ path: filePath,
65
+ success: parseResult.success,
66
+ errors: parseResult.errors,
67
+ warnings: parseResult.warnings,
68
+ });
69
+ if (parseResult.success) {
70
+ valid++;
71
+ }
72
+ else {
73
+ invalid++;
74
+ }
75
+ }
76
+ return {
77
+ valid,
78
+ invalid,
79
+ total: valid + invalid,
80
+ results,
81
+ };
82
+ }
83
+ export function listGuardrails(inputPath) {
84
+ const validateResult = validatePath(inputPath);
85
+ const guardrails = [];
86
+ for (const r of validateResult.results) {
87
+ if (r.success) {
88
+ const parseResult = parseGuardrailFile(r.path);
89
+ if (parseResult.guardrail) {
90
+ guardrails.push({
91
+ path: parseResult.guardrail.path,
92
+ name: parseResult.guardrail.name,
93
+ description: parseResult.guardrail.description,
94
+ });
95
+ }
96
+ }
97
+ }
98
+ return guardrails;
99
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "guardrails-ref",
3
+ "version": "1.0.0",
4
+ "description": "Validate and manage Agent Guardrails (GUARDRAIL.md) — init, setup, add, validate",
5
+ "type": "module",
6
+ "main": "dist/validate.js",
7
+ "bin": {
8
+ "guardrails-ref": "./dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "validate": "node dist/cli.js validate",
13
+ "list": "node dist/cli.js list",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "agent",
18
+ "guardrails",
19
+ "ai",
20
+ "cursor",
21
+ "claude"
22
+ ],
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/9atar6/agent-guardrails.git",
27
+ "directory": "guardrails-ref"
28
+ },
29
+ "bugs": "https://github.com/9atar6/agent-guardrails/issues",
30
+ "homepage": "https://github.com/9atar6/agent-guardrails#readme",
31
+ "dependencies": {
32
+ "chalk": "^5.3.0",
33
+ "commander": "^12.1.0",
34
+ "gray-matter": "^4.0.3"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.10.0",
38
+ "typescript": "^5.3.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ]
46
+ }