@rsaglobaltech/specgate 0.26.0 → 0.27.1
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/CHANGELOG.md +34 -0
- package/README.md +6 -0
- package/dist/packages/core/src/domain/ChangeMeasures.js +198 -0
- package/dist/packages/core/src/domain/ChangeMeasures.js.map +1 -0
- package/dist/packages/core/src/domain/ConventionalCommit.js +73 -0
- package/dist/packages/core/src/domain/ConventionalCommit.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -1
- package/dist/packages/core/src/domain/HarnessReport.js +2 -0
- package/dist/packages/core/src/domain/HarnessReport.js.map +1 -1
- package/dist/packages/core/src/domain/QualityChecks.js +218 -0
- package/dist/packages/core/src/domain/QualityChecks.js.map +1 -0
- package/dist/packages/core/src/domain/QualitySummary.js +58 -0
- package/dist/packages/core/src/domain/QualitySummary.js.map +1 -0
- package/dist/packages/core/src/domain/Sarif.js +121 -0
- package/dist/packages/core/src/domain/Sarif.js.map +1 -0
- package/dist/packages/core/src/infrastructure/GitContributionStager.js +1 -1
- package/dist/packages/core/src/infrastructure/GitContributionStager.js.map +1 -1
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +21 -0
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
- package/dist/packages/core/src/infrastructure/QualityBaselineFile.js +67 -0
- package/dist/packages/core/src/infrastructure/QualityBaselineFile.js.map +1 -0
- package/dist/scripts/cli/commands/change/TicketCommand.js +1 -1
- package/dist/scripts/cli/commands/change/TicketCommand.js.map +1 -1
- package/dist/scripts/cli/commands/harness/RunCommand.js +212 -15
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/CheckCommand.js +54 -3
- package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/QualityCommand.js +314 -0
- package/dist/scripts/cli/commands/quality/QualityCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/DoneCommand.js +4 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StudioCommand.js +14 -0
- package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -1
- package/dist/scripts/docs/nav.js +13 -2
- package/dist/scripts/docs/nav.js.map +1 -1
- package/dist/scripts/harness/prompt.js +4 -0
- package/dist/scripts/harness/prompt.js.map +1 -1
- package/dist/scripts/harness/report.js +13 -0
- package/dist/scripts/harness/report.js.map +1 -1
- package/dist/scripts/lib/quality-presets.js +378 -0
- package/dist/scripts/lib/quality-presets.js.map +1 -0
- package/dist/scripts/lib/quality-run.js +579 -0
- package/dist/scripts/lib/quality-run.js.map +1 -0
- package/dist/scripts/lib/sonar-local.js +308 -0
- package/dist/scripts/lib/sonar-local.js.map +1 -0
- package/dist/scripts/lib/studio-app-page.js +74 -11
- package/dist/scripts/lib/studio-app-page.js.map +1 -1
- package/dist/scripts/lib/studio-app.js +80 -0
- package/dist/scripts/lib/studio-app.js.map +1 -1
- package/dist/scripts/lib/studio-route.js +64 -1
- package/dist/scripts/lib/studio-route.js.map +1 -1
- package/dist/scripts/lib/surface.js +11 -0
- package/dist/scripts/lib/surface.js.map +1 -1
- package/dist/scripts/lib/ticket-keys.js +1 -1
- package/dist/scripts/lib/ticket-keys.js.map +1 -1
- package/dist/scripts/quality.js +10 -0
- package/dist/scripts/quality.js.map +1 -0
- package/package.json +1 -1
|
@@ -84,6 +84,7 @@ const harness_events_1 = require("../../../lib/harness-events");
|
|
|
84
84
|
const clarification_1 = require("../../../lib/clarification");
|
|
85
85
|
const screen_requirements_1 = require("../../../lib/screen-requirements");
|
|
86
86
|
const harness_guard_1 = require("../../../lib/harness-guard");
|
|
87
|
+
const quality_run_1 = require("../../../lib/quality-run");
|
|
87
88
|
const HarnessEvents_1 = require("../../../../packages/core/src/domain/HarnessEvents");
|
|
88
89
|
/**
|
|
89
90
|
* `read` against the project as it will be after its open changes are
|
|
@@ -497,6 +498,7 @@ function runGate(worktreeDir, testCmd, timeoutMs, req = {}, settings = {}, repor
|
|
|
497
498
|
// The agent's tests are new evidence for a generated matrix: refresh it so
|
|
498
499
|
// the gate judges the attempt, not the matrix as it was before it.
|
|
499
500
|
(0, MatrixCommand_1.refreshDerivedMatrix)(worktreeDir);
|
|
501
|
+
let passHint = "";
|
|
500
502
|
const validate = judged(worktreeDir, (dir, toProject) => {
|
|
501
503
|
const validateArgs = [VALIDATE_SCRIPT, dir, "--strict"];
|
|
502
504
|
if (req.requirement)
|
|
@@ -537,13 +539,16 @@ function runGate(worktreeDir, testCmd, timeoutMs, req = {}, settings = {}, repor
|
|
|
537
539
|
// and its failure goes back to the agent like any other gate failure.
|
|
538
540
|
const archCmd = String(settings.archCmd || "").trim();
|
|
539
541
|
if (archCmd) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
542
|
+
// REQ-231: with arch_sarif, only a violation this attempt adds fails it.
|
|
543
|
+
const arch = settings.archSarif
|
|
544
|
+
? (0, quality_run_1.archRun)(worktreeDir, archCmd, settings.archSarif, "", req.requirement || "")
|
|
545
|
+
: (0, node_child_process_1.spawnSync)(archCmd, {
|
|
546
|
+
shell: true,
|
|
547
|
+
cwd: worktreeDir,
|
|
548
|
+
encoding: "utf8",
|
|
549
|
+
timeout: timeoutMs,
|
|
550
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
551
|
+
});
|
|
547
552
|
report(`architecture check: ${archCmd}`, arch.status === 0 ? "pass" : "fail");
|
|
548
553
|
if (arch.status !== 0) {
|
|
549
554
|
return {
|
|
@@ -625,12 +630,52 @@ function runGate(worktreeDir, testCmd, timeoutMs, req = {}, settings = {}, repor
|
|
|
625
630
|
errors.map((e) => ` ${e.message}\n fix: ${e.fix}`).join("\n\n"),
|
|
626
631
|
};
|
|
627
632
|
}
|
|
628
|
-
if (warnings.length > 0)
|
|
629
|
-
|
|
633
|
+
if (warnings.length > 0)
|
|
634
|
+
passHint = warnings[0].message;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// REQ-220, REQ-221: the quality checks, after the tests — the attempt ones,
|
|
638
|
+
// then the end ones. A failure goes back to the agent like a failing test.
|
|
639
|
+
const quality = settings.quality;
|
|
640
|
+
if (quality && quality.checks.length > 0) {
|
|
641
|
+
const ctx = (0, quality_run_1.gateContext)(worktreeDir, req.requirement || "", "");
|
|
642
|
+
const outcomes = [];
|
|
643
|
+
for (const stage of ["attempt", "end"]) {
|
|
644
|
+
for (const o of (0, quality_run_1.runStages)(worktreeDir, quality, [stage], ctx, undefined, agentChecks(worktreeDir, req, settings, timeoutMs, testCmd))) {
|
|
645
|
+
outcomes.push(o);
|
|
646
|
+
// REQ-229: the step says what was measured, so /live shows it as it happens.
|
|
647
|
+
const said = o.skipped
|
|
648
|
+
? " (not run)"
|
|
649
|
+
: o.measure && o.measure.value !== null
|
|
650
|
+
? ` ${o.measure.value}%`
|
|
651
|
+
: o.findings
|
|
652
|
+
? ` · ${o.findings.freshCount} new`
|
|
653
|
+
: "";
|
|
654
|
+
report(`quality: ${o.name}${said}`, o.ok || !!o.skipped || !o.gate ? "pass" : "fail", {
|
|
655
|
+
ms: o.ms,
|
|
656
|
+
...(o.skipped ? { skipped: o.skipped } : {}),
|
|
657
|
+
...(o.measure ? { value: o.measure.value } : {}),
|
|
658
|
+
});
|
|
659
|
+
if (o.gate && !o.ok && !o.skipped) {
|
|
660
|
+
return {
|
|
661
|
+
ok: false,
|
|
662
|
+
stage: `quality check: ${o.name}`,
|
|
663
|
+
output: o.output,
|
|
664
|
+
hint: o.measure && o.measure.kind === "coverage"
|
|
665
|
+
? "Write the tests that run the lines named above; do not lower the threshold."
|
|
666
|
+
: o.measure && o.measure.kind === "mutation"
|
|
667
|
+
? "Strengthen the tests so each surviving mutant named above would fail one."
|
|
668
|
+
: o.name === "commits"
|
|
669
|
+
? "Reword the commits named above as Conventional Commits, or do not commit: the harness commits for you."
|
|
670
|
+
: "Fix what the check found. Do not silence it: no @SuppressWarnings, eslint-disable or baseline edit.",
|
|
671
|
+
checks: outcomes,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
630
674
|
}
|
|
631
675
|
}
|
|
676
|
+
return { ok: true, stage: "", output: "", hint: passHint, checks: outcomes };
|
|
632
677
|
}
|
|
633
|
-
return { ok: true, stage: "", output: "", hint:
|
|
678
|
+
return { ok: true, stage: "", output: "", hint: passHint };
|
|
634
679
|
}
|
|
635
680
|
/**
|
|
636
681
|
* Undo whatever an advisory agent touched.
|
|
@@ -673,6 +718,151 @@ function discardWorktreeChanges(worktreeDir) {
|
|
|
673
718
|
// reviewer's edits must not discard the evidence.
|
|
674
719
|
git(worktreeDir, ["clean", "-fd", "-e", EmptyAttempt_1.PROMPT_ARCHIVE_DIR]);
|
|
675
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* The worktree exactly as it is, to put back after an agent that must not
|
|
723
|
+
* change it: tracked files through `git stash create` (which touches
|
|
724
|
+
* nothing), untracked ones by content. An agent check runs before the commit,
|
|
725
|
+
* so the implementer's work is uncommitted — `discardWorktreeChanges` would
|
|
726
|
+
* throw it away (REQ-227).
|
|
727
|
+
*/
|
|
728
|
+
function snapshotWorktree(worktreeDir) {
|
|
729
|
+
const stash = (git(worktreeDir, ["stash", "create"]).stdout || "").trim();
|
|
730
|
+
const untracked = (git(worktreeDir, ["ls-files", "--others", "--exclude-standard", "-z"]).stdout || "")
|
|
731
|
+
.split("\0")
|
|
732
|
+
.filter(Boolean);
|
|
733
|
+
const saved = new Map(untracked.map((rel) => [rel, fs.readFileSync(path.join(worktreeDir, rel))]));
|
|
734
|
+
return () => {
|
|
735
|
+
git(worktreeDir, ["checkout", stash || "HEAD", "--", "."]);
|
|
736
|
+
const now = (git(worktreeDir, ["ls-files", "--others", "--exclude-standard", "-z"]).stdout || "")
|
|
737
|
+
.split("\0")
|
|
738
|
+
.filter(Boolean);
|
|
739
|
+
for (const rel of now) {
|
|
740
|
+
if (!saved.has(rel) && !rel.startsWith(EmptyAttempt_1.PROMPT_ARCHIVE_DIR)) {
|
|
741
|
+
fs.rmSync(path.join(worktreeDir, rel), { force: true });
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
for (const [rel, content] of saved) {
|
|
745
|
+
fs.mkdirSync(path.dirname(path.join(worktreeDir, rel)), { recursive: true });
|
|
746
|
+
fs.writeFileSync(path.join(worktreeDir, rel), content);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/** Run an agent on an instruction built into the requirement's prompt; its output. */
|
|
751
|
+
function runAgentOnce(worktreeDir, command, prompt, timeoutMs, tag) {
|
|
752
|
+
const promptFile = path.join(os.tmpdir(), `specgate-${tag}-${crypto.randomBytes(4).toString("hex")}.md`);
|
|
753
|
+
try {
|
|
754
|
+
fs.writeFileSync(promptFile, prompt, "utf8");
|
|
755
|
+
const r = (0, node_child_process_1.spawnSync)((0, HarnessRun_1.substituteAgentCommand)(command, promptFile), {
|
|
756
|
+
shell: true,
|
|
757
|
+
cwd: worktreeDir,
|
|
758
|
+
encoding: "utf8",
|
|
759
|
+
timeout: timeoutMs,
|
|
760
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
761
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
762
|
+
});
|
|
763
|
+
return `${r.stdout || ""}${r.stderr || ""}`.trim();
|
|
764
|
+
}
|
|
765
|
+
finally {
|
|
766
|
+
fs.rmSync(promptFile, { force: true });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* The agent checks of REQ-227, lent to the quality stages of a harness gate:
|
|
771
|
+
* `builtin:review` — a second agent reads the change against the requirement
|
|
772
|
+
* and answers VERDICT: PASS or FAIL; `builtin:adversary` — one writes a test
|
|
773
|
+
* that tries to break it, which is run and then removed. Neither may change
|
|
774
|
+
* the work: the worktree is put back exactly as it was.
|
|
775
|
+
*/
|
|
776
|
+
function agentChecks(worktreeDir, req, settings, timeoutMs, testCmd) {
|
|
777
|
+
const commandFor = (check, fallback) => {
|
|
778
|
+
const profile = check.profile || fallback;
|
|
779
|
+
if (!profile)
|
|
780
|
+
return settings.agent || "";
|
|
781
|
+
const known = (settings.profileAgents || {})[profile];
|
|
782
|
+
if (known)
|
|
783
|
+
return known;
|
|
784
|
+
try {
|
|
785
|
+
// A profile only a check names is not resolved with the others.
|
|
786
|
+
return (0, HarnessConfigFile_1.resolveProfileAgent)(worktreeDir, profile);
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
return settings.agent || "";
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
const promptFor = (instruction) => (0, prompt_1.buildPrompt)(req, worktreeDir, {
|
|
793
|
+
promptPrefix: settings.promptPrefix,
|
|
794
|
+
hint: instruction,
|
|
795
|
+
withPrecedents: settings.promptPrecedents === true,
|
|
796
|
+
});
|
|
797
|
+
return {
|
|
798
|
+
"builtin:review": (check) => {
|
|
799
|
+
const command = commandFor(check, settings.reviewProfile || "");
|
|
800
|
+
if (!command)
|
|
801
|
+
return { ok: true, output: "no agent configured to review" };
|
|
802
|
+
const diff = (git(worktreeDir, ["diff", "HEAD"]).stdout || "").slice(0, 60000);
|
|
803
|
+
const restore = snapshotWorktree(worktreeDir);
|
|
804
|
+
let said = "";
|
|
805
|
+
try {
|
|
806
|
+
said = runAgentOnce(worktreeDir, command, promptFor(`Review only; change no file. Compare the change below with ${req.requirement}, its ` +
|
|
807
|
+
"statement and every scenario. Answer with a first line `VERDICT: PASS` when the " +
|
|
808
|
+
"change does what the requirement says and nothing it forbids, or `VERDICT: FAIL` " +
|
|
809
|
+
"followed by each reason as `- path:line — what is wrong`.\n\n```diff\n" +
|
|
810
|
+
diff +
|
|
811
|
+
"\n```"), timeoutMs, "review");
|
|
812
|
+
}
|
|
813
|
+
finally {
|
|
814
|
+
restore();
|
|
815
|
+
}
|
|
816
|
+
const verdict = /VERDICT:\s*(PASS|FAIL)/i.exec(said);
|
|
817
|
+
if (!verdict) {
|
|
818
|
+
return {
|
|
819
|
+
ok: true,
|
|
820
|
+
output: `The reviewer gave no verdict:
|
|
821
|
+
${said.slice(0, 2000)}`,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
return { ok: verdict[1].toUpperCase() === "PASS", output: said.slice(0, 4000) };
|
|
825
|
+
},
|
|
826
|
+
"builtin:adversary": (check) => {
|
|
827
|
+
const command = commandFor(check, settings.adversaryProfile || "");
|
|
828
|
+
if (!command || !testCmd)
|
|
829
|
+
return { ok: true, output: "no agent or test command to probe with" };
|
|
830
|
+
const restore = snapshotWorktree(worktreeDir);
|
|
831
|
+
try {
|
|
832
|
+
const before = new Set((git(worktreeDir, ["status", "--porcelain"]).stdout || "").split("\n").filter(Boolean));
|
|
833
|
+
const said = runAgentOnce(worktreeDir, command, promptFor(`Try to **break** the implementation of ${req.requirement}: write one additional ` +
|
|
834
|
+
"test, in the project's test style, for a case it should handle and might not — an " +
|
|
835
|
+
"empty input, a boundary, a duplicate, a failure path. Write the test and nothing " +
|
|
836
|
+
"else; do not modify the implementation, the specification or any existing test."), timeoutMs, "adversary");
|
|
837
|
+
const wrote = (git(worktreeDir, ["status", "--porcelain"]).stdout || "")
|
|
838
|
+
.split("\n")
|
|
839
|
+
.filter((l) => l && !before.has(l) && !l.slice(3).startsWith(EmptyAttempt_1.PROMPT_ARCHIVE_DIR));
|
|
840
|
+
if (wrote.length === 0)
|
|
841
|
+
return { ok: true, output: "The adversary wrote no test." };
|
|
842
|
+
const test = (0, node_child_process_1.spawnSync)((0, HarnessRun_1.substituteGateCommand)(testCmd, req), {
|
|
843
|
+
shell: true,
|
|
844
|
+
cwd: worktreeDir,
|
|
845
|
+
encoding: "utf8",
|
|
846
|
+
timeout: timeoutMs,
|
|
847
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
848
|
+
});
|
|
849
|
+
return {
|
|
850
|
+
ok: test.status === 0,
|
|
851
|
+
output: [
|
|
852
|
+
`The adversary wrote ${wrote.map((l) => l.slice(3)).join(", ")}; the tests ${test.status === 0 ? "still pass" : "fail"}.`,
|
|
853
|
+
said.slice(0, 1500),
|
|
854
|
+
test.status === 0 ? "" : `${test.stdout || ""}${test.stderr || ""}`.slice(-3000),
|
|
855
|
+
]
|
|
856
|
+
.filter(Boolean)
|
|
857
|
+
.join("\n\n"),
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
finally {
|
|
861
|
+
restore();
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
}
|
|
676
866
|
/**
|
|
677
867
|
* Let an adversary try to break a green implementation (D3).
|
|
678
868
|
*
|
|
@@ -716,7 +906,10 @@ function runAdversarialProbe(worktreeDir, req, settings, timeoutMs, promptFor) {
|
|
|
716
906
|
// case is telling you something mildly reassuring.
|
|
717
907
|
return { ran: true, broke: false, detail: "The adversary wrote no test.", wrote: [] };
|
|
718
908
|
}
|
|
719
|
-
const after = runGate(worktreeDir, settings.testCmd, timeoutMs, req,
|
|
909
|
+
const after = runGate(worktreeDir, settings.testCmd, timeoutMs, req, {
|
|
910
|
+
...settings,
|
|
911
|
+
quality: undefined,
|
|
912
|
+
});
|
|
720
913
|
return {
|
|
721
914
|
ran: true,
|
|
722
915
|
broke: !after.ok,
|
|
@@ -1269,6 +1462,7 @@ function attemptRequirement(req, ctx) {
|
|
|
1269
1462
|
// The probe never changes the verdict; it travels beside it so
|
|
1270
1463
|
// `harness report` and the run record can show what it found.
|
|
1271
1464
|
...(probe.ran ? { adversarial: probe } : {}),
|
|
1465
|
+
...(gate.checks ? { checks: gate.checks.map(({ output: _o, ...rest }) => rest) } : {}),
|
|
1272
1466
|
};
|
|
1273
1467
|
}
|
|
1274
1468
|
// Every attempt is spent. Commit what the agent produced anyway, on the
|
|
@@ -1572,13 +1766,14 @@ function resumeFrom(worktreeDir, requirement) {
|
|
|
1572
1766
|
return { attempt: 1, previousFailure: "" };
|
|
1573
1767
|
}
|
|
1574
1768
|
// Interrupted or exhausted? The two deserve different answers, and the branch
|
|
1575
|
-
// says which: `preserveFailedAttempt` leaves a `
|
|
1769
|
+
// says which: `preserveFailedAttempt` leaves a `chore(...): attempt FAILED the gate`
|
|
1576
1770
|
// commit when the attempts run out, and an interruption leaves none.
|
|
1577
1771
|
//
|
|
1578
1772
|
// exhausted → the last attempt reached a verdict → resume at the next one
|
|
1579
1773
|
// interrupted → it never did → re-run it, without charging the budget
|
|
1580
1774
|
const head = git(worktreeDir, ["log", "-1", "--format=%s"]);
|
|
1581
|
-
|
|
1775
|
+
// `chore(…): attempt FAILED` since REQ-234; `wip(…): FAILED` on branches older versions left.
|
|
1776
|
+
const lastCompleted = /^(?:chore\([^)]*\): attempt|wip\([^)]*\):) FAILED the gate/.test((head.stdout || "").trim());
|
|
1582
1777
|
const point = (0, ResumeState_1.resumePoint)(names, requirement, lastCompleted);
|
|
1583
1778
|
if (!point.latest)
|
|
1584
1779
|
return { attempt: 1, previousFailure: "" };
|
|
@@ -1599,7 +1794,7 @@ function preserveFailedAttempt(worktreeDir, req, failure) {
|
|
|
1599
1794
|
const commit = git(worktreeDir, [
|
|
1600
1795
|
"commit",
|
|
1601
1796
|
"-m",
|
|
1602
|
-
`
|
|
1797
|
+
`chore(${req.requirement}): attempt FAILED the gate — do not merge as is\n\n` +
|
|
1603
1798
|
`${firstLine}\n\n` +
|
|
1604
1799
|
"Committed by `specgate harness run` so the attempt is reviewable rather than\n" +
|
|
1605
1800
|
"discarded. The requirement is still Draft: `specgate apply` never ran.",
|
|
@@ -2184,7 +2379,9 @@ async function runChain(pending, ctx, hintByReq) {
|
|
|
2184
2379
|
const eventsFile = (0, harness_events_1.eventsFileFor)(projectDir, req.requirement);
|
|
2185
2380
|
const attempt = Number(result.attempts) || undefined;
|
|
2186
2381
|
(0, harness_events_1.appendEvent)(eventsFile, { type: "merge", into: "HEAD", branch }, req.requirement, attempt);
|
|
2187
|
-
const gate = runGate(projectDir, ctx.settings.testCmd, ctx.timeoutMs, req,
|
|
2382
|
+
const gate = runGate(projectDir, ctx.settings.testCmd, ctx.timeoutMs, req,
|
|
2383
|
+
// The checks judged the branch already; the post-merge gate is the suite's.
|
|
2384
|
+
{ ...ctx.settings, quality: undefined }, (stage, res, extra) => (0, harness_events_1.appendEvent)(eventsFile, { type: "gate", stage: `post-merge ${stage}`, result: res, ...(extra || {}) }, req.requirement, attempt));
|
|
2188
2385
|
if (!gate.ok) {
|
|
2189
2386
|
git(projectDir, ["reset", "-q", "--hard", "HEAD~1"]);
|
|
2190
2387
|
Object.assign(result, {
|