@securityreviewai/securityreview-kit 0.1.42 → 0.1.44
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 +4 -4
- package/package.json +1 -1
- package/src/generators/mcp/claude.js +2 -2
- package/src/generators/mcp/claude.test.js +3 -0
- package/src/generators/rules/claude.js +14 -27
- package/src/generators/rules/claude.test.js +12 -15
- package/src/generators/rules/codex.js +22 -27
- package/src/generators/rules/codex.test.js +16 -15
- package/src/generators/rules/content.js +9 -23
- package/src/generators/rules/content.md +11 -8
- package/src/generators/rules/cursor.js +27 -21
- package/src/generators/rules/guardrails-selection/SKILL.md +6 -6
- package/src/generators/rules/guardrails-selection/references/category-threat-map.md +1 -1
- package/src/generators/rules/guardrails_rule.md +5 -5
- package/src/generators/rules/hooks.json +1 -1
- package/src/generators/rules/skill.md +28 -17
- package/src/generators/rules/vibereview-sync/SKILL.md +358 -0
- package/src/generators/rules/vscode.js +22 -11
- package/src/generators/rules/vscode.test.js +15 -12
- package/src/utils/constants.js +6 -6
- package/src/generators/rules/create-ide-workflow.md +0 -34
- package/src/generators/rules/ctm_sync.md +0 -215
- package/src/generators/rules/ctm_sync_rule.md +0 -88
package/README.md
CHANGED
|
@@ -27,11 +27,11 @@ npx @securityreviewai/securityreview-kit init --switch-project
|
|
|
27
27
|
|
|
28
28
|
| Target | Flag | MCP Config | Workspace Rule |
|
|
29
29
|
|---|---|---|---|
|
|
30
|
-
| Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/
|
|
31
|
-
| Claude Code | `claude` | `.mcp.json` | `.claude/CLAUDE.md`, `.claude/settings.json`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/
|
|
32
|
-
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md`, `.github/skills/threat-modelling/SKILL.md`, `.github/skills/
|
|
30
|
+
| Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/guardrails_rule.mdc`, `.cursor/commands/srai-profile.md`, `.cursor/commands/guardrails-init-profile.md`, `.cursor/skills/threat-modelling/SKILL.md`, `.cursor/skills/vibereview-sync/SKILL.md`, `.cursor/hooks.json` |
|
|
31
|
+
| Claude Code | `claude` | `.mcp.json` | `.claude/CLAUDE.md`, `.claude/settings.json`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/vibereview-sync/SKILL.md`, `.claude/skills/guardrails-profiler/SKILL.md`, `.claude/skills/guardrails-selection/SKILL.md`, `.claude/commands/guardrails-init-profile.md` |
|
|
32
|
+
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md`, `.github/skills/threat-modelling/SKILL.md`, `.github/skills/vibereview-sync/SKILL.md`, `.github/skills/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.md`, `.github/hooks/srai-session-policy.json` |
|
|
33
33
|
| Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
|
|
34
|
-
| Codex | `codex` | `.codex/config.toml` | `.codex/AGENTS.md`, `.codex/skills/threat-modelling/SKILL.md`, `.codex/skills/
|
|
34
|
+
| Codex | `codex` | `.codex/config.toml` | `.codex/AGENTS.md`, `.codex/skills/threat-modelling/SKILL.md`, `.codex/skills/vibereview-sync/SKILL.md`, `.codex/skills/guardrails-profiler/SKILL.md`, `.codex/skills/guardrails-selection/SKILL.md`, `.codex/hooks.json`, `.codex/commands/guardrails-init-profile.md` |
|
|
35
35
|
| Gemini CLI | `gemini` | `.gemini/settings.json` | `GEMINI.md` |
|
|
36
36
|
| Antigravity | `antigravity` | `.gemini/settings.json` | `.agents/rules/srai-security-review.md` |
|
|
37
37
|
|
package/package.json
CHANGED
|
@@ -10,9 +10,9 @@ function getClaudeSessionStartHooks() {
|
|
|
10
10
|
'1. Fetch Vibe Guardrails first using .claude/skills/guardrails-selection/SKILL.md.',
|
|
11
11
|
'2. Run PWNISMS threat modelling using .claude/skills/threat-modelling/SKILL.md.',
|
|
12
12
|
'3. Implement secure code using the hydrated guardrails and threat findings.',
|
|
13
|
-
'4.
|
|
13
|
+
'4. Read .claude/skills/vibereview-sync/SKILL.md. Write a structured .md artifact under vibereview/, do not read sibling markdown files there just to infer format, validate it, and call sync_ai_ide_markdown directly after implementation or threat-model updates.',
|
|
14
14
|
'',
|
|
15
|
-
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third,
|
|
15
|
+
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third, VibeReview sync last.',
|
|
16
16
|
].join('\n');
|
|
17
17
|
|
|
18
18
|
return [
|
|
@@ -19,6 +19,9 @@ test('Claude MCP generator writes .mcp.json, enables the project MCP server, and
|
|
|
19
19
|
assert.deepEqual(settings.enabledMcpjsonServers, ['security-review-mcp']);
|
|
20
20
|
assert.equal(Array.isArray(settings.SessionStart), true);
|
|
21
21
|
assert.match(settings.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
|
|
22
|
+
assert.match(settings.SessionStart[0].hooks[0].prompt, /vibereview\//);
|
|
23
|
+
assert.match(settings.SessionStart[0].hooks[0].prompt, /sync_ai_ide_markdown/);
|
|
24
|
+
assert.match(settings.SessionStart[0].hooks[0].prompt, /vibereview-sync\/SKILL\.md/);
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
test('Claude MCP generator preserves existing SessionStart hooks', () => {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
5
4
|
GUARDRAILS_PROFILER_SKILL_REL_DIR,
|
|
6
5
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
7
6
|
SENTINEL_END,
|
|
8
7
|
SENTINEL_START,
|
|
9
8
|
THREAT_MODELLING_SKILL_REL_DIR,
|
|
9
|
+
VIBEREVIEW_SYNC_SKILL_REL_DIR,
|
|
10
10
|
} from '../../utils/constants.js';
|
|
11
11
|
import { readText, upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
12
12
|
import {
|
|
13
|
-
getCtmSyncWorkflowContent,
|
|
14
13
|
getGuardrailsInitProfileContent,
|
|
15
14
|
getRuleContent,
|
|
16
15
|
getThreatModellingSkillContent,
|
|
16
|
+
getVibeReviewSyncSkillContent,
|
|
17
17
|
} from './content.js';
|
|
18
18
|
|
|
19
19
|
function writeGeneratedText(filePath, content) {
|
|
@@ -22,25 +22,6 @@ function writeGeneratedText(filePath, content) {
|
|
|
22
22
|
return { filePath, action };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function getAgentBody(content) {
|
|
26
|
-
return content.replace(/^---\n[\s\S]*?\n---\n*/, '').trim();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getClaudeCtmSyncAgentContent(options = {}) {
|
|
30
|
-
const body = getAgentBody(getCtmSyncWorkflowContent(options));
|
|
31
|
-
return [
|
|
32
|
-
'---',
|
|
33
|
-
'name: ctm_sync',
|
|
34
|
-
'description: Use this agent after security-relevant implementation or threat modelling work to synchronize the latest threat model, mitigations, and guardrails to SRAI.',
|
|
35
|
-
'model: inherit',
|
|
36
|
-
'color: blue',
|
|
37
|
-
'---',
|
|
38
|
-
'',
|
|
39
|
-
body,
|
|
40
|
-
'',
|
|
41
|
-
].join('\n');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
25
|
/**
|
|
45
26
|
* Generate Claude Code workspace rule — appends to .claude/CLAUDE.md
|
|
46
27
|
*/
|
|
@@ -49,7 +30,7 @@ export function generate(cwd, options = {}) {
|
|
|
49
30
|
...options,
|
|
50
31
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.claude,
|
|
51
32
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.claude,
|
|
52
|
-
|
|
33
|
+
vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.claude,
|
|
53
34
|
};
|
|
54
35
|
const legacyRootPath = join(cwd, 'CLAUDE.md');
|
|
55
36
|
const filePath = join(cwd, '.claude', 'CLAUDE.md');
|
|
@@ -76,12 +57,17 @@ export function generate(cwd, options = {}) {
|
|
|
76
57
|
getThreatModellingSkillContent(optionsWithSkillDirs),
|
|
77
58
|
);
|
|
78
59
|
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.claude, 'SKILL.md');
|
|
61
|
+
const vibereviewSyncSkill = writeGeneratedText(
|
|
62
|
+
vibereviewSyncSkillPath,
|
|
63
|
+
getVibeReviewSyncSkillContent(optionsWithSkillDirs),
|
|
83
64
|
);
|
|
84
65
|
|
|
66
|
+
const deletedLegacyAgentPath = join(cwd, '.claude', 'agents', 'ctm_sync.md');
|
|
67
|
+
const deletedLegacyAgent = existsSync(deletedLegacyAgentPath)
|
|
68
|
+
? (unlinkSync(deletedLegacyAgentPath), { filePath: deletedLegacyAgentPath, action: 'deleted' })
|
|
69
|
+
: null;
|
|
70
|
+
|
|
85
71
|
const guardrailsInitPath = join(cwd, '.claude', 'commands', 'guardrails-init-profile.md');
|
|
86
72
|
const guardrailsInit = writeGeneratedText(
|
|
87
73
|
guardrailsInitPath,
|
|
@@ -94,7 +80,8 @@ export function generate(cwd, options = {}) {
|
|
|
94
80
|
return [
|
|
95
81
|
{ filePath, action, kind: 'rule' },
|
|
96
82
|
{ ...threatSkill, kind: 'skill' },
|
|
97
|
-
{ ...
|
|
83
|
+
{ ...vibereviewSyncSkill, kind: 'skill' },
|
|
98
84
|
{ ...guardrailsInit, kind: 'command' },
|
|
85
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
99
86
|
];
|
|
100
87
|
}
|
|
@@ -5,7 +5,7 @@ import { test } from 'node:test';
|
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
6
|
import { generate } from './claude.js';
|
|
7
7
|
|
|
8
|
-
test('Claude generator writes .claude/CLAUDE.md, threat skill,
|
|
8
|
+
test('Claude generator writes .claude/CLAUDE.md, threat skill, and profiling command', () => {
|
|
9
9
|
const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-'));
|
|
10
10
|
|
|
11
11
|
const results = generate(cwd, { projectName: 'SmokeProject' });
|
|
@@ -13,7 +13,7 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, ctm_sync agent, a
|
|
|
13
13
|
const expectedPaths = [
|
|
14
14
|
'.claude/CLAUDE.md',
|
|
15
15
|
'.claude/skills/threat-modelling/SKILL.md',
|
|
16
|
-
'.claude/
|
|
16
|
+
'.claude/skills/vibereview-sync/SKILL.md',
|
|
17
17
|
'.claude/commands/guardrails-init-profile.md',
|
|
18
18
|
];
|
|
19
19
|
|
|
@@ -21,27 +21,24 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, ctm_sync agent, a
|
|
|
21
21
|
assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', '
|
|
24
|
+
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'command']);
|
|
25
25
|
|
|
26
26
|
const instructions = readFileSync(join(cwd, '.claude/CLAUDE.md'), 'utf8');
|
|
27
27
|
assert.match(instructions, /\.claude\/skills\/guardrails-selection\/SKILL\.md/);
|
|
28
28
|
assert.match(instructions, /\.claude\/skills\/threat-modelling\/SKILL\.md/);
|
|
29
|
-
assert.match(instructions, /\.claude\/
|
|
29
|
+
assert.match(instructions, /\.claude\/skills\/vibereview-sync\/SKILL\.md/);
|
|
30
|
+
assert.match(instructions, /sync_ai_ide_markdown/);
|
|
31
|
+
assert.match(instructions, /vibereview\//);
|
|
30
32
|
|
|
31
33
|
const threatSkill = readFileSync(join(cwd, '.claude/skills/threat-modelling/SKILL.md'), 'utf8');
|
|
32
|
-
assert.match(threatSkill,
|
|
34
|
+
assert.match(threatSkill, /sync_ai_ide_markdown/);
|
|
35
|
+
assert.match(threatSkill, /vibereview\//);
|
|
33
36
|
assert.doesNotMatch(threatSkill, /get_project_profile_description/);
|
|
34
37
|
|
|
35
|
-
const
|
|
36
|
-
assert.match(
|
|
37
|
-
assert.match(
|
|
38
|
-
assert.match(
|
|
39
|
-
assert.match(agent, /sync_ai_ide_markdown/);
|
|
40
|
-
assert.match(agent, /structured markdown artifact/i);
|
|
41
|
-
assert.match(agent, /chat_session_id/);
|
|
42
|
-
assert.match(agent, /Guardrails Applied/);
|
|
43
|
-
assert.match(agent, /vibereview\//);
|
|
44
|
-
assert.doesNotMatch(agent, /Call `create_ai_ide_event` with/i);
|
|
38
|
+
const vibereviewSkill = readFileSync(join(cwd, '.claude/skills/vibereview-sync/SKILL.md'), 'utf8');
|
|
39
|
+
assert.match(vibereviewSkill, /Do not read other/i);
|
|
40
|
+
assert.match(vibereviewSkill, /sync_ai_ide_markdown/);
|
|
41
|
+
assert.match(vibereviewSkill, /Event Identity/);
|
|
45
42
|
});
|
|
46
43
|
|
|
47
44
|
test('Claude generator migrates the kit-managed root CLAUDE.md block into .claude/CLAUDE.md', () => {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
5
4
|
GUARDRAILS_PROFILER_SKILL_REL_DIR,
|
|
6
5
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
7
6
|
THREAT_MODELLING_SKILL_REL_DIR,
|
|
7
|
+
VIBEREVIEW_SYNC_SKILL_REL_DIR,
|
|
8
8
|
} from '../../utils/constants.js';
|
|
9
9
|
import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
10
10
|
import {
|
|
11
|
-
getCtmSyncWorkflowContent,
|
|
12
11
|
getGuardrailsInitProfileContent,
|
|
13
12
|
getRuleContent,
|
|
14
13
|
getThreatModellingSkillContent,
|
|
14
|
+
getVibeReviewSyncSkillContent,
|
|
15
15
|
} from './content.js';
|
|
16
16
|
|
|
17
17
|
function writeGeneratedText(filePath, content) {
|
|
@@ -20,6 +20,14 @@ function writeGeneratedText(filePath, content) {
|
|
|
20
20
|
return { filePath, action };
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function removeGeneratedText(filePath) {
|
|
24
|
+
if (existsSync(filePath)) {
|
|
25
|
+
unlinkSync(filePath);
|
|
26
|
+
return { filePath, action: 'deleted' };
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
function getCodexSessionHookContent() {
|
|
24
32
|
const additionalContext = [
|
|
25
33
|
'## MANDATORY SECURITY GATE (Session Policy)',
|
|
@@ -29,9 +37,9 @@ function getCodexSessionHookContent() {
|
|
|
29
37
|
'1. Fetch Vibe Guardrails first using .codex/skills/guardrails-selection/SKILL.md. Resolve the project, call get_guardrails, shortlist relevant guardrails, then call get_guardrail_by_id for the shortlist.',
|
|
30
38
|
'2. Run PWNISMS threat modelling using .codex/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
|
|
31
39
|
'3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
|
|
32
|
-
'4.
|
|
40
|
+
'4. Read .codex/skills/vibereview-sync/SKILL.md. Write a structured .md artifact under vibereview/, do not read sibling markdown files there just to infer format, validate it, and call sync_ai_ide_markdown directly after threat modelling or guardrail enforcement. Reuse the exact guardrail shortlist; do not re-query guardrails during the final sync.',
|
|
33
41
|
'',
|
|
34
|
-
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third,
|
|
42
|
+
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third, VibeReview sync last.',
|
|
35
43
|
].join('\n');
|
|
36
44
|
|
|
37
45
|
const sessionStartPayload = JSON.stringify({
|
|
@@ -82,22 +90,6 @@ function getCodexSessionHookContent() {
|
|
|
82
90
|
) + '\n';
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
function getAgentBody(content) {
|
|
86
|
-
return content.replace(/^---\n[\s\S]*?\n---\n*/, '').trim();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function getCodexCtmSyncAgentContent(options = {}) {
|
|
90
|
-
const developerInstructions = getAgentBody(getCtmSyncWorkflowContent(options));
|
|
91
|
-
return [
|
|
92
|
-
'name = "ctm_sync"',
|
|
93
|
-
'description = "Synchronize threat-model and guardrail data to SRAI after security-relevant work."',
|
|
94
|
-
"developer_instructions = '''",
|
|
95
|
-
developerInstructions,
|
|
96
|
-
"'''",
|
|
97
|
-
'',
|
|
98
|
-
].join('\n');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
93
|
/**
|
|
102
94
|
* Generate Codex workspace rule — appends to AGENTS.md
|
|
103
95
|
*/
|
|
@@ -106,7 +98,7 @@ export function generate(cwd, options = {}) {
|
|
|
106
98
|
...options,
|
|
107
99
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.codex,
|
|
108
100
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.codex,
|
|
109
|
-
|
|
101
|
+
vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.codex,
|
|
110
102
|
};
|
|
111
103
|
const filePath = join(cwd, '.codex', 'AGENTS.md');
|
|
112
104
|
const content = getRuleContent(optionsWithSkillDirs);
|
|
@@ -118,12 +110,14 @@ export function generate(cwd, options = {}) {
|
|
|
118
110
|
getThreatModellingSkillContent(optionsWithSkillDirs),
|
|
119
111
|
);
|
|
120
112
|
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.codex, 'SKILL.md');
|
|
114
|
+
const vibereviewSyncSkill = writeGeneratedText(
|
|
115
|
+
vibereviewSyncSkillPath,
|
|
116
|
+
getVibeReviewSyncSkillContent(optionsWithSkillDirs),
|
|
125
117
|
);
|
|
126
118
|
|
|
119
|
+
const deletedLegacyAgent = removeGeneratedText(join(cwd, '.codex', 'agents', 'ctm_sync.toml'));
|
|
120
|
+
|
|
127
121
|
const hooksPath = join(cwd, '.codex', 'hooks.json');
|
|
128
122
|
const hooks = writeGeneratedText(hooksPath, getCodexSessionHookContent());
|
|
129
123
|
|
|
@@ -139,8 +133,9 @@ export function generate(cwd, options = {}) {
|
|
|
139
133
|
return [
|
|
140
134
|
{ filePath, action, kind: 'rule' },
|
|
141
135
|
{ ...threatSkill, kind: 'skill' },
|
|
142
|
-
{ ...
|
|
136
|
+
{ ...vibereviewSyncSkill, kind: 'skill' },
|
|
143
137
|
{ ...hooks, kind: 'hooks' },
|
|
144
138
|
{ ...guardrailsInit, kind: 'command' },
|
|
139
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
145
140
|
];
|
|
146
141
|
}
|
|
@@ -5,7 +5,7 @@ import { test } from 'node:test';
|
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
6
|
import { generate } from './codex.js';
|
|
7
7
|
|
|
8
|
-
test('Codex generator writes AGENTS, skills,
|
|
8
|
+
test('Codex generator writes AGENTS, skills, hooks, and profiling command', () => {
|
|
9
9
|
const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-codex-'));
|
|
10
10
|
|
|
11
11
|
const results = generate(cwd, { projectName: 'SmokeProject' });
|
|
@@ -13,7 +13,7 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
|
|
|
13
13
|
const expectedPaths = [
|
|
14
14
|
'.codex/AGENTS.md',
|
|
15
15
|
'.codex/skills/threat-modelling/SKILL.md',
|
|
16
|
-
'.codex/
|
|
16
|
+
'.codex/skills/vibereview-sync/SKILL.md',
|
|
17
17
|
'.codex/hooks.json',
|
|
18
18
|
'.codex/commands/guardrails-init-profile.md',
|
|
19
19
|
];
|
|
@@ -22,12 +22,14 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
|
|
|
22
22
|
assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', '
|
|
25
|
+
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'hooks', 'command']);
|
|
26
26
|
|
|
27
27
|
const instructions = readFileSync(join(cwd, '.codex/AGENTS.md'), 'utf8');
|
|
28
28
|
assert.match(instructions, /\.codex\/skills\/guardrails-selection\/SKILL\.md/);
|
|
29
29
|
assert.match(instructions, /\.codex\/skills\/threat-modelling\/SKILL\.md/);
|
|
30
|
-
assert.match(instructions, /\.codex\/
|
|
30
|
+
assert.match(instructions, /\.codex\/skills\/vibereview-sync\/SKILL\.md/);
|
|
31
|
+
assert.match(instructions, /sync_ai_ide_markdown/);
|
|
32
|
+
assert.match(instructions, /vibereview\//);
|
|
31
33
|
assert.doesNotMatch(instructions, /\.cursor/);
|
|
32
34
|
assert.doesNotMatch(instructions, /\.agents\/skills/);
|
|
33
35
|
|
|
@@ -35,22 +37,21 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
|
|
|
35
37
|
for (const heading of ['Product', 'Workload', 'Network', 'IAM', 'Secrets', 'Monitoring', 'Supply Chain']) {
|
|
36
38
|
assert.match(threatSkill, new RegExp(heading));
|
|
37
39
|
}
|
|
38
|
-
assert.match(threatSkill,
|
|
40
|
+
assert.match(threatSkill, /sync_ai_ide_markdown/);
|
|
41
|
+
assert.match(threatSkill, /vibereview\//);
|
|
39
42
|
assert.doesNotMatch(threatSkill, /get_project_profile_description/);
|
|
40
43
|
|
|
41
|
-
const
|
|
42
|
-
assert.match(
|
|
43
|
-
assert.match(
|
|
44
|
-
assert.match(
|
|
45
|
-
assert.match(
|
|
46
|
-
assert.match(agent, /structured markdown artifact/i);
|
|
47
|
-
assert.match(agent, /chat_session_id/);
|
|
48
|
-
assert.match(agent, /Best Practices Achieved/);
|
|
49
|
-
assert.match(agent, /vibereview\//);
|
|
50
|
-
assert.doesNotMatch(agent, /Call `create_ai_ide_event` with/i);
|
|
44
|
+
const vibereviewSkill = readFileSync(join(cwd, '.codex/skills/vibereview-sync/SKILL.md'), 'utf8');
|
|
45
|
+
assert.match(vibereviewSkill, /do not read other `?\.md`? files in `?vibereview\/`?/i);
|
|
46
|
+
assert.match(vibereviewSkill, /sync_ai_ide_markdown/);
|
|
47
|
+
assert.match(vibereviewSkill, /Event Identity/);
|
|
48
|
+
assert.match(vibereviewSkill, /OWASP Top 10 2025 Mappings/);
|
|
51
49
|
|
|
52
50
|
const hooks = JSON.parse(readFileSync(join(cwd, '.codex/hooks.json'), 'utf8'));
|
|
53
51
|
assert.equal(hooks.hooks.SessionStart[0].hooks[0].type, 'command');
|
|
54
52
|
assert.equal(hooks.hooks.UserPromptSubmit[0].hooks[0].type, 'command');
|
|
55
53
|
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /"hookEventName":"UserPromptSubmit"/);
|
|
54
|
+
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview/);
|
|
55
|
+
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /sync_ai_ide_markdown/);
|
|
56
|
+
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview-sync\/SKILL\.md/);
|
|
56
57
|
});
|
|
@@ -52,9 +52,9 @@ function readTemplate(templateFileName, options = {}) {
|
|
|
52
52
|
);
|
|
53
53
|
out = injectPathPlaceholder(
|
|
54
54
|
out,
|
|
55
|
-
'{{
|
|
56
|
-
'.cursor/
|
|
57
|
-
options.
|
|
55
|
+
'{{VIBEREVIEW_SYNC_SKILL_DIR}}',
|
|
56
|
+
'.cursor/skills/vibereview-sync',
|
|
57
|
+
options.vibereviewSyncSkillDir,
|
|
58
58
|
);
|
|
59
59
|
|
|
60
60
|
return out;
|
|
@@ -75,31 +75,17 @@ export function getProfileCommandContent(options = {}) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Returns the
|
|
79
|
-
*/
|
|
80
|
-
export function getCtmSyncWorkflowContent(options = {}) {
|
|
81
|
-
return readTemplate('ctm_sync.md', options);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Returns the Cursor CTM sync trigger rule markdown.
|
|
86
|
-
*/
|
|
87
|
-
export function getCtmSyncTriggerRuleContent(options = {}) {
|
|
88
|
-
return readTemplate('ctm_sync_rule.md', options);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns the Cursor create-ide-workflow command markdown.
|
|
78
|
+
* Returns the threat-modelling skill markdown.
|
|
93
79
|
*/
|
|
94
|
-
export function
|
|
95
|
-
return readTemplate('
|
|
80
|
+
export function getThreatModellingSkillContent(options = {}) {
|
|
81
|
+
return readTemplate('skill.md', options);
|
|
96
82
|
}
|
|
97
83
|
|
|
98
84
|
/**
|
|
99
|
-
* Returns the
|
|
85
|
+
* Returns the VibeReview sync skill markdown.
|
|
100
86
|
*/
|
|
101
|
-
export function
|
|
102
|
-
return readTemplate('
|
|
87
|
+
export function getVibeReviewSyncSkillContent(options = {}) {
|
|
88
|
+
return readTemplate(join('vibereview-sync', 'SKILL.md'), options);
|
|
103
89
|
}
|
|
104
90
|
|
|
105
91
|
/**
|
|
@@ -12,7 +12,7 @@ For any task that touches auth, authorization, input handling, secrets, network,
|
|
|
12
12
|
- Read and follow `{{GUARDRAILS_SELECTION_SKILL_DIR}}/SKILL.md`.
|
|
13
13
|
- Resolve the SRAI project with `find_project_by_name` using `name="<SRAI_PROJECT_NAME>"`.
|
|
14
14
|
- Call `get_guardrails`, shortlist only the relevant project guardrails, then hydrate the exact shortlist with `get_guardrail_by_id`.
|
|
15
|
-
- Preserve that exact shortlist in context for implementation and
|
|
15
|
+
- Preserve that exact shortlist in context for implementation and the final VibeReview sync step.
|
|
16
16
|
|
|
17
17
|
2. **Run PWNISMS threat modelling before implementation.**
|
|
18
18
|
- Read and follow `{{THREAT_MODELLING_SKILL_DIR}}/SKILL.md`.
|
|
@@ -26,11 +26,14 @@ For any task that touches auth, authorization, input handling, secrets, network,
|
|
|
26
26
|
- Use the PWNISMS findings to guide design, validation, authorization, logging, secrets handling, dependency choices, and abuse controls.
|
|
27
27
|
- If PWNISMS reveals a recurring security rule that is not covered by existing guardrails, create and apply an `ide_generated` guardrail in context.
|
|
28
28
|
|
|
29
|
-
4. **
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
29
|
+
4. **Write and sync the VibeReview markdown last.**
|
|
30
|
+
- Read and follow `{{VIBEREVIEW_SYNC_SKILL_DIR}}/SKILL.md`.
|
|
31
|
+
- After threat modelling is created or updated, or after guardrails are enforced during implementation, the main agent must write or update a structured `.md` artifact under `vibereview/`.
|
|
32
|
+
- Do not delegate markdown authoring to a subagent. The same agent that performed the work should author the artifact so the context stays intact.
|
|
33
|
+
- Use the skill's structure and validation checklist rather than improvising the markdown format.
|
|
34
|
+
- Reuse the exact existing guardrails shortlisted earlier, include any `ide_generated` guardrails, and only include grounded code snippets from actual code.
|
|
35
|
+
- Call `sync_ai_ide_markdown` directly with the finished markdown artifact before finalizing the task.
|
|
36
|
+
- If sync fails, keep the file in `vibereview/`, report the failure, and do not pretend synchronization succeeded.
|
|
34
37
|
|
|
35
38
|
## When To Skip
|
|
36
39
|
|
|
@@ -42,7 +45,7 @@ If in doubt, run the workflow. A quick PWNISMS pass is better than silently miss
|
|
|
42
45
|
|
|
43
46
|
Do not call project-profile exploration tools during normal coding tasks. The old profile walkthrough is no longer part of the agent workflow.
|
|
44
47
|
|
|
45
|
-
The normal coding workflow is guardrails selection, PWNISMS threat modelling, secure implementation, then
|
|
48
|
+
The normal coding workflow is guardrails selection, PWNISMS threat modelling, secure implementation, then VibeReview markdown sync.
|
|
46
49
|
|
|
47
50
|
## Tool Reference
|
|
48
51
|
|
|
@@ -50,5 +53,5 @@ The normal coding workflow is guardrails selection, PWNISMS threat modelling, se
|
|
|
50
53
|
|---|---|
|
|
51
54
|
| Project resolution | `find_project_by_name`, `list_projects`, `create_project`, `get_project` |
|
|
52
55
|
| Guardrails | `get_guardrails`, `get_guardrail_by_id` |
|
|
53
|
-
|
|
|
56
|
+
| VibeReview sync | `sync_ai_ide_markdown` |
|
|
54
57
|
| Profiler only | `update_vibe_profile`, `write_default_pack` are used by init-time profiling, not normal coding tasks |
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
GUARDRAILS_PROFILER_SKILL_REL_DIR,
|
|
5
5
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
6
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
7
6
|
MCP_SERVER_NAME,
|
|
8
7
|
THREAT_MODELLING_SKILL_REL_DIR,
|
|
8
|
+
VIBEREVIEW_SYNC_SKILL_REL_DIR,
|
|
9
9
|
} from '../../utils/constants.js';
|
|
10
10
|
import { readJson, writeJson, writeText } from '../../utils/fs-helpers.js';
|
|
11
11
|
import {
|
|
12
12
|
getRuleContent,
|
|
13
13
|
getProfileCommandContent,
|
|
14
|
-
getCtmSyncWorkflowContent,
|
|
15
|
-
getCtmSyncTriggerRuleContent,
|
|
16
|
-
getCreateIdeWorkflowCommandContent,
|
|
17
14
|
getThreatModellingSkillContent,
|
|
15
|
+
getVibeReviewSyncSkillContent,
|
|
18
16
|
getGuardrailsRuleContent,
|
|
19
17
|
getGuardrailsInitProfileContent,
|
|
20
18
|
getHooksContent,
|
|
@@ -40,6 +38,14 @@ function writeCursorCommand(filePath, content) {
|
|
|
40
38
|
return { filePath, action };
|
|
41
39
|
}
|
|
42
40
|
|
|
41
|
+
function removeGeneratedText(filePath) {
|
|
42
|
+
if (existsSync(filePath)) {
|
|
43
|
+
unlinkSync(filePath);
|
|
44
|
+
return { filePath, action: 'deleted' };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
/**
|
|
44
50
|
* Merge `.cursor/cli.json` so Cursor CLI / Agent auto-allows security-review-mcp tools (no MCP approval prompts).
|
|
45
51
|
* @see https://cursor.com/docs/cli/reference/permissions
|
|
@@ -77,30 +83,24 @@ export function generate(cwd, options = {}) {
|
|
|
77
83
|
...options,
|
|
78
84
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.cursor,
|
|
79
85
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.cursor,
|
|
80
|
-
|
|
86
|
+
vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.cursor,
|
|
81
87
|
};
|
|
82
88
|
const baseRulePath = join(cwd, '.cursor', 'rules', 'srai-security-review.mdc');
|
|
83
|
-
const ctmSyncTriggerRulePath = join(cwd, '.cursor', 'rules', 'ctm_sync_rule.mdc');
|
|
84
89
|
const guardrailsRulePath = join(cwd, '.cursor', 'rules', 'guardrails_rule.mdc');
|
|
85
|
-
const ctmSyncWorkflowPath = join(cwd, '.cursor', 'commands', 'ctm_sync.md');
|
|
86
|
-
const ctmSyncAgentPath = join(cwd, '.cursor', 'agents', 'ctm_sync.md');
|
|
87
|
-
const createIdeWorkflowCommandPath = join(cwd, '.cursor', 'commands', 'create-ide-workflow.md');
|
|
88
90
|
const profileCommandPath = join(cwd, '.cursor', 'commands', 'srai-profile.md');
|
|
89
91
|
const guardrailsInitProfileCommandPath = join(cwd, '.cursor', 'commands', 'guardrails-init-profile.md');
|
|
90
92
|
const skillPath = join(cwd, '.cursor', 'skills', 'threat-modelling', 'SKILL.md');
|
|
91
93
|
const hooksPath = join(cwd, '.cursor', 'hooks.json');
|
|
92
94
|
|
|
93
95
|
const baseRuleContent = getRuleContent(optionsWithSkillDirs);
|
|
94
|
-
const ctmSyncTriggerRuleContent = getCtmSyncTriggerRuleContent(optionsWithSkillDirs);
|
|
95
96
|
const guardrailsRuleContent = getGuardrailsRuleContent(optionsWithSkillDirs);
|
|
96
|
-
const ctmSyncWorkflowContent = getCtmSyncWorkflowContent(optionsWithSkillDirs);
|
|
97
|
-
const createIdeWorkflowCommandContent = getCreateIdeWorkflowCommandContent(optionsWithSkillDirs);
|
|
98
97
|
const profileCommandContent = getProfileCommandContent(optionsWithSkillDirs);
|
|
99
98
|
const guardrailsInitProfileCommandContent = getGuardrailsInitProfileContent({
|
|
100
99
|
...optionsWithSkillDirs,
|
|
101
100
|
guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.cursor,
|
|
102
101
|
});
|
|
103
102
|
const skillContent = getThreatModellingSkillContent(optionsWithSkillDirs);
|
|
103
|
+
const vibereviewSyncSkillContent = getVibeReviewSyncSkillContent(optionsWithSkillDirs);
|
|
104
104
|
|
|
105
105
|
const baseRule = writeCursorRule(
|
|
106
106
|
baseRulePath,
|
|
@@ -108,15 +108,17 @@ export function generate(cwd, options = {}) {
|
|
|
108
108
|
baseRuleContent,
|
|
109
109
|
);
|
|
110
110
|
|
|
111
|
-
const ctmSyncTriggerRule = writeCursorCommand(ctmSyncTriggerRulePath, ctmSyncTriggerRuleContent);
|
|
112
111
|
const guardrailsRule = writeCursorRule(
|
|
113
112
|
guardrailsRulePath,
|
|
114
113
|
'Vibe Guardrails — fetch and enforce project-specific secure coding guardrails from SRAI',
|
|
115
114
|
guardrailsRuleContent,
|
|
116
115
|
);
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
116
|
+
const deletedLegacyRule = removeGeneratedText(join(cwd, '.cursor', 'rules', 'ctm_sync_rule.mdc'));
|
|
117
|
+
const deletedLegacyCommand = removeGeneratedText(join(cwd, '.cursor', 'commands', 'ctm_sync.md'));
|
|
118
|
+
const deletedLegacyAgent = removeGeneratedText(join(cwd, '.cursor', 'agents', 'ctm_sync.md'));
|
|
119
|
+
const deletedLegacyWorkflowCommand = removeGeneratedText(
|
|
120
|
+
join(cwd, '.cursor', 'commands', 'create-ide-workflow.md'),
|
|
121
|
+
);
|
|
120
122
|
|
|
121
123
|
const profileCommand = writeCursorCommand(profileCommandPath, profileCommandContent);
|
|
122
124
|
const guardrailsInitProfileCommand = writeCursorCommand(
|
|
@@ -125,6 +127,9 @@ export function generate(cwd, options = {}) {
|
|
|
125
127
|
);
|
|
126
128
|
const skillAction = existsSync(skillPath) ? 'updated' : 'created';
|
|
127
129
|
writeText(skillPath, skillContent);
|
|
130
|
+
const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.cursor, 'SKILL.md');
|
|
131
|
+
const vibereviewSyncSkillAction = existsSync(vibereviewSyncSkillPath) ? 'updated' : 'created';
|
|
132
|
+
writeText(vibereviewSyncSkillPath, vibereviewSyncSkillContent);
|
|
128
133
|
|
|
129
134
|
const hooksContent = getHooksContent();
|
|
130
135
|
const hooksAction = existsSync(hooksPath) ? 'updated' : 'created';
|
|
@@ -134,15 +139,16 @@ export function generate(cwd, options = {}) {
|
|
|
134
139
|
|
|
135
140
|
return [
|
|
136
141
|
{ ...baseRule, kind: 'rule' },
|
|
137
|
-
{ ...ctmSyncTriggerRule, kind: 'rule' },
|
|
138
142
|
{ ...guardrailsRule, kind: 'rule' },
|
|
139
|
-
{ ...ctmSyncWorkflow, kind: 'command' },
|
|
140
|
-
{ ...ctmSyncAgent, kind: 'agent' },
|
|
141
|
-
{ ...createIdeWorkflowCommand, kind: 'command' },
|
|
142
143
|
{ ...profileCommand, kind: 'command' },
|
|
143
144
|
{ ...guardrailsInitProfileCommand, kind: 'command' },
|
|
144
145
|
{ filePath: skillPath, action: skillAction, kind: 'skill' },
|
|
146
|
+
{ filePath: vibereviewSyncSkillPath, action: vibereviewSyncSkillAction, kind: 'skill' },
|
|
145
147
|
{ filePath: hooksPath, action: hooksAction, kind: 'hooks' },
|
|
146
148
|
{ ...cliPermissions, kind: 'config' },
|
|
149
|
+
...(deletedLegacyRule ? [{ ...deletedLegacyRule, kind: 'cleanup' }] : []),
|
|
150
|
+
...(deletedLegacyCommand ? [{ ...deletedLegacyCommand, kind: 'cleanup' }] : []),
|
|
151
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
152
|
+
...(deletedLegacyWorkflowCommand ? [{ ...deletedLegacyWorkflowCommand, kind: 'cleanup' }] : []),
|
|
147
153
|
];
|
|
148
154
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: guardrails-selection
|
|
3
|
-
description: Analyze the developer request, infer the security categories and likely threats involved, shortlist the most relevant project guardrails, then hydrate the exact guardrails with get_guardrail_by_id before implementation. Use for every security-relevant code task before code is written and preserve the shortlist for
|
|
3
|
+
description: Analyze the developer request, infer the security categories and likely threats involved, shortlist the most relevant project guardrails, then hydrate the exact guardrails with get_guardrail_by_id before implementation. Use for every security-relevant code task before code is written and preserve the shortlist for the final VibeReview sync.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Guardrails Selection
|
|
@@ -16,7 +16,7 @@ This skill exists to stop the IDE from treating the full `get_guardrails` result
|
|
|
16
16
|
3. Predict the threats that might occur for this exact task.
|
|
17
17
|
4. Shortlist only the guardrails that mitigate those threats.
|
|
18
18
|
5. Fetch the exact shortlisted guardrails with `get_guardrail_by_id`.
|
|
19
|
-
6. Carry that same shortlist forward into implementation and
|
|
19
|
+
6. Carry that same shortlist forward into implementation and the final VibeReview markdown sync.
|
|
20
20
|
|
|
21
21
|
Do not skip the analysis step. Do not rely on title-matching alone. Do not dump every guardrail into the final answer.
|
|
22
22
|
|
|
@@ -160,11 +160,11 @@ Once the shortlist is hydrated:
|
|
|
160
160
|
|
|
161
161
|
When deciding whether a guardrail applies, prefer security-preserving inclusion over risky omission. If it plausibly mitigates a realistic path to abuse for the current task, keep it in scope.
|
|
162
162
|
|
|
163
|
-
##
|
|
163
|
+
## VibeReview Sync Contract
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
The final sync step must reuse the shortlist from this skill. It must not call `get_guardrails` or `get_guardrail_by_id` again.
|
|
166
166
|
|
|
167
|
-
Before `
|
|
167
|
+
Before `sync_ai_ide_markdown` is called, ensure the main agent context clearly contains:
|
|
168
168
|
|
|
169
169
|
- the exact existing guardrails shortlisted earlier
|
|
170
170
|
- which of them were applied
|
|
@@ -182,6 +182,6 @@ A good selection does all of the following:
|
|
|
182
182
|
- captures adjacent controls that stop bypass chains
|
|
183
183
|
- avoids irrelevant noise
|
|
184
184
|
- produces a small, defensible set of guardrails that can actually guide implementation
|
|
185
|
-
- leaves
|
|
185
|
+
- leaves the final VibeReview markdown with an exact list of what the IDE selected and enforced
|
|
186
186
|
|
|
187
187
|
If your shortlist feels generic, it is probably incomplete or over-broad. Re-check the prompt, the code patterns, and the threat map.
|