@securityreviewai/securityreview-kit 0.1.40 → 0.1.42

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
@@ -28,7 +28,7 @@ npx @securityreviewai/securityreview-kit init --switch-project
28
28
  | Target | Flag | MCP Config | Workspace Rule |
29
29
  |---|---|---|---|
30
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` | `.claude/settings.json` | `CLAUDE.md`, `.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` |
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
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` |
33
33
  | Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
34
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` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
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",
@@ -657,7 +657,7 @@ export async function initCommand(options) {
657
657
  claudeProfilerAuth = await resolveClaudeProfilerAuth(options, interactive, cwd);
658
658
  console.log(
659
659
  chalk.dim(
660
- ` Claude Code: profiling uses \`.claude/settings.json\`, the configured MCP server, and \`${claudeProfilerAuth.model}\` for this run.`,
660
+ ` Claude Code: profiling uses \`.mcp.json\`, \`.claude/settings.json\`, bypassed tool prompts for this profiling pass, and \`${claudeProfilerAuth.model}\` for this run.`,
661
661
  ),
662
662
  );
663
663
  console.log(chalk.dim(` Auth mode: ${claudeProfilerAuth.summary}.`));
@@ -819,7 +819,7 @@ export async function initCommand(options) {
819
819
  );
820
820
  console.log(
821
821
  chalk.dim(
822
- ' • MCP missing: re-run init with Claude Code selected and MCP installation enabled so `.claude/settings.json` is written.',
822
+ ' • MCP missing: re-run init with Claude Code selected and MCP installation enabled so `.mcp.json` is written and `.claude/settings.json` enables `security-review-mcp`.',
823
823
  ),
824
824
  );
825
825
  } else if (agentTarget === 'codex') {
@@ -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. Invoke the ctm_sync agent using .claude/agents/ctm_sync.md after implementation or threat-model updates.',
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.',
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, ctm_sync last.',
16
16
  ].join('\n');
@@ -29,17 +29,17 @@ function getClaudeSessionStartHooks() {
29
29
  }
30
30
 
31
31
  /**
32
- * Generate Claude Code MCP config at .claude/settings.json
32
+ * Generate Claude Code project MCP config at .mcp.json and project settings at .claude/settings.json
33
33
  */
34
34
  export function generate(cwd, envVars) {
35
- const filePath = join(cwd, '.claude', 'settings.json');
36
- const existing = readJson(filePath) || {};
35
+ const mcpPath = join(cwd, '.mcp.json');
36
+ const mcpConfig = readJson(mcpPath) || {};
37
37
 
38
- if (!existing.mcpServers) {
39
- existing.mcpServers = {};
38
+ if (!mcpConfig.mcpServers) {
39
+ mcpConfig.mcpServers = {};
40
40
  }
41
41
 
42
- existing.mcpServers[MCP_SERVER_NAME] = {
42
+ mcpConfig.mcpServers[MCP_SERVER_NAME] = {
43
43
  command: 'npx',
44
44
  args: ['-y', `${MCP_SERVER_PACKAGE}@latest`],
45
45
  env: {
@@ -48,6 +48,17 @@ export function generate(cwd, envVars) {
48
48
  },
49
49
  };
50
50
 
51
+ writeJson(mcpPath, mcpConfig);
52
+
53
+ const settingsPath = join(cwd, '.claude', 'settings.json');
54
+ const existing = readJson(settingsPath) || {};
55
+ const enabledServers = Array.isArray(existing.enabledMcpjsonServers)
56
+ ? existing.enabledMcpjsonServers.filter((name) => typeof name === 'string' && name.trim())
57
+ : [];
58
+ if (!enabledServers.includes(MCP_SERVER_NAME)) {
59
+ existing.enabledMcpjsonServers = [...enabledServers, MCP_SERVER_NAME];
60
+ }
61
+
51
62
  const existingSessionStart = Array.isArray(existing.SessionStart) ? existing.SessionStart : [];
52
63
  const marker = 'MANDATORY SECURITY GATE (Claude Code Session Policy)';
53
64
  const ours = getClaudeSessionStartHooks();
@@ -57,6 +68,6 @@ export function generate(cwd, envVars) {
57
68
  );
58
69
  existing.SessionStart = hasOurs ? existingSessionStart : [...existingSessionStart, ...ours];
59
70
 
60
- writeJson(filePath, existing);
61
- return filePath;
71
+ writeJson(settingsPath, existing);
72
+ return mcpPath;
62
73
  }
@@ -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 MCP generator writes mcp server and session hooks', () => {
8
+ test('Claude MCP generator writes .mcp.json, enables the project MCP server, and adds session hooks', () => {
9
9
  const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-mcp-'));
10
10
 
11
11
  generate(cwd, {
@@ -13,10 +13,12 @@ test('Claude MCP generator writes mcp server and session hooks', () => {
13
13
  apiToken: 'secret-token',
14
14
  });
15
15
 
16
- const config = JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8'));
17
- assert.equal(config.mcpServers['security-review-mcp'].command, 'npx');
18
- assert.equal(Array.isArray(config.SessionStart), true);
19
- assert.match(config.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
16
+ const mcpConfig = JSON.parse(readFileSync(join(cwd, '.mcp.json'), 'utf8'));
17
+ const settings = JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8'));
18
+ assert.equal(mcpConfig.mcpServers['security-review-mcp'].command, 'npx');
19
+ assert.deepEqual(settings.enabledMcpjsonServers, ['security-review-mcp']);
20
+ assert.equal(Array.isArray(settings.SessionStart), true);
21
+ assert.match(settings.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
20
22
  });
21
23
 
22
24
  test('Claude MCP generator preserves existing SessionStart hooks', () => {
@@ -50,4 +52,5 @@ test('Claude MCP generator preserves existing SessionStart hooks', () => {
50
52
  assert.equal(config.SessionStart.length, 2);
51
53
  assert.match(config.SessionStart[0].hooks[0].prompt, /Existing hook/);
52
54
  assert.match(config.SessionStart[1].hooks[0].prompt, /MANDATORY SECURITY GATE/);
55
+ assert.deepEqual(config.enabledMcpjsonServers, ['security-review-mcp']);
53
56
  });
@@ -1,12 +1,14 @@
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
  CTM_SYNC_AGENT_REL_PATH,
5
5
  GUARDRAILS_PROFILER_SKILL_REL_DIR,
6
6
  GUARDRAILS_SELECTION_SKILL_REL_DIR,
7
+ SENTINEL_END,
8
+ SENTINEL_START,
7
9
  THREAT_MODELLING_SKILL_REL_DIR,
8
10
  } from '../../utils/constants.js';
9
- import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
11
+ import { readText, upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
10
12
  import {
11
13
  getCtmSyncWorkflowContent,
12
14
  getGuardrailsInitProfileContent,
@@ -40,7 +42,7 @@ function getClaudeCtmSyncAgentContent(options = {}) {
40
42
  }
41
43
 
42
44
  /**
43
- * Generate Claude Code workspace rule — appends to CLAUDE.md
45
+ * Generate Claude Code workspace rule — appends to .claude/CLAUDE.md
44
46
  */
45
47
  export function generate(cwd, options = {}) {
46
48
  const optionsWithSkillDirs = {
@@ -49,10 +51,25 @@ export function generate(cwd, options = {}) {
49
51
  threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.claude,
50
52
  ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.claude,
51
53
  };
52
- const filePath = join(cwd, 'CLAUDE.md');
54
+ const legacyRootPath = join(cwd, 'CLAUDE.md');
55
+ const filePath = join(cwd, '.claude', 'CLAUDE.md');
53
56
  const content = getRuleContent(optionsWithSkillDirs);
54
57
  const action = upsertSentinelBlock(filePath, content);
55
58
 
59
+ const legacyContent = readText(legacyRootPath);
60
+ if (legacyContent.includes(SENTINEL_START) && legacyContent.includes(SENTINEL_END)) {
61
+ const startIdx = legacyContent.indexOf(SENTINEL_START);
62
+ const endIdx = legacyContent.indexOf(SENTINEL_END);
63
+ const before = legacyContent.substring(0, startIdx);
64
+ const after = legacyContent.substring(endIdx + SENTINEL_END.length);
65
+ const migrated = `${before}${after}`.trim();
66
+ if (migrated) {
67
+ writeText(legacyRootPath, migrated + '\n');
68
+ } else if (existsSync(legacyRootPath)) {
69
+ unlinkSync(legacyRootPath);
70
+ }
71
+ }
72
+
56
73
  const threatSkillPath = join(cwd, THREAT_MODELLING_SKILL_REL_DIR.claude, 'SKILL.md');
57
74
  const threatSkill = writeGeneratedText(
58
75
  threatSkillPath,
@@ -1,17 +1,17 @@
1
- import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
1
+ import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  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.md, threat skill, ctm_sync agent, and profiling command', () => {
8
+ test('Claude generator writes .claude/CLAUDE.md, threat skill, ctm_sync agent, and profiling command', () => {
9
9
  const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-'));
10
10
 
11
11
  const results = generate(cwd, { projectName: 'SmokeProject' });
12
12
 
13
13
  const expectedPaths = [
14
- 'CLAUDE.md',
14
+ '.claude/CLAUDE.md',
15
15
  '.claude/skills/threat-modelling/SKILL.md',
16
16
  '.claude/agents/ctm_sync.md',
17
17
  '.claude/commands/guardrails-init-profile.md',
@@ -23,7 +23,7 @@ test('Claude generator writes CLAUDE.md, threat skill, ctm_sync agent, and profi
23
23
 
24
24
  assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'agent', 'command']);
25
25
 
26
- const instructions = readFileSync(join(cwd, 'CLAUDE.md'), 'utf8');
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
29
  assert.match(instructions, /\.claude\/agents\/ctm_sync\.md/);
@@ -36,4 +36,26 @@ test('Claude generator writes CLAUDE.md, threat skill, ctm_sync agent, and profi
36
36
  assert.match(agent, /^---\nname: ctm_sync/m);
37
37
  assert.match(agent, /model: inherit/);
38
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);
45
+ });
46
+
47
+ test('Claude generator migrates the kit-managed root CLAUDE.md block into .claude/CLAUDE.md', () => {
48
+ const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-migrate-'));
49
+ const legacyPath = join(cwd, 'CLAUDE.md');
50
+
51
+ writeFileSync(
52
+ legacyPath,
53
+ '<!-- securityreview-kit:start -->\nold managed block\n<!-- securityreview-kit:end -->\n',
54
+ 'utf8',
55
+ );
56
+
57
+ generate(cwd, { projectName: 'SmokeProject' });
58
+
59
+ assert.equal(existsSync(join(cwd, '.claude/CLAUDE.md')), true);
60
+ assert.equal(existsSync(legacyPath), false);
39
61
  });
@@ -29,7 +29,7 @@ function getCodexSessionHookContent() {
29
29
  '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
30
  '2. Run PWNISMS threat modelling using .codex/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
31
31
  '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.',
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.',
33
33
  '',
34
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.',
35
35
  ].join('\n');
@@ -42,6 +42,12 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
42
42
  assert.match(agent, /name = "ctm_sync"/);
43
43
  assert.match(agent, /developer_instructions = '''/);
44
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);
45
51
 
46
52
  const hooks = JSON.parse(readFileSync(join(cwd, '.codex/hooks.json'), 'utf8'));
47
53
  assert.equal(hooks.hooks.SessionStart[0].hooks[0].type, 'command');
@@ -28,8 +28,8 @@ For any task that touches auth, authorization, input handling, secrets, network,
28
28
 
29
29
  4. **Run CTM sync last.**
30
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 detailed payload contract.
32
- - Pass the current threat model summary, the stable `chat_session_id`, the exact existing guardrails shortlisted earlier, and any `ide_generated` guardrails.
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
33
  - Do not re-query guardrails during CTM sync; reuse the shortlist selected before implementation.
34
34
 
35
35
  ## When To Skip
@@ -50,5 +50,5 @@ The normal coding workflow is guardrails selection, PWNISMS threat modelling, se
50
50
  |---|---|
51
51
  | Project resolution | `find_project_by_name`, `list_projects`, `create_project`, `get_project` |
52
52
  | Guardrails | `get_guardrails`, `get_guardrail_by_id` |
53
- | CTM sync | `create_ai_ide_workflow`, `create_ai_ide_event`, `get_current_user` or equivalent user identity tool |
53
+ | CTM sync | `sync_ai_ide_markdown` |
54
54
  | Profiler only | `update_vibe_profile`, `write_default_pack` are used by init-time profiling, not normal coding tasks |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ctm_sync
3
- description: Command/workflow triggered whenever a threat model is generated or updated, or guardrails are proposed/modified. Builds and uploads CTM sync details and guardrail updates through security-review-mcp.
3
+ description: Command/workflow triggered whenever a threat model is generated or updated, or guardrails are proposed/modified. Writes a grounded AI IDE sync markdown file and uploads it through security-review-mcp.
4
4
  ---
5
5
 
6
6
  # CTM Sync Workflow
@@ -9,114 +9,167 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
9
9
 
10
10
  When invoked:
11
11
 
12
- 0. Verify `create_ai_ide_event` and `create_ai_ide_workflow` exist in `security-review-mcp`. If a `list_*` tool for AI IDE workflows exists, prefer it for workflow discovery.
13
- 1. Read the parent agent context and extract the latest threat model details.
14
- 2. **Chat session identity (required)** — The event payload MUST include a stable `chat_session_id` for the current IDE chat session:
15
- - Use a session identifier supplied by the host environment when one exists (e.g. conversation or session id from the IDE/agent runtime).
16
- - If none exists, use a single UUID (or equivalent) generated **once** at the start of this chat and reused for every `ctm_sync` in the same session. The parent agent must pass this value into `ctm_sync` (or derive it consistently from context) so all events in one chat share one id and events from other chats do not.
17
- 3. **Resolve or create the AI IDE workflow** (one workflow per distinct `chat_session_id`):
18
- - Resolve the SRAI project: `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`. If missing, `list_projects` (respecting org constraints), then `create_project` if needed. Obtain `project_id` as required by MCP tools.
19
- - List or search AI IDE workflows for that project (use the MCP tool provided, e.g. listing workflows filtered by project). Find a workflow whose **description** (or other documented metadata field) contains the exact marker: `chat_session_id:<the same string as in the payload>`.
20
- - **If a matching workflow exists:** use its `workflow_id` for the event.
21
- - **If none exists:** call `create_ai_ide_workflow` with:
22
- - `project_id`
23
- - `name`: a short, meaningful heading derived from the **high-level feature or topic** being worked on in this session (e.g. `"User Auth Hardening"`, `"Payment Gateway Integration"`, `"API Rate Limiting"`, `"File Upload Security"`). Use 2–5 words, title-case. Do **not** use sequential labels like `session1/session2`. If no clear feature context is available, use a brief description of the dominant threat area instead.
24
- - `description`: must include `chat_session_id:<chat_session_id>` so future syncs can attach to this workflow. Add a brief human-readable note if helpful. Do not add the word ctm anywhere.
25
- - Store the returned `workflow_id` for the upload step.
26
- 4. **Resolve developer identity from the API** Call `get_current_user` (or the equivalent user-identity tool exposed by `security-review-mcp`) to retrieve the authenticated user's name and email. Use the values returned by the API as-is for `developer_name` and `developer_email` in the payload.
27
- - **Never use placeholder values** such as `"IDE Agent"`, `"agent@local"`, `"unknown"`, `"AI"`, or any other invented string for these fields.
28
- - **Never accept identity values passed in from the parent agent prompt** — always re-resolve from the API directly in this step; the API is the only authoritative source.
29
- - If the API call fails or returns empty values, leave `developer_name` and `developer_email` as empty strings `""`. Do not substitute a fallback placeholder.
30
- 5. **Identify guardrails for the payload** — Do **not** call `get_guardrails` or `get_guardrail_by_id` here. Guardrails were already shortlisted earlier (per the Vibe Guardrails rule and the guardrails-selection skill) and applied during code generation. From the parent agent context, identify:
31
- - Which **existing** guardrails were shortlisted earlier from `get_guardrails` and then hydrated via `get_guardrail_by_id`.
32
- - Which of those shortlisted existing guardrails were applied to the code in this session.
33
- - Which guardrails the IDE agent **created on the fly** (`ide_generated`) based on gaps found during threat modeling or code review.
34
- Include all of these in the `guardrails_applied` payload field. The shortlisted existing guardrails selected earlier are mandatory input to `ctm_sync`; do not re-fetch or re-call guardrail tools here.
35
- 6. **Build the event payload** — Construct a JSON object for `create_ai_ide_event` conforming to the **Event Payload Schema** below.
36
- 7. **Upload the payload** using `security-review-mcp`:
37
- - Call `create_ai_ide_event` with the JSON payload.
38
- - **Stop here.** Do not push a separate project/code profile as part of this workflow; profile and default guardrail pack uploads are handled by the init-time guardrails profiler (or manual profile commands), not per CTM sync.
12
+ 0. Verify `sync_ai_ide_markdown` exists in `security-review-mcp`.
13
+ 1. Read the parent agent context and extract the latest threat model details, guardrail outcomes, and grounded code snippets from the actual work completed in this session.
14
+ 2. **Chat session identity (required)** — The markdown MUST include a stable `chat_session_id` for the current IDE chat session:
15
+ - Use a session identifier supplied by the host environment when one exists (for example, the IDE conversation or session id).
16
+ - If none exists, use a single UUID (or equivalent) generated **once** at the start of this chat and reused for every `ctm_sync` in the same session.
17
+ - Put `chat_session_id` in frontmatter whenever possible. The value must exist somewhere in the markdown even if frontmatter is unavailable.
18
+ 3. **Create a structured markdown artifact** — `ctm_sync` is now a thin sync shim. Do not build a JSON payload and do not perform workflow lookup or creation client-side. Instead, write a `.md` file under `vibereview/` so all sync artifacts stay in one predictable place. Create the directory if it does not already exist. The markdown file should give the server everything it needs to extract the structured event.
19
+ 4. **Populate the markdown with grounded content**:
20
+ - Include the exact shortlisted existing guardrails selected earlier plus any `ide_generated` guardrails created during the session.
21
+ - Use real code snippets from the files changed or inspected in this session. Never invent code or mitigation text.
22
+ - Keep threat names, severities, PWNISMS categories, guardrail satisfaction, and OWASP mappings aligned with the actual work performed.
23
+ 5. **Upload the markdown**:
24
+ - Call `sync_ai_ide_markdown` with the markdown artifact.
25
+ - The server is responsible for normalizing author identity, deriving a workflow name from the title when needed, and extracting structured output from the markdown.
26
+ 6. **Stop there** Do not call `create_ai_ide_event`, `create_ai_ide_workflow`, or client-side user identity tools from `ctm_sync`.
39
27
 
40
28
  ---
41
29
 
42
- ## Event Payload Schema
43
-
44
- The `create_ai_ide_event` payload MUST be a JSON object with the following structure. Use this exact schema do not add, rename, or omit required keys.
45
-
46
- ```json
47
- {
48
- "workflow_id": "<string — resolved or newly created AI IDE workflow id>",
49
- "chat_session_id": "<string — stable session identifier, same for all events in this chat>",
50
- "title": "<string — concise title describing what was threat-modeled or implemented, 5-15 words>",
51
- "summary": "<string — 2-5 sentence summary of the threat model findings, key risks identified, mitigations applied, and any guardrails enforced>",
52
- "developer_name": "<string from API/user context provided by MCP or host runtime>",
53
- "developer_email": "<string — from API/user context provided by MCP or host runtime>",
54
- "threats_mitigated": [
55
- {
56
- "threat_name": "<string short threat title>",
57
- "pwnisms_category": "<string — one of: Product, Workload, Network, IAM, Secrets, Monitoring, Supply Chain>",
58
- "severity": "<string — Critical | High | Medium | Low>",
59
- "mitigation_applied": "<string — what was done to address the threat>",
60
- "code_snippet": {
61
- "file_path": "<string relative path to the actual source file where mitigation is implemented>",
62
- "language": "<string programming language>",
63
- "snippet": "<string the exact source code lines implementing the mitigation, max 30 lines, must be grounded in the actual codebase not invented>",
64
- "explanation": "<string how this specific code addresses the threat>"
65
- }
66
- }
67
- ],
68
- "best_practises_achieved": [
69
- "<string — each entry is a concise statement of a security best practice that was followed during implementation>"
70
- ],
71
- "secure_code_snippets": [
72
- {
73
- "file_path": "<string relative path to the file>",
74
- "language": "<string programming language>",
75
- "snippet": "<string — the security-relevant code snippet, max 50 lines>",
76
- "explanation": "<string why this snippet is security-relevant and what it protects against>"
77
- }
78
- ],
79
- "guardrails_applied": [
80
- {
81
- "title": "<string — guardrail title>",
82
- "rule_type": "<string must | must_not>",
83
- "category": "<string | null — grouping label>",
84
- "instruction": "<string the actionable coding directive>",
85
- "source": "<string — 'existing' if selected earlier from project guardrails, 'ide_generated' if newly created by the IDE agent>",
86
- "satisfied": "<boolean — true if the guardrail was fully satisfied, false if partially or not satisfied>",
87
- "notes": "<string optional: how it was applied, why it could not be fully satisfied, or rationale for a new guardrail>"
88
- }
89
- ],
90
- "owasp_top_10_2025_mappings": [
91
- {
92
- "category_id": "<string — OWASP Top 10 2025 category ID, e.g. A01>",
93
- "category_name": "<string OWASP Top 10 2025 category name, e.g. Broken Access Control>"
30
+ ## Markdown Contract
31
+
32
+ The markdown file must be a `.md` document written under `vibereview/`, with frontmatter when available and clear, structured sections underneath. Keep the structure deterministic so the server can parse it reliably.
33
+
34
+ Recommended path pattern:
35
+
36
+ ```text
37
+ vibereview/<chat_session_id>-<slugified-title-or-event-name>.md
38
+ ```
39
+
40
+ If a safe filename cannot be derived, use any stable `.md` filename inside `vibereview/`.
41
+
42
+ ### Frontmatter
43
+
44
+ Use YAML frontmatter at the top of the file whenever possible:
45
+
46
+ ```yaml
47
+ ---
48
+ chat_session_id: "<required stable session id>"
49
+ workflow_name: "<optional short workflow name>"
50
+ workflow_description: "<optional workflow description>"
51
+ title: "<required event title if event_name is omitted>"
52
+ event_name: "<required event name if title is omitted>"
53
+ summary: "<required concise summary>"
54
+ ---
55
+ ```
56
+
57
+ Rules:
58
+
59
+ - `chat_session_id` must exist somewhere in the markdown, but frontmatter is preferred.
60
+ - `workflow_name` is optional. If omitted, the server derives it from the title.
61
+ - `workflow_description` is optional.
62
+ - Either `event_name` or `title` must exist. Including both is allowed.
63
+ - `summary` must exist.
64
+ - `developer_name` and `developer_email` in the markdown are ignored. The server always uses the authenticated API user.
65
+
66
+ ### Body sections
67
+
68
+ Use clear Markdown headings. The exact wording below is preferred because it keeps parsing simple and consistent.
69
+
70
+ #### Best Practices Achieved
71
+
72
+ Use plain bullet strings:
73
+
74
+ ```md
75
+ ## Best Practices Achieved
76
+
77
+ - Enforced server-side authorization before updating workflow state.
78
+ - Used parameterized queries for threat-model persistence.
79
+ ```
80
+
81
+ Internally the server accepts both `best_practises_achieved` and `best_practices_achieved`, but authored markdown should prefer a normal `Best Practices Achieved` section with bullets.
82
+
83
+ #### Threats Mitigated
84
+
85
+ Each threat entry should include all of the following:
86
+
87
+ - `threat_name`
88
+ - `pwnisms_category`
89
+ - `severity`
90
+ - `mitigation_applied`
91
+ - grounded code snippet info if available
92
+
93
+ Preferred structure:
94
+
95
+ ```md
96
+ ## Threats Mitigated
97
+
98
+ ### Threat 1
99
+ - threat_name: Missing authorization check on workflow sync endpoint
100
+ - pwnisms_category: IAM
101
+ - severity: High
102
+ - mitigation_applied: Added server-side authorization before allowing markdown sync writes.
103
+ - file_path: src/routes/sync.js
104
+ - language: javascript
105
+ - snippet: |
106
+ if (!user || !user.canSyncWorkflows) {
107
+ throw new ForbiddenError('Not allowed to sync workflow markdown');
94
108
  }
95
- ]
96
- }
109
+ - explanation: Prevents unauthorized users from creating or updating AI IDE sync events.
110
+ ```
111
+
112
+ Notes:
113
+
114
+ - Snippets must be real code from the repo or actual generated output, not invented text.
115
+ - If no code snippet exists for a threat, omit the snippet block and explain the mitigation in prose.
116
+
117
+ #### Secure Code Snippets
118
+
119
+ Include security-relevant snippets that may or may not map 1:1 with a threat:
120
+
121
+ ```md
122
+ ## Secure Code Snippets
123
+
124
+ ### Snippet 1
125
+ - file_path: src/server/markdown-sync.ts
126
+ - language: typescript
127
+ - snippet: |
128
+ const markdown = await fs.readFile(inputPath, 'utf8');
129
+ await securityReview.syncAiIdeMarkdown({ markdown });
130
+ - explanation: Sends the structured markdown artifact directly to the server-side sync pipeline.
131
+ ```
132
+
133
+ #### Guardrails Applied
134
+
135
+ Each guardrail should include:
136
+
137
+ - `title`
138
+ - `rule_type` as `must` or `must_not`
139
+ - `source` as `existing` or `ide_generated`
140
+ - `satisfied` as `true` or `false`
141
+
142
+ Preferred structure:
143
+
144
+ ```md
145
+ ## Guardrails Applied
146
+
147
+ ### Guardrail 1
148
+ - title: Enforce authorization on workflow updates
149
+ - rule_type: must
150
+ - category: authorization
151
+ - instruction: Require an authenticated, authorized user before mutating workflow records.
152
+ - source: existing
153
+ - satisfied: true
154
+ - notes: Applied in the server-side markdown sync handler.
97
155
  ```
98
156
 
99
- ### Field rules
157
+ The exact shortlist from earlier in the session must be carried forward. Do not call `get_guardrails` or `get_guardrail_by_id` inside `ctm_sync`.
158
+
159
+ #### OWASP Top 10 2025 Mappings
100
160
 
101
- | Field | Required | Notes |
102
- |---|---|---|
103
- | `workflow_id` | Yes | From step 3 |
104
- | `chat_session_id` | Yes | From step 2 |
105
- | `title` | Yes | 5-15 words, descriptive |
106
- | `summary` | Yes | 2-5 sentences |
107
- | `developer_name` | Yes | From API/user context (never read from git config) |
108
- | `developer_email` | Yes | From API/user context (never read from git config) |
109
- | `threats_mitigated` | Yes | Array, may be empty `[]` if no threats were identified. Each entry must include a `code_snippet` grounded in actual source code |
110
- | `best_practises_achieved` | Yes | Array of strings, may be empty `[]` |
111
- | `secure_code_snippets` | Yes | Array, may be empty `[]` |
112
- | `guardrails_applied` | Yes | Array of all guardrails enforced during this session — both existing ones shortlisted earlier from project guardrails and new ones the IDE agent created. Use `source` to distinguish origin. Empty `[]` if none |
113
- | `owasp_top_10_2025_mappings` | Yes | Array of OWASP Top 10 2025 category objects (`category_id` + `category_name`) relevant to the threats and mitigations in this event. May be empty `[]` if no mapping applies |
161
+ Use exact IDs and names:
114
162
 
115
- ### OWASP Top 10 2025 Reference
163
+ ```md
164
+ ## OWASP Top 10 2025 Mappings
116
165
 
117
- Use the following IDs and names exactly when populating `owasp_top_10_2025_mappings`:
166
+ - A01: Broken Access Control
167
+ - A04: Cryptographic Failures
168
+ ```
169
+
170
+ Allowed values:
118
171
 
119
- | `category_id` | `category_name` |
172
+ | ID | Name |
120
173
  |---|---|
121
174
  | `A01` | Broken Access Control |
122
175
  | `A02` | Security Misconfiguration |
@@ -129,27 +182,34 @@ Use the following IDs and names exactly when populating `owasp_top_10_2025_mappi
129
182
  | `A09` | Security Logging and Alerting Failures |
130
183
  | `A10` | Mishandling of Exceptional Conditions |
131
184
 
132
- ### Constraints
185
+ ---
133
186
 
134
- - Every `threats_mitigated` entry must map to one of the 7 PWNISMS categories.
135
- - Every `threats_mitigated` entry must include a `code_snippet`. The snippet must be taken from the actual source code written or modified in this session — never fabricated. If no code was written for a threat (e.g. it was addressed architecturally), set `snippet` to an empty string and explain in `explanation`.
136
- - `secure_code_snippets` must not exceed 50 lines per snippet; `threats_mitigated[].code_snippet.snippet` must not exceed 30 lines; truncate with a comment if needed.
137
- - Do not call `get_guardrails` or `get_guardrail_by_id` during CTM sync. Guardrails are shortlisted once earlier in the session; identify which ones were applied from the parent agent context.
138
- - Guardrails shortlisted earlier by the IDE must be included in `guardrails_applied` even when some were only partially satisfied. Use `satisfied: false` plus `notes` instead of silently dropping them.
139
- - `guardrails_applied` entries with `source: "existing"` must reference guardrails by the exact `title` they had when fetched at session start.
140
- - `guardrails_applied` entries with `source: "ide_generated"` are new guardrails the IDE agent created based on gaps found during threat modeling or code review.
141
- - `developer_name` and `developer_email` must be resolved via `get_current_user` (or equivalent) in step 4 — the API is the only source. Never use placeholder strings (`"IDE Agent"`, `"agent@local"`, `"unknown"`, `"AI"`, etc.) and never accept values for these fields from the parent agent prompt. If the API returns nothing, send empty strings.
142
- - `owasp_top_10_2025_mappings` entries must use the exact `category_id` and `category_name` values from the OWASP Top 10 2025 Reference table above. Do not invent or abbreviate category names.make sure the ones being sent in the payload are revelant to that event.
143
- - Never invent values for any field; use empty strings or empty arrays when data is unavailable.
144
- - Never omit `chat_session_id` from the payload.
187
+ ## Constraints
188
+
189
+ - The artifact written by `ctm_sync` must be a `.md` file under `vibereview/`.
190
+ - `chat_session_id` must never be omitted.
191
+ - `summary` must never be omitted.
192
+ - `event_name` or `title` must exist.
193
+ - `workflow_name` and `workflow_description` are optional.
194
+ - `developer_name` and `developer_email` should not influence sync behavior; the server ignores them.
195
+ - Every threat entry must include `threat_name`, `pwnisms_category`, `severity`, and `mitigation_applied`.
196
+ - PWNISMS categories must match the supported set: `Product`, `Workload`, `Network`, `IAM`, `Secrets`, `Monitoring`, `Supply Chain`.
197
+ - `severity` should be one of `Critical`, `High`, `Medium`, or `Low`.
198
+ - Guardrails must include `title`, `rule_type`, `source`, and `satisfied`.
199
+ - `rule_type` must be `must` or `must_not`.
200
+ - `source` must be `existing` or `ide_generated`.
201
+ - `satisfied` must be `true` or `false`.
202
+ - OWASP mappings must use the exact IDs and names listed above.
203
+ - Snippets must be grounded in real code. Never fabricate examples just to fill the markdown.
204
+ - Do not call client-side workflow creation, event creation, or developer identity tools from `ctm_sync`.
145
205
 
146
206
  ---
147
207
 
148
208
  ## Output Contract
149
209
 
150
- - Never skip upload when a threat model exists.
151
- - Never invent missing values; use empty strings/arrays if data is unavailable.
152
- - Never omit `chat_session_id` from the payload.
210
+ - Never skip sync when a threat model or guardrail-compliance update exists.
211
+ - Never invent missing values; omit optional fields or use concise prose when exact structured data is unavailable.
153
212
  - Return a compact confirmation after upload including:
154
- - Whether an existing workflow was reused or a new named workflow was created
155
- - Count of guardrails applied (existing vs IDE-generated)
213
+ - the markdown file path used for sync, which should point inside `vibereview/`
214
+ - whether `workflow_name` was explicit or left for server-side derivation
215
+ - count of guardrails included (existing vs IDE-generated)
@@ -67,14 +67,15 @@ This includes both:
67
67
  - Where the latest threat content can be found (e.g., "from this conversation" or "from the last threat modeling step")
68
68
  - The `chat_session_id` value to use for this sync
69
69
  - Whether guardrails were applied (existing and/or IDE-generated)
70
- - **Do NOT include developer name or email in the handoff prompt.** The `ctm_sync` agent resolves user identity directly from the API. Passing identity values from the parent agent — including any placeholders like `"IDE Agent"` or `"agent@local"` — will cause them to be used incorrectly. Leave identity resolution entirely to `ctm_sync`.
70
+ - That `ctm_sync` should write a structured `.md` artifact and upload it via `sync_ai_ide_markdown`
71
+ - **Do NOT include developer name or email in the handoff prompt.** Any identity fields in the markdown are ignored server-side; the authenticated API user is authoritative.
71
72
 
72
73
  **Example invocation (conceptual, not literal code):**
73
74
 
74
75
  Use Task tool with:
75
76
  - subagent_type: "ctm_sync"
76
77
  - description: "Sync latest threat model for <system/component>"
77
- - prompt: Brief summary of what was threat-modeled, what artifacts were produced (threat scenarios, countermeasures, components, data dictionary, guardrails applied/proposed, etc.), and that the agent should upload/update CTM data accordingly.
78
+ - prompt: Brief summary of what was threat-modeled, what artifacts were produced (threat scenarios, countermeasures, components, data dictionary, guardrails applied/proposed, etc.), and that the agent should write the sync markdown and upload it accordingly.
78
79
  - readonly: false (it must be allowed to write/update CTM data)
79
80
 
80
81
  3. Let `ctm_sync` complete its work
@@ -33,9 +33,9 @@ You can still sign in manually with `agent login` (or `cursor-agent login`). To
33
33
 
34
34
  ## Claude Code CLI (scripted)
35
35
 
36
- From the repo root, non-interactive runs should execute with the project settings file and the Haiku model for this profiling pass:
36
+ From the repo root, non-interactive runs should execute with the project settings file, the project `.mcp.json` server config, explicit MCP-only loading, bypassed tool prompts for the profiling pass, and the Haiku model:
37
37
 
38
- `claude -p "<your profiling instructions>" --settings .claude/settings.json --model haiku`
38
+ `claude -p "<your profiling instructions>" --settings .claude/settings.json --mcp-config "$(cat .mcp.json)" --strict-mcp-config --permission-mode bypassPermissions --model haiku`
39
39
 
40
40
  During `securityreview-kit init`, choose **Yes** when asked to run Claude Code login, or pass **`--profiler-claude-login`** with **`--profile-repo`** so `claude auth login` and profiling stay in one run.
41
41
 
@@ -81,7 +81,7 @@ Guardrails are living artifacts. The IDE agent can create, apply, and update the
81
81
 
82
82
  - **New guardrail**: When threat modeling or code review reveals a recurring security pattern not yet captured, the IDE agent creates and applies a new guardrail on the fly. These are reported to SRAI via CTM sync with `source: "ide_generated"`.
83
83
  - **Edit suggestion**: When an existing guardrail is too broad, too narrow, or outdated, suggest an update to the user.
84
- - All guardrails — existing and IDE-generated — flow into the single `guardrails_applied` array in the CTM sync payload.
84
+ - All guardrails — existing and IDE-generated — must be preserved in the CTM sync markdown under a single `Guardrails Applied` section that the server can normalize into `guardrails_applied`.
85
85
 
86
86
  ---
87
87
 
@@ -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. **ctm_sync (last)** - Push payload to SRAI only after implementation is done. Invoke ctm_sync agent (Task tool, subagent_type=ctm_sync) as the final step. Include the exact shortlisted guardrails from earlier plus any IDE-generated guardrails in the guardrails_applied payload field; do not query again during ctm_sync. See .cursor/agents/ctm_sync.md and .cursor/rules/ctm_sync_rule.mdc.\\n\\nNo blocking. No deferral. Guardrails enforce secure code; threat modeling guides the design; ctm_sync runs last to push to SRAI. Both ctm_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. **ctm_sync (last)** - Write a structured `.md` sync artifact and push it to SRAI only after implementation is done. Invoke ctm_sync agent (Task tool, subagent_type=ctm_sync) as the final step. Include the exact shortlisted guardrails from earlier plus any IDE-generated guardrails in the markdown; do not query again during ctm_sync. See .cursor/agents/ctm_sync.md and .cursor/rules/ctm_sync_rule.mdc.\\n\\nNo blocking. No deferral. Guardrails enforce secure code; threat modeling guides the design; ctm_sync runs last to sync the markdown artifact to SRAI. Both ctm_sync and guardrail compliance are mandatory and should be part of your to-do.\"}'",
7
7
  "timeout": 5
8
8
  }
9
9
  ]
@@ -209,12 +209,13 @@ When discussing designs before code exists:
209
209
 
210
210
  ### What CTM sync uploads
211
211
 
212
- The `ctm_sync` agent builds and pushes an event payload containing:
212
+ The `ctm_sync` agent writes a structured `.md` artifact 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
215
  - **Best practices achieved**: security patterns followed during implementation
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
+ - **Workflow metadata**: `chat_session_id`, `event_name` or `title`, required `summary`, and optional `workflow_name` / `workflow_description`
218
219
 
219
220
  ### How to invoke
220
221
 
@@ -223,7 +224,7 @@ Use the host's `ctm_sync` agent/workflow with:
223
224
  - The `chat_session_id` for workflow routing
224
225
  - Whether this is a new threat model or an update
225
226
 
226
- See `{{CTM_SYNC_AGENT_PATH}}` for the full workflow and payload schema.
227
+ See `{{CTM_SYNC_AGENT_PATH}}` for the full workflow and markdown schema.
227
228
 
228
229
  ---
229
230
 
@@ -23,7 +23,7 @@ function getCopilotSessionHookContent() {
23
23
  '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.',
24
24
  '2. Run PWNISMS threat modelling using .github/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
25
25
  '3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
26
- '4. Run the ctm_sync custom agent using .github/agents/ctm_sync.agent.md after threat modelling or guardrail enforcement. Reuse the exact guardrail shortlist; do not re-query guardrails during CTM sync.',
26
+ '4. Run the ctm_sync custom agent using .github/agents/ctm_sync.agent.md after threat modelling or guardrail enforcement. Reuse the exact guardrail shortlist; do not re-query guardrails during CTM sync. The agent should write a structured .md sync artifact and upload it with sync_ai_ide_markdown.',
27
27
  '',
28
28
  '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.',
29
29
  ].join('\n');
@@ -36,6 +36,14 @@ test('VS Code Copilot generator writes instructions, skills, agent, and hooks',
36
36
  assert.match(threatSkill, /\.github\/agents\/ctm_sync\.agent\.md/);
37
37
  assert.doesNotMatch(threatSkill, /get_project_profile_description/);
38
38
 
39
+ const agent = readFileSync(join(cwd, '.github/agents/ctm_sync.agent.md'), 'utf8');
40
+ assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
41
+ assert.match(agent, /sync_ai_ide_markdown/);
42
+ assert.match(agent, /structured markdown artifact/i);
43
+ assert.match(agent, /OWASP Top 10 2025 Mappings/);
44
+ assert.match(agent, /vibereview\//);
45
+ assert.doesNotMatch(agent, /Call `create_ai_ide_event` with/i);
46
+
39
47
  const hooks = JSON.parse(readFileSync(join(cwd, '.github/hooks/srai-session-policy.json'), 'utf8'));
40
48
  assert.equal(hooks.hooks.SessionStart[0].type, 'command');
41
49
  });
@@ -17,8 +17,8 @@ export const TARGETS = {
17
17
  },
18
18
  claude: {
19
19
  name: 'Claude Code',
20
- mcpConfigPath: '.claude/settings.json',
21
- rulePath: 'CLAUDE.md',
20
+ mcpConfigPath: '.mcp.json',
21
+ rulePath: '.claude/CLAUDE.md',
22
22
  ruleMode: 'append',
23
23
  detectDirs: ['.claude'],
24
24
  },
@@ -213,6 +213,21 @@ export function buildCodexConfigOverrides(cwd, overrides = {}) {
213
213
  ];
214
214
  }
215
215
 
216
+ export function buildClaudeAdditionalMcpConfig(cwd) {
217
+ const projectMcp = readJson(join(cwd, '.mcp.json'));
218
+ const sourceServer = projectMcp?.mcpServers?.[MCP_SERVER_NAME];
219
+
220
+ if (!sourceServer || typeof sourceServer !== 'object') {
221
+ return null;
222
+ }
223
+
224
+ return JSON.stringify({
225
+ mcpServers: {
226
+ [MCP_SERVER_NAME]: sourceServer,
227
+ },
228
+ });
229
+ }
230
+
216
231
  export function pickProfilerAgentTarget(targets) {
217
232
  for (const t of PREFERRED_ORDER) {
218
233
  if (targets.includes(t)) {
@@ -288,7 +303,26 @@ export function runProfilerAgent(
288
303
  return { ok: false, message: 'claude not on PATH' };
289
304
  }
290
305
  const settingsPath = join(cwd, '.claude', 'settings.json');
291
- const args = ['-p', '--settings', settingsPath, '--model', modelOverride || 'haiku'];
306
+ const mcpConfig = buildClaudeAdditionalMcpConfig(cwd);
307
+ if (!mcpConfig) {
308
+ return {
309
+ ok: false,
310
+ message:
311
+ 'Claude profiling needs the SRAI MCP server in .mcp.json. Re-run init with MCP installation enabled.',
312
+ };
313
+ }
314
+ const args = [
315
+ '-p',
316
+ '--settings',
317
+ settingsPath,
318
+ '--mcp-config',
319
+ mcpConfig,
320
+ '--strict-mcp-config',
321
+ '--permission-mode',
322
+ 'bypassPermissions',
323
+ '--model',
324
+ modelOverride || 'haiku',
325
+ ];
292
326
  if (streamProgress) {
293
327
  args.push('--output-format', 'stream-json', '--include-partial-messages', '--include-hook-events', '--verbose');
294
328
  }