feat: deliver policy-driven Git Validator 2.0

This commit is contained in:
NuklearRabbit
2026-07-29 18:24:27 +02:00
parent 7b05c953b6
commit a0733875c4
18 changed files with 502 additions and 133 deletions
+42 -17
View File
@@ -75,8 +75,14 @@ async function handleShellActions(event, target, action, repository) {
} else if (action === "git-validator-repair") {
const check = ui.gitValidation?.checks?.[Number(target.dataset.checkIndex)];
if (!check?.fixAction) return;
if (!check.safe && !confirm(check.confirmation || `Apply ${check.title}?`))
let preview;
try {
preview = await window.forgeflow.gitValidatorPreviewRepair(repository.fullName, check);
} catch (error) {
showToast("Preview failed", error.message, "error");
return;
}
if (!confirm(`${check.confirmation || `Apply ${check.title}?`}\n\nReviewable change:\n${preview.diff}\n\nNothing will be committed or pushed.`)) return;
setLoading(true, `Repairing ${check.title}`);
try {
await window.forgeflow.gitValidatorRepair(repository.fullName, check);
@@ -90,27 +96,46 @@ async function handleShellActions(event, target, action, repository) {
} finally {
setLoading(false);
}
} else if (action === "git-validator-repair-safe") {
const checks = (ui.gitValidation?.checks || []).filter(
(check) => check.fixAction && check.safe,
);
setLoading(true, `Applying ${checks.length} safe Git fixes…`);
let repaired = 0;
} else if (action === "git-validator-policy") {
setLoading(true, "Applying assurance policy…");
try {
for (const check of checks) {
await window.forgeflow.gitValidatorRepair(repository.fullName, check);
repaired += 1;
}
await refreshRepositories(false, true);
ui.gitValidation = await window.forgeflow.gitValidatorScan(
repository.fullName,
);
showToast("Safe Git fixes applied", `${repaired} repaired.`, "success");
await window.forgeflow.gitValidatorSetPolicy(repository.fullName, { id: target.value });
ui.gitValidation = await window.forgeflow.gitValidatorScan(repository.fullName);
showToast("Policy updated", ui.gitValidation.policy.label, "success");
} catch (error) {
showToast("Safe repair stopped", error.message, "error");
showToast("Policy update failed", error.message, "error");
} finally {
setLoading(false);
}
} else if (action === "git-validator-export") {
try {
const exported = await window.forgeflow.gitValidatorExport(repository.fullName, target.dataset.format || "markdown");
const url = URL.createObjectURL(new Blob([exported.content], { type: exported.mimeType }));
const link = document.createElement("a");
link.href = url;
link.download = `${repository.name || "repository"}-git-assurance.${exported.extension}`;
link.click();
URL.revokeObjectURL(url);
showToast("Report exported", link.download, "success");
} catch (error) {
showToast("Export failed", error.message, "error");
}
} else if (action === "git-validator-suppress") {
const check = ui.gitValidation?.checks?.[Number(target.dataset.checkIndex)];
if (!check) return;
const reason = prompt("Reason for this temporary exception (minimum 10 characters):", "Accepted temporarily while remediation is tracked.");
if (!reason) return;
const author = prompt("Exception owner:", ui.boot?.state?.gitea?.user?.login || "");
if (!author) return;
const ticket = prompt("Ticket reference (optional):", "") || "";
const expiresAt = new Date(Date.now() + 7 * 86_400_000).toISOString();
try {
await window.forgeflow.gitValidatorSuppress(repository.fullName, { checkId: check.id, reason, author, ticket, expiresAt, scope: "repository", evidence: `${ui.gitValidation.commitSha || "unknown"}:${check.id}:${check.status}` });
ui.gitValidation = await window.forgeflow.gitValidatorScan(repository.fullName);
showToast("Exception documented", `Expires ${formatDate(expiresAt)}.`, "success");
} catch (error) {
showToast("Exception rejected", error.message, "error");
}
} else if (action === "toggle-favorite") {
ui.boot.state = await window.forgeflow.favoriteRepository(
repository.fullName,
+2
View File
@@ -55,6 +55,8 @@ app.addEventListener("change", async (event) => {
} else if (event.target.id === "action-profile-select") {
ui.selectedProfileId = event.target.value;
render();
} else if (event.target.id === "validator-policy") {
await handleShellActions(event, event.target, "git-validator-policy", selectedRepository());
} else if (event.target.id === "profile-provider") {
ui.modal.provider = event.target.value;
render();
+17
View File
@@ -466,6 +466,11 @@ function createMockDeploymentBridge(context) {
checkedAt: iso(),
score: 78,
grade: "Good",
policy: { id: "standard", label: "Standard", requiredScore: 70 },
ready: true,
commitSha: "8cbaf303aa3bb9b4023a7c89aa13fb70ce612847",
trend: { newlyFound: ["working-tree"], resolved: ["editorconfig"], regressions: [], suppressions: [] },
expiredSuppressions: [],
summary: { passed: 7, warnings: 3, errors: 0, repairable: 2 },
checks: [
{
@@ -566,6 +571,18 @@ function createMockDeploymentBridge(context) {
],
};
},
async gitValidatorSetPolicy(_fullName, policy) {
return { id: policy.id, label: policy.id[0].toUpperCase() + policy.id.slice(1) };
},
async gitValidatorSuppress(_fullName, suppression) {
return { ...suppression, id: `suppression-${Date.now()}`, createdAt: iso() };
},
async gitValidatorPreviewRepair(_fullName, check) {
return { checkId: check.id, action: check.fixAction, files: [".git/config"], diff: "+ reviewed configuration change\n", remoteMutation: check.fixAction === "protect-default-branch" };
},
async gitValidatorExport(fullName, format) {
return { extension: format === "markdown" ? "md" : format, mimeType: "text/plain", content: `# Git assurance — ${fullName}\n` };
},
async gitValidatorRepair() {
await wait(180);
return { repaired: true };
+3 -5
View File
@@ -390,14 +390,12 @@ function renderGitValidator(repository) {
return `<div class="validator-empty panel">${projectIllustration("diagnostics")}<div><div class="eyebrow">Repository assurance</div><h2>Validate Git best practices</h2><p>Inspect repository identity, branch governance, tracked secrets, file hygiene and safe local synchronization settings.</p><button class="button primary" data-action="git-validator-scan">${icon("shield")}Run Git Validator</button></div></div>`;
const tone =
report.score >= 90 ? "success" : report.score >= 70 ? "warning" : "danger";
const safeFixes = report.checks.filter(
(check) => check.fixAction && check.safe,
);
const groups = report.checks.reduce((grouped, check) => {
(grouped[check.category] ||= []).push(check);
return grouped;
}, {});
return `<div class="validator-page"><section class="validator-hero panel ${tone}"><div class="validator-score"><strong>${report.score}</strong><span>/ 100</span></div><div><div class="eyebrow">Git assurance score</div><h2>${escapeHtml(report.grade)}</h2><p>${report.summary.passed} passed · ${report.summary.warnings} recommendations · ${report.summary.errors} critical</p></div>${projectIllustration("diagnostics")}<div class="validator-actions"><button class="button" data-action="git-validator-scan">${icon("refresh")}Scan again</button>${safeFixes.length ? `<button class="button primary" data-action="git-validator-repair-safe">${icon("wrench")}Apply ${safeFixes.length} safe fix${safeFixes.length === 1 ? "" : "es"}</button>` : ""}</div></section><div class="validator-groups">${Object.entries(
const trend = report.trend || {};
return `<div class="validator-page"><section class="validator-hero panel ${tone}"><div class="validator-score"><strong>${report.score}</strong><span>/ 100</span></div><div><div class="eyebrow">${escapeHtml(report.policy?.label || "Standard")} policy · ${report.ready ? "release-ready" : "review required"}</div><h2>${escapeHtml(report.grade)}</h2><p>${report.summary.passed} passed · ${report.summary.warnings} recommendations · ${report.summary.errors} critical</p><p class="meta">${trend.newlyFound?.length || 0} new · ${trend.resolved?.length || 0} resolved · ${trend.regressions?.length || 0} regressions · ${report.expiredSuppressions?.length || 0} expired exceptions</p></div>${projectIllustration("diagnostics")}<div class="validator-actions"><label class="sr-only" for="validator-policy">Assurance policy</label><select id="validator-policy" class="select" data-action="git-validator-policy">${["minimal", "standard", "strict", "production"].map((policy) => `<option value="${policy}" ${report.policy?.id === policy ? "selected" : ""}>${policy[0].toUpperCase() + policy.slice(1)}</option>`).join("")}</select><button class="button" data-action="git-validator-scan">${icon("refresh")}Scan again</button><button class="button" data-action="git-validator-export" data-format="markdown">Export report</button></div></section><div class="validator-groups">${Object.entries(
groups,
)
.map(
@@ -405,7 +403,7 @@ function renderGitValidator(repository) {
`<section class="panel validator-group"><div class="panel-header"><h3>${escapeHtml(category)}</h3><span class="meta">${checks.filter((check) => check.status === "pass").length}/${checks.length} passed</span></div><div class="validator-checks">${checks
.map((check) => {
const checkIndex = report.checks.indexOf(check);
return `<article class="validator-check ${check.status}"><span class="validator-check-icon">${icon(check.status === "pass" ? "check" : check.status === "error" ? "error" : "warning")}</span><div><strong>${escapeHtml(check.title)}</strong><p>${escapeHtml(check.detail)}</p></div>${check.fixAction ? `<button class="button ${check.safe ? "" : "primary"}" data-action="git-validator-repair" data-check-index="${checkIndex}">${icon("wrench")}${check.safe ? "Fix safely" : "Review & fix"}</button>` : `<span class="status-pill ${check.status === "pass" ? "success" : check.status === "error" ? "danger" : "warning"}">${check.status === "pass" ? "Best practice" : "Review"}</span>`}</article>`;
return `<article class="validator-check ${check.status}"><span class="validator-check-icon">${icon(check.status === "pass" ? "check" : check.status === "error" ? "error" : "warning")}</span><div><strong>${escapeHtml(check.title)}</strong><p>${escapeHtml(check.detail)}</p>${check.suppressed ? `<small>Suppressed until ${formatDate(check.suppression.expiresAt)} · ${escapeHtml(check.suppression.reason)}</small>` : check.expiredSuppression ? `<small>Exception expired; finding is active again.</small>` : ""}</div>${check.fixAction ? `<button class="button ${check.safe ? "" : "primary"}" data-action="git-validator-repair" data-check-index="${checkIndex}">${icon("wrench")}Preview fix</button>` : check.status !== "pass" && !check.suppressed ? `<button class="button" data-action="git-validator-suppress" data-check-index="${checkIndex}">Document exception</button>` : `<span class="status-pill ${check.suppressed ? "warning" : check.status === "pass" ? "success" : check.status === "error" ? "danger" : "warning"}">${check.suppressed ? "Suppressed" : check.status === "pass" ? "Best practice" : "Review"}</span>`}</article>`;
})
.join("")}</div></section>`,
)