@massu/core 0.6.1 → 0.6.3

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.
Files changed (29) hide show
  1. package/commands/_shared-preamble.md +14 -0
  2. package/commands/_shared-references/verification-table.md +0 -3
  3. package/commands/massu-ci-fix.md +2 -2
  4. package/commands/massu-gap-enhancement-analyzer.md +85 -345
  5. package/commands/massu-golden-path/references/approval-points.md +9 -12
  6. package/commands/massu-golden-path/references/competitive-mode.md +9 -7
  7. package/commands/massu-golden-path/references/error-handling.md +4 -2
  8. package/commands/massu-golden-path/references/phase-0-requirements.md +3 -3
  9. package/commands/massu-golden-path/references/phase-1-plan-creation.md +41 -52
  10. package/commands/massu-golden-path/references/phase-2-implementation.md +50 -157
  11. package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +14 -48
  12. package/commands/massu-golden-path/references/phase-3-simplify.md +5 -5
  13. package/commands/massu-golden-path/references/phase-4-commit.md +20 -46
  14. package/commands/massu-golden-path/references/phase-5-push.md +14 -47
  15. package/commands/massu-golden-path/references/phase-6-completion.md +8 -58
  16. package/commands/massu-golden-path.md +27 -43
  17. package/commands/massu-loop/references/checkpoint-audit.md +14 -18
  18. package/commands/massu-loop/references/guardrails.md +3 -3
  19. package/commands/massu-loop/references/iteration-structure.md +46 -14
  20. package/commands/massu-loop/references/loop-controller.md +72 -63
  21. package/commands/massu-loop/references/plan-extraction.md +19 -11
  22. package/commands/massu-loop/references/vr-plan-spec.md +20 -28
  23. package/commands/massu-loop.md +36 -56
  24. package/commands/massu-review.md +2 -2
  25. package/dist/cli.js +442 -81
  26. package/package.json +1 -1
  27. package/src/mcp-bridge-tools.ts +459 -0
  28. package/src/tools.ts +8 -0
  29. package/commands/massu-golden-path/references/phase-3.5-security-audit.md +0 -108
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massu/core",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "type": "module",
5
5
  "description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
6
6
  "main": "src/server.ts",
@@ -0,0 +1,459 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ import { spawn, type ChildProcess } from 'child_process';
5
+ import { readFileSync, existsSync } from 'fs';
6
+ import { resolve } from 'path';
7
+ import { getConfig, getProjectRoot } from './config.ts';
8
+ import type { ToolDefinition, ToolResult } from './tools.ts';
9
+
10
+ /** Prefix a base tool name with the configured tool prefix. */
11
+ function p(baseName: string): string {
12
+ return `${getConfig().toolPrefix}_${baseName}`;
13
+ }
14
+
15
+ // ============================================================
16
+ // MCP Bridge: Cross-project tool mesh
17
+ // ============================================================
18
+
19
+ interface MCPServerConfig {
20
+ command: string;
21
+ args?: string[];
22
+ cwd?: string;
23
+ type?: string;
24
+ }
25
+
26
+ interface MCPConnection {
27
+ config: MCPServerConfig;
28
+ process: ChildProcess | null;
29
+ connected: boolean;
30
+ tools: MCPToolDef[];
31
+ requestId: number;
32
+ }
33
+
34
+ interface MCPToolDef {
35
+ name: string;
36
+ description: string;
37
+ inputSchema: Record<string, unknown>;
38
+ }
39
+
40
+ // Active MCP connections (module-level to persist across tool calls)
41
+ const connections = new Map<string, MCPConnection>();
42
+
43
+ // Clean up all MCP subprocesses on exit to prevent orphans
44
+ process.on('exit', () => {
45
+ for (const [, conn] of connections) {
46
+ if (conn.process && !conn.process.killed) {
47
+ try { conn.process.kill('SIGTERM'); } catch { /* already dead */ }
48
+ }
49
+ }
50
+ });
51
+ process.on('SIGTERM', () => {
52
+ for (const [name] of connections) disconnectServer(name);
53
+ process.exit(0);
54
+ });
55
+
56
+ // Environment variables safe to forward to MCP subprocesses.
57
+ // Generic for any Massu npm user — no project-specific hardcoding.
58
+ const ENV_ALLOW_LIST = new Set([
59
+ 'PATH', 'HOME', 'LANG', 'LC_ALL', 'TERM',
60
+ 'PYTHONPATH', 'NODE_PATH',
61
+ ]);
62
+ const ENV_DENY_PATTERNS = [
63
+ 'PRIVATE_KEY', 'SECRET_KEY', 'API_SECRET',
64
+ 'AUTH_DISABLED', 'COLD_STORAGE',
65
+ 'PASSWORD', 'TOKEN',
66
+ ];
67
+
68
+ function buildSubprocessEnv(): Record<string, string> {
69
+ const env: Record<string, string> = {};
70
+ // Derive safe env prefixes from the project name in massu.config.yaml.
71
+ // e.g., project name "hedge" -> allow HEDGE_CONFIG_, HEDGE_LOG_ etc.
72
+ // This makes the bridge work for any Massu user's project without hardcoding.
73
+ const projectName = getConfig().project?.name?.toUpperCase() || '';
74
+ const safePrefixes = projectName
75
+ ? [`${projectName}_CONFIG_`, `${projectName}_LOG_`]
76
+ : [];
77
+
78
+ for (const [key, value] of Object.entries(process.env)) {
79
+ if (!value) continue;
80
+ if (ENV_DENY_PATTERNS.some(pat => key.includes(pat))) continue;
81
+ if (ENV_ALLOW_LIST.has(key)) {
82
+ env[key] = value;
83
+ } else if (safePrefixes.length > 0 && safePrefixes.some(pfx => key.startsWith(pfx))) {
84
+ env[key] = value;
85
+ }
86
+ }
87
+ return env;
88
+ }
89
+
90
+ /**
91
+ * Load MCP server configurations from .mcp.json in project root.
92
+ * Only loads servers that are NOT the massu server itself (avoid self-connection).
93
+ */
94
+ function loadMcpConfig(): Record<string, MCPServerConfig> {
95
+ const root = getProjectRoot();
96
+ const mcpPath = resolve(root, '.mcp.json');
97
+ if (!existsSync(mcpPath)) return {};
98
+
99
+ try {
100
+ const raw = JSON.parse(readFileSync(mcpPath, 'utf-8'));
101
+ const servers: Record<string, MCPServerConfig> = {};
102
+ const mcpServers = raw.mcpServers || {};
103
+ const pfx = getConfig().toolPrefix;
104
+
105
+ for (const [name, config] of Object.entries(mcpServers)) {
106
+ // Skip self (the massu server)
107
+ if (name === pfx) continue;
108
+ servers[name] = config as MCPServerConfig;
109
+ }
110
+ return servers;
111
+ } catch (e) {
112
+ console.error('[mcp-bridge] Failed to parse .mcp.json:', e);
113
+ return {};
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Send a JSON-RPC 2.0 request to an MCP subprocess and wait for response.
119
+ */
120
+ async function mcpRequest(
121
+ conn: MCPConnection,
122
+ method: string,
123
+ params: Record<string, unknown> = {},
124
+ ): Promise<Record<string, unknown>> {
125
+ if (!conn.process || !conn.process.stdin || !conn.process.stdout) {
126
+ throw new Error('MCP process not connected');
127
+ }
128
+
129
+ conn.requestId++;
130
+ const request = {
131
+ jsonrpc: '2.0',
132
+ id: conn.requestId,
133
+ method,
134
+ params,
135
+ };
136
+
137
+ return new Promise((resolve, reject) => {
138
+ const timeout = setTimeout(() => {
139
+ conn.process?.stdout?.removeListener('data', onData);
140
+ reject(new Error(`MCP request timed out: ${method}`));
141
+ }, 30000);
142
+
143
+ // Buffer partial chunks until we have a complete newline-delimited response
144
+ let buffer = '';
145
+ const onData = (data: Buffer) => {
146
+ buffer += data.toString('utf-8');
147
+ const lines = buffer.split('\n');
148
+ // Keep the last incomplete chunk in the buffer
149
+ buffer = lines.pop() || '';
150
+
151
+ for (const line of lines) {
152
+ if (!line.trim()) continue;
153
+ try {
154
+ const response = JSON.parse(line);
155
+ if (response.id === conn.requestId) {
156
+ clearTimeout(timeout);
157
+ conn.process?.stdout?.removeListener('data', onData);
158
+ if (response.error) {
159
+ reject(new Error(`MCP error ${response.error.code}: ${response.error.message}`));
160
+ } else {
161
+ resolve(response.result || {});
162
+ }
163
+ return;
164
+ }
165
+ } catch (e) {
166
+ clearTimeout(timeout);
167
+ conn.process?.stdout?.removeListener('data', onData);
168
+ reject(new Error(`Failed to parse MCP response: ${e}`));
169
+ return;
170
+ }
171
+ }
172
+ };
173
+
174
+ conn.process!.stdout!.on('data', onData);
175
+ conn.process!.stdin!.write(JSON.stringify(request) + '\n');
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Connect to an MCP server subprocess and perform handshake.
181
+ */
182
+ async function connectToServer(name: string, config: MCPServerConfig): Promise<MCPConnection> {
183
+ const existing = connections.get(name);
184
+ if (existing?.connected && existing.process && !existing.process.killed) {
185
+ return existing;
186
+ }
187
+
188
+ const root = getProjectRoot();
189
+ const cwd = config.cwd ? resolve(root, config.cwd) : root;
190
+ const args = config.args || [];
191
+
192
+ const proc = spawn(config.command, args, {
193
+ cwd,
194
+ stdio: ['pipe', 'pipe', 'pipe'],
195
+ env: buildSubprocessEnv(),
196
+ });
197
+
198
+ const conn: MCPConnection = {
199
+ config,
200
+ process: proc,
201
+ connected: false,
202
+ tools: [],
203
+ requestId: 0,
204
+ };
205
+
206
+ try {
207
+ // Handshake
208
+ await mcpRequest(conn, 'initialize', {
209
+ protocolVersion: '2024-11-05',
210
+ capabilities: {},
211
+ clientInfo: { name: 'massu-mcp-bridge', version: '1.0.0' },
212
+ });
213
+
214
+ // Send initialized notification
215
+ if (proc.stdin) {
216
+ proc.stdin.write(JSON.stringify({
217
+ jsonrpc: '2.0',
218
+ method: 'notifications/initialized',
219
+ params: {},
220
+ }) + '\n');
221
+ }
222
+
223
+ conn.connected = true;
224
+
225
+ // Discover tools
226
+ const toolsResult = await mcpRequest(conn, 'tools/list', {}) as { tools?: MCPToolDef[] };
227
+ conn.tools = toolsResult.tools || [];
228
+
229
+ // Store in map only after successful handshake
230
+ connections.set(name, conn);
231
+ return conn;
232
+ } catch (e) {
233
+ // Clean up on handshake failure — don't leave a broken connection in the map
234
+ if (!proc.killed) proc.kill('SIGTERM');
235
+ throw e;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Disconnect an MCP server.
241
+ */
242
+ function disconnectServer(name: string): void {
243
+ const conn = connections.get(name);
244
+ if (conn) {
245
+ conn.connected = false;
246
+ if (conn.process && !conn.process.killed) {
247
+ conn.process.kill('SIGTERM');
248
+ // Escalate to SIGKILL if SIGTERM doesn't work within 3 seconds
249
+ const proc = conn.process;
250
+ setTimeout(() => {
251
+ if (!proc.killed) {
252
+ try { proc.kill('SIGKILL'); } catch { /* already dead */ }
253
+ }
254
+ }, 3000);
255
+ }
256
+ connections.delete(name);
257
+ }
258
+ }
259
+
260
+ function text(s: string): ToolResult {
261
+ return { content: [{ type: 'text', text: s }] };
262
+ }
263
+
264
+ // ============================================================
265
+ // Tool Definitions
266
+ // ============================================================
267
+
268
+ export function getMcpBridgeToolDefinitions(): ToolDefinition[] {
269
+ return [
270
+ {
271
+ name: p('mcp_servers'),
272
+ description: 'List all configured MCP servers from .mcp.json and their connection status.',
273
+ inputSchema: {
274
+ type: 'object',
275
+ properties: {},
276
+ required: [],
277
+ },
278
+ },
279
+ {
280
+ name: p('mcp_tools'),
281
+ description: 'List tools available from a specific MCP server. Connects to the server if not already connected.',
282
+ inputSchema: {
283
+ type: 'object',
284
+ properties: {
285
+ server: { type: 'string', description: 'MCP server name from .mcp.json' },
286
+ },
287
+ required: ['server'],
288
+ },
289
+ },
290
+ {
291
+ name: p('mcp_call'),
292
+ description: 'Call a tool on a connected MCP server. Connects automatically if needed.',
293
+ inputSchema: {
294
+ type: 'object',
295
+ properties: {
296
+ server: { type: 'string', description: 'MCP server name from .mcp.json' },
297
+ tool: { type: 'string', description: 'Tool name to call on the remote server' },
298
+ arguments: {
299
+ type: 'object',
300
+ description: 'Arguments to pass to the remote tool',
301
+ additionalProperties: true,
302
+ },
303
+ },
304
+ required: ['server', 'tool'],
305
+ },
306
+ },
307
+ {
308
+ name: p('mcp_status'),
309
+ description: 'Health check all MCP server connections. Shows which are connected, disconnected, or errored.',
310
+ inputSchema: {
311
+ type: 'object',
312
+ properties: {},
313
+ required: [],
314
+ },
315
+ },
316
+ ];
317
+ }
318
+
319
+ export function isMcpBridgeTool(name: string): boolean {
320
+ const pfx = getConfig().toolPrefix;
321
+ return name.startsWith(`${pfx}_mcp_`);
322
+ }
323
+
324
+ export async function handleMcpBridgeToolCall(
325
+ name: string,
326
+ args: Record<string, unknown>,
327
+ ): Promise<ToolResult> {
328
+ const pfx = getConfig().toolPrefix;
329
+ const baseName = name.startsWith(`${pfx}_`) ? name.slice(pfx.length + 1) : name;
330
+
331
+ switch (baseName) {
332
+ case 'mcp_servers':
333
+ return handleMcpServers();
334
+ case 'mcp_tools':
335
+ return await handleMcpTools(args.server as string);
336
+ case 'mcp_call':
337
+ return await handleMcpCall(
338
+ args.server as string,
339
+ args.tool as string,
340
+ (args.arguments as Record<string, unknown>) || {},
341
+ );
342
+ case 'mcp_status':
343
+ return handleMcpStatus();
344
+ default:
345
+ return text(`Unknown MCP bridge tool: ${name}`);
346
+ }
347
+ }
348
+
349
+ // ============================================================
350
+ // Tool Handlers
351
+ // ============================================================
352
+
353
+ function handleMcpServers(): ToolResult {
354
+ const configs = loadMcpConfig();
355
+ const servers = Object.entries(configs).map(([name, config]) => {
356
+ const conn = connections.get(name);
357
+ return {
358
+ name,
359
+ command: config.command,
360
+ args: config.args || [],
361
+ cwd: config.cwd || '.',
362
+ status: conn?.connected ? 'connected' : 'disconnected',
363
+ toolCount: conn?.tools.length || 0,
364
+ };
365
+ });
366
+
367
+ if (servers.length === 0) {
368
+ return text('No MCP servers configured in .mcp.json (excluding self).');
369
+ }
370
+
371
+ const lines = ['# MCP Servers\n'];
372
+ for (const srv of servers) {
373
+ const status = srv.status === 'connected' ? 'CONNECTED' : 'DISCONNECTED';
374
+ lines.push(`- **${srv.name}** [${status}] — \`${srv.command} ${srv.args.join(' ')}\` (${srv.toolCount} tools)`);
375
+ }
376
+ return text(lines.join('\n'));
377
+ }
378
+
379
+ async function handleMcpTools(server: string): Promise<ToolResult> {
380
+ const configs = loadMcpConfig();
381
+ const config = configs[server];
382
+ if (!config) {
383
+ return text(`MCP server "${server}" not found in .mcp.json. Available: ${Object.keys(configs).join(', ') || 'none'}`);
384
+ }
385
+
386
+ try {
387
+ const conn = await connectToServer(server, config);
388
+ if (conn.tools.length === 0) {
389
+ return text(`MCP server "${server}" is connected but has no tools.`);
390
+ }
391
+
392
+ const lines = [`# Tools from ${server} (${conn.tools.length})\n`];
393
+ for (const tool of conn.tools) {
394
+ lines.push(`- **${tool.name}**: ${tool.description}`);
395
+ }
396
+ return text(lines.join('\n'));
397
+ } catch (e) {
398
+ return text(`Failed to connect to MCP server "${server}": ${e}`);
399
+ }
400
+ }
401
+
402
+ async function handleMcpCall(
403
+ server: string,
404
+ tool: string,
405
+ args: Record<string, unknown>,
406
+ ): Promise<ToolResult> {
407
+ const configs = loadMcpConfig();
408
+ const config = configs[server];
409
+ if (!config) {
410
+ return text(`MCP server "${server}" not found in .mcp.json.`);
411
+ }
412
+
413
+ try {
414
+ const conn = await connectToServer(server, config);
415
+ const result = await mcpRequest(conn, 'tools/call', { name: tool, arguments: args });
416
+
417
+ // MCP tools/call returns { content: [...] } or { content: [...], isError: true }
418
+ const content = (result as any).content;
419
+ if (Array.isArray(content)) {
420
+ const texts = content
421
+ .filter((c: any) => c.type === 'text')
422
+ .map((c: any) => c.text);
423
+ if ((result as any).isError) {
424
+ return text(`MCP tool error: ${texts.join('\n')}`);
425
+ }
426
+ return text(texts.join('\n'));
427
+ }
428
+
429
+ return text(JSON.stringify(result, null, 2));
430
+ } catch (e) {
431
+ // Mark as disconnected but don't delete — operator can see the failure in mcp_status
432
+ const conn = connections.get(server);
433
+ if (conn) conn.connected = false;
434
+ return text(`MCP call failed (${server}/${tool}): ${e}`);
435
+ }
436
+ }
437
+
438
+ function handleMcpStatus(): ToolResult {
439
+ const configs = loadMcpConfig();
440
+ const lines = ['# MCP Connection Status\n'];
441
+
442
+ for (const [name] of Object.entries(configs)) {
443
+ const conn = connections.get(name);
444
+ if (!conn) {
445
+ lines.push(`- **${name}**: NOT CONNECTED`);
446
+ } else if (conn.connected && conn.process && !conn.process.killed) {
447
+ lines.push(`- **${name}**: HEALTHY (pid=${conn.process.pid}, ${conn.tools.length} tools)`);
448
+ } else {
449
+ lines.push(`- **${name}**: DISCONNECTED`);
450
+ disconnectServer(name);
451
+ }
452
+ }
453
+
454
+ if (Object.keys(configs).length === 0) {
455
+ lines.push('No MCP servers configured.');
456
+ }
457
+
458
+ return text(lines.join('\n'));
459
+ }
package/src/tools.ts CHANGED
@@ -39,6 +39,7 @@ import { getKnowledgeDb } from './knowledge-db.ts';
39
39
  import { getPythonToolDefinitions, isPythonTool, handlePythonToolCall } from './python-tools.ts';
40
40
  import { getConfig, getProjectRoot, getResolvedPaths } from './config.ts';
41
41
  import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall } from './license.ts';
42
+ import { getMcpBridgeToolDefinitions, isMcpBridgeTool, handleMcpBridgeToolCall } from './mcp-bridge-tools.ts';
42
43
 
43
44
  export interface ToolDefinition {
44
45
  name: string;
@@ -166,6 +167,8 @@ export function getToolDefinitions(): ToolDefinition[] {
166
167
  ...getKnowledgeToolDefinitions(),
167
168
  // Python code intelligence tools
168
169
  ...getPythonToolDefinitions(),
170
+ // MCP bridge tools (cross-project tool mesh)
171
+ ...getMcpBridgeToolDefinitions(),
169
172
  // License tools (always available)
170
173
  ...getLicenseToolDefinitions(),
171
174
  // Core tools
@@ -380,6 +383,11 @@ export async function handleToolCall(
380
383
  return handlePythonToolCall(name, args, dataDb);
381
384
  }
382
385
 
386
+ // Route MCP bridge tools (cross-project tool mesh)
387
+ if (isMcpBridgeTool(name)) {
388
+ return await handleMcpBridgeToolCall(name, args);
389
+ }
390
+
383
391
  // Route license tools
384
392
  if (isLicenseTool(name)) {
385
393
  const memDb = getMemoryDb();
@@ -1,108 +0,0 @@
1
- # Phase 3.5: Deep Security Audit
2
-
3
- > Reference doc for `/massu-golden-path`. Return to main file for overview.
4
-
5
- ```
6
- [GOLDEN PATH -- PHASE 3.5: DEEP SECURITY AUDIT]
7
- ```
8
-
9
- ## Purpose
10
-
11
- Run a full adversarial security audit loop against ALL files changed in this golden path run. This is a deep, iterative audit with parallel red-team agents that converges to zero findings. It runs AFTER simplification (Phase 3) so the audit targets the final, cleaned-up code -- and BEFORE pre-commit verification (Phase 4) so all security fixes are included in the verification gates.
12
-
13
- **This phase is NEVER skipped.** Security is non-negotiable regardless of change size, type, or scope.
14
-
15
- ---
16
-
17
- ## 3.5.1 Determine Audit Scope
18
-
19
- Collect ALL files changed during this golden path run:
20
-
21
- ```bash
22
- git diff --name-only HEAD
23
- ```
24
-
25
- If files were already committed in earlier phases, also include:
26
- ```bash
27
- git diff --name-only main...HEAD
28
- ```
29
-
30
- The audit scope is the union of all changed files. Do NOT narrow scope -- every changed file gets audited.
31
-
32
- **Output:**
33
- ```
34
- SECURITY AUDIT SCOPE:
35
- Files: [N]
36
- [list of files]
37
- ```
38
-
39
- ---
40
-
41
- ## 3.5.2 Execute Deep Security Audit
42
-
43
- Run the full security audit protocol against the scoped files:
44
-
45
- 1. **Launch 2-4 parallel adversarial reviewer agents** adapted to the codebase area:
46
- - Backend/API code: 4 agents (Injection, Network/Leakage, DoS/Resources, Red Team Bypass)
47
- - Frontend code: 3 agents (XSS/Injection, Auth/Data Exposure, Input Validation/Logic)
48
- - Infrastructure/config: 2 agents (Secrets/Config, Dependencies/Supply Chain)
49
-
50
- 2. **Consolidate findings** -- deduplicate across agents, take higher severity on disagreements
51
-
52
- 3. **Fix ALL findings** -- CRITICAL first, then HIGH, MEDIUM, LOW. INFO documented only.
53
-
54
- 4. **Verify fixes** -- import checks, input validation tests, functionality preserved
55
-
56
- 5. **Loop until zero findings** -- max 5 iterations, escalate to user if still failing after 5
57
-
58
- ---
59
-
60
- ## 3.5.3 Attack Vector Coverage
61
-
62
- Every audit iteration MUST verify the complete attack vector checklist:
63
-
64
- ### Universal
65
- - Hardcoded secrets / API keys / credentials
66
- - Error messages leaking internal details
67
- - Dependency vulnerabilities
68
- - Input validation on ALL external boundaries
69
-
70
- ### Backend / API
71
- - SQL injection, command injection, path traversal
72
- - SSRF, authentication bypass, authorization bypass
73
- - DoS via unbounded inputs, memory leaks, race conditions
74
- - Response validation, type confusion
75
-
76
- ### Frontend
77
- - XSS, open redirects, sensitive data in client state
78
- - CSRF, client-side auth bypass
79
-
80
- ### LLM / AI Specific
81
- - Prompt injection, model output trust
82
- - Tool argument injection, vision/multimodal injection
83
-
84
- ---
85
-
86
- ## 3.5.4 Completion Gate
87
-
88
- The phase completes ONLY when the audit loop achieves a clean pass with zero findings.
89
-
90
- ```
91
- SECURITY_AUDIT_GATE: PASS
92
- Iterations: [N]
93
- Total findings fixed: [N]
94
- Breakdown: [X] CRITICAL, [X] HIGH, [X] MEDIUM, [X] LOW fixed
95
- Clean pass: Iteration [N]
96
- ```
97
-
98
- **Do NOT proceed to Phase 4 until SECURITY_AUDIT_GATE = PASS.**
99
-
100
- ---
101
-
102
- ## Rules
103
-
104
- 1. **NEVER skip this phase** -- not for small changes, not for docs, not for config
105
- 2. **NEVER proceed with findings unfixed** -- zero means zero
106
- 3. **ALL severity levels get fixed** -- CRITICAL through LOW
107
- 4. **No commit prompt** -- unlike standalone security audit commands, do NOT offer to commit here (Phase 4 handles commits)
108
- 5. **Findings feed Phase 4** -- security fixes are verified by Phase 4's type check, build, lint, and secrets gates automatically