@mestreyoda/fabrica 0.2.39 → 0.2.41
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/dist/index.js +436 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -113905,8 +113905,8 @@ import fsSync from "node:fs";
|
|
|
113905
113905
|
import path5 from "node:path";
|
|
113906
113906
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
113907
113907
|
function getCurrentVersion() {
|
|
113908
|
-
if ("0.2.
|
|
113909
|
-
return "0.2.
|
|
113908
|
+
if ("0.2.41") {
|
|
113909
|
+
return "0.2.41";
|
|
113910
113910
|
}
|
|
113911
113911
|
try {
|
|
113912
113912
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -126576,6 +126576,32 @@ function sanitizePublicOutput(input) {
|
|
|
126576
126576
|
}
|
|
126577
126577
|
|
|
126578
126578
|
// lib/tools/tasks/qa-evidence.ts
|
|
126579
|
+
function normalizeQaEvidenceForFingerprint(text) {
|
|
126580
|
+
return text.toLowerCase().replace(/\r/g, "").replace(/exit code:\s*-?\d+/gi, "exit code").replace(/\d+(?:\.\d+)?%/g, "<percent>").replace(/\s+/g, " ").trim();
|
|
126581
|
+
}
|
|
126582
|
+
function computeQaFingerprint(text) {
|
|
126583
|
+
const normalized = normalizeQaEvidenceForFingerprint(text);
|
|
126584
|
+
if (!normalized) return null;
|
|
126585
|
+
let hash2 = 2166136261;
|
|
126586
|
+
for (let i2 = 0; i2 < normalized.length; i2 += 1) {
|
|
126587
|
+
hash2 ^= normalized.charCodeAt(i2);
|
|
126588
|
+
hash2 = Math.imul(hash2, 16777619);
|
|
126589
|
+
}
|
|
126590
|
+
return `fnv1a:${(hash2 >>> 0).toString(16)}`;
|
|
126591
|
+
}
|
|
126592
|
+
function classifyQaEvidenceSubcause(validation) {
|
|
126593
|
+
if (validation.errors.includes("qa_evidence_missing")) return "qa_schema_missing";
|
|
126594
|
+
if (validation.sectionCount > 1) return "qa_section_count_invalid";
|
|
126595
|
+
if (validation.problems.some((problem) => problem.includes("host-system paths") || problem.includes("secrets") || problem.includes("environment dump"))) {
|
|
126596
|
+
return "qa_sanitization_failed";
|
|
126597
|
+
}
|
|
126598
|
+
if (validation.problems.some((problem) => problem.includes("Exit code: <number>"))) return "qa_exit_code_missing";
|
|
126599
|
+
if (validation.exitCode !== null && validation.exitCode !== 0) return "qa_exit_code_nonzero";
|
|
126600
|
+
if (validation.errors.includes("qa_evidence_only_exit_codes")) return "qa_exit_codes_only";
|
|
126601
|
+
if (validation.errors.some((error48) => error48.startsWith("qa_gate_missing_"))) return "qa_missing_required_gates";
|
|
126602
|
+
if (validation.errors.some((error48) => error48.startsWith("qa_coverage_below_threshold_"))) return "qa_coverage_below_threshold";
|
|
126603
|
+
return validation.errors.length || validation.problems.length ? "qa_unknown" : null;
|
|
126604
|
+
}
|
|
126579
126605
|
function validateQaEvidence(body) {
|
|
126580
126606
|
const text = body ?? "";
|
|
126581
126607
|
const headings = [...text.matchAll(/^## QA Evidence\b[\t ]*$/gim)];
|
|
@@ -126624,9 +126650,11 @@ function validateQaEvidence(body) {
|
|
|
126624
126650
|
tests: ["tests", "pytest", "jest", "vitest", "go test", "test session"],
|
|
126625
126651
|
coverage: ["coverage"]
|
|
126626
126652
|
};
|
|
126653
|
+
const missingGates = [];
|
|
126627
126654
|
for (const [gate, aliases] of Object.entries(GATE_ALIASES)) {
|
|
126628
126655
|
const found = aliases.some((alias) => new RegExp(alias, "i").test(section));
|
|
126629
126656
|
if (!found) {
|
|
126657
|
+
missingGates.push(gate);
|
|
126630
126658
|
errors.push(`qa_gate_missing_${gate}`);
|
|
126631
126659
|
}
|
|
126632
126660
|
}
|
|
@@ -126643,26 +126671,86 @@ function validateQaEvidence(body) {
|
|
|
126643
126671
|
errors.push(`qa_coverage_below_threshold_${Math.floor(cov)}`);
|
|
126644
126672
|
}
|
|
126645
126673
|
}
|
|
126674
|
+
const primarySubcause = classifyQaEvidenceSubcause({
|
|
126675
|
+
sectionCount,
|
|
126676
|
+
exitCode,
|
|
126677
|
+
problems,
|
|
126678
|
+
errors
|
|
126679
|
+
});
|
|
126646
126680
|
return {
|
|
126647
126681
|
valid: problems.length === 0 && errors.length === 0,
|
|
126648
126682
|
sectionCount,
|
|
126649
126683
|
exitCode,
|
|
126650
126684
|
problems,
|
|
126651
|
-
errors
|
|
126685
|
+
errors,
|
|
126686
|
+
missingGates,
|
|
126687
|
+
primarySubcause,
|
|
126688
|
+
fingerprint: computeQaFingerprint(section)
|
|
126652
126689
|
};
|
|
126653
126690
|
}
|
|
126654
126691
|
function validateCanonicalQaEvidence(body) {
|
|
126655
126692
|
return validateQaEvidence(body);
|
|
126656
126693
|
}
|
|
126694
|
+
function buildQaRepairGuidance(validation, actor) {
|
|
126695
|
+
const base = actor === "developer" ? [
|
|
126696
|
+
'Run `scripts/qa.sh` again and replace the existing "## QA Evidence" section with the fresh sanitized output.',
|
|
126697
|
+
"Keep the canonical lint/types/security/tests/coverage gates intact.",
|
|
126698
|
+
"Do not rewrite or weaken `scripts/qa.sh` into ad-hoc scenario checks."
|
|
126699
|
+
] : [
|
|
126700
|
+
"Reject the PR and ask the developer to rerun `scripts/qa.sh`.",
|
|
126701
|
+
'Require the PR body to contain one fresh sanitized "## QA Evidence" section.',
|
|
126702
|
+
"Do not accept ad-hoc scenario scripts or weakened QA gates in place of the canonical contract."
|
|
126703
|
+
];
|
|
126704
|
+
switch (validation.primarySubcause) {
|
|
126705
|
+
case "qa_schema_missing":
|
|
126706
|
+
case "qa_section_count_invalid":
|
|
126707
|
+
return [
|
|
126708
|
+
...base,
|
|
126709
|
+
"Ensure the PR body contains exactly one `## QA Evidence` section."
|
|
126710
|
+
];
|
|
126711
|
+
case "qa_exit_code_missing":
|
|
126712
|
+
return [
|
|
126713
|
+
...base,
|
|
126714
|
+
"The QA Evidence must include an explicit `Exit code: 0` line."
|
|
126715
|
+
];
|
|
126716
|
+
case "qa_exit_code_nonzero":
|
|
126717
|
+
return [
|
|
126718
|
+
...base,
|
|
126719
|
+
"The QA command failed. Fix the underlying lint/type/security/test/coverage problem before calling work_finish again."
|
|
126720
|
+
];
|
|
126721
|
+
case "qa_sanitization_failed":
|
|
126722
|
+
return [
|
|
126723
|
+
...base,
|
|
126724
|
+
"Sanitize host paths, environment dumps, and secrets before updating the PR body."
|
|
126725
|
+
];
|
|
126726
|
+
case "qa_missing_required_gates":
|
|
126727
|
+
return [
|
|
126728
|
+
...base,
|
|
126729
|
+
`Missing gates: ${validation.missingGates.join(", ") || "unknown"}. Include all required gates in the canonical QA output.`
|
|
126730
|
+
];
|
|
126731
|
+
case "qa_exit_codes_only":
|
|
126732
|
+
return [
|
|
126733
|
+
...base,
|
|
126734
|
+
"Do not paste exit codes alone. Include the actual lint/types/security/tests/coverage output summary."
|
|
126735
|
+
];
|
|
126736
|
+
case "qa_coverage_below_threshold":
|
|
126737
|
+
return [
|
|
126738
|
+
...base,
|
|
126739
|
+
"Coverage is below the required threshold. Fix the underlying tests or implementation before retrying."
|
|
126740
|
+
];
|
|
126741
|
+
default:
|
|
126742
|
+
return base;
|
|
126743
|
+
}
|
|
126744
|
+
}
|
|
126657
126745
|
function formatQaEvidenceValidationFailure(validation, actor) {
|
|
126658
126746
|
const intro = actor === "developer" ? "Cannot mark work_finish(done) with invalid QA Evidence in the PR body." : "Cannot approve review with invalid QA Evidence in the PR body.";
|
|
126659
|
-
const guidance = actor === "developer" ? 'Replace the existing "## QA Evidence" section with fresh sanitized output from scripts/qa.sh (exactly one section, Exit code: 0), then call work_finish again. Do not rewrite or weaken scripts/qa.sh into ad-hoc scenario checks \u2014 preserve the canonical lint/types/security/tests/coverage gates and fix the underlying code or project setup instead.' : 'Reject the PR and instruct the developer to replace the existing "## QA Evidence" section in the PR body with fresh sanitized output from scripts/qa.sh (exactly one section, Exit code: 0). Do not accept ad-hoc scenario scripts or weakened QA gates in place of the canonical lint/types/security/tests/coverage contract.';
|
|
126660
126747
|
const allIssues = [...validation.problems, ...validation.errors];
|
|
126748
|
+
const guidance = buildQaRepairGuidance(validation, actor);
|
|
126661
126749
|
return `${intro}
|
|
126662
126750
|
|
|
126663
126751
|
${allIssues.map((issue2) => `- ${issue2}`).join("\n")}
|
|
126664
126752
|
|
|
126665
|
-
${guidance}`;
|
|
126753
|
+
${guidance.map((line) => `- ${line}`).join("\n")}`;
|
|
126666
126754
|
}
|
|
126667
126755
|
|
|
126668
126756
|
// lib/tools/worker/work-finish.ts
|
|
@@ -126968,6 +127056,10 @@ function shouldAutoRecoverToFeedback(summary) {
|
|
|
126968
127056
|
const text = summary.toLowerCase();
|
|
126969
127057
|
return /retarget/.test(text) || /mismatch de escopo/.test(text) || /mismatch de escopo\/rastreabilidade/.test(text) || /new pr/.test(text) || /novo pr/.test(text) || /não pode satisfazer a issue/.test(text) || /cannot satisfy issue/.test(text);
|
|
126970
127058
|
}
|
|
127059
|
+
async function getCanonicalQaEvidenceValidationForPr(provider, issueId, selector) {
|
|
127060
|
+
const prStatus = await provider.getPrStatus(issueId, selector);
|
|
127061
|
+
return validateCanonicalQaEvidence(prStatus.body);
|
|
127062
|
+
}
|
|
126971
127063
|
function createWorkFinishTool(ctx) {
|
|
126972
127064
|
return (toolCtx) => ({
|
|
126973
127065
|
name: "work_finish",
|
|
@@ -130507,6 +130599,8 @@ async function runIssueDoctor(opts) {
|
|
|
130507
130599
|
issue2 = null;
|
|
130508
130600
|
}
|
|
130509
130601
|
const convergenceCause = issueRuntime?.lastConvergenceCause ?? null;
|
|
130602
|
+
const convergenceQaSubcause = issueRuntime?.lastQaSubcause ?? null;
|
|
130603
|
+
const convergenceQaMissingGates = issueRuntime?.lastQaMissingGates ?? [];
|
|
130510
130604
|
const convergenceAction = issueRuntime?.lastConvergenceAction ?? null;
|
|
130511
130605
|
const retryCount = issueRuntime?.lastConvergenceRetryCount ?? 0;
|
|
130512
130606
|
const convergenceReason = issueRuntime?.lastConvergenceReason ?? issueRuntime?.inconclusiveCompletionReason ?? null;
|
|
@@ -130523,7 +130617,18 @@ async function runIssueDoctor(opts) {
|
|
|
130523
130617
|
];
|
|
130524
130618
|
const likelyNextAction = (() => {
|
|
130525
130619
|
if (convergenceAction === "escalate_human") return "human_intervention";
|
|
130526
|
-
if (
|
|
130620
|
+
if ([
|
|
130621
|
+
"invalid_qa_evidence",
|
|
130622
|
+
"qa_schema_missing",
|
|
130623
|
+
"qa_section_count_invalid",
|
|
130624
|
+
"qa_exit_code_missing",
|
|
130625
|
+
"qa_exit_code_nonzero",
|
|
130626
|
+
"qa_sanitization_failed",
|
|
130627
|
+
"qa_missing_required_gates",
|
|
130628
|
+
"qa_exit_codes_only",
|
|
130629
|
+
"qa_coverage_below_threshold",
|
|
130630
|
+
"qa_stale_or_unchanged"
|
|
130631
|
+
].includes(convergenceCause ?? "")) return "repair_qa_evidence";
|
|
130527
130632
|
if (convergenceCause === "merge_conflict") return "repair_merge_conflict";
|
|
130528
130633
|
if (convergenceCause === "stalled_with_artifact") return "force_convergence_review";
|
|
130529
130634
|
if (hasArtifact) return "post_pr_convergence";
|
|
@@ -130532,6 +130637,7 @@ async function runIssueDoctor(opts) {
|
|
|
130532
130637
|
return {
|
|
130533
130638
|
projectSlug: project.slug,
|
|
130534
130639
|
projectName: project.name,
|
|
130640
|
+
stack: project.stack ?? project.environment?.stack ?? null,
|
|
130535
130641
|
issueId: opts.issueId,
|
|
130536
130642
|
issueRuntime,
|
|
130537
130643
|
hasArtifact,
|
|
@@ -130545,6 +130651,8 @@ async function runIssueDoctor(opts) {
|
|
|
130545
130651
|
},
|
|
130546
130652
|
convergence: {
|
|
130547
130653
|
cause: convergenceCause,
|
|
130654
|
+
qaSubcause: convergenceQaSubcause,
|
|
130655
|
+
qaMissingGates: convergenceQaMissingGates,
|
|
130548
130656
|
action: convergenceAction,
|
|
130549
130657
|
retryCount,
|
|
130550
130658
|
reason: convergenceReason,
|
|
@@ -130575,6 +130683,7 @@ async function runIssueDoctor(opts) {
|
|
|
130575
130683
|
function formatIssueDoctor(result) {
|
|
130576
130684
|
const lines = [
|
|
130577
130685
|
`Issue run doctor \u2014 ${result.projectSlug}#${result.issueId}`,
|
|
130686
|
+
` Stack: ${result.stack ?? "unknown"}`,
|
|
130578
130687
|
` Artifact: ${result.hasArtifact ? "yes" : "no"}`,
|
|
130579
130688
|
` PR: ${result.pr?.url ?? "n/a"} (${result.pr?.state ?? "unknown"})`,
|
|
130580
130689
|
` Issue: ${result.issue?.url ?? "n/a"} (${result.issue?.state ?? "unknown"})`,
|
|
@@ -130586,6 +130695,8 @@ function formatIssueDoctor(result) {
|
|
|
130586
130695
|
` First worker activity: ${result.lifecycle.firstWorkerActivityAt ?? "n/a"}`,
|
|
130587
130696
|
` Session completed: ${result.lifecycle.sessionCompletedAt ?? "n/a"}`,
|
|
130588
130697
|
` Convergence cause: ${result.convergence.cause ?? "none"}`,
|
|
130698
|
+
` QA subcause: ${result.convergence.qaSubcause ?? "n/a"}`,
|
|
130699
|
+
` Missing QA gates: ${result.convergence.qaMissingGates.length ? result.convergence.qaMissingGates.join(", ") : "n/a"}`,
|
|
130589
130700
|
` Convergence action: ${result.convergence.action ?? "none"}`,
|
|
130590
130701
|
` Retry count: ${result.convergence.retryCount}`,
|
|
130591
130702
|
` Convergence head SHA: ${result.convergence.headSha ?? "n/a"}`,
|
|
@@ -131390,12 +131501,69 @@ init_workflow();
|
|
|
131390
131501
|
init_context3();
|
|
131391
131502
|
init_labels();
|
|
131392
131503
|
|
|
131504
|
+
// lib/services/doctor-snapshot.ts
|
|
131505
|
+
init_audit();
|
|
131506
|
+
async function captureIssueDoctorSnapshot(opts) {
|
|
131507
|
+
try {
|
|
131508
|
+
const result = await runIssueDoctor({
|
|
131509
|
+
workspacePath: opts.workspaceDir,
|
|
131510
|
+
projectSlug: opts.projectSlug,
|
|
131511
|
+
issueId: opts.issueId,
|
|
131512
|
+
runCommand: opts.runCommand,
|
|
131513
|
+
pluginConfig: opts.pluginConfig
|
|
131514
|
+
});
|
|
131515
|
+
await log(opts.workspaceDir, opts.event, {
|
|
131516
|
+
projectSlug: opts.projectSlug,
|
|
131517
|
+
issueId: opts.issueId,
|
|
131518
|
+
trigger: opts.trigger,
|
|
131519
|
+
summary: result.recommendation.summary,
|
|
131520
|
+
likelyNextAction: result.recommendation.likelyNextAction,
|
|
131521
|
+
stack: result.stack,
|
|
131522
|
+
doctor: {
|
|
131523
|
+
artifact: result.hasArtifact,
|
|
131524
|
+
progressState: result.lifecycle.progressState,
|
|
131525
|
+
dispatchCycleId: result.lifecycle.dispatchCycleId,
|
|
131526
|
+
dispatchRunId: result.lifecycle.dispatchRunId,
|
|
131527
|
+
prUrl: result.pr?.url ?? null,
|
|
131528
|
+
prState: result.pr?.state ?? null,
|
|
131529
|
+
labels: result.issue?.labels ?? [],
|
|
131530
|
+
convergenceCause: result.convergence.cause,
|
|
131531
|
+
qaSubcause: result.convergence.qaSubcause,
|
|
131532
|
+
qaMissingGates: result.convergence.qaMissingGates,
|
|
131533
|
+
convergenceAction: result.convergence.action,
|
|
131534
|
+
convergenceRetryCount: result.convergence.retryCount,
|
|
131535
|
+
convergenceHeadSha: result.convergence.headSha,
|
|
131536
|
+
headShaChangedSinceLastConvergence: result.convergence.headShaChangedSinceLastConvergence
|
|
131537
|
+
},
|
|
131538
|
+
...opts.extra ?? {}
|
|
131539
|
+
});
|
|
131540
|
+
} catch (error48) {
|
|
131541
|
+
await log(opts.workspaceDir, `${opts.event}_failed`, {
|
|
131542
|
+
projectSlug: opts.projectSlug,
|
|
131543
|
+
issueId: opts.issueId,
|
|
131544
|
+
trigger: opts.trigger,
|
|
131545
|
+
error: error48 instanceof Error ? error48.message : String(error48),
|
|
131546
|
+
...opts.extra ?? {}
|
|
131547
|
+
}).catch(() => {
|
|
131548
|
+
});
|
|
131549
|
+
}
|
|
131550
|
+
}
|
|
131551
|
+
|
|
131393
131552
|
// lib/services/post-pr-convergence.ts
|
|
131394
131553
|
init_types3();
|
|
131395
131554
|
function classifyConvergenceCause(reason) {
|
|
131396
131555
|
const text = String(reason ?? "").toLowerCase();
|
|
131397
131556
|
if (!text) return "other";
|
|
131398
|
-
if (text.includes("
|
|
131557
|
+
if (text.includes("qa_evidence_missing")) return "qa_schema_missing";
|
|
131558
|
+
if (text.includes("exactly one `## qa evidence` section") || text.includes("qa_section_count_invalid")) return "qa_section_count_invalid";
|
|
131559
|
+
if (text.includes("exit code: <number>") || text.includes("qa_exit_code_missing")) return "qa_exit_code_missing";
|
|
131560
|
+
if (text.includes("exit code must be 0") || text.includes("qa_exit_code_nonzero")) return "qa_exit_code_nonzero";
|
|
131561
|
+
if (text.includes("host-system paths") || text.includes("secrets or environment values") || text.includes("environment dump") || text.includes("qa_sanitization_failed")) return "qa_sanitization_failed";
|
|
131562
|
+
if (text.includes("qa_evidence_only_exit_codes") || text.includes("qa_exit_codes_only")) return "qa_exit_codes_only";
|
|
131563
|
+
if (text.includes("qa_coverage_below_threshold") || text.includes("coverage below threshold")) return "qa_coverage_below_threshold";
|
|
131564
|
+
if (text.includes("qa_stale_or_unchanged")) return "qa_stale_or_unchanged";
|
|
131565
|
+
if (text.includes("qa_gate_missing_") || text.includes("missing required gates")) return "qa_missing_required_gates";
|
|
131566
|
+
if (text.includes("invalid qa evidence")) return "invalid_qa_evidence";
|
|
131399
131567
|
if (text.includes("merge conflict") || text.includes("pr_still_conflicting")) return "merge_conflict";
|
|
131400
131568
|
if (text.includes("stalled_with_artifact")) return "stalled_with_artifact";
|
|
131401
131569
|
if (text.includes("stalled_without_artifact")) return "stalled_without_artifact";
|
|
@@ -131417,6 +131585,17 @@ function getConvergenceRetryBudget(cause) {
|
|
|
131417
131585
|
switch (cause) {
|
|
131418
131586
|
case "invalid_qa_evidence":
|
|
131419
131587
|
return 2;
|
|
131588
|
+
case "qa_schema_missing":
|
|
131589
|
+
case "qa_section_count_invalid":
|
|
131590
|
+
case "qa_exit_code_missing":
|
|
131591
|
+
case "qa_missing_required_gates":
|
|
131592
|
+
return 2;
|
|
131593
|
+
case "qa_exit_code_nonzero":
|
|
131594
|
+
case "qa_exit_codes_only":
|
|
131595
|
+
case "qa_coverage_below_threshold":
|
|
131596
|
+
case "qa_stale_or_unchanged":
|
|
131597
|
+
case "qa_sanitization_failed":
|
|
131598
|
+
return 1;
|
|
131420
131599
|
case "merge_conflict":
|
|
131421
131600
|
case "stalled_with_artifact":
|
|
131422
131601
|
case "stale_pr_target":
|
|
@@ -131898,20 +132077,50 @@ async function defaultValidateDeveloperDone(opts) {
|
|
|
131898
132077
|
return { ok: true, prStatus };
|
|
131899
132078
|
} catch (error48) {
|
|
131900
132079
|
let prStatus;
|
|
132080
|
+
let qaEvidence;
|
|
131901
132081
|
try {
|
|
131902
132082
|
const fallbackPr = await opts.provider.getPrStatus(opts.issueId);
|
|
131903
132083
|
if (fallbackPr.url && fallbackPr.state !== "merged" && fallbackPr.state !== "closed") {
|
|
131904
132084
|
prStatus = fallbackPr;
|
|
132085
|
+
qaEvidence = await getCanonicalQaEvidenceValidationForPr(opts.provider, opts.issueId).catch(() => void 0);
|
|
131905
132086
|
}
|
|
131906
132087
|
} catch {
|
|
131907
132088
|
}
|
|
131908
132089
|
return {
|
|
131909
132090
|
ok: false,
|
|
131910
132091
|
reason: error48 instanceof Error ? error48.message : "developer_validation_failed",
|
|
131911
|
-
prStatus
|
|
132092
|
+
prStatus,
|
|
132093
|
+
qaEvidence
|
|
131912
132094
|
};
|
|
131913
132095
|
}
|
|
131914
132096
|
}
|
|
132097
|
+
function extractQaMissingGatesFromReason(reason) {
|
|
132098
|
+
return [...String(reason ?? "").matchAll(/qa_gate_missing_([a-z_]+)/gi)].map((match) => match[1] ?? "").filter(Boolean);
|
|
132099
|
+
}
|
|
132100
|
+
function inferQaSubcauseFromReason(reason) {
|
|
132101
|
+
const text = String(reason ?? "").toLowerCase();
|
|
132102
|
+
if (!text) return null;
|
|
132103
|
+
if (text.includes("qa_evidence_missing")) return "qa_schema_missing";
|
|
132104
|
+
if (text.includes("exactly one `## qa evidence` section") || text.includes("qa_section_count_invalid")) return "qa_section_count_invalid";
|
|
132105
|
+
if (text.includes("exit code: <number>") || text.includes("qa_exit_code_missing")) return "qa_exit_code_missing";
|
|
132106
|
+
if (text.includes("exit code must be 0") || text.includes("qa_exit_code_nonzero")) return "qa_exit_code_nonzero";
|
|
132107
|
+
if (text.includes("host-system paths") || text.includes("secrets or environment values") || text.includes("environment dump") || text.includes("qa_sanitization_failed")) return "qa_sanitization_failed";
|
|
132108
|
+
if (text.includes("qa_evidence_only_exit_codes") || text.includes("qa_exit_codes_only")) return "qa_exit_codes_only";
|
|
132109
|
+
if (text.includes("qa_coverage_below_threshold") || text.includes("coverage below threshold")) return "qa_coverage_below_threshold";
|
|
132110
|
+
if (text.includes("qa_gate_missing_")) return "qa_missing_required_gates";
|
|
132111
|
+
if (text.includes("invalid qa evidence")) return "invalid_qa_evidence";
|
|
132112
|
+
return null;
|
|
132113
|
+
}
|
|
132114
|
+
function computeStringFingerprint(text) {
|
|
132115
|
+
const normalized = String(text ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
132116
|
+
if (!normalized) return null;
|
|
132117
|
+
let hash2 = 2166136261;
|
|
132118
|
+
for (let i2 = 0; i2 < normalized.length; i2 += 1) {
|
|
132119
|
+
hash2 ^= normalized.charCodeAt(i2);
|
|
132120
|
+
hash2 = Math.imul(hash2, 16777619);
|
|
132121
|
+
}
|
|
132122
|
+
return `fnv1a:${(hash2 >>> 0).toString(16)}`;
|
|
132123
|
+
}
|
|
131915
132124
|
async function persistDeveloperPrBinding(opts) {
|
|
131916
132125
|
const prStatus = opts.prStatus;
|
|
131917
132126
|
if (!prStatus) return;
|
|
@@ -132000,11 +132209,22 @@ async function applyWorkerResult(opts) {
|
|
|
132000
132209
|
if (!validation.ok) {
|
|
132001
132210
|
const validationReason = validation.reason ?? "developer_validation_failed";
|
|
132002
132211
|
const feedbackQueueLabel = getQueueLabels(workflow, "developer").find((label) => isFeedbackState(workflow, label)) ?? "To Improve";
|
|
132212
|
+
const qaMissingGates = validation.qaEvidence?.missingGates ?? extractQaMissingGatesFromReason(validationReason);
|
|
132213
|
+
const qaSubcause = validation.qaEvidence?.primarySubcause ?? inferQaSubcauseFromReason(validationReason);
|
|
132214
|
+
const qaObservedHeadSha = context2.issueRuntime?.currentPrHeadSha ?? context2.issueRuntime?.lastHeadSha ?? null;
|
|
132215
|
+
const qaEvidenceFingerprint = validation.qaEvidence?.fingerprint ?? computeStringFingerprint([qaSubcause, ...qaMissingGates].join("|"));
|
|
132216
|
+
const qaUnchanged = Boolean(
|
|
132217
|
+
context2.issueRuntime?.lastQaEvidenceHash && qaEvidenceFingerprint && context2.issueRuntime.lastQaEvidenceHash === qaEvidenceFingerprint && context2.issueRuntime.lastQaObservedHeadSha && qaObservedHeadSha && context2.issueRuntime.lastQaObservedHeadSha === qaObservedHeadSha
|
|
132218
|
+
);
|
|
132219
|
+
const convergenceReason = qaUnchanged ? `qa_stale_or_unchanged
|
|
132220
|
+
|
|
132221
|
+
${validationReason}` : validationReason;
|
|
132003
132222
|
const convergenceIssueRuntime = validation.prStatus ? {
|
|
132004
132223
|
...context2.issueRuntime,
|
|
132005
132224
|
currentPrNumber: validation.prStatus.number ?? context2.issueRuntime?.currentPrNumber ?? null,
|
|
132006
132225
|
currentPrUrl: validation.prStatus.url ?? context2.issueRuntime?.currentPrUrl ?? null,
|
|
132007
|
-
currentPrState: validation.prStatus.state ?? context2.issueRuntime?.currentPrState ?? null
|
|
132226
|
+
currentPrState: validation.prStatus.state ?? context2.issueRuntime?.currentPrState ?? null,
|
|
132227
|
+
currentPrHeadSha: context2.issueRuntime?.currentPrHeadSha ?? null
|
|
132008
132228
|
} : context2.issueRuntime;
|
|
132009
132229
|
if (validation.prStatus) {
|
|
132010
132230
|
await persistDeveloperPrBinding({
|
|
@@ -132018,7 +132238,7 @@ async function applyWorkerResult(opts) {
|
|
|
132018
132238
|
const convergence = decidePostPrConvergence({
|
|
132019
132239
|
workflow,
|
|
132020
132240
|
issueRuntime: convergenceIssueRuntime,
|
|
132021
|
-
reason:
|
|
132241
|
+
reason: convergenceReason,
|
|
132022
132242
|
feedbackQueueLabel
|
|
132023
132243
|
});
|
|
132024
132244
|
const blockedSummary = convergence.action === "escalate_human" ? [
|
|
@@ -132037,10 +132257,16 @@ ${validationReason}`;
|
|
|
132037
132257
|
issueId: context2.issueId,
|
|
132038
132258
|
role: context2.parsed.role,
|
|
132039
132259
|
result: opts.result.value,
|
|
132260
|
+
stack: context2.project.stack ?? context2.project.environment?.stack ?? null,
|
|
132040
132261
|
reason: validationReason,
|
|
132041
132262
|
convergenceCause: convergence.cause,
|
|
132042
132263
|
convergenceAction: convergence.action,
|
|
132043
|
-
convergenceRetryCount: convergence.retryCount
|
|
132264
|
+
convergenceRetryCount: convergence.retryCount,
|
|
132265
|
+
qaSubcause,
|
|
132266
|
+
qaMissingGates,
|
|
132267
|
+
qaObservedHeadSha,
|
|
132268
|
+
qaEvidenceFingerprint,
|
|
132269
|
+
qaUnchanged
|
|
132044
132270
|
}).catch(() => {
|
|
132045
132271
|
});
|
|
132046
132272
|
await updateIssueRuntime(opts.workspaceDir, context2.projectSlug, context2.issueId, {
|
|
@@ -132049,11 +132275,34 @@ ${validationReason}`;
|
|
|
132049
132275
|
lastConvergenceCause: convergence.cause,
|
|
132050
132276
|
lastConvergenceAction: convergence.action,
|
|
132051
132277
|
lastConvergenceRetryCount: convergence.retryCount,
|
|
132052
|
-
lastConvergenceReason:
|
|
132278
|
+
lastConvergenceReason: convergenceReason,
|
|
132053
132279
|
lastConvergenceAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
132054
|
-
lastConvergenceHeadSha: convergence.progressHeadSha
|
|
132280
|
+
lastConvergenceHeadSha: convergence.progressHeadSha,
|
|
132281
|
+
lastQaEvidenceAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
132282
|
+
lastQaExitCode: validationReason.includes("Exit code must be 0") ? 1 : null,
|
|
132283
|
+
lastQaMissingGates: qaMissingGates,
|
|
132284
|
+
lastQaEvidenceHash: qaEvidenceFingerprint,
|
|
132285
|
+
lastQaObservedHeadSha: qaObservedHeadSha,
|
|
132286
|
+
lastQaSubcause: qaSubcause
|
|
132055
132287
|
}).catch(() => {
|
|
132056
132288
|
});
|
|
132289
|
+
if (convergenceIssueRuntime?.currentPrUrl || convergence.action === "escalate_human") {
|
|
132290
|
+
await captureIssueDoctorSnapshot({
|
|
132291
|
+
workspaceDir: opts.workspaceDir,
|
|
132292
|
+
projectSlug: context2.projectSlug,
|
|
132293
|
+
issueId: context2.issueId,
|
|
132294
|
+
runCommand: opts.runCommand,
|
|
132295
|
+
pluginConfig: opts.pluginConfig,
|
|
132296
|
+
event: "doctor_snapshot",
|
|
132297
|
+
trigger: convergence.action === "escalate_human" ? "worker_completion_escalated" : "worker_completion_blocked_with_artifact",
|
|
132298
|
+
extra: {
|
|
132299
|
+
convergenceCause: convergence.cause,
|
|
132300
|
+
convergenceAction: convergence.action,
|
|
132301
|
+
convergenceRetryCount: convergence.retryCount
|
|
132302
|
+
}
|
|
132303
|
+
}).catch(() => {
|
|
132304
|
+
});
|
|
132305
|
+
}
|
|
132057
132306
|
await executeCompletion({
|
|
132058
132307
|
workspaceDir: opts.workspaceDir,
|
|
132059
132308
|
projectSlug: context2.projectSlug,
|
|
@@ -132178,7 +132427,13 @@ ${validationReason}`;
|
|
|
132178
132427
|
lastConvergenceRetryCount: 0,
|
|
132179
132428
|
lastConvergenceReason: null,
|
|
132180
132429
|
lastConvergenceAt: null,
|
|
132181
|
-
lastConvergenceHeadSha: null
|
|
132430
|
+
lastConvergenceHeadSha: null,
|
|
132431
|
+
lastQaEvidenceAt: null,
|
|
132432
|
+
lastQaExitCode: null,
|
|
132433
|
+
lastQaMissingGates: null,
|
|
132434
|
+
lastQaEvidenceHash: null,
|
|
132435
|
+
lastQaObservedHeadSha: null,
|
|
132436
|
+
lastQaSubcause: null
|
|
132182
132437
|
}).catch(() => {
|
|
132183
132438
|
});
|
|
132184
132439
|
} else {
|
|
@@ -132190,7 +132445,13 @@ ${validationReason}`;
|
|
|
132190
132445
|
lastConvergenceRetryCount: 0,
|
|
132191
132446
|
lastConvergenceReason: null,
|
|
132192
132447
|
lastConvergenceAt: null,
|
|
132193
|
-
lastConvergenceHeadSha: null
|
|
132448
|
+
lastConvergenceHeadSha: null,
|
|
132449
|
+
lastQaEvidenceAt: null,
|
|
132450
|
+
lastQaExitCode: null,
|
|
132451
|
+
lastQaMissingGates: null,
|
|
132452
|
+
lastQaEvidenceHash: null,
|
|
132453
|
+
lastQaObservedHeadSha: null,
|
|
132454
|
+
lastQaSubcause: null
|
|
132194
132455
|
}).catch(() => {
|
|
132195
132456
|
});
|
|
132196
132457
|
}
|
|
@@ -132741,6 +133002,22 @@ async function checkWorkerHealth(opts) {
|
|
|
132741
133002
|
toLabel: convergence.targetLabel,
|
|
132742
133003
|
deliveryState
|
|
132743
133004
|
});
|
|
133005
|
+
if (runCommand) {
|
|
133006
|
+
await captureIssueDoctorSnapshot({
|
|
133007
|
+
workspaceDir,
|
|
133008
|
+
projectSlug,
|
|
133009
|
+
issueId: issueIdNum,
|
|
133010
|
+
runCommand,
|
|
133011
|
+
event: "doctor_snapshot",
|
|
133012
|
+
trigger: convergence.action === "escalate_human" ? "completion_recovery_escalated" : "completion_recovery_requeued",
|
|
133013
|
+
extra: {
|
|
133014
|
+
convergenceCause: convergence.cause,
|
|
133015
|
+
convergenceAction: convergence.action,
|
|
133016
|
+
convergenceRetryCount: convergence.retryCount
|
|
133017
|
+
}
|
|
133018
|
+
}).catch(() => {
|
|
133019
|
+
});
|
|
133020
|
+
}
|
|
132744
133021
|
}
|
|
132745
133022
|
}
|
|
132746
133023
|
fixes.push(fix);
|
|
@@ -133109,6 +133386,23 @@ async function checkWorkerHealth(opts) {
|
|
|
133109
133386
|
idleMinutes: Math.round(quietMinutes),
|
|
133110
133387
|
deliveryState
|
|
133111
133388
|
});
|
|
133389
|
+
if (runCommand) {
|
|
133390
|
+
await captureIssueDoctorSnapshot({
|
|
133391
|
+
workspaceDir,
|
|
133392
|
+
projectSlug,
|
|
133393
|
+
issueId: issueIdNum,
|
|
133394
|
+
runCommand,
|
|
133395
|
+
event: "doctor_snapshot",
|
|
133396
|
+
trigger: convergence.action === "escalate_human" ? "stalled_with_artifact_escalated" : "stalled_with_artifact_requeued",
|
|
133397
|
+
extra: {
|
|
133398
|
+
convergenceCause: convergence.cause,
|
|
133399
|
+
convergenceAction: convergence.action,
|
|
133400
|
+
convergenceRetryCount: convergence.retryCount,
|
|
133401
|
+
idleMinutes: Math.round(quietMinutes)
|
|
133402
|
+
}
|
|
133403
|
+
}).catch(() => {
|
|
133404
|
+
});
|
|
133405
|
+
}
|
|
133112
133406
|
}
|
|
133113
133407
|
}
|
|
133114
133408
|
fixes.push(fix);
|
|
@@ -149129,12 +149423,42 @@ async function readAuditLines(filePath) {
|
|
|
149129
149423
|
}
|
|
149130
149424
|
return entries;
|
|
149131
149425
|
}
|
|
149426
|
+
function keyFor(entry) {
|
|
149427
|
+
const projectSlug = entry.projectSlug ?? entry.project ?? null;
|
|
149428
|
+
const issueId = entry.issueId ?? entry.issue ?? null;
|
|
149429
|
+
if (!projectSlug || issueId == null) return null;
|
|
149430
|
+
return `${projectSlug}:${issueId}`;
|
|
149431
|
+
}
|
|
149432
|
+
function normalizeCause(entry) {
|
|
149433
|
+
const explicitQaSubcause = entry.qaSubcause ?? null;
|
|
149434
|
+
if (explicitQaSubcause) return String(explicitQaSubcause);
|
|
149435
|
+
const cause = entry.convergenceCause ?? entry.reason ?? null;
|
|
149436
|
+
if (!cause) return null;
|
|
149437
|
+
const text = String(cause).toLowerCase();
|
|
149438
|
+
if (text.includes("qa_stale_or_unchanged")) return "qa_stale_or_unchanged";
|
|
149439
|
+
if (text.includes("qa_gate_missing_")) return "qa_missing_required_gates";
|
|
149440
|
+
if (text.includes("qa_evidence_missing")) return "qa_schema_missing";
|
|
149441
|
+
if (text.includes("exactly one `## qa evidence` section") || text.includes("qa_section_count_invalid")) return "qa_section_count_invalid";
|
|
149442
|
+
if (text.includes("exit code must be 0") || text.includes("qa_exit_code_nonzero")) return "qa_exit_code_nonzero";
|
|
149443
|
+
if (text.includes("exit code: <number>") || text.includes("qa_exit_code_missing")) return "qa_exit_code_missing";
|
|
149444
|
+
if (text.includes("qa_evidence_only_exit_codes") || text.includes("qa_exit_codes_only")) return "qa_exit_codes_only";
|
|
149445
|
+
if (text.includes("qa_coverage_below_threshold") || text.includes("coverage below threshold")) return "qa_coverage_below_threshold";
|
|
149446
|
+
if (text.includes("host-system paths") || text.includes("secrets or environment values") || text.includes("environment dump") || text.includes("qa_sanitization_failed")) return "qa_sanitization_failed";
|
|
149447
|
+
return String(cause);
|
|
149448
|
+
}
|
|
149132
149449
|
async function computeMetrics(workspaceDir) {
|
|
149133
149450
|
const auditLogPath = join4(workspaceDir, DATA_DIR, "log", "audit.log");
|
|
149451
|
+
const bakEntries3 = await readAuditLines(`${auditLogPath}.3.bak`);
|
|
149134
149452
|
const bakEntries2 = await readAuditLines(`${auditLogPath}.2.bak`);
|
|
149135
149453
|
const bakEntries = await readAuditLines(`${auditLogPath}.bak`);
|
|
149136
149454
|
const currentEntries = await readAuditLines(auditLogPath);
|
|
149137
|
-
|
|
149455
|
+
const entries = [...bakEntries3, ...bakEntries2, ...bakEntries, ...currentEntries];
|
|
149456
|
+
const projectsData = await readProjects(workspaceDir).catch(() => ({ projects: {} }));
|
|
149457
|
+
const stackByProject = /* @__PURE__ */ new Map();
|
|
149458
|
+
for (const [slug, project] of Object.entries(projectsData.projects ?? {})) {
|
|
149459
|
+
const stack = project.stack ?? project.environment?.stack ?? null;
|
|
149460
|
+
if (stack) stackByProject.set(slug, String(stack));
|
|
149461
|
+
}
|
|
149138
149462
|
const entriesScanned = entries.length;
|
|
149139
149463
|
let dispatches = 0;
|
|
149140
149464
|
let completionsTotal = 0;
|
|
@@ -149144,16 +149468,40 @@ async function computeMetrics(workspaceDir) {
|
|
|
149144
149468
|
let completionsOther = 0;
|
|
149145
149469
|
let conflictsDetected = 0;
|
|
149146
149470
|
let sessionBudgetResets = 0;
|
|
149471
|
+
let humanEscalations = 0;
|
|
149472
|
+
const causeCounts = {};
|
|
149147
149473
|
const dispatchTimes = /* @__PURE__ */ new Map();
|
|
149474
|
+
const firstPrTimes = /* @__PURE__ */ new Map();
|
|
149148
149475
|
const completionDeltas = [];
|
|
149476
|
+
const firstPrDeltas = [];
|
|
149477
|
+
const stackMetrics = /* @__PURE__ */ new Map();
|
|
149478
|
+
function stackBucket(entry) {
|
|
149479
|
+
const slug = entry.projectSlug ?? entry.project ?? null;
|
|
149480
|
+
const stack = entry.stack ?? (slug ? stackByProject.get(String(slug)) : null) ?? "unknown";
|
|
149481
|
+
if (!stackMetrics.has(String(stack))) {
|
|
149482
|
+
stackMetrics.set(String(stack), {
|
|
149483
|
+
issues: /* @__PURE__ */ new Set(),
|
|
149484
|
+
dispatches: 0,
|
|
149485
|
+
escalations: 0,
|
|
149486
|
+
causeCounts: {},
|
|
149487
|
+
completionDeltas: [],
|
|
149488
|
+
firstPrDeltas: []
|
|
149489
|
+
});
|
|
149490
|
+
}
|
|
149491
|
+
return String(stack);
|
|
149492
|
+
}
|
|
149149
149493
|
for (const entry of entries) {
|
|
149494
|
+
const issueKey = keyFor(entry);
|
|
149495
|
+
const stack = stackBucket(entry);
|
|
149496
|
+
const stackBucketState = stackMetrics.get(stack);
|
|
149497
|
+
if (issueKey) stackBucketState.issues.add(issueKey);
|
|
149150
149498
|
switch (entry.event) {
|
|
149151
|
-
case "dispatch":
|
|
149499
|
+
case "dispatch": {
|
|
149152
149500
|
dispatches++;
|
|
149153
|
-
|
|
149154
|
-
|
|
149155
|
-
}
|
|
149501
|
+
stackBucketState.dispatches++;
|
|
149502
|
+
if (issueKey) dispatchTimes.set(issueKey, Date.parse(entry.ts));
|
|
149156
149503
|
break;
|
|
149504
|
+
}
|
|
149157
149505
|
case "work_finish": {
|
|
149158
149506
|
completionsTotal++;
|
|
149159
149507
|
const result = String(entry.result ?? "");
|
|
@@ -149161,14 +149509,13 @@ async function computeMetrics(workspaceDir) {
|
|
|
149161
149509
|
else if (result === "pass") completionsPass++;
|
|
149162
149510
|
else if (result === "fail") completionsFail++;
|
|
149163
149511
|
else completionsOther++;
|
|
149164
|
-
if (
|
|
149165
|
-
const
|
|
149166
|
-
const
|
|
149167
|
-
if (dispatchTime !== void 0) {
|
|
149168
|
-
const
|
|
149169
|
-
|
|
149170
|
-
|
|
149171
|
-
}
|
|
149512
|
+
if (issueKey) {
|
|
149513
|
+
const dispatchTime = dispatchTimes.get(issueKey);
|
|
149514
|
+
const completionTime = Date.parse(entry.ts);
|
|
149515
|
+
if (dispatchTime !== void 0 && !Number.isNaN(completionTime) && completionTime > dispatchTime) {
|
|
149516
|
+
const delta = (completionTime - dispatchTime) / 6e4;
|
|
149517
|
+
completionDeltas.push(delta);
|
|
149518
|
+
stackBucketState.completionDeltas.push(delta);
|
|
149172
149519
|
}
|
|
149173
149520
|
}
|
|
149174
149521
|
break;
|
|
@@ -149182,9 +149529,47 @@ async function computeMetrics(workspaceDir) {
|
|
|
149182
149529
|
case "session_budget_reset":
|
|
149183
149530
|
sessionBudgetResets++;
|
|
149184
149531
|
break;
|
|
149532
|
+
case "pr_discovered_via_polling":
|
|
149533
|
+
case "pr_updated_via_polling": {
|
|
149534
|
+
if (issueKey && !firstPrTimes.has(issueKey)) {
|
|
149535
|
+
const prTime = Date.parse(entry.ts);
|
|
149536
|
+
const dispatchTime = dispatchTimes.get(issueKey);
|
|
149537
|
+
firstPrTimes.set(issueKey, prTime);
|
|
149538
|
+
if (dispatchTime !== void 0 && !Number.isNaN(prTime) && prTime > dispatchTime) {
|
|
149539
|
+
const delta = (prTime - dispatchTime) / 6e4;
|
|
149540
|
+
firstPrDeltas.push(delta);
|
|
149541
|
+
stackBucketState.firstPrDeltas.push(delta);
|
|
149542
|
+
}
|
|
149543
|
+
}
|
|
149544
|
+
break;
|
|
149545
|
+
}
|
|
149546
|
+
case "worker_completion_skipped":
|
|
149547
|
+
case "doctor_snapshot":
|
|
149548
|
+
case "health_fix_applied": {
|
|
149549
|
+
const cause = normalizeCause(entry);
|
|
149550
|
+
if (cause) {
|
|
149551
|
+
causeCounts[cause] = (causeCounts[cause] ?? 0) + 1;
|
|
149552
|
+
stackBucketState.causeCounts[cause] = (stackBucketState.causeCounts[cause] ?? 0) + 1;
|
|
149553
|
+
}
|
|
149554
|
+
if (entry.convergenceAction === "escalate_human" || entry.action === "escalate_human") {
|
|
149555
|
+
humanEscalations++;
|
|
149556
|
+
stackBucketState.escalations++;
|
|
149557
|
+
}
|
|
149558
|
+
break;
|
|
149559
|
+
}
|
|
149185
149560
|
}
|
|
149186
149561
|
}
|
|
149187
|
-
const
|
|
149562
|
+
const avg = (values) => values.length ? values.reduce((a, b) => a + b, 0) / values.length : null;
|
|
149563
|
+
const stackMetricsObject = Object.fromEntries(
|
|
149564
|
+
[...stackMetrics.entries()].map(([stack, data]) => [stack, {
|
|
149565
|
+
issues: data.issues.size,
|
|
149566
|
+
dispatches: data.dispatches,
|
|
149567
|
+
escalations: data.escalations,
|
|
149568
|
+
causeCounts: data.causeCounts,
|
|
149569
|
+
avgDispatchToCompletionMinutes: avg(data.completionDeltas),
|
|
149570
|
+
avgDispatchToFirstPrMinutes: avg(data.firstPrDeltas)
|
|
149571
|
+
}])
|
|
149572
|
+
);
|
|
149188
149573
|
return {
|
|
149189
149574
|
entriesScanned,
|
|
149190
149575
|
dispatches,
|
|
@@ -149195,28 +149580,40 @@ async function computeMetrics(workspaceDir) {
|
|
|
149195
149580
|
fail: completionsFail,
|
|
149196
149581
|
other: completionsOther
|
|
149197
149582
|
},
|
|
149198
|
-
avgDispatchToCompletionMinutes,
|
|
149583
|
+
avgDispatchToCompletionMinutes: avg(completionDeltas),
|
|
149584
|
+
avgDispatchToFirstPrMinutes: avg(firstPrDeltas),
|
|
149199
149585
|
conflictsDetected,
|
|
149200
149586
|
sessionBudgetResets,
|
|
149587
|
+
humanEscalations,
|
|
149588
|
+
causeCounts,
|
|
149589
|
+
stackMetrics: stackMetricsObject,
|
|
149201
149590
|
auditLogPath
|
|
149202
149591
|
};
|
|
149203
149592
|
}
|
|
149204
149593
|
function formatMetrics(metrics2) {
|
|
149205
149594
|
const lines = [
|
|
149206
|
-
`Fabrica \u2014
|
|
149595
|
+
`Fabrica \u2014 M\xE9tricas (${metrics2.entriesScanned} entradas do audit.log)`,
|
|
149207
149596
|
` Dispatches: ${metrics2.dispatches}`
|
|
149208
149597
|
];
|
|
149209
149598
|
const c = metrics2.completions;
|
|
149210
|
-
lines.push(
|
|
149211
|
-
|
|
149212
|
-
);
|
|
149213
|
-
if (metrics2.avgDispatchToCompletionMinutes !== null) {
|
|
149214
|
-
lines.push(` Tempo medio dispatch \u2192 completion: ${metrics2.avgDispatchToCompletionMinutes.toFixed(1)} min`);
|
|
149215
|
-
} else {
|
|
149216
|
-
lines.push(` Tempo medio dispatch \u2192 completion: n/a`);
|
|
149217
|
-
}
|
|
149599
|
+
lines.push(` Conclus\xF5es: ${c.total} (done: ${c.done}, pass: ${c.pass}, fail: ${c.fail}${c.other > 0 ? `, other: ${c.other}` : ""})`);
|
|
149600
|
+
lines.push(` Tempo m\xE9dio dispatch \u2192 completion: ${metrics2.avgDispatchToCompletionMinutes?.toFixed(1) ?? "n/a"} min`);
|
|
149601
|
+
lines.push(` Tempo m\xE9dio dispatch \u2192 primeira PR: ${metrics2.avgDispatchToFirstPrMinutes?.toFixed(1) ?? "n/a"} min`);
|
|
149218
149602
|
lines.push(` Conflitos detectados: ${metrics2.conflictsDetected}`);
|
|
149219
149603
|
lines.push(` Session budget resets: ${metrics2.sessionBudgetResets}`);
|
|
149604
|
+
lines.push(` Escalonamentos humanos: ${metrics2.humanEscalations}`);
|
|
149605
|
+
if (Object.keys(metrics2.causeCounts).length > 0) {
|
|
149606
|
+
lines.push(" Causas tipadas:");
|
|
149607
|
+
for (const [cause, count] of Object.entries(metrics2.causeCounts).sort((a, b) => b[1] - a[1])) {
|
|
149608
|
+
lines.push(` - ${cause}: ${count}`);
|
|
149609
|
+
}
|
|
149610
|
+
}
|
|
149611
|
+
if (Object.keys(metrics2.stackMetrics).length > 0) {
|
|
149612
|
+
lines.push(" Por stack:");
|
|
149613
|
+
for (const [stack, data] of Object.entries(metrics2.stackMetrics)) {
|
|
149614
|
+
lines.push(` - ${stack}: issues=${data.issues}, dispatches=${data.dispatches}, escalations=${data.escalations}, avgPR=${data.avgDispatchToFirstPrMinutes?.toFixed(1) ?? "n/a"}m, avgDone=${data.avgDispatchToCompletionMinutes?.toFixed(1) ?? "n/a"}m`);
|
|
149615
|
+
}
|
|
149616
|
+
}
|
|
149220
149617
|
return lines.join("\n");
|
|
149221
149618
|
}
|
|
149222
149619
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mestreyoda/fabrica",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.41",
|
|
4
4
|
"description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|