@securityreviewai/securityreview-kit 0.1.43 → 0.1.45

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/guardrails_rule.mdc`, `.cursor/commands/srai-profile.md`, `.cursor/commands/guardrails-init-profile.md`, `.cursor/skills/threat-modelling/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/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/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.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/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.43",
3
+ "version": "0.1.45",
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,7 +10,7 @@ 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. Write a structured .md artifact under vibereview/, validate it, and call sync_ai_ide_markdown directly after implementation or threat-model updates.',
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
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');
@@ -21,6 +21,7 @@ test('Claude MCP generator writes .mcp.json, enables the project MCP server, and
21
21
  assert.match(settings.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
22
22
  assert.match(settings.SessionStart[0].hooks[0].prompt, /vibereview\//);
23
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/);
24
25
  });
25
26
 
26
27
  test('Claude MCP generator preserves existing SessionStart hooks', () => {
@@ -6,12 +6,14 @@ import {
6
6
  SENTINEL_END,
7
7
  SENTINEL_START,
8
8
  THREAT_MODELLING_SKILL_REL_DIR,
9
+ VIBEREVIEW_SYNC_SKILL_REL_DIR,
9
10
  } from '../../utils/constants.js';
10
11
  import { readText, upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
11
12
  import {
12
13
  getGuardrailsInitProfileContent,
13
14
  getRuleContent,
14
15
  getThreatModellingSkillContent,
16
+ getVibeReviewSyncSkillContent,
15
17
  } from './content.js';
16
18
 
17
19
  function writeGeneratedText(filePath, content) {
@@ -28,6 +30,7 @@ export function generate(cwd, options = {}) {
28
30
  ...options,
29
31
  guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.claude,
30
32
  threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.claude,
33
+ vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.claude,
31
34
  };
32
35
  const legacyRootPath = join(cwd, 'CLAUDE.md');
33
36
  const filePath = join(cwd, '.claude', 'CLAUDE.md');
@@ -54,6 +57,12 @@ export function generate(cwd, options = {}) {
54
57
  getThreatModellingSkillContent(optionsWithSkillDirs),
55
58
  );
56
59
 
60
+ const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.claude, 'SKILL.md');
61
+ const vibereviewSyncSkill = writeGeneratedText(
62
+ vibereviewSyncSkillPath,
63
+ getVibeReviewSyncSkillContent(optionsWithSkillDirs),
64
+ );
65
+
57
66
  const deletedLegacyAgentPath = join(cwd, '.claude', 'agents', 'ctm_sync.md');
58
67
  const deletedLegacyAgent = existsSync(deletedLegacyAgentPath)
59
68
  ? (unlinkSync(deletedLegacyAgentPath), { filePath: deletedLegacyAgentPath, action: 'deleted' })
@@ -71,6 +80,7 @@ export function generate(cwd, options = {}) {
71
80
  return [
72
81
  { filePath, action, kind: 'rule' },
73
82
  { ...threatSkill, kind: 'skill' },
83
+ { ...vibereviewSyncSkill, kind: 'skill' },
74
84
  { ...guardrailsInit, kind: 'command' },
75
85
  ...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
76
86
  ];
@@ -13,6 +13,7 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, and profiling com
13
13
  const expectedPaths = [
14
14
  '.claude/CLAUDE.md',
15
15
  '.claude/skills/threat-modelling/SKILL.md',
16
+ '.claude/skills/vibereview-sync/SKILL.md',
16
17
  '.claude/commands/guardrails-init-profile.md',
17
18
  ];
18
19
 
@@ -20,11 +21,12 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, and profiling com
20
21
  assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
21
22
  }
22
23
 
23
- assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'command']);
24
+ assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'command']);
24
25
 
25
26
  const instructions = readFileSync(join(cwd, '.claude/CLAUDE.md'), 'utf8');
26
27
  assert.match(instructions, /\.claude\/skills\/guardrails-selection\/SKILL\.md/);
27
28
  assert.match(instructions, /\.claude\/skills\/threat-modelling\/SKILL\.md/);
29
+ assert.match(instructions, /\.claude\/skills\/vibereview-sync\/SKILL\.md/);
28
30
  assert.match(instructions, /sync_ai_ide_markdown/);
29
31
  assert.match(instructions, /vibereview\//);
30
32
 
@@ -32,6 +34,13 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, and profiling com
32
34
  assert.match(threatSkill, /sync_ai_ide_markdown/);
33
35
  assert.match(threatSkill, /vibereview\//);
34
36
  assert.doesNotMatch(threatSkill, /get_project_profile_description/);
37
+
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/);
42
+ assert.match(vibereviewSkill, /Practice 1/);
43
+ assert.match(vibereviewSkill, /practice_name:/);
35
44
  });
36
45
 
37
46
  test('Claude generator migrates the kit-managed root CLAUDE.md block into .claude/CLAUDE.md', () => {
@@ -4,12 +4,14 @@ import {
4
4
  GUARDRAILS_PROFILER_SKILL_REL_DIR,
5
5
  GUARDRAILS_SELECTION_SKILL_REL_DIR,
6
6
  THREAT_MODELLING_SKILL_REL_DIR,
7
+ VIBEREVIEW_SYNC_SKILL_REL_DIR,
7
8
  } from '../../utils/constants.js';
8
9
  import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
9
10
  import {
10
11
  getGuardrailsInitProfileContent,
11
12
  getRuleContent,
12
13
  getThreatModellingSkillContent,
14
+ getVibeReviewSyncSkillContent,
13
15
  } from './content.js';
14
16
 
15
17
  function writeGeneratedText(filePath, content) {
@@ -35,7 +37,7 @@ function getCodexSessionHookContent() {
35
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.',
36
38
  '2. Run PWNISMS threat modelling using .codex/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
37
39
  '3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
38
- '4. Write a structured .md artifact under vibereview/, 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.',
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.',
39
41
  '',
40
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.',
41
43
  ].join('\n');
@@ -96,6 +98,7 @@ export function generate(cwd, options = {}) {
96
98
  ...options,
97
99
  guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.codex,
98
100
  threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.codex,
101
+ vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.codex,
99
102
  };
100
103
  const filePath = join(cwd, '.codex', 'AGENTS.md');
101
104
  const content = getRuleContent(optionsWithSkillDirs);
@@ -107,6 +110,12 @@ export function generate(cwd, options = {}) {
107
110
  getThreatModellingSkillContent(optionsWithSkillDirs),
108
111
  );
109
112
 
113
+ const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.codex, 'SKILL.md');
114
+ const vibereviewSyncSkill = writeGeneratedText(
115
+ vibereviewSyncSkillPath,
116
+ getVibeReviewSyncSkillContent(optionsWithSkillDirs),
117
+ );
118
+
110
119
  const deletedLegacyAgent = removeGeneratedText(join(cwd, '.codex', 'agents', 'ctm_sync.toml'));
111
120
 
112
121
  const hooksPath = join(cwd, '.codex', 'hooks.json');
@@ -124,6 +133,7 @@ export function generate(cwd, options = {}) {
124
133
  return [
125
134
  { filePath, action, kind: 'rule' },
126
135
  { ...threatSkill, kind: 'skill' },
136
+ { ...vibereviewSyncSkill, kind: 'skill' },
127
137
  { ...hooks, kind: 'hooks' },
128
138
  { ...guardrailsInit, kind: 'command' },
129
139
  ...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
@@ -13,6 +13,7 @@ test('Codex generator writes AGENTS, skills, hooks, and profiling command', () =
13
13
  const expectedPaths = [
14
14
  '.codex/AGENTS.md',
15
15
  '.codex/skills/threat-modelling/SKILL.md',
16
+ '.codex/skills/vibereview-sync/SKILL.md',
16
17
  '.codex/hooks.json',
17
18
  '.codex/commands/guardrails-init-profile.md',
18
19
  ];
@@ -21,11 +22,12 @@ test('Codex generator writes AGENTS, skills, hooks, and profiling command', () =
21
22
  assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
22
23
  }
23
24
 
24
- assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'hooks', 'command']);
25
+ assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'hooks', 'command']);
25
26
 
26
27
  const instructions = readFileSync(join(cwd, '.codex/AGENTS.md'), 'utf8');
27
28
  assert.match(instructions, /\.codex\/skills\/guardrails-selection\/SKILL\.md/);
28
29
  assert.match(instructions, /\.codex\/skills\/threat-modelling\/SKILL\.md/);
30
+ assert.match(instructions, /\.codex\/skills\/vibereview-sync\/SKILL\.md/);
29
31
  assert.match(instructions, /sync_ai_ide_markdown/);
30
32
  assert.match(instructions, /vibereview\//);
31
33
  assert.doesNotMatch(instructions, /\.cursor/);
@@ -39,10 +41,19 @@ test('Codex generator writes AGENTS, skills, hooks, and profiling command', () =
39
41
  assert.match(threatSkill, /vibereview\//);
40
42
  assert.doesNotMatch(threatSkill, /get_project_profile_description/);
41
43
 
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/);
49
+ assert.match(vibereviewSkill, /Practice 1/);
50
+ assert.match(vibereviewSkill, /practice_name:/);
51
+
42
52
  const hooks = JSON.parse(readFileSync(join(cwd, '.codex/hooks.json'), 'utf8'));
43
53
  assert.equal(hooks.hooks.SessionStart[0].hooks[0].type, 'command');
44
54
  assert.equal(hooks.hooks.UserPromptSubmit[0].hooks[0].type, 'command');
45
55
  assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /"hookEventName":"UserPromptSubmit"/);
46
56
  assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview/);
47
57
  assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /sync_ai_ide_markdown/);
58
+ assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview-sync\/SKILL\.md/);
48
59
  });
@@ -50,6 +50,12 @@ function readTemplate(templateFileName, options = {}) {
50
50
  '.cursor/skills/threat-modelling',
51
51
  options.threatModellingSkillDir,
52
52
  );
53
+ out = injectPathPlaceholder(
54
+ out,
55
+ '{{VIBEREVIEW_SYNC_SKILL_DIR}}',
56
+ '.cursor/skills/vibereview-sync',
57
+ options.vibereviewSyncSkillDir,
58
+ );
53
59
 
54
60
  return out;
55
61
  }
@@ -75,6 +81,13 @@ export function getThreatModellingSkillContent(options = {}) {
75
81
  return readTemplate('skill.md', options);
76
82
  }
77
83
 
84
+ /**
85
+ * Returns the VibeReview sync skill markdown.
86
+ */
87
+ export function getVibeReviewSyncSkillContent(options = {}) {
88
+ return readTemplate(join('vibereview-sync', 'SKILL.md'), options);
89
+ }
90
+
78
91
  /**
79
92
  * Returns the vibe guardrails always-applied rule markdown.
80
93
  */
@@ -27,13 +27,11 @@ For any task that touches auth, authorization, input handling, secrets, network,
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
29
  4. **Write and sync the VibeReview markdown last.**
30
+ - Read and follow `{{VIBEREVIEW_SYNC_SKILL_DIR}}/SKILL.md`.
30
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/`.
31
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.
32
- - Use a stable path such as `vibereview/<chat_session_id>-<slugified-title-or-event-name>.md`.
33
- - Required frontmatter: `chat_session_id`, `summary`, and either `title` or `event_name`. Optional frontmatter: `workflow_name`, `workflow_description`.
34
- - Required sections: `Best Practices Achieved`, `Threats Mitigated`, `Secure Code Snippets`, `Guardrails Applied`, and `OWASP Top 10 2025 Mappings`.
33
+ - Use the skill's structure and validation checklist rather than improvising the markdown format.
35
34
  - Reuse the exact existing guardrails shortlisted earlier, include any `ide_generated` guardrails, and only include grounded code snippets from actual code.
36
- - Validate the markdown before sync: do not leave out `chat_session_id`, `summary`, threat metadata, guardrail satisfaction, or exact OWASP IDs/names.
37
35
  - Call `sync_ai_ide_markdown` directly with the finished markdown artifact before finalizing the task.
38
36
  - If sync fails, keep the file in `vibereview/`, report the failure, and do not pretend synchronization succeeded.
39
37
 
@@ -5,12 +5,14 @@ import {
5
5
  GUARDRAILS_SELECTION_SKILL_REL_DIR,
6
6
  MCP_SERVER_NAME,
7
7
  THREAT_MODELLING_SKILL_REL_DIR,
8
+ VIBEREVIEW_SYNC_SKILL_REL_DIR,
8
9
  } from '../../utils/constants.js';
9
10
  import { readJson, writeJson, writeText } from '../../utils/fs-helpers.js';
10
11
  import {
11
12
  getRuleContent,
12
13
  getProfileCommandContent,
13
14
  getThreatModellingSkillContent,
15
+ getVibeReviewSyncSkillContent,
14
16
  getGuardrailsRuleContent,
15
17
  getGuardrailsInitProfileContent,
16
18
  getHooksContent,
@@ -81,6 +83,7 @@ export function generate(cwd, options = {}) {
81
83
  ...options,
82
84
  guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.cursor,
83
85
  threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.cursor,
86
+ vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.cursor,
84
87
  };
85
88
  const baseRulePath = join(cwd, '.cursor', 'rules', 'srai-security-review.mdc');
86
89
  const guardrailsRulePath = join(cwd, '.cursor', 'rules', 'guardrails_rule.mdc');
@@ -97,6 +100,7 @@ export function generate(cwd, options = {}) {
97
100
  guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.cursor,
98
101
  });
99
102
  const skillContent = getThreatModellingSkillContent(optionsWithSkillDirs);
103
+ const vibereviewSyncSkillContent = getVibeReviewSyncSkillContent(optionsWithSkillDirs);
100
104
 
101
105
  const baseRule = writeCursorRule(
102
106
  baseRulePath,
@@ -123,6 +127,9 @@ export function generate(cwd, options = {}) {
123
127
  );
124
128
  const skillAction = existsSync(skillPath) ? 'updated' : 'created';
125
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);
126
133
 
127
134
  const hooksContent = getHooksContent();
128
135
  const hooksAction = existsSync(hooksPath) ? 'updated' : 'created';
@@ -136,6 +143,7 @@ export function generate(cwd, options = {}) {
136
143
  { ...profileCommand, kind: 'command' },
137
144
  { ...guardrailsInitProfileCommand, kind: 'command' },
138
145
  { filePath: skillPath, action: skillAction, kind: 'skill' },
146
+ { filePath: vibereviewSyncSkillPath, action: vibereviewSyncSkillAction, kind: 'skill' },
139
147
  { filePath: hooksPath, action: hooksAction, kind: 'hooks' },
140
148
  { ...cliPermissions, kind: 'config' },
141
149
  ...(deletedLegacyRule ? [{ ...deletedLegacyRule, kind: 'cleanup' }] : []),
@@ -3,7 +3,7 @@
3
3
  "hooks": {
4
4
  "sessionStart": [
5
5
  {
6
- "command": "printf '%s\\n' '{\"additional_context\":\"## MANDATORY SECURITY GATE (Session Policy)\\n\\nFor ANY user request that has security impact (auth, crypto, input handling, secrets, network, data storage, dependencies, new APIs/endpoints, or any code handling untrusted data):\\n\\n**Plan order:**\\n1. **Fetch Vibe Guardrails (first)** - Use .cursor/skills/guardrails-selection/SKILL.md. Analyze the task, call get_guardrails from security-review-mcp to load the broad catalog, shortlist the relevant guardrails, then call get_guardrail_by_id for the shortlist. These exact shortlisted guardrails are the hard constraints on generated code.\\n2. **PWNISMS threat modeling (second)** - Run threat modeling as a guide before writing code. Use .cursor/skills/threat-modelling/SKILL.md. Cross-reference guardrails with PWNISMS findings to identify gaps. Document findings to inform secure implementation.\\n3. **Implement secure code (third)** - Use the threat model findings AND shortlisted guardrails as guides to create secure code. Every must guardrail must be satisfied; no must_not guardrail may be violated.\\n4. **VibeReview sync (last)** - The main agent must write a structured `.md` artifact under `vibereview/`, validate that all required metadata and sections are present, and call `sync_ai_ide_markdown` directly after implementation is done. Include the exact shortlisted guardrails from earlier plus any IDE-generated guardrails in the markdown; do not query guardrails again during sync.\\n\\nNo blocking. No deferral. Guardrails enforce secure code; threat modeling guides the design; the main agent owns the final markdown sync to SRAI. Both sync and guardrail compliance are mandatory and should be part of your to-do.\"}'",
6
+ "command": "printf '%s\\n' '{\"additional_context\":\"## MANDATORY SECURITY GATE (Session Policy)\\n\\nFor ANY user request that has security impact (auth, crypto, input handling, secrets, network, data storage, dependencies, new APIs/endpoints, or any code handling untrusted data):\\n\\n**Plan order:**\\n1. **Fetch Vibe Guardrails (first)** - Use .cursor/skills/guardrails-selection/SKILL.md. Analyze the task, call get_guardrails from security-review-mcp to load the broad catalog, shortlist the relevant guardrails, then call get_guardrail_by_id for the shortlist. These exact shortlisted guardrails are the hard constraints on generated code.\\n2. **PWNISMS threat modeling (second)** - Run threat modeling as a guide before writing code. Use .cursor/skills/threat-modelling/SKILL.md. Cross-reference guardrails with PWNISMS findings to identify gaps. Document findings to inform secure implementation.\\n3. **Implement secure code (third)** - Use the threat model findings AND shortlisted guardrails as guides to create secure code. Every must guardrail must be satisfied; no must_not guardrail may be violated.\\n4. **VibeReview sync (last)** - Read .cursor/skills/vibereview-sync/SKILL.md. The main agent must write a structured `.md` artifact under `vibereview/`, must not read sibling markdown files there just to infer format, must validate that all required metadata and sections are present, and must call `sync_ai_ide_markdown` directly after implementation is done. Include the exact shortlisted guardrails from earlier plus any IDE-generated guardrails in the markdown; do not query guardrails again during sync.\\n\\nNo blocking. No deferral. Guardrails enforce secure code; threat modeling guides the design; the main agent owns the final markdown sync to SRAI. Both sync and guardrail compliance are mandatory and should be part of your to-do.\"}'",
7
7
  "timeout": 5
8
8
  }
9
9
  ]
@@ -212,28 +212,31 @@ When discussing designs before code exists:
212
212
  The main agent writes a structured `.md` artifact under `vibereview/` and uploads it through `sync_ai_ide_markdown`. That markdown should contain:
213
213
 
214
214
  - **Threat model findings**: threats mitigated, PWNISMS categories, severities, mitigations applied
215
- - **Best practices achieved**: security patterns followed during implementation
215
+ - **Best practices achieved**: structured practice entries with `practice_name`, `description`, and `category`
216
216
  - **Secure code snippets**: security-relevant code with explanations
217
217
  - **Guardrails applied**: all guardrails enforced during this session — both existing ones shortlisted earlier via `get_guardrails` + `get_guardrail_by_id` (`source: "existing"`) and new ones the IDE agent created on the fly (`source: "ide_generated"`), each with satisfaction status
218
218
  - **Workflow metadata**: `chat_session_id`, `event_name` or `title`, required `summary`, and optional `workflow_name` / `workflow_description`
219
219
 
220
220
  ### How to sync
221
221
 
222
- 1. Write or update a file under `vibereview/`, ideally `vibereview/<chat_session_id>-<slugified-title-or-event-name>.md`.
223
- 2. Put `chat_session_id`, `summary`, and either `title` or `event_name` in frontmatter.
224
- 3. Include the required sections:
222
+ 1. Read and follow `{{VIBEREVIEW_SYNC_SKILL_DIR}}/SKILL.md`.
223
+ 2. Write or update a file under `vibereview/`, ideally `vibereview/<chat_session_id>-<slugified-title-or-event-name>.md`.
224
+ 3. Put `chat_session_id`, `summary`, and either `title` or `event_name` in frontmatter.
225
+ 4. Include the required sections:
225
226
  - `Best Practices Achieved`
226
227
  - `Threats Mitigated`
227
228
  - `Secure Code Snippets`
228
229
  - `Guardrails Applied`
229
230
  - `OWASP Top 10 2025 Mappings`
230
- 4. Validate that:
231
- - every threat entry includes `threat_name`, `pwnisms_category`, `severity`, and `mitigation_applied`
231
+ 5. Validate that:
232
+ - every threat entry includes `threat_name`, `pwnisms_category`, `severity`, and `mitigation_applied`
233
+ - every best-practice entry includes `practice_name`, `description`, and `category`
232
234
  - every guardrail includes `title`, `rule_type`, `source`, and `satisfied`
233
235
  - OWASP mappings use exact IDs and names
234
236
  - snippets are grounded in actual code, not invented text
235
- 5. Call `sync_ai_ide_markdown` directly with the finished markdown artifact.
236
- 6. If sync fails, leave the artifact in `vibereview/` and report the failure clearly.
237
+ - no sibling `.md` files in `vibereview/` were read just to infer format or content
238
+ 6. Call `sync_ai_ide_markdown` directly with the finished markdown artifact.
239
+ 7. If sync fails, leave the artifact in `vibereview/` and report the failure clearly.
237
240
 
238
241
  ---
239
242
 
@@ -0,0 +1,378 @@
1
+ ---
2
+ name: vibereview-sync
3
+ description: Write and synchronize a structured VibeReview markdown artifact under vibereview/ for the current security-relevant session. Use after threat modelling or guardrail-enforced implementation.
4
+ ---
5
+
6
+ # VibeReview Markdown Sync
7
+
8
+ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
9
+
10
+ Use this skill whenever threat modelling output exists or security guardrail compliance must be synchronized to SRAI.
11
+
12
+ This skill exists to make the markdown deterministic, grounded, and parseable without depending on a separate subagent. The same agent that performed the work should author the markdown and call `sync_ai_ide_markdown` directly.
13
+
14
+ ## Core Rules
15
+
16
+ 1. Write the artifact under `vibereview/`.
17
+ 2. Author the markdown from the current task context only.
18
+ 3. Do not read other `.md` files in `vibereview/` to infer structure, copy content, or merge state.
19
+ 4. If the current session file path is already known, update that one file directly.
20
+ 5. If the current session file does not exist yet, create a new file using a stable name such as:
21
+
22
+ ```text
23
+ vibereview/<chat_session_id>-<slugified-title-or-event-name>.md
24
+ ```
25
+
26
+ 6. Validate the markdown before calling `sync_ai_ide_markdown`.
27
+ 7. If sync fails, leave the file on disk and report the failure clearly.
28
+
29
+ ## Why You Must Not Read Sibling Files
30
+
31
+ Other markdown files in `vibereview/` may belong to different sessions, different workflows, or partially complete work. Reading them introduces drift and causes downstream extraction failures.
32
+
33
+ Treat each VibeReview artifact as self-contained. The current artifact must be fully authorable from:
34
+
35
+ - the current user request
36
+ - the current session context
37
+ - the threat model produced in this task
38
+ - the exact guardrail shortlist preserved in context
39
+ - the real code snippets touched in this task
40
+
41
+ ## Required Authoring Workflow
42
+
43
+ ### Step 1: Collect the required inputs
44
+
45
+ Before writing the markdown, confirm you have:
46
+
47
+ - a stable `chat_session_id`
48
+ - a concise `summary`
49
+ - either `title` or `event_name`
50
+ - the exact existing guardrails shortlisted earlier
51
+ - any `ide_generated` guardrails created during this task
52
+ - grounded threat entries
53
+ - grounded code snippets from actual files
54
+ - exact OWASP Top 10 2025 mappings when applicable
55
+
56
+ If any required field is missing, resolve it from current context before writing. Do not invent data.
57
+
58
+ ### Step 2: Write YAML frontmatter
59
+
60
+ The frontmatter should use this shape:
61
+
62
+ ```yaml
63
+ ---
64
+ chat_session_id: "cursor-chat-7f3b2f44-8c4d-4d4a-9f1e-2b6a4c91b201"
65
+ workflow_name: "User Auth Hardening"
66
+ workflow_description: "Security improvements for auth flow handling in the current IDE session"
67
+ event_name: "Hardened AI IDE sync ingestion and workflow reuse"
68
+ summary: "This change moved AI IDE sync handling to the server so workflow resolution and event creation happen deterministically after markdown upload. We enforced stable session identity, validated required event fields, and added structured extraction for threats, guardrails, secure code snippets, and OWASP mappings. The endpoint now accepts markdown, returns immediately, and continues processing in the background."
69
+ external_id: "ai-ide-sync-2026-04-21-001"
70
+ ---
71
+ ```
72
+
73
+ ### Frontmatter rules
74
+
75
+ - `chat_session_id` is required.
76
+ - `summary` is required.
77
+ - At least one of `event_name` or `title` is required.
78
+ - `workflow_name` is optional.
79
+ - `workflow_description` is optional.
80
+ - `external_id` is optional.
81
+ - If `workflow_name` is omitted, the server may derive it from the title.
82
+ - Do not rely on `developer_name` or `developer_email` in markdown. The server ignores them when authenticated API identity exists.
83
+
84
+ ## Required Markdown Structure
85
+
86
+ Use this top-level shape:
87
+
88
+ ```md
89
+ # AI IDE Sync Event
90
+
91
+ ## Event Identity
92
+ ...
93
+
94
+ ## Summary
95
+ ...
96
+
97
+ # Threats Mitigated
98
+ ...
99
+
100
+ # Best Practices Achieved
101
+ ...
102
+
103
+ # Secure Code Snippets
104
+ ...
105
+
106
+ # Guardrails Applied
107
+ ...
108
+
109
+ # OWASP Top 10 2025 Mappings
110
+ ...
111
+ ```
112
+
113
+ The exact heading levels may vary slightly, but the section names should stay stable and obvious.
114
+
115
+ ## Event Identity Section
116
+
117
+ Preferred structure:
118
+
119
+ ```md
120
+ ## Event Identity
121
+
122
+ - chat_session_id: `cursor-chat-7f3b2f44-8c4d-4d4a-9f1e-2b6a4c91b201`
123
+ - workflow_name: `User Auth Hardening`
124
+ - event_name: `Hardened AI IDE sync ingestion and workflow reuse`
125
+ - external_id: `ai-ide-sync-2026-04-21-001`
126
+ ```
127
+
128
+ ## Summary Section
129
+
130
+ Repeat the summary in prose under `## Summary`.
131
+
132
+ ## Threats Mitigated Section
133
+
134
+ Each threat entry must include:
135
+
136
+ - `threat_name`
137
+ - `pwnisms_category`
138
+ - `severity`
139
+ - `mitigation_applied`
140
+
141
+ When code exists, include a `### Code Snippet` subsection with:
142
+
143
+ - `file_path`
144
+ - `language`
145
+ - `explanation`
146
+ - a fenced code block containing real code
147
+
148
+ Preferred example:
149
+
150
+ ````md
151
+ # Threats Mitigated
152
+
153
+ ## Threat 1
154
+
155
+ - threat_name: Missing session identity can attach events to the wrong workflow
156
+ - pwnisms_category: IAM
157
+ - severity: High
158
+ - mitigation_applied: The server now requires a stable `chat_session_id` and uses it to resolve or create the workflow before persisting the event.
159
+
160
+ ### Code Snippet
161
+
162
+ - file_path: `app/api/external_ai_ide.py`
163
+ - language: `python`
164
+ - explanation: This prevents ambiguous workflow association and ensures repeated syncs from the same IDE chat session map to the same workflow.
165
+
166
+ ```python
167
+ chat_session_id = str(
168
+ frontmatter.get("chat_session_id")
169
+ or extracted.chat_session_id
170
+ or ""
171
+ ).strip()
172
+
173
+ if not chat_session_id:
174
+ raise ValueError("chat_session_id could not be resolved from markdown content")
175
+ ```
176
+ ````
177
+
178
+ ### Threat authoring rules
179
+
180
+ - Keep each threat clearly separated.
181
+ - Do not collapse multiple threats into one malformed block.
182
+ - Always close fenced code blocks.
183
+ - Never let prose spill into the next threat entry.
184
+ - PWNISMS categories must be one of:
185
+ - `Product`
186
+ - `Workload`
187
+ - `Network`
188
+ - `IAM`
189
+ - `Secrets`
190
+ - `Monitoring`
191
+ - `Supply Chain`
192
+ - Severity should be one of:
193
+ - `Critical`
194
+ - `High`
195
+ - `Medium`
196
+ - `Low`
197
+
198
+ ## Best Practices Achieved Section
199
+
200
+ Use structured practice entries, not plain bullet-only lists.
201
+
202
+ ````md
203
+ # Best Practices Achieved
204
+
205
+ ## Practice 1
206
+ - practice_name: Stable workflow correlation
207
+ - description: Used a stable chat session identifier to ensure repeated syncs map to the same workflow.
208
+ - category: identity
209
+
210
+ ## Practice 2
211
+ - practice_name: Server-side workflow orchestration
212
+ - description: Kept workflow and event creation on the server instead of relying on IDE-side orchestration.
213
+ - category: design
214
+
215
+ ## Practice 3
216
+ - practice_name: Required field validation
217
+ - description: Validated required event identity fields before persisting security review data.
218
+ - category: validation
219
+ ````
220
+
221
+ ### Best practice rules
222
+
223
+ - Each entry should be introduced with a stable heading such as `## Practice 1`, `## Practice 2`, and so on.
224
+ - Each entry must include:
225
+ - `practice_name`
226
+ - `description`
227
+ - `category`
228
+ - Keep the values concise and grounded in the actual work completed.
229
+ - Do not turn this section into freeform paragraphs.
230
+ - Do not reduce this section to plain bullet statements without field names.
231
+
232
+ ## Secure Code Snippets Section
233
+
234
+ Each snippet entry should include:
235
+
236
+ - `file_path`
237
+ - `language`
238
+ - `explanation`
239
+ - a fenced code block with real code
240
+
241
+ Preferred example:
242
+
243
+ ````md
244
+ # Secure Code Snippets
245
+
246
+ ## Snippet 1
247
+
248
+ - file_path: `app/api/external_ai_ide.py`
249
+ - language: `python`
250
+ - explanation: This is security-relevant because it isolates background processing from the request lifecycle and ensures the authenticated developer identity is propagated server-side.
251
+
252
+ ```python
253
+ async def _process_ai_ide_markdown_ingestion(
254
+ *,
255
+ project_id: int,
256
+ ingestion_id: str,
257
+ filename: str,
258
+ markdown_text: str,
259
+ developer_name: str,
260
+ developer_email: str,
261
+ ) -> None:
262
+ logger.info(
263
+ "Starting AI IDE markdown ingestion %s for project %s",
264
+ ingestion_id,
265
+ project_id,
266
+ )
267
+ ```
268
+ ````
269
+
270
+ ### Secure snippet rules
271
+
272
+ - Snippets must be real code, not invented examples.
273
+ - Prefer snippets already cited in threat mitigations when they are strongly relevant.
274
+ - Use fenced code blocks.
275
+ - Keep snippets focused and bounded.
276
+
277
+ ## Guardrails Applied Section
278
+
279
+ Each guardrail entry must include:
280
+
281
+ - `title`
282
+ - `rule_type`
283
+ - `category`
284
+ - `instruction`
285
+ - `source`
286
+ - `satisfied`
287
+ - `notes`
288
+
289
+ Preferred example:
290
+
291
+ ```md
292
+ # Guardrails Applied
293
+
294
+ ## Guardrail 1
295
+
296
+ - title: Require stable AI IDE session identity
297
+ - rule_type: must
298
+ - category: Identity
299
+ - instruction: Every AI IDE sync markdown must include a stable chat_session_id so workflow association is deterministic.
300
+ - source: existing
301
+ - satisfied: true
302
+ - notes: Enforced during markdown ingestion before workflow lookup or workflow creation.
303
+ ```
304
+
305
+ ### Guardrail rules
306
+
307
+ - `rule_type` must be `must` or `must_not`.
308
+ - `source` must be `existing` or `ide_generated`.
309
+ - `satisfied` must be `true` or `false`.
310
+ - Do not drop shortlisted existing guardrails even when unsatisfied.
311
+ - If a guardrail was not fully satisfied, keep it and explain why in `notes`.
312
+
313
+ ## OWASP Top 10 2025 Mappings Section
314
+
315
+ Use exact IDs and names. Preferred structures:
316
+
317
+ ```md
318
+ # OWASP Top 10 2025 Mappings
319
+
320
+ - A07: Authentication Failures
321
+ - A06: Insecure Design
322
+ - A10: Mishandling of Exceptional Conditions
323
+ ```
324
+
325
+ or
326
+
327
+ ```md
328
+ # OWASP Top 10 2025 Mappings
329
+
330
+ - category_id: A07
331
+ category_name: Authentication Failures
332
+ - category_id: A06
333
+ category_name: Insecure Design
334
+ ```
335
+
336
+ Allowed values:
337
+
338
+ - `A01` Broken Access Control
339
+ - `A02` Security Misconfiguration
340
+ - `A03` Software Supply Chain Failures
341
+ - `A04` Cryptographic Failures
342
+ - `A05` Injection
343
+ - `A06` Insecure Design
344
+ - `A07` Authentication Failures
345
+ - `A08` Software or Data Integrity Failures
346
+ - `A09` Security Logging and Alerting Failures
347
+ - `A10` Mishandling of Exceptional Conditions
348
+
349
+ ## Validation Checklist Before Sync
350
+
351
+ Before calling `sync_ai_ide_markdown`, verify all of the following:
352
+
353
+ - The file is under `vibereview/`.
354
+ - You did not read sibling markdown files in `vibereview/`.
355
+ - `chat_session_id` exists in frontmatter.
356
+ - `summary` exists in frontmatter.
357
+ - `event_name` or `title` exists in frontmatter.
358
+ - The `Event Identity` section exists.
359
+ - The `Summary` section exists.
360
+ - The `Threats Mitigated` section exists.
361
+ - The `Best Practices Achieved` section exists.
362
+ - The `Secure Code Snippets` section exists.
363
+ - The `Guardrails Applied` section exists.
364
+ - The `OWASP Top 10 2025 Mappings` section exists.
365
+ - Every threat entry is structurally complete.
366
+ - Every code fence is closed.
367
+ - Every snippet is grounded in real code.
368
+ - Every guardrail entry is structurally complete.
369
+ - OWASP IDs and names are exact.
370
+
371
+ ## Final Step
372
+
373
+ After validation:
374
+
375
+ 1. Save the markdown artifact under `vibereview/`.
376
+ 2. Call `sync_ai_ide_markdown` directly with that file's contents or path, depending on the tool interface exposed by the host.
377
+ 3. If sync succeeds, report success briefly.
378
+ 4. If sync fails, report failure clearly and leave the markdown artifact intact for retry.
@@ -3,9 +3,10 @@ import { join } from 'node:path';
3
3
  import {
4
4
  GUARDRAILS_SELECTION_SKILL_REL_DIR,
5
5
  THREAT_MODELLING_SKILL_REL_DIR,
6
+ VIBEREVIEW_SYNC_SKILL_REL_DIR,
6
7
  } from '../../utils/constants.js';
7
8
  import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
8
- import { getRuleContent, getThreatModellingSkillContent } from './content.js';
9
+ import { getRuleContent, getThreatModellingSkillContent, getVibeReviewSyncSkillContent } from './content.js';
9
10
 
10
11
  function writeGeneratedText(filePath, content) {
11
12
  const action = existsSync(filePath) ? 'updated' : 'created';
@@ -30,7 +31,7 @@ function getCopilotSessionHookContent() {
30
31
  '1. Fetch Vibe Guardrails first using .github/skills/guardrails-selection/SKILL.md. Resolve the project, call get_guardrails, shortlist relevant guardrails, then call get_guardrail_by_id for the shortlist.',
31
32
  '2. Run PWNISMS threat modelling using .github/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
32
33
  '3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
33
- '4. Write a structured .md artifact under vibereview/, 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.',
34
+ '4. Read .github/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.',
34
35
  '',
35
36
  '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.',
36
37
  ].join('\n');
@@ -67,6 +68,7 @@ export function generate(cwd, options = {}) {
67
68
  ...options,
68
69
  guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.vscode,
69
70
  threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.vscode,
71
+ vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.vscode,
70
72
  };
71
73
  const filePath = join(cwd, '.github', 'copilot-instructions.md');
72
74
  const content = getRuleContent(optionsWithSkillDirs);
@@ -78,6 +80,12 @@ export function generate(cwd, options = {}) {
78
80
  getThreatModellingSkillContent(optionsWithSkillDirs),
79
81
  );
80
82
 
83
+ const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.vscode, 'SKILL.md');
84
+ const vibereviewSyncSkill = writeGeneratedText(
85
+ vibereviewSyncSkillPath,
86
+ getVibeReviewSyncSkillContent(optionsWithSkillDirs),
87
+ );
88
+
81
89
  const deletedLegacyAgent = removeGeneratedText(join(cwd, '.github', 'agents', 'ctm_sync.agent.md'));
82
90
 
83
91
  const hooksPath = join(cwd, '.github', 'hooks', 'srai-session-policy.json');
@@ -86,6 +94,7 @@ export function generate(cwd, options = {}) {
86
94
  return [
87
95
  { filePath, action, kind: 'rule' },
88
96
  { ...threatSkill, kind: 'skill' },
97
+ { ...vibereviewSyncSkill, kind: 'skill' },
89
98
  { ...hooks, kind: 'hooks' },
90
99
  ...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
91
100
  ];
@@ -13,6 +13,7 @@ test('VS Code Copilot generator writes instructions, skills, and hooks', () => {
13
13
  const expectedPaths = [
14
14
  '.github/copilot-instructions.md',
15
15
  '.github/skills/threat-modelling/SKILL.md',
16
+ '.github/skills/vibereview-sync/SKILL.md',
16
17
  '.github/hooks/srai-session-policy.json',
17
18
  ];
18
19
 
@@ -20,11 +21,12 @@ test('VS Code Copilot generator writes instructions, skills, and hooks', () => {
20
21
  assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
21
22
  }
22
23
 
23
- assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'hooks']);
24
+ assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'hooks']);
24
25
 
25
26
  const instructions = readFileSync(join(cwd, '.github/copilot-instructions.md'), 'utf8');
26
27
  assert.match(instructions, /\.github\/skills\/guardrails-selection\/SKILL\.md/);
27
28
  assert.match(instructions, /\.github\/skills\/threat-modelling\/SKILL\.md/);
29
+ assert.match(instructions, /\.github\/skills\/vibereview-sync\/SKILL\.md/);
28
30
  assert.match(instructions, /sync_ai_ide_markdown/);
29
31
  assert.match(instructions, /vibereview\//);
30
32
  assert.doesNotMatch(instructions, /\.cursor/);
@@ -37,8 +39,16 @@ test('VS Code Copilot generator writes instructions, skills, and hooks', () => {
37
39
  assert.match(threatSkill, /vibereview\//);
38
40
  assert.doesNotMatch(threatSkill, /get_project_profile_description/);
39
41
 
42
+ const vibereviewSkill = readFileSync(join(cwd, '.github/skills/vibereview-sync/SKILL.md'), 'utf8');
43
+ assert.match(vibereviewSkill, /Do not read other/i);
44
+ assert.match(vibereviewSkill, /sync_ai_ide_markdown/);
45
+ assert.match(vibereviewSkill, /Guardrails Applied/);
46
+ assert.match(vibereviewSkill, /Practice 1/);
47
+ assert.match(vibereviewSkill, /practice_name:/);
48
+
40
49
  const hooks = JSON.parse(readFileSync(join(cwd, '.github/hooks/srai-session-policy.json'), 'utf8'));
41
50
  assert.equal(hooks.hooks.SessionStart[0].type, 'command');
42
51
  assert.match(hooks.hooks.SessionStart[0].command, /vibereview/);
43
52
  assert.match(hooks.hooks.SessionStart[0].command, /sync_ai_ide_markdown/);
53
+ assert.match(hooks.hooks.SessionStart[0].command, /vibereview-sync\/SKILL\.md/);
44
54
  });
@@ -83,5 +83,13 @@ export const THREAT_MODELLING_SKILL_REL_DIR = {
83
83
  codex: '.codex/skills/threat-modelling',
84
84
  };
85
85
 
86
+ /** Relative workspace dirs for the VibeReview markdown sync skill (per IDE / CLI). */
87
+ export const VIBEREVIEW_SYNC_SKILL_REL_DIR = {
88
+ cursor: '.cursor/skills/vibereview-sync',
89
+ claude: '.claude/skills/vibereview-sync',
90
+ vscode: '.github/skills/vibereview-sync',
91
+ codex: '.codex/skills/vibereview-sync',
92
+ };
93
+
86
94
  export const SENTINEL_START = '<!-- securityreview-kit:start -->';
87
95
  export const SENTINEL_END = '<!-- securityreview-kit:end -->';