x-readiness-mcp 0.3.0 → 0.5.0
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 +325 -0
- package/USAGE_GUIDE.md +271 -0
- package/package.json +2 -2
- package/server.js +103 -213
- package/tools/autofix.js +151 -0
- package/tools/execution.js +193 -814
- package/tools/gitignore-helper.js +88 -0
- package/tools/planning.js +120 -333
- package/tools/rule-checkers.js +394 -0
- package/tools/template-parser.js +204 -0
- package/templates/master_template.json +0 -524
package/server.js
CHANGED
|
@@ -1,169 +1,107 @@
|
|
|
1
1
|
// mcp/server.js
|
|
2
|
-
// X-Readiness MCP Server
|
|
3
|
-
//
|
|
4
|
-
// Tools:
|
|
5
|
-
// 1) planner → Generate checklist + persist plan file
|
|
6
|
-
// 2) execute_checklist → Load plan file + execute checks ONLY after explicit user approval code
|
|
7
|
-
//
|
|
8
|
-
// Why approval code?
|
|
9
|
-
// - Copilot/agents may auto-call tools without asking.
|
|
10
|
-
// - A user-provided approvalCode forces explicit human permission (copy/paste).
|
|
11
|
-
|
|
12
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
4
|
import * as z from "zod";
|
|
15
|
-
|
|
16
|
-
import fs from "node:fs/promises";
|
|
17
|
-
import path from "node:path";
|
|
18
5
|
import crypto from "node:crypto";
|
|
19
6
|
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
const server = new McpServer(
|
|
24
|
-
{ name: "x-readiness-mcp", version: "0.3.0" },
|
|
25
|
-
{ capabilities: { tools: {} } }
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
// -----------------------------
|
|
29
|
-
// Plan persistence helpers
|
|
30
|
-
// -----------------------------
|
|
31
|
-
const PLAN_DIR = path.resolve(process.cwd(), ".xreadiness", "plan");
|
|
32
|
-
|
|
33
|
-
async function ensurePlanDir() {
|
|
34
|
-
await fs.mkdir(PLAN_DIR, { recursive: true });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function savePlanToDisk(plan) {
|
|
38
|
-
await ensurePlanDir();
|
|
39
|
-
|
|
40
|
-
const planId =
|
|
41
|
-
(typeof plan?.checklist_id === "string" && plan.checklist_id.trim()) ||
|
|
42
|
-
`plan_${crypto.randomUUID()}`;
|
|
43
|
-
|
|
44
|
-
// Sanitize filename for Windows (replace colons with hyphens)
|
|
45
|
-
const sanitizedPlanId = planId.replace(/:/g, "-");
|
|
46
|
-
|
|
47
|
-
const planPath = path.join(PLAN_DIR, `${sanitizedPlanId}.json`);
|
|
48
|
-
await fs.writeFile(planPath, JSON.stringify(plan, null, 2), "utf8");
|
|
49
|
-
|
|
50
|
-
return { planId, planPath };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function loadPlanFromDisk(planPath) {
|
|
54
|
-
const raw = await fs.readFile(planPath, "utf8");
|
|
55
|
-
return JSON.parse(raw);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function countRulesFromPlan(plan) {
|
|
59
|
-
if (Array.isArray(plan?.features)) {
|
|
60
|
-
return plan.features.reduce((acc, f) => acc + (f?.rules?.length ?? 0), 0);
|
|
61
|
-
}
|
|
62
|
-
if (Array.isArray(plan?.rules)) return plan.rules.length;
|
|
63
|
-
return 0;
|
|
64
|
-
}
|
|
7
|
+
import { planTool } from "./tools/planning.js";
|
|
8
|
+
import { executeTool } from "./tools/execution.js";
|
|
9
|
+
import { autoFixTool } from "./tools/autofix.js";
|
|
65
10
|
|
|
66
11
|
// -----------------------------
|
|
67
|
-
//
|
|
12
|
+
// Approval gate (in-memory)
|
|
68
13
|
// -----------------------------
|
|
69
|
-
// In-memory approvals keyed by (repoPath + planPath). Resets on server restart.
|
|
70
14
|
const approvals = new Map(); // key -> { code, createdAt }
|
|
71
|
-
const APPROVAL_TTL_MS = 10 * 60 * 1000;
|
|
72
|
-
|
|
73
|
-
function approvalKey(repoPath, planPath) {
|
|
74
|
-
return `${repoPath}||${planPath}`;
|
|
75
|
-
}
|
|
15
|
+
const APPROVAL_TTL_MS = 10 * 60 * 1000;
|
|
76
16
|
|
|
77
17
|
function newApprovalCode() {
|
|
78
|
-
// 6-digit numeric code (easy to copy/paste)
|
|
79
18
|
return String(Math.floor(100000 + Math.random() * 900000));
|
|
80
19
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
20
|
+
function approvalKey(kind, repoPath, ref) {
|
|
21
|
+
return `${kind}||${repoPath}||${ref}`;
|
|
22
|
+
}
|
|
23
|
+
function getOrCreateApproval(kind, repoPath, ref) {
|
|
24
|
+
const key = approvalKey(kind, repoPath, ref);
|
|
84
25
|
const existing = approvals.get(key);
|
|
85
26
|
const now = Date.now();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const code = newApprovalCode();
|
|
92
|
-
const createdAt = now;
|
|
93
|
-
approvals.set(key, { code, createdAt });
|
|
94
|
-
return { key, code, createdAt };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function clearApproval(repoPath, planPath) {
|
|
98
|
-
approvals.delete(approvalKey(repoPath, planPath));
|
|
27
|
+
if (existing && now - existing.createdAt <= APPROVAL_TTL_MS) return existing;
|
|
28
|
+
const rec = { code: newApprovalCode(), createdAt: now };
|
|
29
|
+
approvals.set(key, rec);
|
|
30
|
+
return rec;
|
|
99
31
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const key = approvalKey(repoPath, planPath);
|
|
32
|
+
function isApprovalValid(kind, repoPath, ref, code) {
|
|
33
|
+
const key = approvalKey(kind, repoPath, ref);
|
|
103
34
|
const rec = approvals.get(key);
|
|
104
35
|
if (!rec) return false;
|
|
105
36
|
if (Date.now() - rec.createdAt > APPROVAL_TTL_MS) {
|
|
106
37
|
approvals.delete(key);
|
|
107
38
|
return false;
|
|
108
39
|
}
|
|
109
|
-
return String(
|
|
40
|
+
return String(code) === String(rec.code);
|
|
41
|
+
}
|
|
42
|
+
function clearApproval(kind, repoPath, ref) {
|
|
43
|
+
approvals.delete(approvalKey(kind, repoPath, ref));
|
|
110
44
|
}
|
|
111
45
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
46
|
+
const server = new McpServer(
|
|
47
|
+
{ name: "x-readiness-mcp", version: "0.4.0" },
|
|
48
|
+
{ capabilities: { tools: {} } }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// -----------------------------
|
|
52
|
+
// TOOL: plan
|
|
53
|
+
// -----------------------------
|
|
115
54
|
server.registerTool(
|
|
116
|
-
"
|
|
55
|
+
"plan",
|
|
117
56
|
{
|
|
118
57
|
description:
|
|
119
|
-
"Planning Tool -
|
|
58
|
+
"Planning Tool: Generate a high-level X-Readiness plan with rule checklist based on scope and developer intent. Creates plan files in mcp/plan folder. This is Step 1 - after generating the plan, you'll need to get approval before executing it.",
|
|
120
59
|
inputSchema: z.object({
|
|
60
|
+
repoPath: z.string().describe("Path to repo/workspace root to analyze"),
|
|
121
61
|
scope: z
|
|
62
|
+
.enum(["x_api_readiness", "pagination_ready", "security_ready", "error_handling_ready", "versioning_ready"])
|
|
63
|
+
.describe("Which readiness scope to plan for"),
|
|
64
|
+
developerPrompt: z
|
|
122
65
|
.string()
|
|
123
|
-
.describe(
|
|
124
|
-
|
|
125
|
-
),
|
|
126
|
-
format: z
|
|
127
|
-
.enum(["json", "markdown", "both"])
|
|
128
|
-
.optional()
|
|
129
|
-
.describe("Output format (default: 'json')"),
|
|
130
|
-
intent: z
|
|
131
|
-
.string()
|
|
132
|
-
.optional()
|
|
133
|
-
.describe("Optional intent/context string to store with the plan")
|
|
66
|
+
.describe("Developer intent: what to focus on, specific requirements, or keywords (e.g., 'pagination', 'error handling')"),
|
|
67
|
+
outputFormat: z.enum(["markdown", "json", "both"]).optional().default("markdown")
|
|
134
68
|
})
|
|
135
69
|
},
|
|
136
|
-
async (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const { planId, planPath } = await savePlanToDisk(checklist);
|
|
143
|
-
|
|
144
|
-
const featuresCount = Array.isArray(checklist?.features)
|
|
145
|
-
? checklist.features.length
|
|
146
|
-
: 0;
|
|
147
|
-
|
|
148
|
-
const rulesCount = countRulesFromPlan(checklist);
|
|
70
|
+
async (input) => {
|
|
71
|
+
const result = await planTool(input);
|
|
72
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
73
|
+
}
|
|
74
|
+
);
|
|
149
75
|
|
|
76
|
+
// -----------------------------
|
|
77
|
+
// TOOL: execute (gated)
|
|
78
|
+
// -----------------------------
|
|
79
|
+
server.registerTool(
|
|
80
|
+
"execute",
|
|
81
|
+
{
|
|
82
|
+
description:
|
|
83
|
+
"Execution Tool: Execute a saved plan against source code, checking each rule systematically. Generates detailed violation report with file/line references. Step 2 - REQUIRES APPROVAL. You will receive an approval code that must be provided to proceed with execution.",
|
|
84
|
+
inputSchema: z.object({
|
|
85
|
+
repoPath: z.string().describe("Path to repo/workspace root"),
|
|
86
|
+
planPath: z.string().describe("Path to the plan JSON file (from plan tool output)"),
|
|
87
|
+
approvalCode: z.string().optional().describe("6-digit approval code (will be provided when first called without code)")
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
async ({ repoPath, planPath, approvalCode }) => {
|
|
91
|
+
if (!approvalCode || !isApprovalValid("execute", repoPath, planPath, approvalCode)) {
|
|
92
|
+
const { code } = getOrCreateApproval("execute", repoPath, planPath);
|
|
150
93
|
return {
|
|
151
94
|
content: [
|
|
152
95
|
{
|
|
153
96
|
type: "text",
|
|
154
97
|
text: JSON.stringify(
|
|
155
98
|
{
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
features: featuresCount,
|
|
163
|
-
rules: rulesCount
|
|
164
|
-
},
|
|
165
|
-
next_step:
|
|
166
|
-
"To execute, call execute_checklist with repoPath, planPath, and the approvalCode (you will be prompted for it)."
|
|
99
|
+
status: "CONFIRMATION_REQUIRED",
|
|
100
|
+
message:
|
|
101
|
+
"⚠️ APPROVAL REQUIRED: Execution will scan your source code and check against all rules in the plan.\n\nTo proceed, call the 'execute' tool again with the same parameters and include the approvalCode below.",
|
|
102
|
+
approvalCode: code,
|
|
103
|
+
expires_in_minutes: Math.floor(APPROVAL_TTL_MS / 60000),
|
|
104
|
+
next_action: "Re-run execute tool with approvalCode parameter"
|
|
167
105
|
},
|
|
168
106
|
null,
|
|
169
107
|
2
|
|
@@ -171,114 +109,66 @@ server.registerTool(
|
|
|
171
109
|
}
|
|
172
110
|
]
|
|
173
111
|
};
|
|
174
|
-
} catch (error) {
|
|
175
|
-
return {
|
|
176
|
-
content: [
|
|
177
|
-
{
|
|
178
|
-
type: "text",
|
|
179
|
-
text: `Planning Error: ${error?.message ?? String(error)}\n${
|
|
180
|
-
error?.stack ?? ""
|
|
181
|
-
}`
|
|
182
|
-
}
|
|
183
|
-
],
|
|
184
|
-
isError: true
|
|
185
|
-
};
|
|
186
112
|
}
|
|
113
|
+
|
|
114
|
+
clearApproval("execute", repoPath, planPath);
|
|
115
|
+
const result = await executeTool({ repoPath, planPath });
|
|
116
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
187
117
|
}
|
|
188
118
|
);
|
|
189
119
|
|
|
190
|
-
//
|
|
191
|
-
// TOOL
|
|
192
|
-
//
|
|
193
|
-
// - Loads plan JSON from disk (planPath)
|
|
194
|
-
// - Requires approvalCode to proceed
|
|
195
|
-
// - If approvalCode missing/wrong, returns CONFIRMATION_REQUIRED (no execution)
|
|
196
|
-
// ============================================================================
|
|
120
|
+
// -----------------------------
|
|
121
|
+
// TOOL: auto_fix (gated)
|
|
122
|
+
// -----------------------------
|
|
197
123
|
server.registerTool(
|
|
198
|
-
"
|
|
124
|
+
"auto_fix",
|
|
199
125
|
{
|
|
200
126
|
description:
|
|
201
|
-
"
|
|
127
|
+
"Auto-fix Tool: Generate fixes for violations found in the execution report. Step 3 - REQUIRES APPROVAL. Can propose fixes or apply them directly.",
|
|
202
128
|
inputSchema: z.object({
|
|
203
|
-
repoPath: z
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.string()
|
|
208
|
-
.describe("Path to the saved plan JSON file (from planner output)"),
|
|
209
|
-
approvalCode: z
|
|
210
|
-
.string()
|
|
211
|
-
.optional()
|
|
212
|
-
.describe(
|
|
213
|
-
"User approval code required to run. If omitted, the tool returns a code and stops."
|
|
214
|
-
)
|
|
129
|
+
repoPath: z.string().describe("Path to repo/workspace root"),
|
|
130
|
+
runId: z.string().describe("run_id from execute output"),
|
|
131
|
+
mode: z.enum(["propose", "apply"]).default("propose").describe("'propose' shows fixes without applying, 'apply' applies fixes to source code"),
|
|
132
|
+
approvalCode: z.string().optional().describe("6-digit approval code (will be provided when first called without code)")
|
|
215
133
|
})
|
|
216
134
|
},
|
|
217
|
-
async ({ repoPath,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const { code } = getOrCreateApproval(repoPath, planPath);
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
content: [
|
|
225
|
-
{
|
|
226
|
-
type: "text",
|
|
227
|
-
text: JSON.stringify(
|
|
228
|
-
{
|
|
229
|
-
status: "CONFIRMATION_REQUIRED",
|
|
230
|
-
message:
|
|
231
|
-
"Execution not started. Please confirm you want to run analysis by re-running execute_checklist with the approvalCode below.",
|
|
232
|
-
approvalCode: code,
|
|
233
|
-
expires_in_minutes: Math.floor(APPROVAL_TTL_MS / 60000),
|
|
234
|
-
next_call_example: {
|
|
235
|
-
tool: "execute_checklist",
|
|
236
|
-
input: { repoPath, planPath, approvalCode: code }
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
null,
|
|
240
|
-
2
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
]
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// If we’re here, permission is confirmed
|
|
248
|
-
clearApproval(repoPath, planPath);
|
|
249
|
-
|
|
250
|
-
const checklist = await loadPlanFromDisk(planPath);
|
|
251
|
-
const results = await executionTool(repoPath, checklist);
|
|
252
|
-
|
|
253
|
-
// Optional: make "all skipped" clearer so it isn't interpreted as 0% failure
|
|
254
|
-
const rs = results?.readiness_summary;
|
|
255
|
-
if (rs && rs.total_rules_checked === 0 && (rs.rules_skipped ?? 0) > 0) {
|
|
256
|
-
rs.status = "NOT_EVALUATED";
|
|
257
|
-
rs.message =
|
|
258
|
-
"No rules were evaluated (all skipped). Score is not meaningful without detectable API signals.";
|
|
259
|
-
rs.score = "N/A";
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
|
|
264
|
-
};
|
|
265
|
-
} catch (error) {
|
|
135
|
+
async ({ repoPath, runId, mode, approvalCode }) => {
|
|
136
|
+
const ref = `${runId}||${mode}`;
|
|
137
|
+
if (!approvalCode || !isApprovalValid("auto_fix", repoPath, ref, approvalCode)) {
|
|
138
|
+
const { code } = getOrCreateApproval("auto_fix", repoPath, ref);
|
|
266
139
|
return {
|
|
267
140
|
content: [
|
|
268
141
|
{
|
|
269
142
|
type: "text",
|
|
270
|
-
text:
|
|
143
|
+
text: JSON.stringify(
|
|
144
|
+
{
|
|
145
|
+
status: "CONFIRMATION_REQUIRED",
|
|
146
|
+
message:
|
|
147
|
+
"⚠️ APPROVAL REQUIRED: Auto-fix will analyze violations and " +
|
|
148
|
+
(mode === "apply" ? "MODIFY your source code files." : "propose changes to your source code.") +
|
|
149
|
+
"\n\nTo proceed, call the 'auto_fix' tool again with the same parameters and include the approvalCode below.",
|
|
150
|
+
approvalCode: code,
|
|
151
|
+
expires_in_minutes: Math.floor(APPROVAL_TTL_MS / 60000),
|
|
152
|
+
next_action: "Re-run auto_fix tool with approvalCode parameter"
|
|
153
|
+
},
|
|
154
|
+
null,
|
|
155
|
+
2
|
|
156
|
+
)
|
|
271
157
|
}
|
|
272
|
-
]
|
|
273
|
-
isError: true
|
|
158
|
+
]
|
|
274
159
|
};
|
|
275
160
|
}
|
|
161
|
+
|
|
162
|
+
clearApproval("auto_fix", repoPath, ref);
|
|
163
|
+
const result = await autoFixTool({ repoPath, runId, mode });
|
|
164
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
276
165
|
}
|
|
277
166
|
);
|
|
278
167
|
|
|
279
|
-
//
|
|
168
|
+
// -----------------------------
|
|
280
169
|
// START SERVER
|
|
281
|
-
//
|
|
170
|
+
// -----------------------------
|
|
282
171
|
console.error("x-readiness-mcp: starting (stdio)...");
|
|
283
172
|
const transport = new StdioServerTransport();
|
|
284
173
|
await server.connect(transport);
|
|
174
|
+
|
package/tools/autofix.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// mcp/tools/autofix.js
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
function reportsPath(repoPath, runId) {
|
|
10
|
+
return path.resolve(repoPath, ".xreadiness", "reports", `${runId}.json`);
|
|
11
|
+
}
|
|
12
|
+
function patchesDir(repoPath) {
|
|
13
|
+
return path.resolve(repoPath, ".xreadiness", "patches");
|
|
14
|
+
}
|
|
15
|
+
async function ensureDir(p) {
|
|
16
|
+
await fs.mkdir(p, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function gitIsClean(repoPath) {
|
|
20
|
+
try {
|
|
21
|
+
const { stdout } = await execFileAsync("git", ["status", "--porcelain"], { cwd: repoPath });
|
|
22
|
+
return stdout.trim().length === 0;
|
|
23
|
+
} catch {
|
|
24
|
+
// If git isn't available, be conservative
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getGitDiff(repoPath) {
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await execFileAsync("git", ["diff", "--stat"], { cwd: repoPath });
|
|
32
|
+
const { stdout: diffContent } = await execFileAsync("git", ["diff"], { cwd: repoPath });
|
|
33
|
+
return { stat: stdout, diff: diffContent };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return { stat: "Error getting git diff", diff: err.message };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildPatchFromViolations(report) {
|
|
40
|
+
// For v1, only generate a patch if violations include a ready-to-apply patch chunk.
|
|
41
|
+
// You can extend your rule checkers to attach suggestedFixUnifiedDiff per violation.
|
|
42
|
+
const diffs = report.violations
|
|
43
|
+
.map(v => v.suggested_patch)
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
|
|
46
|
+
return diffs.join("\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Auto-fix tool with 2-stage approval:
|
|
51
|
+
* - mode="suggest": Shows proposed patches/diffs without applying
|
|
52
|
+
* - mode="apply": Applies patches to source code (requires clean git state)
|
|
53
|
+
*/
|
|
54
|
+
export async function autoFixTool({ repoPath, runId, mode = "suggest" }) {
|
|
55
|
+
const reportFile = reportsPath(repoPath, runId);
|
|
56
|
+
|
|
57
|
+
let reportData;
|
|
58
|
+
try {
|
|
59
|
+
const raw = await fs.readFile(reportFile, "utf8");
|
|
60
|
+
reportData = JSON.parse(raw);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return {
|
|
63
|
+
status: "ERROR",
|
|
64
|
+
message: `Failed to load report file: ${reportFile}. Error: ${err.message}`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const patch = buildPatchFromViolations(reportData);
|
|
69
|
+
if (!patch) {
|
|
70
|
+
return {
|
|
71
|
+
status: "NO_AUTOFIX_AVAILABLE",
|
|
72
|
+
message: "No violations contain auto-fix patches yet. Implement suggested_patch in rule checkers first.",
|
|
73
|
+
violations_count: reportData.violations?.length || 0,
|
|
74
|
+
note: "Auto-fix requires rule checkers to provide suggested_patch field for each violation"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await ensureDir(patchesDir(repoPath));
|
|
79
|
+
const patchPath = path.join(patchesDir(repoPath), `${runId}.patch`);
|
|
80
|
+
await fs.writeFile(patchPath, patch, "utf8");
|
|
81
|
+
|
|
82
|
+
// SUGGEST mode: just show the proposed changes
|
|
83
|
+
if (mode === "suggest") {
|
|
84
|
+
const violationsWithFixes = reportData.violations.filter(v => v.suggested_patch);
|
|
85
|
+
const filesImpacted = [...new Set(violationsWithFixes.map(v => v.file_path))];
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
status: "PATCH_PROPOSED",
|
|
89
|
+
mode: "suggest",
|
|
90
|
+
patch_path: patchPath,
|
|
91
|
+
patch_preview: patch.substring(0, 2000) + (patch.length > 2000 ? "\n... (truncated)" : ""),
|
|
92
|
+
files_impacted: filesImpacted,
|
|
93
|
+
violations_with_fixes: violationsWithFixes.length,
|
|
94
|
+
total_violations: reportData.violations?.length || 0,
|
|
95
|
+
applied: false,
|
|
96
|
+
message: `✅ Auto-fix analysis complete!\n\n📊 Summary:\n- ${violationsWithFixes.length} violations have suggested fixes\n- ${filesImpacted.length} files will be modified\n\n📄 Patch saved to: ${patchPath}\n\n⚠️ NEXT STEP:\n- Review the patch file carefully\n- To apply fixes, call 'auto_fix' tool again with mode='apply'\n- Application requires approval code and clean git state`,
|
|
97
|
+
risk_notes: [
|
|
98
|
+
"Patches are auto-generated and may need manual review",
|
|
99
|
+
"Always commit your changes before applying auto-fixes",
|
|
100
|
+
"Test thoroughly after applying fixes"
|
|
101
|
+
],
|
|
102
|
+
next_action: "Review patch, then call auto_fix with mode='apply' and approvalCode"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// APPLY mode: actually modify files
|
|
107
|
+
const clean = await gitIsClean(repoPath);
|
|
108
|
+
if (!clean) {
|
|
109
|
+
return {
|
|
110
|
+
status: "REQUIRES_CLEAN_GIT",
|
|
111
|
+
message: "⚠️ Git repository has uncommitted changes.\n\nBefore applying auto-fixes:\n1. Commit your current changes: git commit -am 'your message'\n2. Or stash changes: git stash\n3. Then call auto_fix again with mode='apply'",
|
|
112
|
+
patch_path: patchPath,
|
|
113
|
+
applied: false,
|
|
114
|
+
instruction: "Clean your git working directory before applying patches"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Apply patch via git apply
|
|
119
|
+
try {
|
|
120
|
+
await execFileAsync("git", ["apply", patchPath], { cwd: repoPath });
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return {
|
|
123
|
+
status: "PATCH_FAILED",
|
|
124
|
+
message: `Failed to apply patch: ${err.message}`,
|
|
125
|
+
patch_path: patchPath,
|
|
126
|
+
applied: false,
|
|
127
|
+
suggestion: "Review the patch file manually and apply fixes by hand, or check for conflicts"
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get git diff summary after applying
|
|
132
|
+
const { stat, diff } = await getGitDiff(repoPath);
|
|
133
|
+
const modifiedFiles = stat.split("\n").filter(line => line.trim()).slice(0, -1); // Remove summary line
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
status: "PATCH_APPLIED",
|
|
137
|
+
mode: "apply",
|
|
138
|
+
patch_path: patchPath,
|
|
139
|
+
applied: true,
|
|
140
|
+
files_modified: modifiedFiles,
|
|
141
|
+
git_diff_stat: stat,
|
|
142
|
+
git_diff_preview: diff.substring(0, 3000) + (diff.length > 3000 ? "\n... (truncated, use git diff for full output)" : ""),
|
|
143
|
+
message: `✅ Auto-fixes applied successfully!\n\n📊 Changes:\n${stat}\n\n⚠️ IMPORTANT:\n1. Review the changes: git diff\n2. Test your application thoroughly\n3. Commit if satisfied: git commit -am 'Applied X-Readiness auto-fixes'\n4. Or revert if needed: git checkout .`,
|
|
144
|
+
next_steps: [
|
|
145
|
+
"Review git diff output",
|
|
146
|
+
"Run tests to verify fixes",
|
|
147
|
+
"Commit changes if satisfied",
|
|
148
|
+
"Re-run execution to verify compliance"
|
|
149
|
+
]
|
|
150
|
+
};
|
|
151
|
+
}
|