guardrails-ref 1.0.9 → 1.1.1
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/README.md +24 -6
- package/dist/cli.js +56 -21
- package/dist/init.d.ts +1 -1
- package/dist/init.js +8 -1
- package/dist/parse.js +7 -8
- package/dist/setup.d.ts +22 -2
- package/dist/setup.js +243 -38
- package/dist/why.d.ts +1 -0
- package/dist/why.js +13 -0
- package/examples/README.md +9 -1
- package/examples/no-deprecated-apis/GUARDRAIL.md +35 -0
- package/examples/no-hardcoded-urls/GUARDRAIL.md +3 -0
- package/examples/no-hardcoded-user-facing-strings/GUARDRAIL.md +35 -0
- package/examples/no-inline-styles/GUARDRAIL.md +4 -2
- package/examples/no-magic-numbers/GUARDRAIL.md +3 -0
- package/examples/no-modifying-git-history/GUARDRAIL.md +36 -0
- package/examples/no-placeholder-credentials/GUARDRAIL.md +36 -0
- package/examples/no-raw-sql/GUARDRAIL.md +3 -0
- package/examples/no-silent-error-handling/GUARDRAIL.md +36 -0
- package/examples/no-sudo-commands/GUARDRAIL.md +3 -0
- package/examples/no-unsafe-env-assumptions/GUARDRAIL.md +35 -0
- package/examples/prefer-existing-code/GUARDRAIL.md +36 -0
- package/examples/require-access-control/GUARDRAIL.md +37 -0
- package/examples/require-tests/GUARDRAIL.md +3 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
CLI for [Agent Guardrails](https://github.com/9atar6/agent-guardrails) — init, add, remove, setup, validate, check, upgrade, and
|
|
7
|
+
CLI for [Agent Guardrails](https://github.com/9atar6/agent-guardrails) — init, add, remove, setup, validate, check, upgrade, list, and why GUARDRAIL.md files.
|
|
8
8
|
|
|
9
9
|
## Why?
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ AI coding agents (Cursor, Claude Code, etc.) don't remember across sessions. Gua
|
|
|
16
16
|
npx guardrails-ref init
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Creates `.agents/guardrails/`, adds the `no-plaintext-secrets` example, and configures Cursor and
|
|
19
|
+
Creates `.agents/guardrails/`, adds the `no-plaintext-secrets` example, and configures Cursor, Claude Code, and VS Code Copilot to read your guardrails. No global install needed.
|
|
20
20
|
|
|
21
21
|
> **Note:** IDEs don't yet recognize guardrails natively. The `setup` command adds a rule so the AI reads them. Once IDEs add support, this won't be needed.
|
|
22
22
|
|
|
@@ -24,19 +24,26 @@ Creates `.agents/guardrails/`, adds the `no-plaintext-secrets` example, and conf
|
|
|
24
24
|
|
|
25
25
|
| Command | Description |
|
|
26
26
|
|---------|-------------|
|
|
27
|
-
| `npx guardrails-ref init [path]` | Create `.agents/guardrails/`, add no-plaintext-secrets, configure Cursor and
|
|
27
|
+
| `npx guardrails-ref init [path]` | Create `.agents/guardrails/`, add no-plaintext-secrets, configure Cursor, Claude Code, and VS Code Copilot |
|
|
28
|
+
| `npx guardrails-ref init --minimal [path]` | Create `.agents/guardrails/` only (no example, no setup) |
|
|
28
29
|
| `npx guardrails-ref add <name> [name2 ...] [path]` | Add example guardrail(s) — pass multiple names to add several at once |
|
|
29
30
|
| `npx guardrails-ref remove <name> [path]` | Remove a guardrail (name required) |
|
|
30
|
-
| `npx guardrails-ref setup [path]` | Add the guardrail rule to Cursor
|
|
31
|
+
| `npx guardrails-ref setup [path]` | Add the guardrail rule to Cursor, Claude Code, and VS Code Copilot |
|
|
32
|
+
| `npx guardrails-ref setup --remove [path]` | Remove the guardrail rule from IDE configs |
|
|
33
|
+
| `npx guardrails-ref setup --ide <name> [path]` | Target IDE: `cursor`, `claude`, `copilot`, or `auto` (only configured IDEs) |
|
|
34
|
+
| `npx guardrails-ref setup --dry-run [path]` | Show what would be added/removed without writing files |
|
|
35
|
+
| `npx guardrails-ref setup --check [path]` | Show which IDEs are configured and whether they have the rule |
|
|
31
36
|
| `npx guardrails-ref validate [path]` | Validate GUARDRAIL.md files (use `--json` for JSON, `--strict` to fail on warnings) |
|
|
32
37
|
| `npx guardrails-ref check [path]` | Validate with minimal output (CI-friendly, use `--strict` to fail on warnings) |
|
|
33
38
|
| `npx guardrails-ref upgrade [path]` | Update installed guardrails to latest templates (use `--dry-run` to preview, `--diff` to show changes) |
|
|
34
39
|
| `npx guardrails-ref list [path]` | List discovered guardrails (use `--json` for JSON output) |
|
|
40
|
+
| `npx guardrails-ref why <name>` | Show guardrail template content (e.g. `why no-destructive-commands`) |
|
|
35
41
|
|
|
36
42
|
## Supported IDEs
|
|
37
43
|
|
|
38
44
|
- **Cursor** — via `.cursor/rules/` or `.cursorrules`
|
|
39
45
|
- **Claude Code** — via `.claude/instructions.md`
|
|
46
|
+
- **VS Code Copilot** — via `.github/copilot-instructions.md`
|
|
40
47
|
|
|
41
48
|
## CI/CD
|
|
42
49
|
|
|
@@ -54,12 +61,15 @@ Or with full output or JSON:
|
|
|
54
61
|
run: npx guardrails-ref validate . --json
|
|
55
62
|
```
|
|
56
63
|
|
|
64
|
+
**Note:** `list` exits with code 1 when no guardrails are found, so it can be used in scripts to check for guardrail presence.
|
|
65
|
+
|
|
57
66
|
## Examples
|
|
58
67
|
|
|
59
68
|
```bash
|
|
60
69
|
npx guardrails-ref init
|
|
61
70
|
npx guardrails-ref add no-destructive-commands no-hardcoded-urls
|
|
62
71
|
npx guardrails-ref add no-new-deps-without-approval
|
|
72
|
+
npx guardrails-ref why no-destructive-commands
|
|
63
73
|
npx guardrails-ref validate .
|
|
64
74
|
npx guardrails-ref list .
|
|
65
75
|
```
|
|
@@ -69,6 +79,9 @@ npx guardrails-ref list .
|
|
|
69
79
|
| Name | What it prevents |
|
|
70
80
|
|------|------------------|
|
|
71
81
|
| `no-plaintext-secrets` | Logging or committing credentials |
|
|
82
|
+
| `no-placeholder-credentials` | Fake or placeholder API keys instead of asking for real values |
|
|
83
|
+
| `no-silent-error-handling` | Catching errors without surfacing them to the user |
|
|
84
|
+
| `require-access-control` | Exposing sensitive data or admin actions without role checks |
|
|
72
85
|
| `database-migrations` | Direct schema changes instead of migrations |
|
|
73
86
|
| `no-destructive-commands` | rm -rf, DROP TABLE, TRUNCATE without approval |
|
|
74
87
|
| `no-new-deps-without-approval` | New packages without approval |
|
|
@@ -77,15 +90,20 @@ npx guardrails-ref list .
|
|
|
77
90
|
| `rate-limiting` | Runaway tool calls and API loops |
|
|
78
91
|
| `no-console-in-production` | console.log in production code |
|
|
79
92
|
| `require-tests` | Merging code without tests |
|
|
93
|
+
| `prefer-existing-code` | Reimplementing when existing code or helpers exist |
|
|
80
94
|
| `no-inline-styles` | Inline `style=` in HTML/JSX |
|
|
81
95
|
| `no-raw-sql` | Raw SQL without parameterization |
|
|
82
96
|
| `no-magic-numbers` | Unexplained numeric literals |
|
|
97
|
+
| `no-modifying-git-history` | git push --force, destructive rebase without approval |
|
|
98
|
+
| `no-deprecated-apis` | Suggesting deprecated or obsolete APIs |
|
|
99
|
+
| `no-unsafe-env-assumptions` | Assuming env vars exist without validation |
|
|
100
|
+
| `no-hardcoded-user-facing-strings` | Hardcoded labels, messages, errors in UI |
|
|
83
101
|
|
|
84
|
-
Use `npx guardrails-ref add --list` to see all available guardrails.
|
|
102
|
+
Use `npx guardrails-ref add --list` to see all available guardrails. Use `npx guardrails-ref why <name>` to show a guardrail's full content (from templates).
|
|
85
103
|
|
|
86
104
|
## Troubleshooting
|
|
87
105
|
|
|
88
|
-
- **"Unknown guardrail"** — Run `npx guardrails-ref list
|
|
106
|
+
- **"Unknown guardrail"** — Run `npx guardrails-ref add --list` to see available guardrail names
|
|
89
107
|
- **Setup not working** — Try `npx guardrails-ref setup --remove` then `npx guardrails-ref setup` again
|
|
90
108
|
|
|
91
109
|
## Links
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,10 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { program } from "commander";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { validatePath, listGuardrails } from "./validate.js";
|
|
8
|
-
import { runSetup, runSetupRemove } from "./setup.js";
|
|
8
|
+
import { runSetup, runSetupRemove, runSetupCheck } from "./setup.js";
|
|
9
9
|
import { runInit } from "./init.js";
|
|
10
10
|
import { runAdd } from "./add.js";
|
|
11
|
+
import { runWhy } from "./why.js";
|
|
11
12
|
import { runRemove } from "./remove.js";
|
|
12
13
|
import { runUpgrade } from "./upgrade.js";
|
|
13
14
|
import { TEMPLATE_NAMES } from "./templates.js";
|
|
@@ -92,31 +93,33 @@ program
|
|
|
92
93
|
.description("Validate GUARDRAIL.md files in a directory or a single file")
|
|
93
94
|
.option("-j, --json", "Output as JSON")
|
|
94
95
|
.option("-s, --strict", "Fail on warnings (CI mode)")
|
|
95
|
-
.action((path
|
|
96
|
-
const opts =
|
|
97
|
-
runValidate(path, opts);
|
|
96
|
+
.action(function (path) {
|
|
97
|
+
const opts = this.opts();
|
|
98
|
+
runValidate(path ?? ".", opts);
|
|
98
99
|
});
|
|
99
100
|
program
|
|
100
101
|
.command("check [path]")
|
|
101
102
|
.description("Validate guardrails with minimal output (CI-friendly alias)")
|
|
102
103
|
.option("-s, --strict", "Fail on warnings")
|
|
103
|
-
.action((path
|
|
104
|
-
const opts =
|
|
105
|
-
runValidate(path, { ...opts, minimal: true });
|
|
104
|
+
.action(function (path) {
|
|
105
|
+
const opts = this.opts();
|
|
106
|
+
runValidate(path ?? ".", { ...opts, minimal: true });
|
|
106
107
|
});
|
|
107
108
|
program
|
|
108
109
|
.command("init [path]")
|
|
109
110
|
.description("Create .agents/guardrails/, add no-plaintext-secrets, and run setup (one command to get started)")
|
|
110
|
-
.
|
|
111
|
-
|
|
111
|
+
.option("-m, --minimal", "Create .agents/guardrails/ only, no example and no setup")
|
|
112
|
+
.action(function (path) {
|
|
113
|
+
const opts = this.opts();
|
|
114
|
+
runInit(path ?? ".", opts.minimal);
|
|
112
115
|
});
|
|
113
116
|
program
|
|
114
117
|
.command("add [names...]")
|
|
115
118
|
.description("Add example guardrail(s) by name — pass multiple to add several at once")
|
|
116
119
|
.option("-l, --list", "List available guardrails to add")
|
|
117
120
|
.option("-p, --path <path>", "Target directory", ".")
|
|
118
|
-
.action((names = []
|
|
119
|
-
const opts =
|
|
121
|
+
.action(function (names = []) {
|
|
122
|
+
const opts = this.opts();
|
|
120
123
|
if (opts.list) {
|
|
121
124
|
console.log("Available guardrails:");
|
|
122
125
|
for (const n of TEMPLATE_NAMES) {
|
|
@@ -162,8 +165,8 @@ program
|
|
|
162
165
|
.description("Update installed guardrails to latest template versions")
|
|
163
166
|
.option("-n, --dry-run", "Show what would be updated without writing")
|
|
164
167
|
.option("-d, --diff", "Show diff for each updated guardrail")
|
|
165
|
-
.action((path
|
|
166
|
-
const opts =
|
|
168
|
+
.action(function (path) {
|
|
169
|
+
const opts = this.opts();
|
|
167
170
|
runUpgrade(path ?? ".", opts.dryRun, opts.diff);
|
|
168
171
|
});
|
|
169
172
|
program
|
|
@@ -175,27 +178,59 @@ program
|
|
|
175
178
|
});
|
|
176
179
|
program
|
|
177
180
|
.command("setup [path]")
|
|
178
|
-
.description("Add the guardrail
|
|
179
|
-
.option("-r, --remove", "Remove the guardrail rule from
|
|
180
|
-
.
|
|
181
|
+
.description("Add the guardrail rule to Cursor, Claude Code, and VS Code Copilot (required until IDEs support guardrails natively)")
|
|
182
|
+
.option("-r, --remove", "Remove the guardrail rule from IDE configs")
|
|
183
|
+
.option("-i, --ide <name>", "Target IDE: cursor, claude, copilot, or auto (only configured IDEs)")
|
|
184
|
+
.option("-n, --dry-run", "Show what would be added/removed without writing files")
|
|
185
|
+
.option("-c, --check", "Show which IDEs are configured and whether they have the rule")
|
|
186
|
+
.action(function (path) {
|
|
181
187
|
const p = path ?? ".";
|
|
182
|
-
const opts =
|
|
183
|
-
|
|
188
|
+
const opts = this.opts();
|
|
189
|
+
if (opts.check) {
|
|
190
|
+
const check = runSetupCheck(p);
|
|
191
|
+
const fmt = (name, r) => {
|
|
192
|
+
const status = !r.configured ? "not configured" : r.hasRule ? "has rule" : "no rule";
|
|
193
|
+
const color = !r.configured ? chalk.gray : r.hasRule ? chalk.green : chalk.yellow;
|
|
194
|
+
console.log(` ${name.padEnd(12)} ${color(status)}`);
|
|
195
|
+
};
|
|
196
|
+
console.log("IDE setup status:");
|
|
197
|
+
fmt("Cursor", check.cursor);
|
|
198
|
+
fmt("Claude Code", check.claude);
|
|
199
|
+
fmt("VS Code Copilot", check.copilot);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const ide = opts.ide;
|
|
203
|
+
if (ide && !["cursor", "claude", "copilot", "auto"].includes(ide)) {
|
|
204
|
+
console.error(chalk.red("Invalid --ide. Use: cursor, claude, copilot, or auto"));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const result = opts.remove
|
|
208
|
+
? runSetupRemove(p, ide, opts.dryRun)
|
|
209
|
+
: runSetup(p, ide, opts.dryRun);
|
|
184
210
|
console.log(result.message);
|
|
185
211
|
});
|
|
212
|
+
program
|
|
213
|
+
.command("why <name>")
|
|
214
|
+
.description("Show guardrail content (e.g. npx guardrails-ref why no-destructive-commands)")
|
|
215
|
+
.action((name) => {
|
|
216
|
+
const ok = runWhy(name);
|
|
217
|
+
process.exit(ok ? 0 : 1);
|
|
218
|
+
});
|
|
186
219
|
program
|
|
187
220
|
.command("list [path]")
|
|
188
221
|
.description("List discovered guardrails")
|
|
189
222
|
.option("-j, --json", "Output as JSON")
|
|
190
|
-
.action((path
|
|
191
|
-
const opts =
|
|
192
|
-
const guardrails = listGuardrails(path);
|
|
223
|
+
.action(function (path) {
|
|
224
|
+
const opts = this.opts();
|
|
225
|
+
const guardrails = listGuardrails(path ?? ".");
|
|
193
226
|
if (opts.json) {
|
|
194
227
|
console.log(JSON.stringify({ guardrails, total: guardrails.length }, null, 2));
|
|
228
|
+
process.exit(guardrails.length === 0 ? 1 : 0);
|
|
195
229
|
return;
|
|
196
230
|
}
|
|
197
231
|
if (guardrails.length === 0) {
|
|
198
232
|
console.log(chalk.yellow("No guardrails found"));
|
|
233
|
+
process.exit(1);
|
|
199
234
|
return;
|
|
200
235
|
}
|
|
201
236
|
for (const g of guardrails) {
|
package/dist/init.d.ts
CHANGED
package/dist/init.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolve } from "path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { runSetup } from "./setup.js";
|
|
5
5
|
import { TEMPLATES } from "./templates.js";
|
|
6
|
-
export function runInit(projectPath = ".") {
|
|
6
|
+
export function runInit(projectPath = ".", minimal = false) {
|
|
7
7
|
const root = resolve(projectPath);
|
|
8
8
|
const guardrailsDir = resolve(root, ".agents", "guardrails");
|
|
9
9
|
const exampleDir = resolve(guardrailsDir, "no-plaintext-secrets");
|
|
@@ -13,6 +13,13 @@ export function runInit(projectPath = ".") {
|
|
|
13
13
|
mkdirSync(guardrailsDir, { recursive: true });
|
|
14
14
|
console.log(chalk.green("✓") + " Created .agents/guardrails/");
|
|
15
15
|
}
|
|
16
|
+
if (minimal) {
|
|
17
|
+
return {
|
|
18
|
+
guardrailsDir,
|
|
19
|
+
exampleCreated: false,
|
|
20
|
+
setupDone: "",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
16
23
|
if (!existsSync(exampleFile)) {
|
|
17
24
|
mkdirSync(exampleDir, { recursive: true });
|
|
18
25
|
writeFileSync(exampleFile, TEMPLATES["no-plaintext-secrets"]);
|
package/dist/parse.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import matter from "gray-matter";
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
|
-
import { resolve } from "path";
|
|
3
|
+
import { resolve, dirname, basename } from "path";
|
|
4
4
|
const NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
5
5
|
const MAX_NAME_LENGTH = 64;
|
|
6
6
|
const MAX_DESCRIPTION_LENGTH = 1024;
|
|
@@ -75,13 +75,12 @@ export function parseGuardrailFile(filePath) {
|
|
|
75
75
|
warnings.push("triggers must be a list of strings");
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
// Check directory name matches (only when file is inside a directory)
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
78
|
+
// Check directory name matches (only when file is GUARDRAIL.md inside a directory, not GUARDRAILS.md at root)
|
|
79
|
+
const parentDir = dirname(filePath);
|
|
80
|
+
const dirName = basename(parentDir);
|
|
81
|
+
const fileName = basename(filePath);
|
|
82
|
+
if (fileName === "GUARDRAIL.md" && dirName && dirName !== "." && typeof name === "string" && name !== dirName) {
|
|
83
|
+
warnings.push(`name "${name}" does not match parent directory "${dirName}"`);
|
|
85
84
|
}
|
|
86
85
|
if (errors.length > 0) {
|
|
87
86
|
return {
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
export type IdeName = "cursor" | "claude" | "copilot";
|
|
1
2
|
export interface SetupResult {
|
|
2
3
|
cursor: boolean;
|
|
3
4
|
claude: boolean;
|
|
5
|
+
copilot: boolean;
|
|
4
6
|
message: string;
|
|
5
7
|
}
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
+
export interface SetupCheckResult {
|
|
9
|
+
cursor: {
|
|
10
|
+
configured: boolean;
|
|
11
|
+
hasRule: boolean;
|
|
12
|
+
};
|
|
13
|
+
claude: {
|
|
14
|
+
configured: boolean;
|
|
15
|
+
hasRule: boolean;
|
|
16
|
+
};
|
|
17
|
+
copilot: {
|
|
18
|
+
configured: boolean;
|
|
19
|
+
hasRule: boolean;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check which IDEs are configured and whether they have the guardrail rule.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runSetupCheck(projectPath?: string): SetupCheckResult;
|
|
26
|
+
export declare function runSetup(projectPath?: string, ideFilter?: IdeName | "all" | "auto", dryRun?: boolean): SetupResult;
|
|
27
|
+
export declare function runSetupRemove(projectPath?: string, ideFilter?: IdeName | "all" | "auto", dryRun?: boolean): SetupResult;
|
package/dist/setup.js
CHANGED
|
@@ -3,6 +3,10 @@ import { resolve } from "path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
const GUARDRAIL_RULE = "You MUST read and follow all constraints in .agents/guardrails/. Never violate a guardrail without explicit human approval.";
|
|
5
5
|
const GUARDRAIL_RULE_MARKER = "You MUST read and follow all constraints in .agents/guardrails";
|
|
6
|
+
/**
|
|
7
|
+
* Removes the guardrail rule from file content.
|
|
8
|
+
* When the resulting content is empty, the caller may delete the file (e.g. .cursorrules, .claude/instructions.md).
|
|
9
|
+
*/
|
|
6
10
|
function removeRuleFromContent(content) {
|
|
7
11
|
const lines = content.split("\n").filter((line) => !line.includes(GUARDRAIL_RULE_MARKER));
|
|
8
12
|
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
@@ -10,11 +14,7 @@ function removeRuleFromContent(content) {
|
|
|
10
14
|
function hasRule(content) {
|
|
11
15
|
return content.includes(GUARDRAIL_RULE_MARKER);
|
|
12
16
|
}
|
|
13
|
-
|
|
14
|
-
const root = resolve(projectPath);
|
|
15
|
-
let cursorDone = false;
|
|
16
|
-
let claudeDone = false;
|
|
17
|
-
// Cursor: .cursor/rules/agent-guardrails.md (preferred) or .cursorrules (legacy)
|
|
17
|
+
function setupCursor(root) {
|
|
18
18
|
const cursorRulesDir = resolve(root, ".cursor", "rules");
|
|
19
19
|
const cursorRuleFile = resolve(cursorRulesDir, "agent-guardrails.md");
|
|
20
20
|
const cursorRuleContent = `---
|
|
@@ -29,27 +29,27 @@ ${GUARDRAIL_RULE}
|
|
|
29
29
|
const existing = readFileSync(cursorRuleFile, "utf-8");
|
|
30
30
|
if (!hasRule(existing)) {
|
|
31
31
|
writeFileSync(cursorRuleFile, cursorRuleContent);
|
|
32
|
-
|
|
32
|
+
return true;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
else if (existsSync(cursorrulesPath)) {
|
|
36
|
-
// Legacy: append to .cursorrules
|
|
37
36
|
const existing = readFileSync(cursorrulesPath, "utf-8");
|
|
38
37
|
if (!hasRule(existing)) {
|
|
39
38
|
const appended = existing.trimEnd() + "\n\n" + GUARDRAIL_RULE + "\n";
|
|
40
39
|
writeFileSync(cursorrulesPath, appended);
|
|
41
|
-
|
|
40
|
+
return true;
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
else {
|
|
45
|
-
// Create .cursor/rules/agent-guardrails.md
|
|
46
44
|
if (!existsSync(cursorRulesDir)) {
|
|
47
45
|
mkdirSync(cursorRulesDir, { recursive: true });
|
|
48
46
|
}
|
|
49
47
|
writeFileSync(cursorRuleFile, cursorRuleContent);
|
|
50
|
-
|
|
48
|
+
return true;
|
|
51
49
|
}
|
|
52
|
-
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function setupClaude(root) {
|
|
53
53
|
const claudeDir = resolve(root, ".claude");
|
|
54
54
|
const claudeInstructions = resolve(claudeDir, "instructions.md");
|
|
55
55
|
if (existsSync(claudeInstructions)) {
|
|
@@ -57,7 +57,7 @@ ${GUARDRAIL_RULE}
|
|
|
57
57
|
if (!hasRule(existing)) {
|
|
58
58
|
const appended = existing.trimEnd() + "\n\n" + GUARDRAIL_RULE + "\n";
|
|
59
59
|
writeFileSync(claudeInstructions, appended);
|
|
60
|
-
|
|
60
|
+
return true;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
@@ -65,35 +65,39 @@ ${GUARDRAIL_RULE}
|
|
|
65
65
|
mkdirSync(claudeDir, { recursive: true });
|
|
66
66
|
}
|
|
67
67
|
writeFileSync(claudeInstructions, GUARDRAIL_RULE + "\n");
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
const messages = [];
|
|
71
|
-
if (cursorDone) {
|
|
72
|
-
messages.push(chalk.green("✓") + " Added guardrail rule for Cursor");
|
|
68
|
+
return true;
|
|
73
69
|
}
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
function setupCopilot(root) {
|
|
73
|
+
const githubDir = resolve(root, ".github");
|
|
74
|
+
const copilotFile = resolve(githubDir, "copilot-instructions.md");
|
|
75
|
+
const ruleBlock = `\n\n${GUARDRAIL_RULE}\n`;
|
|
76
|
+
if (existsSync(copilotFile)) {
|
|
77
|
+
const existing = readFileSync(copilotFile, "utf-8");
|
|
78
|
+
if (!hasRule(existing)) {
|
|
79
|
+
const appended = existing.trimEnd() + ruleBlock;
|
|
80
|
+
writeFileSync(copilotFile, appended);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
else {
|
|
85
|
+
if (!existsSync(githubDir)) {
|
|
86
|
+
mkdirSync(githubDir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
writeFileSync(copilotFile, GUARDRAIL_RULE + "\n");
|
|
89
|
+
return true;
|
|
79
90
|
}
|
|
80
|
-
return
|
|
81
|
-
cursor: cursorDone,
|
|
82
|
-
claude: claudeDone,
|
|
83
|
-
message: messages.join("\n"),
|
|
84
|
-
};
|
|
91
|
+
return false;
|
|
85
92
|
}
|
|
86
|
-
|
|
87
|
-
const root = resolve(projectPath);
|
|
88
|
-
let cursorDone = false;
|
|
89
|
-
let claudeDone = false;
|
|
93
|
+
function removeCursor(root) {
|
|
90
94
|
const cursorRulesDir = resolve(root, ".cursor", "rules");
|
|
91
95
|
const cursorRuleFile = resolve(cursorRulesDir, "agent-guardrails.md");
|
|
92
96
|
const cursorrulesPath = resolve(root, ".cursorrules");
|
|
93
|
-
|
|
97
|
+
let done = false;
|
|
94
98
|
if (existsSync(cursorRuleFile)) {
|
|
95
99
|
rmSync(cursorRuleFile);
|
|
96
|
-
|
|
100
|
+
done = true;
|
|
97
101
|
}
|
|
98
102
|
if (existsSync(cursorrulesPath)) {
|
|
99
103
|
const existing = readFileSync(cursorrulesPath, "utf-8");
|
|
@@ -105,9 +109,13 @@ export function runSetupRemove(projectPath = ".") {
|
|
|
105
109
|
else {
|
|
106
110
|
rmSync(cursorrulesPath);
|
|
107
111
|
}
|
|
108
|
-
|
|
112
|
+
done = true;
|
|
109
113
|
}
|
|
110
114
|
}
|
|
115
|
+
return done;
|
|
116
|
+
}
|
|
117
|
+
function removeClaude(root) {
|
|
118
|
+
const claudeInstructions = resolve(root, ".claude", "instructions.md");
|
|
111
119
|
if (existsSync(claudeInstructions)) {
|
|
112
120
|
const existing = readFileSync(claudeInstructions, "utf-8");
|
|
113
121
|
if (hasRule(existing)) {
|
|
@@ -118,22 +126,219 @@ export function runSetupRemove(projectPath = ".") {
|
|
|
118
126
|
else {
|
|
119
127
|
rmSync(claudeInstructions);
|
|
120
128
|
}
|
|
121
|
-
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
function removeCopilot(root) {
|
|
135
|
+
const copilotFile = resolve(root, ".github", "copilot-instructions.md");
|
|
136
|
+
if (existsSync(copilotFile)) {
|
|
137
|
+
const existing = readFileSync(copilotFile, "utf-8");
|
|
138
|
+
if (hasRule(existing)) {
|
|
139
|
+
const cleaned = removeRuleFromContent(existing);
|
|
140
|
+
if (cleaned) {
|
|
141
|
+
writeFileSync(copilotFile, cleaned + "\n");
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
rmSync(copilotFile);
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
function checkCursor(root) {
|
|
152
|
+
const cursorRuleFile = resolve(root, ".cursor", "rules", "agent-guardrails.md");
|
|
153
|
+
const cursorrulesPath = resolve(root, ".cursorrules");
|
|
154
|
+
if (existsSync(cursorRuleFile)) {
|
|
155
|
+
const content = readFileSync(cursorRuleFile, "utf-8");
|
|
156
|
+
return { configured: true, hasRule: hasRule(content) };
|
|
157
|
+
}
|
|
158
|
+
if (existsSync(cursorrulesPath)) {
|
|
159
|
+
const content = readFileSync(cursorrulesPath, "utf-8");
|
|
160
|
+
return { configured: true, hasRule: hasRule(content) };
|
|
161
|
+
}
|
|
162
|
+
return { configured: false, hasRule: false };
|
|
163
|
+
}
|
|
164
|
+
function checkClaude(root) {
|
|
165
|
+
const claudeInstructions = resolve(root, ".claude", "instructions.md");
|
|
166
|
+
if (existsSync(claudeInstructions)) {
|
|
167
|
+
const content = readFileSync(claudeInstructions, "utf-8");
|
|
168
|
+
return { configured: true, hasRule: hasRule(content) };
|
|
169
|
+
}
|
|
170
|
+
return { configured: false, hasRule: false };
|
|
171
|
+
}
|
|
172
|
+
function checkCopilot(root) {
|
|
173
|
+
const copilotFile = resolve(root, ".github", "copilot-instructions.md");
|
|
174
|
+
if (existsSync(copilotFile)) {
|
|
175
|
+
const content = readFileSync(copilotFile, "utf-8");
|
|
176
|
+
return { configured: true, hasRule: hasRule(content) };
|
|
177
|
+
}
|
|
178
|
+
return { configured: false, hasRule: false };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check which IDEs are configured and whether they have the guardrail rule.
|
|
182
|
+
*/
|
|
183
|
+
export function runSetupCheck(projectPath = ".") {
|
|
184
|
+
const root = resolve(projectPath);
|
|
185
|
+
return {
|
|
186
|
+
cursor: checkCursor(root),
|
|
187
|
+
claude: checkClaude(root),
|
|
188
|
+
copilot: checkCopilot(root),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function wouldSetupCursor(root) {
|
|
192
|
+
const cursorRuleFile = resolve(root, ".cursor", "rules", "agent-guardrails.md");
|
|
193
|
+
const cursorrulesPath = resolve(root, ".cursorrules");
|
|
194
|
+
if (existsSync(cursorRuleFile))
|
|
195
|
+
return !hasRule(readFileSync(cursorRuleFile, "utf-8"));
|
|
196
|
+
if (existsSync(cursorrulesPath))
|
|
197
|
+
return !hasRule(readFileSync(cursorrulesPath, "utf-8"));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
function wouldSetupClaude(root) {
|
|
201
|
+
const claudeInstructions = resolve(root, ".claude", "instructions.md");
|
|
202
|
+
if (existsSync(claudeInstructions))
|
|
203
|
+
return !hasRule(readFileSync(claudeInstructions, "utf-8"));
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
function wouldSetupCopilot(root) {
|
|
207
|
+
const copilotFile = resolve(root, ".github", "copilot-instructions.md");
|
|
208
|
+
if (existsSync(copilotFile))
|
|
209
|
+
return !hasRule(readFileSync(copilotFile, "utf-8"));
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
function wouldRemoveCursor(root) {
|
|
213
|
+
const cursorRuleFile = resolve(root, ".cursor", "rules", "agent-guardrails.md");
|
|
214
|
+
const cursorrulesPath = resolve(root, ".cursorrules");
|
|
215
|
+
if (existsSync(cursorRuleFile))
|
|
216
|
+
return true;
|
|
217
|
+
if (existsSync(cursorrulesPath))
|
|
218
|
+
return hasRule(readFileSync(cursorrulesPath, "utf-8"));
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
function wouldRemoveClaude(root) {
|
|
222
|
+
const claudeInstructions = resolve(root, ".claude", "instructions.md");
|
|
223
|
+
return existsSync(claudeInstructions) && hasRule(readFileSync(claudeInstructions, "utf-8"));
|
|
224
|
+
}
|
|
225
|
+
function wouldRemoveCopilot(root) {
|
|
226
|
+
const copilotFile = resolve(root, ".github", "copilot-instructions.md");
|
|
227
|
+
return existsSync(copilotFile) && hasRule(readFileSync(copilotFile, "utf-8"));
|
|
228
|
+
}
|
|
229
|
+
export function runSetup(projectPath = ".", ideFilter, dryRun = false) {
|
|
230
|
+
const root = resolve(projectPath);
|
|
231
|
+
let ides;
|
|
232
|
+
if (ideFilter === "auto") {
|
|
233
|
+
const check = runSetupCheck(root);
|
|
234
|
+
ides = ["cursor", "claude", "copilot"].filter((ide) => check[ide].configured);
|
|
235
|
+
}
|
|
236
|
+
else if (ideFilter && ideFilter !== "all") {
|
|
237
|
+
ides = [ideFilter];
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
ides = ["cursor", "claude", "copilot"];
|
|
241
|
+
}
|
|
242
|
+
let cursorDone = false;
|
|
243
|
+
let claudeDone = false;
|
|
244
|
+
let copilotDone = false;
|
|
245
|
+
if (dryRun) {
|
|
246
|
+
if (ides.includes("cursor"))
|
|
247
|
+
cursorDone = wouldSetupCursor(root);
|
|
248
|
+
if (ides.includes("claude"))
|
|
249
|
+
claudeDone = wouldSetupClaude(root);
|
|
250
|
+
if (ides.includes("copilot"))
|
|
251
|
+
copilotDone = wouldSetupCopilot(root);
|
|
252
|
+
const messages = [];
|
|
253
|
+
if (cursorDone)
|
|
254
|
+
messages.push(chalk.gray("[dry-run] Would add guardrail rule for Cursor"));
|
|
255
|
+
if (claudeDone)
|
|
256
|
+
messages.push(chalk.gray("[dry-run] Would add guardrail rule for Claude Code"));
|
|
257
|
+
if (copilotDone)
|
|
258
|
+
messages.push(chalk.gray("[dry-run] Would add guardrail rule for VS Code Copilot"));
|
|
259
|
+
if (messages.length === 0) {
|
|
260
|
+
messages.push(chalk.yellow("Guardrail rule already present in all target IDEs. Nothing to do."));
|
|
122
261
|
}
|
|
262
|
+
return { cursor: cursorDone, claude: claudeDone, copilot: copilotDone, message: messages.join("\n") };
|
|
123
263
|
}
|
|
264
|
+
if (ides.includes("cursor"))
|
|
265
|
+
cursorDone = setupCursor(root);
|
|
266
|
+
if (ides.includes("claude"))
|
|
267
|
+
claudeDone = setupClaude(root);
|
|
268
|
+
if (ides.includes("copilot"))
|
|
269
|
+
copilotDone = setupCopilot(root);
|
|
124
270
|
const messages = [];
|
|
125
|
-
if (cursorDone)
|
|
126
|
-
messages.push(chalk.green("✓") + "
|
|
271
|
+
if (cursorDone)
|
|
272
|
+
messages.push(chalk.green("✓") + " Added guardrail rule for Cursor");
|
|
273
|
+
if (claudeDone)
|
|
274
|
+
messages.push(chalk.green("✓") + " Added guardrail rule for Claude Code");
|
|
275
|
+
if (copilotDone)
|
|
276
|
+
messages.push(chalk.green("✓") + " Added guardrail rule for VS Code Copilot");
|
|
277
|
+
if (messages.length === 0) {
|
|
278
|
+
messages.push(chalk.yellow("Guardrail rule already present in all configured IDEs."));
|
|
127
279
|
}
|
|
128
|
-
|
|
129
|
-
|
|
280
|
+
return {
|
|
281
|
+
cursor: cursorDone,
|
|
282
|
+
claude: claudeDone,
|
|
283
|
+
copilot: copilotDone,
|
|
284
|
+
message: messages.join("\n"),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export function runSetupRemove(projectPath = ".", ideFilter, dryRun = false) {
|
|
288
|
+
const root = resolve(projectPath);
|
|
289
|
+
let ides;
|
|
290
|
+
if (ideFilter === "auto") {
|
|
291
|
+
const check = runSetupCheck(root);
|
|
292
|
+
ides = ["cursor", "claude", "copilot"].filter((ide) => check[ide].hasRule);
|
|
293
|
+
}
|
|
294
|
+
else if (ideFilter && ideFilter !== "all") {
|
|
295
|
+
ides = [ideFilter];
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
ides = ["cursor", "claude", "copilot"];
|
|
130
299
|
}
|
|
300
|
+
let cursorDone = false;
|
|
301
|
+
let claudeDone = false;
|
|
302
|
+
let copilotDone = false;
|
|
303
|
+
if (dryRun) {
|
|
304
|
+
if (ides.includes("cursor"))
|
|
305
|
+
cursorDone = wouldRemoveCursor(root);
|
|
306
|
+
if (ides.includes("claude"))
|
|
307
|
+
claudeDone = wouldRemoveClaude(root);
|
|
308
|
+
if (ides.includes("copilot"))
|
|
309
|
+
copilotDone = wouldRemoveCopilot(root);
|
|
310
|
+
const messages = [];
|
|
311
|
+
if (cursorDone)
|
|
312
|
+
messages.push(chalk.gray("[dry-run] Would remove guardrail rule from Cursor"));
|
|
313
|
+
if (claudeDone)
|
|
314
|
+
messages.push(chalk.gray("[dry-run] Would remove guardrail rule from Claude Code"));
|
|
315
|
+
if (copilotDone)
|
|
316
|
+
messages.push(chalk.gray("[dry-run] Would remove guardrail rule from VS Code Copilot"));
|
|
317
|
+
if (messages.length === 0) {
|
|
318
|
+
messages.push(chalk.yellow("Guardrail rule not found in any config files. Nothing to do."));
|
|
319
|
+
}
|
|
320
|
+
return { cursor: cursorDone, claude: claudeDone, copilot: copilotDone, message: messages.join("\n") };
|
|
321
|
+
}
|
|
322
|
+
if (ides.includes("cursor"))
|
|
323
|
+
cursorDone = removeCursor(root);
|
|
324
|
+
if (ides.includes("claude"))
|
|
325
|
+
claudeDone = removeClaude(root);
|
|
326
|
+
if (ides.includes("copilot"))
|
|
327
|
+
copilotDone = removeCopilot(root);
|
|
328
|
+
const messages = [];
|
|
329
|
+
if (cursorDone)
|
|
330
|
+
messages.push(chalk.green("✓") + " Removed guardrail rule from Cursor");
|
|
331
|
+
if (claudeDone)
|
|
332
|
+
messages.push(chalk.green("✓") + " Removed guardrail rule from Claude Code");
|
|
333
|
+
if (copilotDone)
|
|
334
|
+
messages.push(chalk.green("✓") + " Removed guardrail rule from VS Code Copilot");
|
|
131
335
|
if (messages.length === 0) {
|
|
132
336
|
messages.push(chalk.yellow("Guardrail rule not found in any config files."));
|
|
133
337
|
}
|
|
134
338
|
return {
|
|
135
339
|
cursor: cursorDone,
|
|
136
340
|
claude: claudeDone,
|
|
341
|
+
copilot: copilotDone,
|
|
137
342
|
message: messages.join("\n"),
|
|
138
343
|
};
|
|
139
344
|
}
|
package/dist/why.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runWhy(name: string): boolean;
|
package/dist/why.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { TEMPLATES, TEMPLATE_NAMES } from "./templates.js";
|
|
3
|
+
export function runWhy(name) {
|
|
4
|
+
const normalized = name.toLowerCase().replace(/\s+/g, "-");
|
|
5
|
+
const content = TEMPLATES[normalized];
|
|
6
|
+
if (!content) {
|
|
7
|
+
console.error(chalk.red("Unknown guardrail:") + " " + name);
|
|
8
|
+
console.error(chalk.gray("Available: " + TEMPLATE_NAMES.join(", ")));
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
console.log(content);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
package/examples/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# Example Guardrails
|
|
2
2
|
|
|
3
|
-
Reference guardrails you can add with `npx guardrails-ref add <name>`.
|
|
3
|
+
Reference guardrails you can add with `npx guardrails-ref add <name>`. Use `npx guardrails-ref why <name>` to show a guardrail's full content.
|
|
4
4
|
|
|
5
5
|
| Name | What it prevents |
|
|
6
6
|
|------|------------------|
|
|
7
7
|
| `no-plaintext-secrets` | Logging or committing API keys, passwords, tokens |
|
|
8
|
+
| `no-placeholder-credentials` | Fake or placeholder API keys instead of asking for real values |
|
|
9
|
+
| `no-silent-error-handling` | Catching errors without surfacing them to the user |
|
|
10
|
+
| `require-access-control` | Exposing sensitive data or admin actions without role checks |
|
|
8
11
|
| `database-migrations` | Direct schema changes instead of migrations |
|
|
9
12
|
| `no-destructive-commands` | `rm -rf`, `DROP TABLE`, `TRUNCATE` without approval |
|
|
10
13
|
| `no-new-deps-without-approval` | New packages without human confirmation |
|
|
@@ -13,8 +16,13 @@ Reference guardrails you can add with `npx guardrails-ref add <name>`.
|
|
|
13
16
|
| `rate-limiting` | Runaway tool calls and API loops |
|
|
14
17
|
| `no-console-in-production` | `console.log` in production code |
|
|
15
18
|
| `require-tests` | Merging code without tests |
|
|
19
|
+
| `prefer-existing-code` | Reimplementing when existing code or helpers exist |
|
|
16
20
|
| `no-inline-styles` | Inline `style=` in HTML/JSX |
|
|
17
21
|
| `no-raw-sql` | Raw SQL without parameterization |
|
|
18
22
|
| `no-magic-numbers` | Unexplained numeric literals |
|
|
23
|
+
| `no-modifying-git-history` | `git push --force`, destructive rebase without approval |
|
|
24
|
+
| `no-deprecated-apis` | Suggesting deprecated or obsolete APIs |
|
|
25
|
+
| `no-unsafe-env-assumptions` | Assuming env vars exist without validation |
|
|
26
|
+
| `no-hardcoded-user-facing-strings` | Hardcoded labels, messages, errors in UI |
|
|
19
27
|
|
|
20
28
|
Each example lives in its own directory with a `GUARDRAIL.md` file. See `pre-commit/README.md` for pre-commit, Husky, or npm script setup. See the [specification](../spec/specification.md) for the format.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-deprecated-apis
|
|
3
|
+
description: Never suggest deprecated or obsolete APIs. Check package docs and current best practices before recommending patterns. Apply when adding dependencies, using libraries, or implementing features.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: warning
|
|
6
|
+
triggers:
|
|
7
|
+
- "Adding dependencies"
|
|
8
|
+
- "Using library"
|
|
9
|
+
- "API usage"
|
|
10
|
+
- "Implementing feature"
|
|
11
|
+
- "Package documentation"
|
|
12
|
+
- "Deprecated"
|
|
13
|
+
license: MIT
|
|
14
|
+
metadata:
|
|
15
|
+
author: agent-guardrails
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# No Deprecated APIs
|
|
20
|
+
|
|
21
|
+
## Trigger
|
|
22
|
+
Adding dependencies, using libraries, implementing features, or recommending APIs or patterns.
|
|
23
|
+
|
|
24
|
+
## Instruction
|
|
25
|
+
- Never suggest deprecated or obsolete APIs; check the package's current documentation first
|
|
26
|
+
- When a pattern might be deprecated: verify against the latest docs before recommending
|
|
27
|
+
- Prefer current, maintained approaches over legacy patterns (e.g. fetch over XMLHttpRequest, modern Node APIs over legacy callbacks)
|
|
28
|
+
- If the project uses a specific version: ensure recommendations match that version's API
|
|
29
|
+
- When unsure about deprecation status: note the uncertainty and suggest verifying in docs
|
|
30
|
+
|
|
31
|
+
## Reason
|
|
32
|
+
Agents often suggest outdated patterns from training data. Deprecated APIs may be removed in future versions and can cause technical debt. Current docs are the source of truth.
|
|
33
|
+
|
|
34
|
+
## Provenance
|
|
35
|
+
Manual addition, agent-guardrails reference.
|
|
@@ -30,3 +30,6 @@ Adding API calls, integrating external services, or referencing URLs (base URLs,
|
|
|
30
30
|
|
|
31
31
|
## Reason
|
|
32
32
|
Hardcoded URLs break across environments (dev/staging/prod), leak internal endpoints, and block easy configuration. Environment-based config is standard practice.
|
|
33
|
+
|
|
34
|
+
## Provenance
|
|
35
|
+
Manual addition, agent-guardrails reference.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-hardcoded-user-facing-strings
|
|
3
|
+
description: Never hardcode user-facing text (labels, messages, errors) in components or views. Use i18n keys, translation files, or shared constants. Apply when adding UI text, error messages, or form labels.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: warning
|
|
6
|
+
triggers:
|
|
7
|
+
- "Adding UI text"
|
|
8
|
+
- "Error messages"
|
|
9
|
+
- "Form labels"
|
|
10
|
+
- "User-facing strings"
|
|
11
|
+
- "Button text"
|
|
12
|
+
- "Placeholders"
|
|
13
|
+
license: MIT
|
|
14
|
+
metadata:
|
|
15
|
+
author: agent-guardrails
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# No Hardcoded User-Facing Strings
|
|
20
|
+
|
|
21
|
+
## Trigger
|
|
22
|
+
Adding user-facing text: labels, buttons, error messages, placeholders, tooltips, or any string displayed to the user in the UI.
|
|
23
|
+
|
|
24
|
+
## Instruction
|
|
25
|
+
- Never hardcode user-facing strings directly in components or views
|
|
26
|
+
- Use i18n/translation keys (e.g. t('errors.network'), i18n.t('submit')) when the project has i18n
|
|
27
|
+
- If no i18n: use shared constants or a strings module (e.g. STRINGS.ERROR_NETWORK) for reuse and consistency
|
|
28
|
+
- For error messages: centralize in a shared location so they can be updated and translated
|
|
29
|
+
- Exception: very small projects or prototypes may use inline strings; prefer externalizing as the project grows
|
|
30
|
+
|
|
31
|
+
## Reason
|
|
32
|
+
Hardcoded strings scatter copy across the codebase, block localization, and make consistent messaging harder. Centralized strings enable i18n and easier content updates.
|
|
33
|
+
|
|
34
|
+
## Provenance
|
|
35
|
+
Manual addition, agent-guardrails reference.
|
|
@@ -24,7 +24,9 @@ Adding styles to HTML, JSX, or component markup (e.g. React, Vue, Svelte).
|
|
|
24
24
|
- Never add inline `style={{ ... }}` or `style="..."` attributes
|
|
25
25
|
- Use CSS classes, CSS modules, Tailwind, or design system tokens instead
|
|
26
26
|
- For dynamic values: use CSS variables or data attributes with stylesheets
|
|
27
|
-
- Inline styles bypass design systems, hurt maintainability, and complicate theming
|
|
28
27
|
|
|
29
28
|
## Reason
|
|
30
|
-
Inline styles scatter styling logic, prevent reuse, and make design consistency harder. Centralized styles (CSS, design tokens) enable theming and maintainability.
|
|
29
|
+
Inline styles scatter styling logic, prevent reuse, and make design consistency harder. They bypass design systems, hurt maintainability, and complicate theming. Centralized styles (CSS, design tokens) enable theming and maintainability.
|
|
30
|
+
|
|
31
|
+
## Provenance
|
|
32
|
+
Manual addition, agent-guardrails reference.
|
|
@@ -29,3 +29,6 @@ Adding numeric literals for timeouts, limits, buffer sizes, retry counts, thresh
|
|
|
29
29
|
|
|
30
30
|
## Reason
|
|
31
31
|
Magic numbers make code hard to understand and change. Named constants document intent and centralize configuration.
|
|
32
|
+
|
|
33
|
+
## Provenance
|
|
34
|
+
Manual addition, agent-guardrails reference.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-modifying-git-history
|
|
3
|
+
description: Never run git push --force, destructive rebase, or history-rewriting commands without explicit human approval. Apply when suggesting git commands that modify shared history.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: critical
|
|
6
|
+
triggers:
|
|
7
|
+
- "git push"
|
|
8
|
+
- "git rebase"
|
|
9
|
+
- "Force push"
|
|
10
|
+
- "Rewriting history"
|
|
11
|
+
- "git reset"
|
|
12
|
+
- "Amending commits"
|
|
13
|
+
license: MIT
|
|
14
|
+
metadata:
|
|
15
|
+
author: agent-guardrails
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# No Modifying Git History
|
|
20
|
+
|
|
21
|
+
## Trigger
|
|
22
|
+
Suggesting or running git commands that modify history: force push, rebase that rewrites commits, reset, or amending pushed commits.
|
|
23
|
+
|
|
24
|
+
## Instruction
|
|
25
|
+
- Never run `git push --force`, `git push -f`, or `git push --force-with-lease` without explicit human approval
|
|
26
|
+
- Never run `git rebase` on branches that may have been pushed or shared without approval
|
|
27
|
+
- Never run `git reset --hard` to undo commits that others may have pulled
|
|
28
|
+
- When the user asks to "fix" history or "undo" pushed commits: explain the impact and wait for confirmation
|
|
29
|
+
- For shared branches (main, develop): always warn that force push can overwrite others' work
|
|
30
|
+
- Prefer `git revert` over history-rewriting when undoing commits on shared branches
|
|
31
|
+
|
|
32
|
+
## Reason
|
|
33
|
+
Agents have suggested force push and rebase that overwrote shared history, causing lost work and broken clones. History-rewriting on shared branches requires explicit human confirmation.
|
|
34
|
+
|
|
35
|
+
## Provenance
|
|
36
|
+
Manual addition, common failure mode in autonomous coding agents.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-placeholder-credentials
|
|
3
|
+
description: Never use fake or placeholder API keys, env vars, or credentials. Ask for real values instead of inventing them. Apply when configuring APIs, integrations, or environment variables.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: critical
|
|
6
|
+
triggers:
|
|
7
|
+
- "API integration"
|
|
8
|
+
- "Environment variables"
|
|
9
|
+
- "API key"
|
|
10
|
+
- "Configuration"
|
|
11
|
+
- "Credentials"
|
|
12
|
+
- "Placeholder"
|
|
13
|
+
- ".env"
|
|
14
|
+
license: MIT
|
|
15
|
+
metadata:
|
|
16
|
+
author: agent-guardrails
|
|
17
|
+
version: "1.0"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# No Placeholder Credentials
|
|
21
|
+
|
|
22
|
+
## Trigger
|
|
23
|
+
Configuring API integrations, environment variables, credentials, or any code that requires API keys, tokens, or secrets.
|
|
24
|
+
|
|
25
|
+
## Instruction
|
|
26
|
+
- Never use fake, placeholder, or invented credentials (e.g. `sk_test_placeholder`, `your-api-key-here`, `xxx`)
|
|
27
|
+
- Never assume env vars exist without validation; ask the user to provide or verify values
|
|
28
|
+
- When a real value is required: stop and ask the user to provide it, or add it to .env
|
|
29
|
+
- Document required env vars in .env.example with placeholders for format only (e.g. `STRIPE_KEY=sk_test_...`), not for actual use
|
|
30
|
+
- For local development: use real test keys when available (e.g. Stripe test mode, sandbox APIs), or explicitly ask the user
|
|
31
|
+
|
|
32
|
+
## Reason
|
|
33
|
+
Agents hallucinate placeholder values that appear to work but fail in production. Users assume the agent configured correctly when it did not. Always ask for real values when they are required.
|
|
34
|
+
|
|
35
|
+
## Provenance
|
|
36
|
+
Manual addition, common failure mode in autonomous coding agents (DAPLab).
|
|
@@ -30,3 +30,6 @@ Writing database queries, SQL statements, or any code that executes SQL against
|
|
|
30
30
|
|
|
31
31
|
## Reason
|
|
32
32
|
Raw SQL with string concatenation leads to SQL injection. Parameterized queries prevent injection and are required for security.
|
|
33
|
+
|
|
34
|
+
## Provenance
|
|
35
|
+
Manual addition, SQL injection prevention.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-silent-error-handling
|
|
3
|
+
description: Never catch errors without surfacing them to the user. Avoid hiding failures in console or generic messages. Apply when adding try/catch, error handling, or API error responses.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: critical
|
|
6
|
+
triggers:
|
|
7
|
+
- "Error handling"
|
|
8
|
+
- "try/catch"
|
|
9
|
+
- "Catching exceptions"
|
|
10
|
+
- "API errors"
|
|
11
|
+
- "Failure handling"
|
|
12
|
+
- "User feedback"
|
|
13
|
+
license: MIT
|
|
14
|
+
metadata:
|
|
15
|
+
author: agent-guardrails
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# No Silent Error Handling
|
|
20
|
+
|
|
21
|
+
## Trigger
|
|
22
|
+
Adding error handling, try/catch blocks, API error responses, or any code that catches or handles failures.
|
|
23
|
+
|
|
24
|
+
## Instruction
|
|
25
|
+
- Never catch errors without surfacing them to the user or logging them appropriately
|
|
26
|
+
- Avoid empty catch blocks or catch blocks that only log to console without user-visible feedback
|
|
27
|
+
- For user-facing flows: show clear error messages, not generic "Something went wrong" or "Generating…"
|
|
28
|
+
- For APIs: return meaningful error responses (status codes, error payloads) instead of swallowing failures
|
|
29
|
+
- When retrying: surface the failure if retries are exhausted
|
|
30
|
+
- Prefer rethrowing or propagating errors when you cannot handle them meaningfully
|
|
31
|
+
|
|
32
|
+
## Reason
|
|
33
|
+
AI agents often suppress errors to "keep going," which hides real failures from users. Silent failures lead to incorrect assumptions, lost data, and debugging nightmares. Users must know when something failed.
|
|
34
|
+
|
|
35
|
+
## Provenance
|
|
36
|
+
Manual addition, common failure mode in autonomous coding agents (DAPLab).
|
|
@@ -32,3 +32,6 @@ Suggesting or running shell commands that require elevated privileges (sudo, su,
|
|
|
32
32
|
|
|
33
33
|
## Reason
|
|
34
34
|
Agents have run sudo commands that overwrote system configs or installed conflicting packages. Elevated privileges require explicit human confirmation.
|
|
35
|
+
|
|
36
|
+
## Provenance
|
|
37
|
+
Manual addition, common failure mode in autonomous coding agents.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-unsafe-env-assumptions
|
|
3
|
+
description: Never assume environment variables exist without validation. Validate required env vars at startup and fail fast with clear errors. Apply when using process.env, os.environ, or config that depends on env.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: critical
|
|
6
|
+
triggers:
|
|
7
|
+
- "Environment variables"
|
|
8
|
+
- "process.env"
|
|
9
|
+
- "os.environ"
|
|
10
|
+
- "Configuration"
|
|
11
|
+
- "Startup"
|
|
12
|
+
- "Config loading"
|
|
13
|
+
license: MIT
|
|
14
|
+
metadata:
|
|
15
|
+
author: agent-guardrails
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# No Unsafe Env Assumptions
|
|
20
|
+
|
|
21
|
+
## Trigger
|
|
22
|
+
Using environment variables, loading configuration from env, or any code that reads `process.env`, `os.environ`, or similar at runtime.
|
|
23
|
+
|
|
24
|
+
## Instruction
|
|
25
|
+
- Never assume required environment variables exist; validate at startup or first use
|
|
26
|
+
- For required vars: fail fast with a clear error message (e.g. "Missing required env: API_KEY. Add it to .env or set it before starting.")
|
|
27
|
+
- Use a config validation step (e.g. check all required vars before the app starts) instead of failing later with cryptic "undefined" errors
|
|
28
|
+
- Document required env vars in .env.example or README
|
|
29
|
+
- For optional vars: provide sensible defaults or handle absence explicitly
|
|
30
|
+
|
|
31
|
+
## Reason
|
|
32
|
+
Code that assumes env vars exist fails at runtime with unclear errors (e.g. "Cannot read property of undefined"). Validating at startup surfaces configuration problems immediately with actionable messages.
|
|
33
|
+
|
|
34
|
+
## Provenance
|
|
35
|
+
Manual addition, complements no-placeholder-credentials (don't invent values; validate when using).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prefer-existing-code
|
|
3
|
+
description: Prefer existing code and shared helpers over reimplementing. Search the codebase before adding new logic. Apply when adding features, refactoring, or implementing functionality.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: warning
|
|
6
|
+
triggers:
|
|
7
|
+
- "Adding features"
|
|
8
|
+
- "Implementing"
|
|
9
|
+
- "Refactoring"
|
|
10
|
+
- "New function"
|
|
11
|
+
- "Helper"
|
|
12
|
+
- "Utility"
|
|
13
|
+
- "Duplicate"
|
|
14
|
+
license: MIT
|
|
15
|
+
metadata:
|
|
16
|
+
author: agent-guardrails
|
|
17
|
+
version: "1.0"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Prefer Existing Code
|
|
21
|
+
|
|
22
|
+
## Trigger
|
|
23
|
+
Adding features, implementing functionality, refactoring, or writing new helpers and utilities.
|
|
24
|
+
|
|
25
|
+
## Instruction
|
|
26
|
+
- Before implementing: search the codebase for existing functions, utilities, or patterns that already do what you need
|
|
27
|
+
- Prefer reusing existing code over copy-pasting or reimplementing
|
|
28
|
+
- When adding shared logic: extract to a common module or helper instead of duplicating
|
|
29
|
+
- If similar code exists elsewhere: refactor to use a shared implementation
|
|
30
|
+
- When the user asks for something that may already exist: check first and suggest the existing solution if applicable
|
|
31
|
+
|
|
32
|
+
## Reason
|
|
33
|
+
Agents often reimplement logic that already exists, leading to duplication, inconsistency, and maintenance burden. Reusing existing code reduces bugs and keeps the codebase coherent.
|
|
34
|
+
|
|
35
|
+
## Provenance
|
|
36
|
+
Manual addition, common failure mode in autonomous coding agents (DAPLab).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: require-access-control
|
|
3
|
+
description: When handling sensitive data or admin actions, enforce role or permission checks. Never assume the current user has access. Apply when adding auth, admin features, or user-specific data.
|
|
4
|
+
scope: project
|
|
5
|
+
severity: critical
|
|
6
|
+
triggers:
|
|
7
|
+
- "Admin features"
|
|
8
|
+
- "User data"
|
|
9
|
+
- "Sensitive data"
|
|
10
|
+
- "Authorization"
|
|
11
|
+
- "Permissions"
|
|
12
|
+
- "Role check"
|
|
13
|
+
- "Private data"
|
|
14
|
+
license: MIT
|
|
15
|
+
metadata:
|
|
16
|
+
author: agent-guardrails
|
|
17
|
+
version: "1.0"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Require Access Control
|
|
21
|
+
|
|
22
|
+
## Trigger
|
|
23
|
+
Adding admin features, handling user-specific or sensitive data, implementing authorization, or any code that differentiates between user roles or permissions.
|
|
24
|
+
|
|
25
|
+
## Instruction
|
|
26
|
+
- Never assume the current user has access to sensitive data or admin actions
|
|
27
|
+
- Add explicit role or permission checks before exposing or modifying sensitive data
|
|
28
|
+
- For admin-only actions: verify the user has admin role or equivalent permission
|
|
29
|
+
- For user-specific data: verify the requester is the owner or has explicit access
|
|
30
|
+
- When adding new endpoints or UI: consider who should access them and add checks accordingly
|
|
31
|
+
- Avoid hardcoding "admin" or "user" assumptions; use the project's auth/permission system
|
|
32
|
+
|
|
33
|
+
## Reason
|
|
34
|
+
Agents sometimes add features without access checks, exposing admin actions or private data to regular users. Access control must be explicit and enforced.
|
|
35
|
+
|
|
36
|
+
## Provenance
|
|
37
|
+
Manual addition, security best practice.
|
|
@@ -29,3 +29,6 @@ Adding features, fixing bugs, modifying production code paths, or refactoring ex
|
|
|
29
29
|
|
|
30
30
|
## Reason
|
|
31
31
|
Untested code leads to regressions and makes refactoring risky. Tests document expected behavior and catch breakage before production.
|
|
32
|
+
|
|
33
|
+
## Provenance
|
|
34
|
+
Manual addition, agent-guardrails reference.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardrails-ref",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Validate and manage Agent Guardrails (GUARDRAIL.md) — init, add, remove, setup, validate, check, upgrade, list",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Validate and manage Agent Guardrails (GUARDRAIL.md) — init, add, remove, setup, validate, check, upgrade, list, why",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/validate.js",
|
|
7
7
|
"bin": {
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"guardrails",
|
|
22
22
|
"ai",
|
|
23
23
|
"cursor",
|
|
24
|
-
"claude"
|
|
24
|
+
"claude",
|
|
25
|
+
"copilot",
|
|
26
|
+
"vscode"
|
|
25
27
|
],
|
|
26
28
|
"license": "MIT",
|
|
27
29
|
"repository": {
|