@securityreviewai/securityreview-kit 0.1.45 → 0.1.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "Bootstrap security-review-mcp for AI IDEs and CLI tools",
5
5
  "author": "Debarshi Das <debarshi.das@we45.com>",
6
6
  "license": "UNLICENSED",
@@ -2,6 +2,8 @@ import { join } from 'node:path';
2
2
  import { readJson, writeJson } from '../../utils/fs-helpers.js';
3
3
  import { MCP_SERVER_NAME, MCP_SERVER_PACKAGE } from '../../utils/constants.js';
4
4
 
5
+ const CLAUDE_MCP_PERMISSION = `mcp__${MCP_SERVER_NAME}`;
6
+
5
7
  function getClaudeSessionStartHooks() {
6
8
  const prompt = [
7
9
  'MANDATORY SECURITY GATE (Claude Code Session Policy)',
@@ -59,6 +61,16 @@ export function generate(cwd, envVars) {
59
61
  existing.enabledMcpjsonServers = [...enabledServers, MCP_SERVER_NAME];
60
62
  }
61
63
 
64
+ if (!existing.permissions || typeof existing.permissions !== 'object' || Array.isArray(existing.permissions)) {
65
+ existing.permissions = {};
66
+ }
67
+ const allowedTools = Array.isArray(existing.permissions.allow)
68
+ ? existing.permissions.allow.filter((entry) => typeof entry === 'string' && entry.trim())
69
+ : [];
70
+ if (!allowedTools.includes(CLAUDE_MCP_PERMISSION)) {
71
+ existing.permissions.allow = [...allowedTools, CLAUDE_MCP_PERMISSION];
72
+ }
73
+
62
74
  const existingSessionStart = Array.isArray(existing.SessionStart) ? existing.SessionStart : [];
63
75
  const marker = 'MANDATORY SECURITY GATE (Claude Code Session Policy)';
64
76
  const ours = getClaudeSessionStartHooks();
@@ -17,6 +17,7 @@ test('Claude MCP generator writes .mcp.json, enables the project MCP server, and
17
17
  const settings = JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8'));
18
18
  assert.equal(mcpConfig.mcpServers['security-review-mcp'].command, 'npx');
19
19
  assert.deepEqual(settings.enabledMcpjsonServers, ['security-review-mcp']);
20
+ assert.deepEqual(settings.permissions.allow, ['mcp__security-review-mcp']);
20
21
  assert.equal(Array.isArray(settings.SessionStart), true);
21
22
  assert.match(settings.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
22
23
  assert.match(settings.SessionStart[0].hooks[0].prompt, /vibereview\//);
@@ -39,6 +40,9 @@ test('Claude MCP generator preserves existing SessionStart hooks', () => {
39
40
  hooks: [{ type: 'prompt', prompt: 'Existing hook' }],
40
41
  },
41
42
  ],
43
+ permissions: {
44
+ allow: ['Bash(npm run test:*)'],
45
+ },
42
46
  },
43
47
  null,
44
48
  2,
@@ -56,4 +60,5 @@ test('Claude MCP generator preserves existing SessionStart hooks', () => {
56
60
  assert.match(config.SessionStart[0].hooks[0].prompt, /Existing hook/);
57
61
  assert.match(config.SessionStart[1].hooks[0].prompt, /MANDATORY SECURITY GATE/);
58
62
  assert.deepEqual(config.enabledMcpjsonServers, ['security-review-mcp']);
63
+ assert.deepEqual(config.permissions.allow, ['Bash(npm run test:*)', 'mcp__security-review-mcp']);
59
64
  });
@@ -38,6 +38,7 @@ export function generate(cwd, envVars) {
38
38
  [mcp_servers.${MCP_SERVER_NAME}]
39
39
  command = "npx"
40
40
  args = ["-y", "${MCP_SERVER_PACKAGE}@latest"]
41
+ default_tools_approval_mode = "approve"
41
42
 
42
43
  [mcp_servers.${MCP_SERVER_NAME}.env]
43
44
  SECURITY_REVIEW_API_URL = "${envVars.apiUrl}"
@@ -16,6 +16,7 @@ test('Codex MCP generator enables hooks and writes MCP server block', () => {
16
16
  const config = readFileSync(join(cwd, '.codex/config.toml'), 'utf8');
17
17
  assert.match(config, /\[features\]\ncodex_hooks = true/);
18
18
  assert.match(config, /\[mcp_servers\.security-review-mcp\]/);
19
+ assert.match(config, /default_tools_approval_mode = "approve"/);
19
20
  assert.match(config, /SECURITY_REVIEW_API_URL = "https:\/\/example\.test"/);
20
21
  });
21
22
 
@@ -1,6 +1,7 @@
1
1
  import { join } from 'node:path';
2
2
  import { readJson, writeJson } from '../../utils/fs-helpers.js';
3
3
  import { MCP_SERVER_NAME, MCP_SERVER_PACKAGE } from '../../utils/constants.js';
4
+ import { mergeCursorCliMcpAllowlist } from '../../utils/cursor-cli-permissions.js';
4
5
 
5
6
  /**
6
7
  * Generate Cursor MCP config at .cursor/mcp.json
@@ -23,5 +24,6 @@ export function generate(cwd, envVars) {
23
24
  };
24
25
 
25
26
  writeJson(filePath, existing);
27
+ mergeCursorCliMcpAllowlist(cwd);
26
28
  return filePath;
27
29
  }
@@ -0,0 +1,50 @@
1
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { test } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { generate } from './cursor.js';
7
+
8
+ test('Cursor MCP generator writes server config and CLI MCP allow rule', () => {
9
+ const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-cursor-mcp-'));
10
+
11
+ generate(cwd, {
12
+ apiUrl: 'https://example.test',
13
+ apiToken: 'secret-token',
14
+ });
15
+
16
+ const mcpConfig = JSON.parse(readFileSync(join(cwd, '.cursor', 'mcp.json'), 'utf8'));
17
+ const cliConfig = JSON.parse(readFileSync(join(cwd, '.cursor', 'cli.json'), 'utf8'));
18
+ assert.equal(mcpConfig.mcpServers['security-review-mcp'].command, 'npx');
19
+ assert.deepEqual(cliConfig.permissions.allow, ['Mcp(security-review-mcp:*)']);
20
+ assert.deepEqual(cliConfig.permissions.deny, []);
21
+ });
22
+
23
+ test('Cursor MCP generator preserves existing CLI permissions', () => {
24
+ const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-cursor-mcp-merge-'));
25
+ const cliPath = join(cwd, '.cursor', 'cli.json');
26
+ mkdirSync(join(cwd, '.cursor'), { recursive: true });
27
+ writeFileSync(
28
+ cliPath,
29
+ JSON.stringify(
30
+ {
31
+ permissions: {
32
+ allow: ['Shell(git)'],
33
+ deny: ['Shell(rm)'],
34
+ },
35
+ },
36
+ null,
37
+ 2,
38
+ ),
39
+ 'utf8',
40
+ );
41
+
42
+ generate(cwd, {
43
+ apiUrl: 'https://example.test',
44
+ apiToken: 'secret-token',
45
+ });
46
+
47
+ const cliConfig = JSON.parse(readFileSync(cliPath, 'utf8'));
48
+ assert.deepEqual(cliConfig.permissions.allow, ['Shell(git)', 'Mcp(security-review-mcp:*)']);
49
+ assert.deepEqual(cliConfig.permissions.deny, ['Shell(rm)']);
50
+ });
@@ -2,6 +2,19 @@ import { join } from 'node:path';
2
2
  import { readJson, writeJson } from '../../utils/fs-helpers.js';
3
3
  import { MCP_SERVER_NAME, MCP_SERVER_PACKAGE } from '../../utils/constants.js';
4
4
 
5
+ function getSandboxAllowedDomains(apiUrl) {
6
+ const domains = ['registry.npmjs.org'];
7
+ try {
8
+ const hostname = new URL(apiUrl).hostname;
9
+ if (hostname) {
10
+ domains.push(hostname);
11
+ }
12
+ } catch {
13
+ // Keep the documented sandbox shape even if the URL is supplied later through another workflow.
14
+ }
15
+ return [...new Set(domains)];
16
+ }
17
+
5
18
  /**
6
19
  * Generate VS Code Copilot MCP config at .vscode/mcp.json
7
20
  * Uses the VS Code input variable pattern for secure credential prompting.
@@ -22,6 +35,12 @@ export function generate(cwd, envVars) {
22
35
  SECURITY_REVIEW_API_URL: envVars.apiUrl,
23
36
  SECURITY_REVIEW_API_TOKEN: envVars.apiToken,
24
37
  },
38
+ sandboxEnabled: true,
39
+ sandbox: {
40
+ network: {
41
+ allowedDomains: getSandboxAllowedDomains(envVars.apiUrl),
42
+ },
43
+ },
25
44
  };
26
45
 
27
46
  writeJson(filePath, existing);
@@ -0,0 +1,21 @@
1
+ import { mkdtempSync, readFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { test } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { generate } from './vscode.js';
7
+
8
+ test('VS Code MCP generator enables sandbox auto-approval for the SRAI server', () => {
9
+ const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-vscode-mcp-'));
10
+
11
+ generate(cwd, {
12
+ apiUrl: 'https://api.example.test/v1',
13
+ apiToken: 'secret-token',
14
+ });
15
+
16
+ const config = JSON.parse(readFileSync(join(cwd, '.vscode', 'mcp.json'), 'utf8'));
17
+ const server = config.servers['security-review-mcp'];
18
+ assert.equal(server.type, 'stdio');
19
+ assert.equal(server.sandboxEnabled, true);
20
+ assert.deepEqual(server.sandbox.network.allowedDomains, ['registry.npmjs.org', 'api.example.test']);
21
+ });
@@ -96,7 +96,7 @@ export function getGuardrailsRuleContent(options = {}) {
96
96
  }
97
97
 
98
98
  /**
99
- * Guardrails init profile command (repo scan + profile.json + SRAI upload).
99
+ * Guardrails init profile command (repo scan + .guardrails/profile.json + SRAI upload).
100
100
  */
101
101
  export function getGuardrailsInitProfileContent(options = {}) {
102
102
  return readTemplate('guardrails-init-profile.md', options);
@@ -3,11 +3,11 @@ import { join } from 'node:path';
3
3
  import {
4
4
  GUARDRAILS_PROFILER_SKILL_REL_DIR,
5
5
  GUARDRAILS_SELECTION_SKILL_REL_DIR,
6
- MCP_SERVER_NAME,
7
6
  THREAT_MODELLING_SKILL_REL_DIR,
8
7
  VIBEREVIEW_SYNC_SKILL_REL_DIR,
9
8
  } from '../../utils/constants.js';
10
- import { readJson, writeJson, writeText } from '../../utils/fs-helpers.js';
9
+ import { writeText } from '../../utils/fs-helpers.js';
10
+ import { mergeCursorCliMcpAllowlist } from '../../utils/cursor-cli-permissions.js';
11
11
  import {
12
12
  getRuleContent,
13
13
  getProfileCommandContent,
@@ -46,34 +46,6 @@ function removeGeneratedText(filePath) {
46
46
  return null;
47
47
  }
48
48
 
49
- /**
50
- * Merge `.cursor/cli.json` so Cursor CLI / Agent auto-allows security-review-mcp tools (no MCP approval prompts).
51
- * @see https://cursor.com/docs/cli/reference/permissions
52
- */
53
- function mergeCursorCliMcpAllowlist(cwd) {
54
- const cliPath = join(cwd, '.cursor', 'cli.json');
55
- const existed = existsSync(cliPath);
56
- const existing = readJson(cliPath) || {};
57
- if (!existing.permissions || typeof existing.permissions !== 'object') {
58
- existing.permissions = {};
59
- }
60
- if (!Array.isArray(existing.permissions.allow)) {
61
- existing.permissions.allow = [];
62
- }
63
- const token = `Mcp(${MCP_SERVER_NAME}:*)`;
64
- if (!existing.permissions.allow.includes(token)) {
65
- existing.permissions.allow.push(token);
66
- }
67
- // Cursor CLI schema requires permissions.deny to be a string[] (may be empty).
68
- // @see https://cursor.com/docs/cli/reference/configuration
69
- if (!Array.isArray(existing.permissions.deny)) {
70
- existing.permissions.deny = [];
71
- }
72
- writeJson(cliPath, existing);
73
- const action = existed ? 'updated' : 'created';
74
- return { filePath: cliPath, action };
75
- }
76
-
77
49
  /**
78
50
  * Generate Cursor workspace rules at .cursor/rules/*.mdc
79
51
  * Cursor uses .mdc format with YAML front matter.
@@ -135,7 +107,9 @@ export function generate(cwd, options = {}) {
135
107
  const hooksAction = existsSync(hooksPath) ? 'updated' : 'created';
136
108
  writeText(hooksPath, hooksContent);
137
109
 
138
- const cliPermissions = mergeCursorCliMcpAllowlist(cwd);
110
+ const cursorCliPath = join(cwd, '.cursor', 'cli.json');
111
+ const cliPermissionsExisted = existsSync(cursorCliPath);
112
+ mergeCursorCliMcpAllowlist(cwd);
139
113
 
140
114
  return [
141
115
  { ...baseRule, kind: 'rule' },
@@ -145,7 +119,7 @@ export function generate(cwd, options = {}) {
145
119
  { filePath: skillPath, action: skillAction, kind: 'skill' },
146
120
  { filePath: vibereviewSyncSkillPath, action: vibereviewSyncSkillAction, kind: 'skill' },
147
121
  { filePath: hooksPath, action: hooksAction, kind: 'hooks' },
148
- { ...cliPermissions, kind: 'config' },
122
+ { filePath: cursorCliPath, action: cliPermissionsExisted ? 'updated' : 'created', kind: 'config' },
149
123
  ...(deletedLegacyRule ? [{ ...deletedLegacyRule, kind: 'cleanup' }] : []),
150
124
  ...(deletedLegacyCommand ? [{ ...deletedLegacyCommand, kind: 'cleanup' }] : []),
151
125
  ...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: guardrails-init-profile
3
- description: Run the Security Review Kit guardrails profiler — scan the repo, write profile.json, push profile and default guardrail pack to SecurityReview.ai via MCP.
3
+ description: Run the Security Review Kit guardrails profiler — scan the repo, write `.guardrails/profile.json`, push the profile and default guardrail pack to SecurityReview.ai via MCP.
4
4
  ---
5
5
 
6
6
  # Guardrails init profile
@@ -12,7 +12,7 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
12
12
  **You must:**
13
13
 
14
14
  1. Read `{{GUARDRAILS_SKILL_DIR}}/SKILL.md` and follow every step (use the signal registry at `{{GUARDRAILS_SKILL_DIR}}/references/signal-registry.json`).
15
- 2. Write `.guardrails/profile.json` and **`profile.json`** at the project root as specified.
15
+ 2. Write `.guardrails/profile.json` as specified.
16
16
  3. Call **`update_vibe_profile`** and **`write_default_pack`** on `security-review-mcp` after resolving `project_id` for `<SRAI_PROJECT_NAME>`.
17
17
 
18
18
  Do not skip MCP upload when credentials and MCP are available.
@@ -5,7 +5,7 @@ description: Profile a codebase to detect its technology stack and generate a gu
5
5
 
6
6
  # Guardrails Profiler
7
7
 
8
- Profile a codebase's technology stack, write `.guardrails/profile.json`, write a combined **`profile.json` in the project root**, and upload to SRAI using `update_vibe_profile` and `write_default_pack`.
8
+ Profile a codebase's technology stack, write `.guardrails/profile.json`, and upload the detected profile and default guardrail pack to SRAI using `update_vibe_profile` and `write_default_pack`.
9
9
 
10
10
  Configured SRAI project name: `<SRAI_PROJECT_NAME>`
11
11
 
@@ -14,7 +14,6 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
14
14
  - **This skill & signal registry (read-only):** `<GUARDRAILS_SKILL_DIR>/` — e.g. `.cursor/skills/guardrails-profiler`, `.github/skills/guardrails-profiler`, `.claude/skills/guardrails-profiler`, or `.codex/skills/guardrails-profiler` for Codex depending on where this file was installed.
15
15
  - **Signal registry file:** `<GUARDRAILS_SKILL_DIR>/references/signal-registry.json`
16
16
  - **Local guardrails file:** `.guardrails/profile.json`
17
- - **Combined manifest (project root):** `profile.json` — includes guardrails profile, vibe profile fields for MCP, and default pack payload
18
17
 
19
18
  ## When This Runs
20
19
 
@@ -102,40 +101,19 @@ Do **not** invent packs or signals; if the repo is empty, use universal baseline
102
101
 
103
102
  Create `.guardrails/` if needed and write the profile file.
104
103
 
105
- ### Step 6: Build `profile.json` (project root)
106
-
107
- Write **`profile.json`** at the project root with **only** these parts:
108
-
109
- ```json
110
- {
111
- "schema_version": "2.0",
112
- "srai_project_name": "<SRAI_PROJECT_NAME>",
113
- "guardrails_profile": {},
114
- "default_guardrail_pack": {
115
- "guardrail_packs": [],
116
- "pack_count": 0
117
- }
118
- }
119
- ```
120
-
121
- - Populate **`guardrails_profile`** with the **same object** written to `.guardrails/profile.json` (detection summary, packs, etc.).
122
- - Populate **`default_guardrail_pack`** with the deduplicated `guardrail_packs` list (same ids as in `guardrails_profile`) and `pack_count`.
123
-
124
- **Do not** add a separate `vibe_profile` block and do **not** populate narrative fields such as long `description`, `architecture_notes`, `tech_categories`, `user_groups`, `compliance_requirements`, or `language_stacks`. The server-facing “vibe” update is driven **only** from the technical **`guardrails_profile`** plus the default pack (see Step 7).
125
-
126
- ### Step 7: Upload to SecurityReview.ai (security-review-mcp)
104
+ ### Step 6: Upload to SecurityReview.ai (security-review-mcp)
127
105
 
128
106
  1. Resolve `project_id`: `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`. If missing, follow existing kit rules (`list_projects`, `create_project`).
129
107
 
130
- 2. Call **`update_vibe_profile`** with `project_id` and arguments mapped **only** from **`profile.json.guardrails_profile`** (and any required `project_id` fields) per the MCP tool’s documented schema. Treat the guardrails detection object as the profile payload — **not** a separate prose vibe document.
108
+ 2. Call **`update_vibe_profile`** with `project_id` and arguments mapped directly from the `.guardrails/profile.json` object (and any required `project_id` fields) per the MCP tool’s documented schema. Treat the guardrails detection object as the profile payload — **not** a separate prose vibe document.
131
109
 
132
- 3. Call **`write_default_pack`** with `project_id` and the payload from **`profile.json.default_guardrail_pack`** (match the MCP tool’s schema).
110
+ 3. Call **`write_default_pack`** with `project_id`, `guardrail_packs`, and `pack_count` derived from `.guardrails/profile.json` (match the MCP tool’s schema).
133
111
 
134
112
  4. **MCP approval:** Do **not** ask the user to “approve MCP” or “say you approve” for `security-review-mcp`. Security Review Kit passes the configured MCP server and approval settings during init-time profiling where the CLI supports it (for example Cursor CLI permissions and Copilot CLI `--additional-mcp-config` / `--allow-all`). Invoke `find_project_by_name`, `update_vibe_profile`, and `write_default_pack` directly. If a call still fails with permissions, report the exact CLI permission error — not a conversational approval step.
135
113
 
136
- 5. Confirm success: paths written (`profile.json`, `.guardrails/profile.json`) and whether both MCP calls succeeded, or the exact error.
114
+ 5. Confirm success: path written (`.guardrails/profile.json`) and whether both MCP calls succeeded, or the exact error.
137
115
 
138
- ### Step 8: Report
116
+ ### Step 7: Report
139
117
 
140
118
  Give a concise summary of detected stack, pack count, and upload status.
141
119
 
@@ -145,7 +123,7 @@ If there are no signals:
145
123
 
146
124
  1. Optionally read `.git/config` for hints.
147
125
  2. Emit minimal profile: `owasp-asvs` only, empty summaries where appropriate.
148
- 3. Still write `profile.json` (minimal `guardrails_profile` + `default_guardrail_pack`) and attempt MCP calls.
126
+ 3. Still write `.guardrails/profile.json` (minimal baseline profile) and attempt MCP calls.
149
127
 
150
128
  ## IDE-Specific Notes
151
129
 
@@ -0,0 +1,28 @@
1
+ import { join } from 'node:path';
2
+ import { MCP_SERVER_NAME } from './constants.js';
3
+ import { readJson, writeJson } from './fs-helpers.js';
4
+
5
+ /**
6
+ * Merge project-level Cursor CLI permissions so cursor-agent can use the SRAI MCP tools.
7
+ * @see https://docs.cursor.com/cli/reference/configuration
8
+ * @see https://docs.cursor.com/cli/reference/permissions
9
+ */
10
+ export function mergeCursorCliMcpAllowlist(cwd) {
11
+ const cliPath = join(cwd, '.cursor', 'cli.json');
12
+ const existing = readJson(cliPath) || {};
13
+ if (!existing.permissions || typeof existing.permissions !== 'object' || Array.isArray(existing.permissions)) {
14
+ existing.permissions = {};
15
+ }
16
+ if (!Array.isArray(existing.permissions.allow)) {
17
+ existing.permissions.allow = [];
18
+ }
19
+ const token = `Mcp(${MCP_SERVER_NAME}:*)`;
20
+ if (!existing.permissions.allow.includes(token)) {
21
+ existing.permissions.allow.push(token);
22
+ }
23
+ if (!Array.isArray(existing.permissions.deny)) {
24
+ existing.permissions.deny = [];
25
+ }
26
+ writeJson(cliPath, existing);
27
+ return cliPath;
28
+ }
@@ -65,7 +65,7 @@ export function buildProfilerAgentPrompt(projectName, agentTarget = 'cursor') {
65
65
  `SRAI project name: "${p}".`,
66
66
  `Open and follow every step in ${skillRoot}/SKILL.md.`,
67
67
  `Use ${skillRoot}/references/signal-registry.json as the signal registry.`,
68
- 'Write .guardrails/profile.json and profile.json at the repository root as defined in the skill.',
68
+ 'Write .guardrails/profile.json as defined in the skill.',
69
69
  `Resolve project_id with find_project_by_name for "${p}", then call update_vibe_profile and write_default_pack via security-review-mcp.`,
70
70
  'Do not invent stack details, compliance, or user groups; ground everything in the repository.',
71
71
  ].join('\n');
@@ -6,6 +6,7 @@ import assert from 'node:assert/strict';
6
6
  import {
7
7
  buildCodexConfigOverrides,
8
8
  buildCopilotAdditionalMcpConfig,
9
+ buildProfilerAgentPrompt,
9
10
  pickProfilerAgentTarget,
10
11
  } from './profiler-agent.js';
11
12
 
@@ -71,3 +72,10 @@ test('buildCodexConfigOverrides builds inline MCP config for profiling', () => {
71
72
  'mcp_servers.security-review-mcp.env.SECURITY_REVIEW_API_TOKEN="token"',
72
73
  ]);
73
74
  });
75
+
76
+ test('buildProfilerAgentPrompt only instructs writing the guardrails profile under .guardrails', () => {
77
+ const prompt = buildProfilerAgentPrompt('demo-project', 'codex');
78
+
79
+ assert.match(prompt, /Write \.guardrails\/profile\.json as defined in the skill\./);
80
+ assert.doesNotMatch(prompt, /profile\.json at the repository root/);
81
+ });