@bytesbrains/pi-contrib-gate 1.7.0 → 1.8.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/package.json +3 -3
- package/src/config.ts +3 -0
- package/src/intercepts.ts +15 -0
- package/src/types.ts +3 -0
- package/src/validate.ts +19 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytesbrains/pi-contrib-gate",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Contribution gateway for AI agents
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "Contribution gateway for AI agents — enforce branch naming, conventional commits, pre-commit quality gates, and PR automation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension",
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"vitest": "^2.1.9"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -71,6 +71,9 @@ export function loadConfig(cwd: string): ContribConfig {
|
|
|
71
71
|
doctorAudit: result["quality.doctorAudit"] !== "false",
|
|
72
72
|
maxFilesChanged: parseInt(result["quality.maxFilesChanged"] as string) || DEFAULT_CONFIG.quality.maxFilesChanged,
|
|
73
73
|
maxLinesAdded: parseInt(result["quality.maxLinesAdded"] as string) || DEFAULT_CONFIG.quality.maxLinesAdded,
|
|
74
|
+
lensErrors: parseBool(result["quality.lensErrors"] as string, DEFAULT_CONFIG.quality.lensErrors),
|
|
75
|
+
maxLensErrors: parseInt(result["quality.maxLensErrors"] as string) || DEFAULT_CONFIG.quality.maxLensErrors,
|
|
76
|
+
secretScan: parseBool(result["quality.secretScan"] as string, DEFAULT_CONFIG.quality.secretScan),
|
|
74
77
|
},
|
|
75
78
|
requireIssueValidation: parseBool(result["requireIssueValidation"] as string, DEFAULT_CONFIG.requireIssueValidation),
|
|
76
79
|
};
|
package/src/intercepts.ts
CHANGED
|
@@ -90,6 +90,21 @@ export async function interceptToolCall(event: any, ctx: ExtensionContext) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// Run secret scan on all direct commits (including main/dev)
|
|
94
|
+
if (config.quality.secretScan) {
|
|
95
|
+
const leak = exec("gitleaks protect --staged --no-banner 2>&1", ctx.cwd);
|
|
96
|
+
if (!leak.ok) {
|
|
97
|
+
const leakLines = leak.stdout
|
|
98
|
+
.split("\n")
|
|
99
|
+
.filter((l: string) => l.includes("Finding:") || l.includes("RuleID:") || l.includes("File:"))
|
|
100
|
+
.join("\n ");
|
|
101
|
+
return {
|
|
102
|
+
block: true,
|
|
103
|
+
reason: `Secrets/credentials detected in staged changes!\n ${leakLines}\n\n Remove secrets before committing. Add a .gitleaks.toml allowlist or gitleaks:allow comment for false positives.`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
93
108
|
if (config.commits.convention === "conventional") {
|
|
94
109
|
const msgMatch = cmd.match(/-m\s+"([^"]+)"/);
|
|
95
110
|
if (msgMatch) {
|
package/src/types.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface ContribConfig {
|
|
|
28
28
|
lensErrors: boolean;
|
|
29
29
|
/** Max allowed LSP errors in staged files (default: 0) */
|
|
30
30
|
maxLensErrors: number;
|
|
31
|
+
/** Scan staged changes for secrets/credentials with gitleaks (default: true) */
|
|
32
|
+
secretScan: boolean;
|
|
31
33
|
};
|
|
32
34
|
/** Validate that the linked Gitea issue actually exists before starting work (default: true) */
|
|
33
35
|
requireIssueValidation: boolean;
|
|
@@ -65,6 +67,7 @@ export const DEFAULT_CONFIG: ContribConfig = {
|
|
|
65
67
|
maxLinesAdded: 500,
|
|
66
68
|
lensErrors: true,
|
|
67
69
|
maxLensErrors: 0,
|
|
70
|
+
secretScan: true,
|
|
68
71
|
},
|
|
69
72
|
requireIssueValidation: true,
|
|
70
73
|
};
|
package/src/validate.ts
CHANGED
|
@@ -48,6 +48,25 @@ export function runQualityGate(
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
|
|
52
|
+
// ── Secret / credential scan with gitleaks ─────────────────
|
|
53
|
+
if (config.quality.secretScan) {
|
|
54
|
+
const leak = exec("gitleaks protect --staged --no-banner 2>&1", cwd);
|
|
55
|
+
if (!leak.ok) {
|
|
56
|
+
// gitleaks exit code 1 = leaks found
|
|
57
|
+
const leakSummary = leak.stdout
|
|
58
|
+
.split("\n")
|
|
59
|
+
.filter((l) => l.includes("Finding:") || l.includes("RuleID:") || l.includes("File:"))
|
|
60
|
+
.join("\n ");
|
|
61
|
+
errors.push(
|
|
62
|
+
`Secrets/credentials detected in staged changes!\n ${leakSummary}\n\n Remove secrets from files before committing. If this is a false positive, add a .gitleaks.toml allowlist or use gitleaks:allow comment.`,
|
|
63
|
+
);
|
|
64
|
+
} else if (leak.stderr && !leak.stderr.includes("no leaks found")) {
|
|
65
|
+
// gitleaks might output to stderr even on success (warnings, etc.)
|
|
66
|
+
// Only flag as error if the exit code was non-zero, already handled above.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
const diff = exec("git diff --cached --name-only", cwd);
|
|
52
71
|
if (diff.ok) {
|
|
53
72
|
const files = diff.stdout.split("\n").filter(Boolean);
|