@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 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/ctm_sync_rule.mdc`, `.cursor/commands/ctm_sync.md`, `.cursor/agents/ctm_sync.md`, `.cursor/commands/create-ide-workflow.md`, `.cursor/commands/srai-profile.md`, `.cursor/skills/threat-modelling/SKILL.md` |
31
- | Claude Code | `claude` | `.mcp.json` | `.claude/CLAUDE.md`, `.claude/settings.json`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/guardrails-profiler/SKILL.md`, `.claude/skills/guardrails-selection/SKILL.md`, `.claude/agents/ctm_sync.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/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.md`, `.github/agents/ctm_sync.agent.md`, `.github/hooks/srai-session-policy.json` |
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/guardrails-profiler/SKILL.md`, `.codex/skills/guardrails-selection/SKILL.md`, `.codex/agents/ctm_sync.toml`, `.codex/hooks.json`, `.codex/commands/guardrails-init-profile.md` |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
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",
@@ -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. Invoke the ctm_sync agent using .claude/agents/ctm_sync.md after implementation or threat-model updates. The agent should write a structured .md sync artifact and upload it with sync_ai_ide_markdown.',
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, ctm_sync last.',
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
- ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.claude,
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 ctmSyncAgentPath = join(cwd, CTM_SYNC_AGENT_REL_PATH.claude);
80
- const ctmSyncAgent = writeGeneratedText(
81
- ctmSyncAgentPath,
82
- getClaudeCtmSyncAgentContent(optionsWithSkillDirs),
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
- { ...ctmSyncAgent, kind: 'agent' },
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, ctm_sync agent, and profiling command', () => {
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/agents/ctm_sync.md',
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', 'agent', 'command']);
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\/agents\/ctm_sync\.md/);
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, /\.claude\/agents\/ctm_sync\.md/);
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 agent = readFileSync(join(cwd, '.claude/agents/ctm_sync.md'), 'utf8');
36
- assert.match(agent, /^---\nname: ctm_sync/m);
37
- assert.match(agent, /model: inherit/);
38
- assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
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. Invoke the ctm_sync subagent using .codex/agents/ctm_sync.toml after threat modelling or guardrail enforcement. Reuse the exact guardrail shortlist; do not re-query guardrails during CTM sync. The subagent should write a structured .md sync artifact and upload it with sync_ai_ide_markdown.',
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, ctm_sync last.',
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
- ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.codex,
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 ctmSyncAgentPath = join(cwd, CTM_SYNC_AGENT_REL_PATH.codex);
122
- const ctmSyncAgent = writeGeneratedText(
123
- ctmSyncAgentPath,
124
- getCodexCtmSyncAgentContent(optionsWithSkillDirs),
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
- { ...ctmSyncAgent, kind: 'agent' },
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, subagent, hooks, and profiling command', () => {
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/agents/ctm_sync.toml',
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', 'agent', 'hooks', 'command']);
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\/agents\/ctm_sync\.toml/);
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, /\.codex\/agents\/ctm_sync\.toml/);
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 agent = readFileSync(join(cwd, '.codex/agents/ctm_sync.toml'), 'utf8');
42
- assert.match(agent, /name = "ctm_sync"/);
43
- assert.match(agent, /developer_instructions = '''/);
44
- assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
45
- assert.match(agent, /sync_ai_ide_markdown/);
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
- '{{CTM_SYNC_AGENT_PATH}}',
56
- '.cursor/agents/ctm_sync.md',
57
- options.ctmSyncAgentPath,
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 CTM sync workflow content markdown.
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 getCreateIdeWorkflowCommandContent(options = {}) {
95
- return readTemplate('create-ide-workflow.md', options);
80
+ export function getThreatModellingSkillContent(options = {}) {
81
+ return readTemplate('skill.md', options);
96
82
  }
97
83
 
98
84
  /**
99
- * Returns the threat-modelling skill markdown.
85
+ * Returns the VibeReview sync skill markdown.
100
86
  */
101
- export function getThreatModellingSkillContent(options = {}) {
102
- return readTemplate('skill.md', options);
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 CTM sync.
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. **Run CTM sync last.**
30
- - After threat modelling is created or updated, or after guardrails are enforced during implementation, invoke the `ctm_sync` agent/workflow.
31
- - Use `{{CTM_SYNC_AGENT_PATH}}` for the markdown contract and sync workflow.
32
- - Pass the current threat model summary, the stable `chat_session_id`, the exact existing guardrails shortlisted earlier, and any `ide_generated` guardrails so `ctm_sync` can write a structured `.md` artifact for server-side extraction.
33
- - Do not re-query guardrails during CTM sync; reuse the shortlist selected before implementation.
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 CTM sync.
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
- | CTM sync | `sync_ai_ide_markdown` |
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
- ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.cursor,
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 ctmSyncWorkflow = writeCursorCommand(ctmSyncWorkflowPath, ctmSyncWorkflowContent);
118
- const ctmSyncAgent = writeCursorCommand(ctmSyncAgentPath, ctmSyncWorkflowContent);
119
- const createIdeWorkflowCommand = writeCursorCommand(createIdeWorkflowCommandPath, createIdeWorkflowCommandContent);
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 CTM sync.
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 `ctm_sync`.
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
- ## CTM Sync Handoff Contract
163
+ ## VibeReview Sync Contract
164
164
 
165
- `ctm_sync` must reuse the shortlist from this skill. It must not call `get_guardrails` or `get_guardrail_by_id` again.
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 `ctm_sync` is invoked, ensure the parent context clearly contains:
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 `ctm_sync` with an exact list of what the IDE selected and enforced
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.