pi-audit-master 0.1.3 → 0.1.5
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/extensions/audit-manager.ts +129 -0
- package/extensions/fix-fleet.ts +70 -0
- package/extensions/index.ts +91 -0
- package/extensions/project-mapper.ts +115 -0
- package/extensions/synthesizer.ts +96 -0
- package/{dist/extensions/types/index.js → extensions/types/index.ts} +32 -9
- package/package.json +3 -5
- package/dist/extensions/audit-manager.d.ts +0 -19
- package/dist/extensions/audit-manager.js +0 -126
- package/dist/extensions/fix-fleet.d.ts +0 -13
- package/dist/extensions/fix-fleet.js +0 -59
- package/dist/extensions/index.d.ts +0 -6
- package/dist/extensions/index.js +0 -59
- package/dist/extensions/project-mapper.d.ts +0 -11
- package/dist/extensions/project-mapper.js +0 -139
- package/dist/extensions/synthesizer.d.ts +0 -18
- package/dist/extensions/synthesizer.js +0 -74
- package/dist/extensions/types/index.d.ts +0 -20
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "pi-coding-agent";
|
|
2
|
+
import { ProjectMapper } from "./project-mapper";
|
|
3
|
+
import { AuditSynthesizer } from "./synthesizer";
|
|
4
|
+
import { FixFleet } from "./fix-fleet";
|
|
5
|
+
import { AGENT_PROMPTS } from "./types";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
|
|
9
|
+
export interface AuditOptions {
|
|
10
|
+
path: string;
|
|
11
|
+
depth?: "surface" | "deep";
|
|
12
|
+
format?: "chat" | "file" | "hybrid";
|
|
13
|
+
fix?: boolean;
|
|
14
|
+
ctx: ExtensionCommandContext;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AuditManager {
|
|
18
|
+
constructor(private pi: ExtensionAPI) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Main entry point for the audit process.
|
|
22
|
+
*/
|
|
23
|
+
public async runAudit(options: AuditOptions): Promise<any> {
|
|
24
|
+
const config = await this.resolveConfig(options);
|
|
25
|
+
|
|
26
|
+
const mapper = new ProjectMapper(options.path);
|
|
27
|
+
const coreFiles = await mapper.mapCoreLogic(config.depth);
|
|
28
|
+
|
|
29
|
+
if (coreFiles.length === 0) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"No core logic files found to audit in the specified path.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. Parallel Audit Dispatch
|
|
36
|
+
const reports = await this.dispatchAuditAgents(coreFiles);
|
|
37
|
+
|
|
38
|
+
// 4. Synthesis
|
|
39
|
+
const synthesizer = new AuditSynthesizer(options.path);
|
|
40
|
+
const finalReport = await synthesizer.synthesize(reports);
|
|
41
|
+
|
|
42
|
+
// 5. Output Handling
|
|
43
|
+
const output = await this.handleOutput(
|
|
44
|
+
finalReport,
|
|
45
|
+
config.format,
|
|
46
|
+
options.ctx,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// 6. Optional Fix-Fleet
|
|
50
|
+
if (config.fix) {
|
|
51
|
+
const fleet = new FixFleet(this.pi);
|
|
52
|
+
const fixResult = await fleet.execute(finalReport, options.ctx);
|
|
53
|
+
return {
|
|
54
|
+
message: "Audit and Fix cycle complete.",
|
|
55
|
+
report: finalReport,
|
|
56
|
+
fixes: fixResult,
|
|
57
|
+
summary: output,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
message: "Audit complete.",
|
|
63
|
+
report: finalReport,
|
|
64
|
+
summary: output,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async resolveConfig(options: AuditOptions): Promise<{
|
|
69
|
+
depth: "surface" | "deep";
|
|
70
|
+
format: "chat" | "file" | "hybrid";
|
|
71
|
+
fix: boolean;
|
|
72
|
+
}> {
|
|
73
|
+
if (
|
|
74
|
+
options.depth &&
|
|
75
|
+
options.format !== undefined &&
|
|
76
|
+
options.fix !== undefined
|
|
77
|
+
) {
|
|
78
|
+
return {
|
|
79
|
+
depth: options.depth,
|
|
80
|
+
format: options.format,
|
|
81
|
+
fix: options.fix,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
depth: options.depth || "deep",
|
|
87
|
+
format: options.format || "hybrid",
|
|
88
|
+
fix: options.fix || false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async dispatchAuditAgents(files: string[]): Promise<string[]> {
|
|
93
|
+
const tasks = Object.entries(AGENT_PROMPTS).map(([persona, prompt]) => ({
|
|
94
|
+
agent: "worker",
|
|
95
|
+
task: `## ${persona} Audit\\n\\n${prompt}\\n\\nTarget Files:\\n${files.join("\\n")}\\n\\nProvide a markdown table with: | Severity | File:Line | Description | Fix Suggestion |`,
|
|
96
|
+
output: `audit-${persona.toLowerCase().replace(" ", "-")}.md`,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const results = await this.pi.subagents.parallel({
|
|
100
|
+
tasks,
|
|
101
|
+
concurrency: 5,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return results.map((r: any) => r.output || "No report generated.");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async handleOutput(
|
|
108
|
+
report: string,
|
|
109
|
+
format: string,
|
|
110
|
+
_ctx: ExtensionCommandContext,
|
|
111
|
+
): Promise<string> {
|
|
112
|
+
let summary = "";
|
|
113
|
+
|
|
114
|
+
if (format === "file" || format === "hybrid") {
|
|
115
|
+
const reportPath = path.join(process.cwd(), "audit-report.md");
|
|
116
|
+
fs.writeFileSync(reportPath, report);
|
|
117
|
+
summary += `[Detailed report saved to ${reportPath}]\\n`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (format === "chat" || format === "hybrid") {
|
|
121
|
+
const criticals = (report.match(/CRITICAL/gi) || []).length;
|
|
122
|
+
const highs = (report.match(/HIGH/gi) || []).length;
|
|
123
|
+
const mediums = (report.match(/MEDIUM/gi) || []).length;
|
|
124
|
+
summary += `Audit Summary: Found ${criticals} Critical, ${highs} High, and ${mediums} Medium issues.`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return summary;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "pi-coding-agent";
|
|
2
|
+
import type { AuditFinding } from "./synthesizer";
|
|
3
|
+
|
|
4
|
+
export interface FixResult {
|
|
5
|
+
issueId: string;
|
|
6
|
+
status: "RESOLVED" | "FAILED" | "SKIPPED";
|
|
7
|
+
details: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class FixFleet {
|
|
11
|
+
constructor(private pi: ExtensionAPI) {}
|
|
12
|
+
|
|
13
|
+
public async execute(report: string, ctx: any): Promise<FixResult[]> {
|
|
14
|
+
const issues = this.parseCriticalIssues(report);
|
|
15
|
+
const results: FixResult[] = [];
|
|
16
|
+
|
|
17
|
+
for (const issue of issues) {
|
|
18
|
+
try {
|
|
19
|
+
const resolved = await this.dispatchFixWorker(issue, ctx);
|
|
20
|
+
results.push({
|
|
21
|
+
issueId: issue.id,
|
|
22
|
+
status: resolved ? "RESOLVED" : "FAILED",
|
|
23
|
+
details: resolved ? "Fixed and verified" : "Worker failed to resolve",
|
|
24
|
+
});
|
|
25
|
+
} catch (e: any) {
|
|
26
|
+
results.push({
|
|
27
|
+
issueId: issue.id,
|
|
28
|
+
status: "FAILED",
|
|
29
|
+
details: e.message,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private parseCriticalIssues(report: string): AuditFinding[] {
|
|
38
|
+
const findings: AuditFinding[] = [];
|
|
39
|
+
const lines = report.split("\n");
|
|
40
|
+
const regex = /\| (CRITICAL|HIGH) \| ([^|]+) \| ([^|]+) \| ([^|]+) \|/i;
|
|
41
|
+
|
|
42
|
+
lines.forEach((line) => {
|
|
43
|
+
const match = line.match(regex);
|
|
44
|
+
if (match) {
|
|
45
|
+
const [_, severity, fileLine, description, fix] = match;
|
|
46
|
+
const [file, lineNum] = fileLine.split(":");
|
|
47
|
+
findings.push({
|
|
48
|
+
id: `issue-${Math.random().toString(36).substr(2, 9)}`,
|
|
49
|
+
file: file?.trim() || "unknown",
|
|
50
|
+
line: parseInt(lineNum?.trim() || "0"),
|
|
51
|
+
severity: severity.toUpperCase() as any,
|
|
52
|
+
description: description.trim(),
|
|
53
|
+
fixSuggestion: fix.trim(),
|
|
54
|
+
agent: "FixFleet",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return findings;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async dispatchFixWorker(
|
|
63
|
+
issue: AuditFinding,
|
|
64
|
+
ctx: any,
|
|
65
|
+
): Promise<boolean> {
|
|
66
|
+
// In a real Pi extension, this would use pi.subagents.parallel
|
|
67
|
+
// To simulate for now, we return true.
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "pi-coding-agent";
|
|
2
|
+
import { AuditManager } from "./audit-manager";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* pi-audit-master
|
|
6
|
+
* Professional multi-agent auditing and repair engine.
|
|
7
|
+
*/
|
|
8
|
+
export default async function piAuditMaster(pi: ExtensionAPI) {
|
|
9
|
+
const auditManager = new AuditManager(pi);
|
|
10
|
+
|
|
11
|
+
// Register the 'audit' tool for AI function-calling
|
|
12
|
+
pi.registerTool({
|
|
13
|
+
name: "audit",
|
|
14
|
+
description:
|
|
15
|
+
"Perform a comprehensive multi-agent audit of a directory. Options: depth (surface/deep), format (chat/file/hybrid), fix (on/off).",
|
|
16
|
+
parameters: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description:
|
|
22
|
+
"Path to the directory or file to audit. Defaults to current directory.",
|
|
23
|
+
},
|
|
24
|
+
depth: {
|
|
25
|
+
type: "string",
|
|
26
|
+
enum: ["surface", "deep"],
|
|
27
|
+
description:
|
|
28
|
+
"Audit depth. Surface: specified files only. Deep: entire project core logic.",
|
|
29
|
+
},
|
|
30
|
+
format: {
|
|
31
|
+
type: "string",
|
|
32
|
+
enum: ["chat", "file", "hybrid"],
|
|
33
|
+
description:
|
|
34
|
+
"Report format. Chat: concise summary. File: detailed .md report. Hybrid: both.",
|
|
35
|
+
},
|
|
36
|
+
fix: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description:
|
|
39
|
+
"Enable the Fix-Fleet to automatically resolve issues after auditing.",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
handler: async (ctx: ExtensionCommandContext, args: any) => {
|
|
44
|
+
const targetPath = args.path || ".";
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await auditManager.runAudit({
|
|
48
|
+
path: targetPath,
|
|
49
|
+
depth: args.depth,
|
|
50
|
+
format: args.format,
|
|
51
|
+
fix: args.fix,
|
|
52
|
+
ctx: ctx,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
ctx.ui.notify(`Audit failed: ${error.message}`, "error");
|
|
58
|
+
return { error: error.message };
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Register as a user-facing slash command for TUI visibility
|
|
64
|
+
// registerCommand is a real Pi runtime method (not in type declarations yet)
|
|
65
|
+
const piAny = pi as any;
|
|
66
|
+
if (typeof piAny.registerCommand === "function") {
|
|
67
|
+
piAny.registerCommand("audit", {
|
|
68
|
+
description: "Perform a comprehensive multi-agent codebase audit",
|
|
69
|
+
handler: async (ctx: ExtensionCommandContext) => {
|
|
70
|
+
ctx.ui.notify("Starting deep audit of the current project...", "info");
|
|
71
|
+
try {
|
|
72
|
+
const result = await auditManager.runAudit({
|
|
73
|
+
path: process.cwd(),
|
|
74
|
+
depth: "deep",
|
|
75
|
+
format: "hybrid",
|
|
76
|
+
fix: false,
|
|
77
|
+
ctx: ctx,
|
|
78
|
+
});
|
|
79
|
+
return JSON.stringify(result, null, 2);
|
|
80
|
+
} catch (error: any) {
|
|
81
|
+
ctx.ui.notify(`Audit failed: ${error.message}`, "error");
|
|
82
|
+
return `Error: ${error.message}`;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(
|
|
89
|
+
"[pi-audit-master] Extension loaded. Use /audit to start a comprehensive audit.",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export class ProjectMapper {
|
|
5
|
+
constructor(private rootPath: string) {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maps the project and returns a list of files that contain core logic.
|
|
9
|
+
* @param depth 'surface' for limited scan, 'deep' for full core scan.
|
|
10
|
+
*/
|
|
11
|
+
public async mapCoreLogic(depth: "surface" | "deep"): Promise<string[]> {
|
|
12
|
+
const absoluteRoot = path.resolve(this.rootPath);
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(absoluteRoot)) {
|
|
15
|
+
throw new Error(`Root path does not exist: ${absoluteRoot}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (depth === "surface") {
|
|
19
|
+
return this.mapSurface(absoluteRoot);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return this.mapDeep(absoluteRoot);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private mapSurface(root: string): string[] {
|
|
26
|
+
// Surface mode returns a very limited set of entry points
|
|
27
|
+
const entryPoints = ["extensions/index.ts", "src/index.ts", "index.ts"];
|
|
28
|
+
const found: string[] = [];
|
|
29
|
+
|
|
30
|
+
for (const ep of entryPoints) {
|
|
31
|
+
const fullPath = path.join(root, ep);
|
|
32
|
+
if (fs.existsSync(fullPath)) {
|
|
33
|
+
found.push(fullPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private mapDeep(root: string): string[] {
|
|
41
|
+
const coreFiles: string[] = [];
|
|
42
|
+
const maxFiles = 50;
|
|
43
|
+
|
|
44
|
+
const excludeDirs = new Set([
|
|
45
|
+
"node_modules",
|
|
46
|
+
"dist",
|
|
47
|
+
".git",
|
|
48
|
+
".vscode",
|
|
49
|
+
"coverage",
|
|
50
|
+
]);
|
|
51
|
+
const priorityDirs = new Set(["extensions", "src", "lib", "core"]);
|
|
52
|
+
const allowedExts = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
53
|
+
const excludeFiles = new Set([
|
|
54
|
+
"package-lock.json",
|
|
55
|
+
"yarn.lock",
|
|
56
|
+
"pnpm-lock.yaml",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const walk = (dir: string) => {
|
|
60
|
+
if (coreFiles.length >= maxFiles) return;
|
|
61
|
+
|
|
62
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
63
|
+
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (coreFiles.length >= maxFiles) break;
|
|
66
|
+
|
|
67
|
+
const fullPath = path.join(dir, entry.name);
|
|
68
|
+
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
if (excludeDirs.has(entry.name)) continue;
|
|
71
|
+
walk(fullPath);
|
|
72
|
+
} else if (entry.isFile()) {
|
|
73
|
+
const ext = path.extname(entry.name);
|
|
74
|
+
if (!allowedExts.has(ext)) continue;
|
|
75
|
+
if (excludeFiles.has(entry.name)) continue;
|
|
76
|
+
|
|
77
|
+
const isPriority = priorityDirs.has(
|
|
78
|
+
path.basename(path.dirname(fullPath)),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (isPriority) {
|
|
82
|
+
coreFiles.push(fullPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
walk(root);
|
|
89
|
+
|
|
90
|
+
if (coreFiles.length < 10) {
|
|
91
|
+
const allFiles: string[] = [];
|
|
92
|
+
const walkAll = (dir: string) => {
|
|
93
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const fullPath = path.join(dir, entry.name);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
if (!excludeDirs.has(entry.name)) walkAll(fullPath);
|
|
98
|
+
} else if (entry.isFile()) {
|
|
99
|
+
if (
|
|
100
|
+
allowedExts.has(path.extname(entry.name)) &&
|
|
101
|
+
!excludeFiles.has(entry.name)
|
|
102
|
+
) {
|
|
103
|
+
allFiles.push(fullPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
walkAll(root);
|
|
109
|
+
const finalSet = new Set([...coreFiles, ...allFiles]);
|
|
110
|
+
return Array.from(finalSet).slice(0, maxFiles);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return coreFiles.slice(0, maxFiles);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export interface AuditFinding {
|
|
5
|
+
id: string;
|
|
6
|
+
file: string;
|
|
7
|
+
line: number;
|
|
8
|
+
severity: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW";
|
|
9
|
+
description: string;
|
|
10
|
+
fixSuggestion: string;
|
|
11
|
+
agent: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class AuditSynthesizer {
|
|
15
|
+
constructor(private rootPath: string) {}
|
|
16
|
+
|
|
17
|
+
public async synthesize(reports: string[]): Promise<string> {
|
|
18
|
+
const allFindings: AuditFinding[] = [];
|
|
19
|
+
|
|
20
|
+
for (const report of reports) {
|
|
21
|
+
allFindings.push(...this.parseReport(report));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const deduplicated = this.deduplicate(allFindings);
|
|
25
|
+
const sorted = this.sortFindings(deduplicated);
|
|
26
|
+
|
|
27
|
+
return this.generateMarkdown(sorted);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private parseReport(report: string): AuditFinding[] {
|
|
31
|
+
const findings: AuditFinding[] = [];
|
|
32
|
+
const lines = report.split("\n");
|
|
33
|
+
|
|
34
|
+
// Simple regex-based table parsing
|
|
35
|
+
const findingRegex =
|
|
36
|
+
/\| (CRITICAL|HIGH|MEDIUM|LOW) \| ([^|]+) \| ([^|]+) \| ([^|]+) \|/i;
|
|
37
|
+
|
|
38
|
+
lines.forEach((line, index) => {
|
|
39
|
+
const match = line.match(findingRegex);
|
|
40
|
+
if (match) {
|
|
41
|
+
const [_, severity, fileLine, description, fix] = match;
|
|
42
|
+
const [file, lineNum] = fileLine.split(":");
|
|
43
|
+
findings.push({
|
|
44
|
+
id: `find-${Math.random().toString(36).substr(2, 9)}`,
|
|
45
|
+
file: file?.trim() || "unknown",
|
|
46
|
+
line: parseInt(lineNum?.trim() || "0"),
|
|
47
|
+
severity: severity.toUpperCase() as any,
|
|
48
|
+
description: description.trim(),
|
|
49
|
+
fixSuggestion: fix.trim(),
|
|
50
|
+
agent: "AuditAgent",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return findings;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private deduplicate(findings: AuditFinding[]): AuditFinding[] {
|
|
59
|
+
const seen = new Set<string>();
|
|
60
|
+
return findings.filter((f) => {
|
|
61
|
+
const key = `${f.file}:${f.line}:${f.description}`;
|
|
62
|
+
if (seen.has(key)) return false;
|
|
63
|
+
seen.add(key);
|
|
64
|
+
return true;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private sortFindings(findings: AuditFinding[]): AuditFinding[] {
|
|
69
|
+
const weight = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
|
|
70
|
+
return findings.sort((a, b) => weight[a.severity] - weight[b.severity]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private generateMarkdown(findings: AuditFinding[]): string {
|
|
74
|
+
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
75
|
+
findings.forEach((f) => counts[f.severity]++);
|
|
76
|
+
|
|
77
|
+
let md = `# 🛡️ Audit Report\n\n`;
|
|
78
|
+
md += `## Executive Summary\n`;
|
|
79
|
+
md += `- **Critical**: ${counts.CRITICAL}\n- **High**: ${counts.HIGH}\n- **Medium**: ${counts.MEDIUM}\n- **Low**: ${counts.LOW}\n\n`;
|
|
80
|
+
|
|
81
|
+
md += `## Findings\n\n`;
|
|
82
|
+
md += `| Severity | Location | Description | Fix Suggestion |\n`;
|
|
83
|
+
md += `| :--- | :--- | :--- | :--- |\n`;
|
|
84
|
+
|
|
85
|
+
findings.forEach((f) => {
|
|
86
|
+
md += `| ${f.severity} | ${f.file}:${f.line} | ${f.description} | ${f.fixSuggestion} |\n`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
md += `\n## Next Steps\n`;
|
|
90
|
+
md += `1. Review Critical and High issues immediately.\n`;
|
|
91
|
+
md += `2. Deploy Fix-Fleet to resolve identified bugs.\n`;
|
|
92
|
+
md += `3. Run full test suite to verify stability.`;
|
|
93
|
+
|
|
94
|
+
return md;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export interface AuditFinding {
|
|
2
|
+
id: string;
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
severity: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW";
|
|
6
|
+
description: string;
|
|
7
|
+
fixSuggestion: string;
|
|
8
|
+
agent: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AuditConfig {
|
|
12
|
+
depth: "surface" | "deep";
|
|
13
|
+
format: "chat" | "file" | "hybrid";
|
|
14
|
+
fix: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AuditResult {
|
|
18
|
+
summary: string;
|
|
19
|
+
report: string;
|
|
20
|
+
fixes?: any[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const AGENT_PROMPTS: Record<string, string> = {
|
|
24
|
+
"Type Sentinel": `You are a Type Safety expert.
|
|
6
25
|
Your goal is to find Null/Undefined leaks.
|
|
7
26
|
Technique: Taint Analysis. Trace data from sources (API, user input) to sinks (function calls).
|
|
8
27
|
Checklist:
|
|
@@ -10,7 +29,8 @@ exports.AGENT_PROMPTS = {
|
|
|
10
29
|
- Unsafe type casting ('as any').
|
|
11
30
|
- Missing guards on async returns.
|
|
12
31
|
Output: A markdown table | Severity | File:Line | Description | Fix Suggestion |`,
|
|
13
|
-
|
|
32
|
+
|
|
33
|
+
"Logic Architect": `You are a Logical Flow expert.
|
|
14
34
|
Your goal is to find algorithmic flaws and race conditions.
|
|
15
35
|
Technique: State-Machine Analysis. Look for unexpected state transitions.
|
|
16
36
|
Checklist:
|
|
@@ -18,7 +38,8 @@ exports.AGENT_PROMPTS = {
|
|
|
18
38
|
- Race conditions in concurrent async calls.
|
|
19
39
|
- Off-by-one errors in loops.
|
|
20
40
|
Output: A markdown table | Severity | File:Line | Description | Fix Suggestion |`,
|
|
21
|
-
|
|
41
|
+
|
|
42
|
+
"Performance Oracle": `You are a Performance and Scaling expert.
|
|
22
43
|
Your goal is to find bottlenecks and memory leaks.
|
|
23
44
|
Technique: Complexity Analysis.
|
|
24
45
|
Checklist:
|
|
@@ -26,7 +47,8 @@ exports.AGENT_PROMPTS = {
|
|
|
26
47
|
- Excessive memory allocations in hot paths.
|
|
27
48
|
- Unnecessary API calls in loops.
|
|
28
49
|
Output: A markdown table | Severity | File:Line | Description | Fix Suggestion |`,
|
|
29
|
-
|
|
50
|
+
|
|
51
|
+
"Ecosystem Integrator": `You are a Pi Extension expert.
|
|
30
52
|
Your goal is to ensure perfect integration with the Pi Runtime.
|
|
31
53
|
Technique: Contract Analysis.
|
|
32
54
|
Checklist:
|
|
@@ -34,7 +56,8 @@ exports.AGENT_PROMPTS = {
|
|
|
34
56
|
- Proper use of event.abort() for blocking.
|
|
35
57
|
- Correct ExtensionAPI factory pattern.
|
|
36
58
|
Output: A markdown table | Severity | File:Line | Description | Fix Suggestion |`,
|
|
37
|
-
|
|
59
|
+
|
|
60
|
+
"Quality Guardian": `You are a Clean Code expert.
|
|
38
61
|
Your goal is to reduce technical debt and improve maintainability.
|
|
39
62
|
Technique: Smell Detection.
|
|
40
63
|
Checklist:
|
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-audit-master",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Professional multi-agent codebase auditing and automated repair engine",
|
|
5
5
|
"keywords": ["pi-package"],
|
|
6
6
|
"pi": {
|
|
7
7
|
"extensions": ["./extensions"]
|
|
8
8
|
},
|
|
9
|
-
"main": "dist/extensions/index.js",
|
|
10
|
-
"types": "dist/extensions/index.d.ts",
|
|
11
9
|
"files": [
|
|
12
|
-
"
|
|
10
|
+
"extensions",
|
|
13
11
|
"README.md",
|
|
14
12
|
"CHANGELOG.md",
|
|
15
13
|
"LICENSE"
|
|
@@ -17,7 +15,7 @@
|
|
|
17
15
|
"scripts": {
|
|
18
16
|
"build": "tsc",
|
|
19
17
|
"test": "jest",
|
|
20
|
-
"
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
21
19
|
},
|
|
22
20
|
"peerDependencies": {
|
|
23
21
|
"pi-coding-agent": "*"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "pi-coding-agent";
|
|
2
|
-
export interface AuditOptions {
|
|
3
|
-
path: string;
|
|
4
|
-
depth?: "surface" | "deep";
|
|
5
|
-
format?: "chat" | "file" | "hybrid";
|
|
6
|
-
fix?: boolean;
|
|
7
|
-
ctx: ExtensionCommandContext;
|
|
8
|
-
}
|
|
9
|
-
export declare class AuditManager {
|
|
10
|
-
private pi;
|
|
11
|
-
constructor(pi: ExtensionAPI);
|
|
12
|
-
/**
|
|
13
|
-
* Main entry point for the audit process.
|
|
14
|
-
*/
|
|
15
|
-
runAudit(options: AuditOptions): Promise<any>;
|
|
16
|
-
private resolveConfig;
|
|
17
|
-
dispatchAuditAgents(files: string[]): Promise<string[]>;
|
|
18
|
-
private handleOutput;
|
|
19
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AuditManager = void 0;
|
|
37
|
-
const project_mapper_1 = require("./project-mapper");
|
|
38
|
-
const synthesizer_1 = require("./synthesizer");
|
|
39
|
-
const fix_fleet_1 = require("./fix-fleet");
|
|
40
|
-
const types_1 = require("./types");
|
|
41
|
-
const fs = __importStar(require("fs"));
|
|
42
|
-
const path = __importStar(require("path"));
|
|
43
|
-
class AuditManager {
|
|
44
|
-
pi;
|
|
45
|
-
constructor(pi) {
|
|
46
|
-
this.pi = pi;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Main entry point for the audit process.
|
|
50
|
-
*/
|
|
51
|
-
async runAudit(options) {
|
|
52
|
-
const config = await this.resolveConfig(options);
|
|
53
|
-
const mapper = new project_mapper_1.ProjectMapper(options.path);
|
|
54
|
-
const coreFiles = await mapper.mapCoreLogic(config.depth);
|
|
55
|
-
if (coreFiles.length === 0) {
|
|
56
|
-
throw new Error("No core logic files found to audit in the specified path.");
|
|
57
|
-
}
|
|
58
|
-
// 3. Parallel Audit Dispatch
|
|
59
|
-
const reports = await this.dispatchAuditAgents(coreFiles);
|
|
60
|
-
// 4. Synthesis
|
|
61
|
-
const synthesizer = new synthesizer_1.AuditSynthesizer(options.path);
|
|
62
|
-
const finalReport = await synthesizer.synthesize(reports);
|
|
63
|
-
// 5. Output Handling
|
|
64
|
-
const output = await this.handleOutput(finalReport, config.format, options.ctx);
|
|
65
|
-
// 6. Optional Fix-Fleet
|
|
66
|
-
if (config.fix) {
|
|
67
|
-
const fleet = new fix_fleet_1.FixFleet(this.pi);
|
|
68
|
-
const fixResult = await fleet.execute(finalReport, options.ctx);
|
|
69
|
-
return {
|
|
70
|
-
message: "Audit and Fix cycle complete.",
|
|
71
|
-
report: finalReport,
|
|
72
|
-
fixes: fixResult,
|
|
73
|
-
summary: output,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
message: "Audit complete.",
|
|
78
|
-
report: finalReport,
|
|
79
|
-
summary: output,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
async resolveConfig(options) {
|
|
83
|
-
if (options.depth &&
|
|
84
|
-
options.format !== undefined &&
|
|
85
|
-
options.fix !== undefined) {
|
|
86
|
-
return {
|
|
87
|
-
depth: options.depth,
|
|
88
|
-
format: options.format,
|
|
89
|
-
fix: options.fix,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
depth: options.depth || "deep",
|
|
94
|
-
format: options.format || "hybrid",
|
|
95
|
-
fix: options.fix || false,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
async dispatchAuditAgents(files) {
|
|
99
|
-
const tasks = Object.entries(types_1.AGENT_PROMPTS).map(([persona, prompt]) => ({
|
|
100
|
-
agent: "worker",
|
|
101
|
-
task: `## ${persona} Audit\\n\\n${prompt}\\n\\nTarget Files:\\n${files.join("\\n")}\\n\\nProvide a markdown table with: | Severity | File:Line | Description | Fix Suggestion |`,
|
|
102
|
-
output: `audit-${persona.toLowerCase().replace(" ", "-")}.md`,
|
|
103
|
-
}));
|
|
104
|
-
const results = await this.pi.subagents.parallel({
|
|
105
|
-
tasks,
|
|
106
|
-
concurrency: 5,
|
|
107
|
-
});
|
|
108
|
-
return results.map((r) => r.output || "No report generated.");
|
|
109
|
-
}
|
|
110
|
-
async handleOutput(report, format, _ctx) {
|
|
111
|
-
let summary = "";
|
|
112
|
-
if (format === "file" || format === "hybrid") {
|
|
113
|
-
const reportPath = path.join(process.cwd(), "audit-report.md");
|
|
114
|
-
fs.writeFileSync(reportPath, report);
|
|
115
|
-
summary += `[Detailed report saved to ${reportPath}]\\n`;
|
|
116
|
-
}
|
|
117
|
-
if (format === "chat" || format === "hybrid") {
|
|
118
|
-
const criticals = (report.match(/CRITICAL/gi) || []).length;
|
|
119
|
-
const highs = (report.match(/HIGH/gi) || []).length;
|
|
120
|
-
const mediums = (report.match(/MEDIUM/gi) || []).length;
|
|
121
|
-
summary += `Audit Summary: Found ${criticals} Critical, ${highs} High, and ${mediums} Medium issues.`;
|
|
122
|
-
}
|
|
123
|
-
return summary;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
exports.AuditManager = AuditManager;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "pi-coding-agent";
|
|
2
|
-
export interface FixResult {
|
|
3
|
-
issueId: string;
|
|
4
|
-
status: "RESOLVED" | "FAILED" | "SKIPPED";
|
|
5
|
-
details: string;
|
|
6
|
-
}
|
|
7
|
-
export declare class FixFleet {
|
|
8
|
-
private pi;
|
|
9
|
-
constructor(pi: ExtensionAPI);
|
|
10
|
-
execute(report: string, ctx: any): Promise<FixResult[]>;
|
|
11
|
-
private parseCriticalIssues;
|
|
12
|
-
private dispatchFixWorker;
|
|
13
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FixFleet = void 0;
|
|
4
|
-
class FixFleet {
|
|
5
|
-
pi;
|
|
6
|
-
constructor(pi) {
|
|
7
|
-
this.pi = pi;
|
|
8
|
-
}
|
|
9
|
-
async execute(report, ctx) {
|
|
10
|
-
const issues = this.parseCriticalIssues(report);
|
|
11
|
-
const results = [];
|
|
12
|
-
for (const issue of issues) {
|
|
13
|
-
try {
|
|
14
|
-
const resolved = await this.dispatchFixWorker(issue, ctx);
|
|
15
|
-
results.push({
|
|
16
|
-
issueId: issue.id,
|
|
17
|
-
status: resolved ? "RESOLVED" : "FAILED",
|
|
18
|
-
details: resolved ? "Fixed and verified" : "Worker failed to resolve",
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
catch (e) {
|
|
22
|
-
results.push({
|
|
23
|
-
issueId: issue.id,
|
|
24
|
-
status: "FAILED",
|
|
25
|
-
details: e.message,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return results;
|
|
30
|
-
}
|
|
31
|
-
parseCriticalIssues(report) {
|
|
32
|
-
const findings = [];
|
|
33
|
-
const lines = report.split("\n");
|
|
34
|
-
const regex = /\| (CRITICAL|HIGH) \| ([^|]+) \| ([^|]+) \| ([^|]+) \|/i;
|
|
35
|
-
lines.forEach((line) => {
|
|
36
|
-
const match = line.match(regex);
|
|
37
|
-
if (match) {
|
|
38
|
-
const [_, severity, fileLine, description, fix] = match;
|
|
39
|
-
const [file, lineNum] = fileLine.split(":");
|
|
40
|
-
findings.push({
|
|
41
|
-
id: `issue-${Math.random().toString(36).substr(2, 9)}`,
|
|
42
|
-
file: file?.trim() || "unknown",
|
|
43
|
-
line: parseInt(lineNum?.trim() || "0"),
|
|
44
|
-
severity: severity.toUpperCase(),
|
|
45
|
-
description: description.trim(),
|
|
46
|
-
fixSuggestion: fix.trim(),
|
|
47
|
-
agent: "FixFleet",
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
return findings;
|
|
52
|
-
}
|
|
53
|
-
async dispatchFixWorker(issue, ctx) {
|
|
54
|
-
// In a real Pi extension, this would use pi.subagents.parallel
|
|
55
|
-
// To simulate for now, we return true.
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
exports.FixFleet = FixFleet;
|
package/dist/extensions/index.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = default_1;
|
|
4
|
-
const audit_manager_1 = require("./audit-manager");
|
|
5
|
-
/**
|
|
6
|
-
* pi-audit-master
|
|
7
|
-
* Professional multi-agent auditing and repair engine.
|
|
8
|
-
*/
|
|
9
|
-
function default_1(pi) {
|
|
10
|
-
const auditManager = new audit_manager_1.AuditManager(pi);
|
|
11
|
-
// Register the 'audit' tool
|
|
12
|
-
pi.registerTool({
|
|
13
|
-
name: "audit",
|
|
14
|
-
description: "Perform a comprehensive multi-agent audit of a directory. Options: depth (surface/deep), format (chat/file/hybrid), fix (on/off).",
|
|
15
|
-
parameters: {
|
|
16
|
-
type: "object",
|
|
17
|
-
properties: {
|
|
18
|
-
path: {
|
|
19
|
-
type: "string",
|
|
20
|
-
description: "Path to the directory or file to audit. Defaults to current directory.",
|
|
21
|
-
},
|
|
22
|
-
depth: {
|
|
23
|
-
type: "string",
|
|
24
|
-
enum: ["surface", "deep"],
|
|
25
|
-
description: "Audit depth. Surface: specified files only. Deep: entire project core logic.",
|
|
26
|
-
},
|
|
27
|
-
format: {
|
|
28
|
-
type: "string",
|
|
29
|
-
enum: ["chat", "file", "hybrid"],
|
|
30
|
-
description: "Report format. Chat: concise summary. File: detailed .md report. Hybrid: both.",
|
|
31
|
-
},
|
|
32
|
-
fix: {
|
|
33
|
-
type: "boolean",
|
|
34
|
-
description: "Enable the Fix-Fleet to automatically resolve issues after auditing.",
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
handler: async (ctx, args) => {
|
|
39
|
-
const targetPath = args.path || ".";
|
|
40
|
-
// If depth/format/fix are missing, we will use ask_user_question inside the manager
|
|
41
|
-
// to make the experience interactive if the tool is called generically.
|
|
42
|
-
try {
|
|
43
|
-
const result = await auditManager.runAudit({
|
|
44
|
-
path: targetPath,
|
|
45
|
-
depth: args.depth,
|
|
46
|
-
format: args.format,
|
|
47
|
-
fix: args.fix,
|
|
48
|
-
ctx: ctx,
|
|
49
|
-
});
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
ctx.ui.notify(`Audit failed: ${error.message}`, "error");
|
|
54
|
-
return { error: error.message };
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
console.log("[pi-audit-master] Extension loaded. Use /audit to start a comprehensive audit.");
|
|
59
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export declare class ProjectMapper {
|
|
2
|
-
private rootPath;
|
|
3
|
-
constructor(rootPath: string);
|
|
4
|
-
/**
|
|
5
|
-
* Maps the project and returns a list of files that contain core logic.
|
|
6
|
-
* @param depth 'surface' for limited scan, 'deep' for full core scan.
|
|
7
|
-
*/
|
|
8
|
-
mapCoreLogic(depth: "surface" | "deep"): Promise<string[]>;
|
|
9
|
-
private mapSurface;
|
|
10
|
-
private mapDeep;
|
|
11
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ProjectMapper = void 0;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
|
-
class ProjectMapper {
|
|
40
|
-
rootPath;
|
|
41
|
-
constructor(rootPath) {
|
|
42
|
-
this.rootPath = rootPath;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Maps the project and returns a list of files that contain core logic.
|
|
46
|
-
* @param depth 'surface' for limited scan, 'deep' for full core scan.
|
|
47
|
-
*/
|
|
48
|
-
async mapCoreLogic(depth) {
|
|
49
|
-
const absoluteRoot = path.resolve(this.rootPath);
|
|
50
|
-
if (!fs.existsSync(absoluteRoot)) {
|
|
51
|
-
throw new Error(`Root path does not exist: ${absoluteRoot}`);
|
|
52
|
-
}
|
|
53
|
-
if (depth === "surface") {
|
|
54
|
-
return this.mapSurface(absoluteRoot);
|
|
55
|
-
}
|
|
56
|
-
return this.mapDeep(absoluteRoot);
|
|
57
|
-
}
|
|
58
|
-
mapSurface(root) {
|
|
59
|
-
// Surface mode returns a very limited set of entry points
|
|
60
|
-
const entryPoints = ["extensions/index.ts", "src/index.ts", "index.ts"];
|
|
61
|
-
const found = [];
|
|
62
|
-
for (const ep of entryPoints) {
|
|
63
|
-
const fullPath = path.join(root, ep);
|
|
64
|
-
if (fs.existsSync(fullPath)) {
|
|
65
|
-
found.push(fullPath);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return found;
|
|
69
|
-
}
|
|
70
|
-
mapDeep(root) {
|
|
71
|
-
const coreFiles = [];
|
|
72
|
-
const maxFiles = 50;
|
|
73
|
-
const excludeDirs = new Set([
|
|
74
|
-
"node_modules",
|
|
75
|
-
"dist",
|
|
76
|
-
".git",
|
|
77
|
-
".vscode",
|
|
78
|
-
"coverage",
|
|
79
|
-
]);
|
|
80
|
-
const priorityDirs = new Set(["extensions", "src", "lib", "core"]);
|
|
81
|
-
const allowedExts = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
82
|
-
const excludeFiles = new Set([
|
|
83
|
-
"package-lock.json",
|
|
84
|
-
"yarn.lock",
|
|
85
|
-
"pnpm-lock.yaml",
|
|
86
|
-
]);
|
|
87
|
-
const walk = (dir) => {
|
|
88
|
-
if (coreFiles.length >= maxFiles)
|
|
89
|
-
return;
|
|
90
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
91
|
-
for (const entry of entries) {
|
|
92
|
-
if (coreFiles.length >= maxFiles)
|
|
93
|
-
break;
|
|
94
|
-
const fullPath = path.join(dir, entry.name);
|
|
95
|
-
if (entry.isDirectory()) {
|
|
96
|
-
if (excludeDirs.has(entry.name))
|
|
97
|
-
continue;
|
|
98
|
-
walk(fullPath);
|
|
99
|
-
}
|
|
100
|
-
else if (entry.isFile()) {
|
|
101
|
-
const ext = path.extname(entry.name);
|
|
102
|
-
if (!allowedExts.has(ext))
|
|
103
|
-
continue;
|
|
104
|
-
if (excludeFiles.has(entry.name))
|
|
105
|
-
continue;
|
|
106
|
-
const isPriority = priorityDirs.has(path.basename(path.dirname(fullPath)));
|
|
107
|
-
if (isPriority) {
|
|
108
|
-
coreFiles.push(fullPath);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
walk(root);
|
|
114
|
-
if (coreFiles.length < 10) {
|
|
115
|
-
const allFiles = [];
|
|
116
|
-
const walkAll = (dir) => {
|
|
117
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
118
|
-
for (const entry of entries) {
|
|
119
|
-
const fullPath = path.join(dir, entry.name);
|
|
120
|
-
if (entry.isDirectory()) {
|
|
121
|
-
if (!excludeDirs.has(entry.name))
|
|
122
|
-
walkAll(fullPath);
|
|
123
|
-
}
|
|
124
|
-
else if (entry.isFile()) {
|
|
125
|
-
if (allowedExts.has(path.extname(entry.name)) &&
|
|
126
|
-
!excludeFiles.has(entry.name)) {
|
|
127
|
-
allFiles.push(fullPath);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
walkAll(root);
|
|
133
|
-
const finalSet = new Set([...coreFiles, ...allFiles]);
|
|
134
|
-
return Array.from(finalSet).slice(0, maxFiles);
|
|
135
|
-
}
|
|
136
|
-
return coreFiles.slice(0, maxFiles);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
exports.ProjectMapper = ProjectMapper;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface AuditFinding {
|
|
2
|
-
id: string;
|
|
3
|
-
file: string;
|
|
4
|
-
line: number;
|
|
5
|
-
severity: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW";
|
|
6
|
-
description: string;
|
|
7
|
-
fixSuggestion: string;
|
|
8
|
-
agent: string;
|
|
9
|
-
}
|
|
10
|
-
export declare class AuditSynthesizer {
|
|
11
|
-
private rootPath;
|
|
12
|
-
constructor(rootPath: string);
|
|
13
|
-
synthesize(reports: string[]): Promise<string>;
|
|
14
|
-
private parseReport;
|
|
15
|
-
private deduplicate;
|
|
16
|
-
private sortFindings;
|
|
17
|
-
private generateMarkdown;
|
|
18
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AuditSynthesizer = void 0;
|
|
4
|
-
class AuditSynthesizer {
|
|
5
|
-
rootPath;
|
|
6
|
-
constructor(rootPath) {
|
|
7
|
-
this.rootPath = rootPath;
|
|
8
|
-
}
|
|
9
|
-
async synthesize(reports) {
|
|
10
|
-
const allFindings = [];
|
|
11
|
-
for (const report of reports) {
|
|
12
|
-
allFindings.push(...this.parseReport(report));
|
|
13
|
-
}
|
|
14
|
-
const deduplicated = this.deduplicate(allFindings);
|
|
15
|
-
const sorted = this.sortFindings(deduplicated);
|
|
16
|
-
return this.generateMarkdown(sorted);
|
|
17
|
-
}
|
|
18
|
-
parseReport(report) {
|
|
19
|
-
const findings = [];
|
|
20
|
-
const lines = report.split("\n");
|
|
21
|
-
// Simple regex-based table parsing
|
|
22
|
-
const findingRegex = /\| (CRITICAL|HIGH|MEDIUM|LOW) \| ([^|]+) \| ([^|]+) \| ([^|]+) \|/i;
|
|
23
|
-
lines.forEach((line, index) => {
|
|
24
|
-
const match = line.match(findingRegex);
|
|
25
|
-
if (match) {
|
|
26
|
-
const [_, severity, fileLine, description, fix] = match;
|
|
27
|
-
const [file, lineNum] = fileLine.split(":");
|
|
28
|
-
findings.push({
|
|
29
|
-
id: `find-${Math.random().toString(36).substr(2, 9)}`,
|
|
30
|
-
file: file?.trim() || "unknown",
|
|
31
|
-
line: parseInt(lineNum?.trim() || "0"),
|
|
32
|
-
severity: severity.toUpperCase(),
|
|
33
|
-
description: description.trim(),
|
|
34
|
-
fixSuggestion: fix.trim(),
|
|
35
|
-
agent: "AuditAgent",
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
return findings;
|
|
40
|
-
}
|
|
41
|
-
deduplicate(findings) {
|
|
42
|
-
const seen = new Set();
|
|
43
|
-
return findings.filter((f) => {
|
|
44
|
-
const key = `${f.file}:${f.line}:${f.description}`;
|
|
45
|
-
if (seen.has(key))
|
|
46
|
-
return false;
|
|
47
|
-
seen.add(key);
|
|
48
|
-
return true;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
sortFindings(findings) {
|
|
52
|
-
const weight = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
|
|
53
|
-
return findings.sort((a, b) => weight[a.severity] - weight[b.severity]);
|
|
54
|
-
}
|
|
55
|
-
generateMarkdown(findings) {
|
|
56
|
-
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
57
|
-
findings.forEach((f) => counts[f.severity]++);
|
|
58
|
-
let md = `# 🛡️ Audit Report\n\n`;
|
|
59
|
-
md += `## Executive Summary\n`;
|
|
60
|
-
md += `- **Critical**: ${counts.CRITICAL}\n- **High**: ${counts.HIGH}\n- **Medium**: ${counts.MEDIUM}\n- **Low**: ${counts.LOW}\n\n`;
|
|
61
|
-
md += `## Findings\n\n`;
|
|
62
|
-
md += `| Severity | Location | Description | Fix Suggestion |\n`;
|
|
63
|
-
md += `| :--- | :--- | :--- | :--- |\n`;
|
|
64
|
-
findings.forEach((f) => {
|
|
65
|
-
md += `| ${f.severity} | ${f.file}:${f.line} | ${f.description} | ${f.fixSuggestion} |\n`;
|
|
66
|
-
});
|
|
67
|
-
md += `\n## Next Steps\n`;
|
|
68
|
-
md += `1. Review Critical and High issues immediately.\n`;
|
|
69
|
-
md += `2. Deploy Fix-Fleet to resolve identified bugs.\n`;
|
|
70
|
-
md += `3. Run full test suite to verify stability.`;
|
|
71
|
-
return md;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
exports.AuditSynthesizer = AuditSynthesizer;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface AuditFinding {
|
|
2
|
-
id: string;
|
|
3
|
-
file: string;
|
|
4
|
-
line: number;
|
|
5
|
-
severity: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW";
|
|
6
|
-
description: string;
|
|
7
|
-
fixSuggestion: string;
|
|
8
|
-
agent: string;
|
|
9
|
-
}
|
|
10
|
-
export interface AuditConfig {
|
|
11
|
-
depth: "surface" | "deep";
|
|
12
|
-
format: "chat" | "file" | "hybrid";
|
|
13
|
-
fix: boolean;
|
|
14
|
-
}
|
|
15
|
-
export interface AuditResult {
|
|
16
|
-
summary: string;
|
|
17
|
-
report: string;
|
|
18
|
-
fixes?: any[];
|
|
19
|
-
}
|
|
20
|
-
export declare const AGENT_PROMPTS: Record<string, string>;
|