@deftai/directive-core 0.96.0 → 0.98.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/dist/authz/classify.js +291 -0
- package/dist/cache/archive.js +10 -4
- package/dist/check/cached-orchestrator.d.ts +5 -0
- package/dist/check/cached-orchestrator.js +18 -1
- package/dist/check/gate-lists.d.ts +20 -0
- package/dist/check/gate-lists.js +54 -9
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +4 -0
- package/dist/check/orchestrator.js +4 -0
- package/dist/consumer-check-contract/evaluate.d.ts +124 -0
- package/dist/consumer-check-contract/evaluate.js +699 -0
- package/dist/consumer-check-contract/index.d.ts +5 -0
- package/dist/consumer-check-contract/index.js +5 -0
- package/dist/delivery-attempt/disk-begin.d.ts +51 -0
- package/dist/delivery-attempt/disk-begin.js +68 -0
- package/dist/delivery-attempt/evaluate.d.ts +26 -0
- package/dist/delivery-attempt/evaluate.js +443 -0
- package/dist/delivery-attempt/fingerprint.d.ts +32 -0
- package/dist/delivery-attempt/fingerprint.js +100 -0
- package/dist/delivery-attempt/handoff.d.ts +25 -0
- package/dist/delivery-attempt/handoff.js +102 -0
- package/dist/delivery-attempt/index.d.ts +17 -0
- package/dist/delivery-attempt/index.js +17 -0
- package/dist/delivery-attempt/ledger.d.ts +169 -0
- package/dist/delivery-attempt/ledger.js +758 -0
- package/dist/delivery-attempt/material-delta.d.ts +38 -0
- package/dist/delivery-attempt/material-delta.js +126 -0
- package/dist/delivery-attempt/types.d.ts +210 -0
- package/dist/delivery-attempt/types.js +77 -0
- package/dist/doctor/checks.d.ts +7 -0
- package/dist/doctor/checks.js +83 -0
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +12 -0
- package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
- package/dist/doctor/openclaw-soft-rebind.js +164 -0
- package/dist/hooks/dispatcher.d.ts +7 -1
- package/dist/hooks/dispatcher.js +117 -13
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/init-deposit/gitignore.js +7 -0
- package/dist/init-deposit/hygiene.d.ts +70 -1
- package/dist/init-deposit/hygiene.js +582 -8
- package/dist/init-deposit/init-deposit.js +5 -0
- package/dist/init-deposit/refresh.js +3 -0
- package/dist/init-deposit/scaffold.js +277 -4
- package/dist/init-deposit/skill-discovery-deposit.js +15 -0
- package/dist/policy/check-resume.d.ts +72 -0
- package/dist/policy/check-resume.js +253 -0
- package/dist/policy/coverage-check-resume-presets.d.ts +46 -0
- package/dist/policy/coverage-check-resume-presets.js +228 -0
- package/dist/policy/coverage-debt.d.ts +76 -0
- package/dist/policy/coverage-debt.js +262 -0
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +50 -21
- package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
- package/dist/pr-merge-readiness/ci-gate.js +191 -24
- package/dist/pr-merge-readiness/compute.js +10 -1
- package/dist/pr-merge-readiness/index.d.ts +2 -1
- package/dist/pr-merge-readiness/index.js +2 -1
- package/dist/pr-merge-readiness/output.js +14 -0
- package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
- package/dist/pr-merge-readiness/platform-status.js +49 -0
- package/dist/pr-watch/constants.d.ts +10 -0
- package/dist/pr-watch/constants.js +12 -1
- package/dist/pr-watch/main.js +16 -1
- package/dist/pr-watch/probe.js +13 -1
- package/dist/pr-watch/types.d.ts +3 -2
- package/dist/pr-watch/watch.js +14 -7
- package/dist/release/auto-hatch.d.ts +114 -0
- package/dist/release/auto-hatch.js +301 -0
- package/dist/release/coverage-debt-ledger.d.ts +22 -0
- package/dist/release/coverage-debt-ledger.js +157 -0
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.js +3 -0
- package/dist/release/pipeline.js +164 -12
- package/dist/release/suite-stamp.d.ts +44 -0
- package/dist/release/suite-stamp.js +133 -0
- package/dist/release/types.d.ts +19 -0
- package/dist/scope-provenance/digest.d.ts +67 -0
- package/dist/scope-provenance/digest.js +188 -0
- package/dist/scope-provenance/evaluate.d.ts +82 -0
- package/dist/scope-provenance/evaluate.js +528 -0
- package/dist/scope-provenance/index.d.ts +6 -0
- package/dist/scope-provenance/index.js +6 -0
- package/dist/session/compact-ritual.d.ts +96 -0
- package/dist/session/compact-ritual.js +237 -0
- package/dist/session/compact-ritual.spec.d.ts +2 -0
- package/dist/session/compact-ritual.spec.js +21 -0
- package/dist/session/coverage-check-resume-nudge.d.ts +34 -0
- package/dist/session/coverage-check-resume-nudge.js +66 -0
- package/dist/session/index.d.ts +3 -0
- package/dist/session/index.js +3 -0
- package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
- package/dist/session/session-start.js +21 -0
- package/dist/test-boundary/evaluate.d.ts +54 -0
- package/dist/test-boundary/evaluate.js +368 -0
- package/dist/test-boundary/index.d.ts +6 -0
- package/dist/test-boundary/index.js +6 -0
- package/dist/test-boundary/policy.d.ts +52 -0
- package/dist/test-boundary/policy.js +182 -0
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +15 -1
- package/dist/triage/classify/label-mirror.d.ts +31 -1
- package/dist/triage/classify/label-mirror.js +78 -6
- package/dist/triage/help/registry-data.d.ts +6 -6
- package/dist/triage/help/registry-data.js +12 -3
- package/dist/vbrief-activate/activate.js +22 -6
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +54 -0
- package/dist/xbrief/styles.js +33 -17
- package/package.json +7 -3
package/dist/pr-watch/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { defaultRunGh } from "../pr-merge-readiness/gh.js";
|
|
4
|
+
import { platformStatusUrlsForWeather } from "../pr-merge-readiness/platform-status.js";
|
|
4
5
|
import { DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, EXIT_CLEAN, EXIT_TERMINAL_ERROR, WATCH_HELP, } from "./constants.js";
|
|
5
6
|
import { watch } from "./watch.js";
|
|
6
7
|
function fail(base, error) {
|
|
@@ -140,7 +141,7 @@ function pythonJsonDumps(value) {
|
|
|
140
141
|
/** AC-4 stable --json shape (same field set as the #1039 Tier-1 instrumentation line). */
|
|
141
142
|
export function watchResultToJson(result) {
|
|
142
143
|
const p = result.probe;
|
|
143
|
-
|
|
144
|
+
const payload = {
|
|
144
145
|
verdict: result.verdict,
|
|
145
146
|
pr_number: result.prNumber,
|
|
146
147
|
head_sha: p.headSha,
|
|
@@ -159,6 +160,13 @@ export function watchResultToJson(result) {
|
|
|
159
160
|
elapsed_seconds: result.elapsedSeconds,
|
|
160
161
|
poll_count: result.pollCount,
|
|
161
162
|
};
|
|
163
|
+
// #3180: static status URLs on weather-class ci_ready_state (no network fetch).
|
|
164
|
+
const statusUrls = platformStatusUrlsForWeather(p.ciReadyState);
|
|
165
|
+
if (statusUrls !== null) {
|
|
166
|
+
payload.platform_status_github = statusUrls.platform_status_github;
|
|
167
|
+
payload.platform_status_blacksmith = statusUrls.platform_status_blacksmith;
|
|
168
|
+
}
|
|
169
|
+
return payload;
|
|
162
170
|
}
|
|
163
171
|
export function emitWatchJson(result) {
|
|
164
172
|
return `${pythonJsonDumps(watchResultToJson(result))}\n`;
|
|
@@ -183,6 +191,13 @@ export function printWatchHuman(result) {
|
|
|
183
191
|
if (p.ciCapacityStalledChecks.length > 0) {
|
|
184
192
|
lines.push(` Capacity-stalled: ${p.ciCapacityStalledChecks.join("; ")}`);
|
|
185
193
|
}
|
|
194
|
+
// #3180: probe these pages before workflow thrash on weather states.
|
|
195
|
+
const statusUrls = platformStatusUrlsForWeather(p.ciReadyState);
|
|
196
|
+
if (statusUrls !== null) {
|
|
197
|
+
lines.push(` Platform status GH: ${statusUrls.platform_status_github}`);
|
|
198
|
+
lines.push(` Platform status BS: ${statusUrls.platform_status_blacksmith}`);
|
|
199
|
+
lines.push(" Probe status pages before workflow edits (#3180)");
|
|
200
|
+
}
|
|
186
201
|
if (p.cleanGateHoldout !== null) {
|
|
187
202
|
lines.push(` Clean-gate holdout: ${p.cleanGateHoldout}`);
|
|
188
203
|
}
|
package/dist/pr-watch/probe.js
CHANGED
|
@@ -82,7 +82,7 @@ export function probeOnce(prNumber, repoArg, runGh = defaultRunGh, projectRoot =
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
const shaMatch = lastReviewedSha !== null && lastReviewedSha === headSha;
|
|
85
|
-
|
|
85
|
+
let [isClean, cleanGateHoldout] = evaluateCleanGate({
|
|
86
86
|
lastReviewedSha,
|
|
87
87
|
headSha,
|
|
88
88
|
hasBlocking: findings.has_blocking,
|
|
@@ -92,6 +92,18 @@ export function probeOnce(prNumber, repoArg, runGh = defaultRunGh, projectRoot =
|
|
|
92
92
|
terminalCheckRun,
|
|
93
93
|
minConfidence,
|
|
94
94
|
});
|
|
95
|
+
// #3167: weather not-ready states must never surface as CLEAN even when the
|
|
96
|
+
// Greptile side of the clean gate is satisfied (empty CI was previously ready).
|
|
97
|
+
if (isClean &&
|
|
98
|
+
(ciReadyState === "ci_never_scheduled" ||
|
|
99
|
+
ciReadyState === "runner_capacity_stall" ||
|
|
100
|
+
ciReadyState === "ci_cancelled_no_failover" ||
|
|
101
|
+
ciReadyState === "ci_failures" ||
|
|
102
|
+
ciReadyState === "blocked" ||
|
|
103
|
+
ciReadyState === "not_ready_yet")) {
|
|
104
|
+
isClean = false;
|
|
105
|
+
cleanGateHoldout = ciReadyState === "not_ready_yet" ? "terminal_check_run" : ciReadyState;
|
|
106
|
+
}
|
|
95
107
|
return {
|
|
96
108
|
found,
|
|
97
109
|
headSha,
|
package/dist/pr-watch/types.d.ts
CHANGED
|
@@ -16,8 +16,9 @@ export interface WatchProbe {
|
|
|
16
16
|
/** Failed required check identities (name + conclusion), when CI was probed. */
|
|
17
17
|
readonly ciFailedChecks: readonly string[];
|
|
18
18
|
/**
|
|
19
|
-
* CI gate ready_state from evaluateCiGate (#2169 / #2672).
|
|
20
|
-
*
|
|
19
|
+
* CI gate ready_state from evaluateCiGate (#2169 / #2672 / #3167).
|
|
20
|
+
* Weather codes: `ci_never_scheduled`, `runner_capacity_stall`,
|
|
21
|
+
* `ci_failures`, `ci_cancelled_no_failover` (plus `not_ready_yet` / `ready`).
|
|
21
22
|
*/
|
|
22
23
|
readonly ciReadyState: string | null;
|
|
23
24
|
/** Required checks classified as capacity-stalled (#2672). */
|
package/dist/pr-watch/watch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultRunGh } from "../pr-merge-readiness/gh.js";
|
|
2
|
-
import { DEFAULT_CI_BLOCKED_THRESHOLD, DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, DEFAULT_STALL_THRESHOLD, EXIT_CLEAN, EXIT_NEW_P0_P1, EXIT_TERMINAL_ERROR, VERDICT_CI_BLOCKED, VERDICT_CLEAN, VERDICT_CONFIG, VERDICT_ERRORED, VERDICT_NEW_P0_P1, VERDICT_PENDING, VERDICT_RUNNER_CAPACITY_STALL, VERDICT_STALL, VERDICT_TIMEOUT, } from "./constants.js";
|
|
2
|
+
import { DEFAULT_CI_BLOCKED_THRESHOLD, DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, DEFAULT_STALL_THRESHOLD, EXIT_CLEAN, EXIT_NEW_P0_P1, EXIT_TERMINAL_ERROR, VERDICT_CI_BLOCKED, VERDICT_CI_CANCELLED_NO_FAILOVER, VERDICT_CI_NEVER_SCHEDULED, VERDICT_CLEAN, VERDICT_CONFIG, VERDICT_ERRORED, VERDICT_NEW_P0_P1, VERDICT_PENDING, VERDICT_RUNNER_CAPACITY_STALL, VERDICT_STALL, VERDICT_TIMEOUT, } from "./constants.js";
|
|
3
3
|
import { probeOnce } from "./probe.js";
|
|
4
4
|
const systemMonotonicClock = {
|
|
5
5
|
now() {
|
|
@@ -81,20 +81,27 @@ export function watch(prNumber, repo, options = {}) {
|
|
|
81
81
|
if (probe.errored) {
|
|
82
82
|
return build(VERDICT_ERRORED, EXIT_TERMINAL_ERROR, probe, poll);
|
|
83
83
|
}
|
|
84
|
-
// #2672:
|
|
85
|
-
//
|
|
84
|
+
// #2672 / #3167: CI weather states are distinct terminal exits (exit 2).
|
|
85
|
+
// Agents must thrash-cap / BLOCKED rather than multi-hour empty-commit loops.
|
|
86
86
|
if (probe.ciReadyState === "runner_capacity_stall") {
|
|
87
87
|
return build(VERDICT_RUNNER_CAPACITY_STALL, EXIT_TERMINAL_ERROR, probe, poll);
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (probe.ciReadyState === "ci_never_scheduled") {
|
|
90
|
+
return build(VERDICT_CI_NEVER_SCHEDULED, EXIT_TERMINAL_ERROR, probe, poll);
|
|
91
|
+
}
|
|
92
|
+
if (probe.ciReadyState === "ci_cancelled_no_failover") {
|
|
93
|
+
return build(VERDICT_CI_CANCELLED_NO_FAILOVER, EXIT_TERMINAL_ERROR, probe, poll);
|
|
94
|
+
}
|
|
95
|
+
// #2688 / #3167: Greptile side satisfied on HEAD but CI red (ci_failures) —
|
|
96
|
+
// fail loud toward a fix loop instead of burning max-wait-minutes.
|
|
97
|
+
const ciProductBlocked = probe.cleanGateHoldout === "ci_failures" || probe.ciReadyState === "ci_failures";
|
|
98
|
+
if (ciProductBlocked) {
|
|
92
99
|
ciBlockedStreak += 1;
|
|
93
100
|
}
|
|
94
101
|
else {
|
|
95
102
|
ciBlockedStreak = 0;
|
|
96
103
|
}
|
|
97
|
-
if (oneShot &&
|
|
104
|
+
if (oneShot && ciProductBlocked) {
|
|
98
105
|
return build(VERDICT_CI_BLOCKED, EXIT_TERMINAL_ERROR, probe, poll);
|
|
99
106
|
}
|
|
100
107
|
if (!oneShot && ciBlockedStreak >= DEFAULT_CI_BLOCKED_THRESHOLD) {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release Step 5 auto-hatch for branch-only coverage hairlines (#3187 / #2866).
|
|
3
|
+
*
|
|
4
|
+
* Pure classifier + ledger helpers + debt-issue body. Network I/O is seamed so
|
|
5
|
+
* unit tests never hit live GitHub.
|
|
6
|
+
*/
|
|
7
|
+
import { type CoverageTotals } from "../vitest-runner/coverage-debt.js";
|
|
8
|
+
/** Machine-checkable Step 5 failure classes (#3187). */
|
|
9
|
+
export type Step5FailureClass = "REAL_FAILURE" | "BRANCH_HAIRLINE" | "OTHER_COVERAGE" | "UNKNOWN";
|
|
10
|
+
export interface ClassifyStep5FailureInput {
|
|
11
|
+
/** Combined reason / stdout / stderr from the Step 5 check (may be thin). */
|
|
12
|
+
readonly output?: string | null;
|
|
13
|
+
/** Aggregated totals from coverage/coverage-final.json when present. */
|
|
14
|
+
readonly totals?: CoverageTotals | null;
|
|
15
|
+
/** Exit code when known (124 = hard timeout). */
|
|
16
|
+
readonly exitCode?: number | null;
|
|
17
|
+
/** True when the check hit the Step 5 wall-clock budget. */
|
|
18
|
+
readonly timedOut?: boolean;
|
|
19
|
+
/** Explicit failed-test count when the runner reports one. */
|
|
20
|
+
readonly failedTests?: number | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Classify metrics alone (no output / test signals).
|
|
24
|
+
* Returns null when totals are absent.
|
|
25
|
+
*/
|
|
26
|
+
export declare function classifyCoverageMetrics(totals: CoverageTotals | null | undefined): "ok" | "branch_hairline" | "other_coverage" | null;
|
|
27
|
+
/**
|
|
28
|
+
* Pure classifier for release Step 5 non-zero outcomes.
|
|
29
|
+
*
|
|
30
|
+
* Prefer coverage-final totals when present; fall back to parseable output.
|
|
31
|
+
* Fail closed to UNKNOWN when evidence is ambiguous.
|
|
32
|
+
*/
|
|
33
|
+
export declare function classifyStep5Failure(input: ClassifyStep5FailureInput): Step5FailureClass;
|
|
34
|
+
/** Open-issue shape used by the coverage-debt ledger probe. */
|
|
35
|
+
export interface CoverageDebtIssueProbe {
|
|
36
|
+
readonly number: number;
|
|
37
|
+
readonly title?: string | null;
|
|
38
|
+
readonly body?: string | null;
|
|
39
|
+
readonly state?: string | null;
|
|
40
|
+
}
|
|
41
|
+
/** True when title/body carry the mandatory debt markers (#2866). */
|
|
42
|
+
export declare function issueHasCoverageDebtMarkers(issue: CoverageDebtIssueProbe): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Filter issues that count as unpaid coverage debt on the ledger.
|
|
45
|
+
*
|
|
46
|
+
* Accepts:
|
|
47
|
+
* - open issues with coverage-debt / --allow-coverage-debt markers
|
|
48
|
+
* - citation-only probes (`{ number }` only) from CHANGELOG scan after state check
|
|
49
|
+
*/
|
|
50
|
+
export declare function filterOpenCoverageDebtIssues(issues: readonly CoverageDebtIssueProbe[]): number[];
|
|
51
|
+
/**
|
|
52
|
+
* Parse `--allow-coverage-debt=#N` / `allow-coverage-debt=#N` citations from
|
|
53
|
+
* CHANGELOG Unreleased + the last few version sections (legacy hatch ledger).
|
|
54
|
+
*/
|
|
55
|
+
export declare function extractCoverageDebtCitationsFromChangelog(changelog: string, maxVersionSections?: number): number[];
|
|
56
|
+
/** Union marker-search hits with CHANGELOG-cited open issues. */
|
|
57
|
+
export declare function mergeOpenDebtLedger(markerHits: readonly number[], changelogCitedOpen: readonly number[]): number[];
|
|
58
|
+
export interface CoverageDebtIssueDraft {
|
|
59
|
+
readonly title: string;
|
|
60
|
+
readonly body: string;
|
|
61
|
+
}
|
|
62
|
+
/** Build a marker-compliant coverage-debt issue for auto-hatch (#3187). */
|
|
63
|
+
export declare function buildCoverageDebtIssueDraft(options: {
|
|
64
|
+
readonly version: string;
|
|
65
|
+
readonly totals: CoverageTotals;
|
|
66
|
+
readonly autoHatched?: boolean;
|
|
67
|
+
}): CoverageDebtIssueDraft;
|
|
68
|
+
export type AutoHatchDecision = {
|
|
69
|
+
readonly kind: "pass_with_debt";
|
|
70
|
+
readonly issue: number;
|
|
71
|
+
readonly created: boolean;
|
|
72
|
+
readonly class: "BRANCH_HAIRLINE";
|
|
73
|
+
readonly totals: CoverageTotals;
|
|
74
|
+
} | {
|
|
75
|
+
readonly kind: "fail_closed";
|
|
76
|
+
readonly class: Step5FailureClass;
|
|
77
|
+
readonly reason: string;
|
|
78
|
+
readonly openDebtIssues?: readonly number[];
|
|
79
|
+
};
|
|
80
|
+
export interface AutoHatchEvaluateInput {
|
|
81
|
+
readonly classification: Step5FailureClass;
|
|
82
|
+
readonly totals: CoverageTotals | null;
|
|
83
|
+
readonly openDebtIssues: readonly number[];
|
|
84
|
+
/** When true, create a new issue via createIssue (seamed). */
|
|
85
|
+
readonly createIssue?: () => number;
|
|
86
|
+
/** Operator already supplied --allow-coverage-debt=#N — never auto-create. */
|
|
87
|
+
readonly existingDebtIssue?: number | null;
|
|
88
|
+
/** Optional hard cut: refuse auto-hatch even on hairline. */
|
|
89
|
+
readonly noAutoCoverageDebt?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Decide whether Step 5 may soft-pass after one suite run (#3187).
|
|
93
|
+
* Never continues without a durable issue number (file-before-continue).
|
|
94
|
+
*/
|
|
95
|
+
export declare function evaluateAutoHatch(input: AutoHatchEvaluateInput): AutoHatchDecision;
|
|
96
|
+
/** Loud stderr banner when Step 5 continues via auto-hatch. */
|
|
97
|
+
export declare function formatAutoHatchBanner(issue: number, totals: CoverageTotals): string;
|
|
98
|
+
/** Parse exit code from the default runReleaseCheck reason string when present. */
|
|
99
|
+
export declare function parseExitCodeFromReason(reason: string): number | null;
|
|
100
|
+
export declare function reasonLooksLikeTimeout(reason: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Refuse to trust a coverage-final.json older than the Step 5 wall budget (+buffer).
|
|
103
|
+
* Prevents a stale hairline report from masking a later non-coverage check failure (#3187 Greptile P1).
|
|
104
|
+
*/
|
|
105
|
+
export declare function isCoverageReportFresh(mtimeMs: number | null | undefined, nowMs?: number, maxAgeMs?: number): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* When totals come only from on-disk coverage-final and the failure reason is thin,
|
|
108
|
+
* require a fresh report before classifying BRANCH_HAIRLINE.
|
|
109
|
+
*/
|
|
110
|
+
export declare function classifyStep5FailureWithFreshness(input: ClassifyStep5FailureInput & {
|
|
111
|
+
readonly coverageReportMtimeMs?: number | null;
|
|
112
|
+
readonly nowMs?: number;
|
|
113
|
+
}): Step5FailureClass;
|
|
114
|
+
//# sourceMappingURL=auto-hatch.d.ts.map
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release Step 5 auto-hatch for branch-only coverage hairlines (#3187 / #2866).
|
|
3
|
+
*
|
|
4
|
+
* Pure classifier + ledger helpers + debt-issue body. Network I/O is seamed so
|
|
5
|
+
* unit tests never hit live GitHub.
|
|
6
|
+
*/
|
|
7
|
+
import { COVERAGE_GOAL, metricsBelowGoal, } from "../vitest-runner/coverage-debt.js";
|
|
8
|
+
const REAL_FAILURE_PATTERNS = [
|
|
9
|
+
/\btimed?\s*out\b/i,
|
|
10
|
+
/\bhang(?:ing|ed)?\b/i,
|
|
11
|
+
/\b\d+\s+failed\b/i,
|
|
12
|
+
/\bfailed\s+tests?\b/i,
|
|
13
|
+
/\bTests?\s+failed\b/i,
|
|
14
|
+
/\bFAIL\s+\S+/i,
|
|
15
|
+
/\bAssertionError\b/,
|
|
16
|
+
/\bError:\s/i,
|
|
17
|
+
/vitest coverage hang/i,
|
|
18
|
+
];
|
|
19
|
+
const COVERAGE_THRESHOLD_PATTERNS = [
|
|
20
|
+
/Coverage for (\w+)\s*\(([\d.]+)%\)\s*does not meet/i,
|
|
21
|
+
/ERROR:\s*Coverage for (\w+)/i,
|
|
22
|
+
/coverage threshold/i,
|
|
23
|
+
/below (?:the )?(?:global )?threshold/i,
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Classify metrics alone (no output / test signals).
|
|
27
|
+
* Returns null when totals are absent.
|
|
28
|
+
*/
|
|
29
|
+
export function classifyCoverageMetrics(totals) {
|
|
30
|
+
if (!totals)
|
|
31
|
+
return null;
|
|
32
|
+
const missed = metricsBelowGoal(totals);
|
|
33
|
+
if (missed.length === 0)
|
|
34
|
+
return "ok";
|
|
35
|
+
if (missed.length === 1 && missed[0] === "branches")
|
|
36
|
+
return "branch_hairline";
|
|
37
|
+
return "other_coverage";
|
|
38
|
+
}
|
|
39
|
+
function outputSuggestsRealFailure(output) {
|
|
40
|
+
// Coverage-threshold lines alone are not real product failures.
|
|
41
|
+
const withoutCoverage = output
|
|
42
|
+
.split(/\r?\n/)
|
|
43
|
+
.filter((line) => !COVERAGE_THRESHOLD_PATTERNS.some((re) => re.test(line)))
|
|
44
|
+
.join("\n");
|
|
45
|
+
return REAL_FAILURE_PATTERNS.some((re) => re.test(withoutCoverage));
|
|
46
|
+
}
|
|
47
|
+
function outputSuggestsCoverageThreshold(output) {
|
|
48
|
+
return COVERAGE_THRESHOLD_PATTERNS.some((re) => re.test(output));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Pure classifier for release Step 5 non-zero outcomes.
|
|
52
|
+
*
|
|
53
|
+
* Prefer coverage-final totals when present; fall back to parseable output.
|
|
54
|
+
* Fail closed to UNKNOWN when evidence is ambiguous.
|
|
55
|
+
*/
|
|
56
|
+
export function classifyStep5Failure(input) {
|
|
57
|
+
const output = (input.output ?? "").trim();
|
|
58
|
+
const timedOut = input.timedOut === true || input.exitCode === 124;
|
|
59
|
+
const failedTests = typeof input.failedTests === "number" && Number.isFinite(input.failedTests)
|
|
60
|
+
? input.failedTests
|
|
61
|
+
: null;
|
|
62
|
+
if (timedOut)
|
|
63
|
+
return "REAL_FAILURE";
|
|
64
|
+
if (failedTests !== null && failedTests > 0)
|
|
65
|
+
return "REAL_FAILURE";
|
|
66
|
+
if (output && outputSuggestsRealFailure(output))
|
|
67
|
+
return "REAL_FAILURE";
|
|
68
|
+
const metricClass = classifyCoverageMetrics(input.totals ?? null);
|
|
69
|
+
if (metricClass === "branch_hairline")
|
|
70
|
+
return "BRANCH_HAIRLINE";
|
|
71
|
+
if (metricClass === "other_coverage")
|
|
72
|
+
return "OTHER_COVERAGE";
|
|
73
|
+
if (metricClass === "ok") {
|
|
74
|
+
// Coverage met the bar but Step 5 still failed → non-coverage defect.
|
|
75
|
+
return output ? "REAL_FAILURE" : "UNKNOWN";
|
|
76
|
+
}
|
|
77
|
+
// No totals — try output-only signals.
|
|
78
|
+
if (output && outputSuggestsCoverageThreshold(output)) {
|
|
79
|
+
// Without totals we cannot prove branch-only; refuse to auto-hatch.
|
|
80
|
+
return "OTHER_COVERAGE";
|
|
81
|
+
}
|
|
82
|
+
if (output)
|
|
83
|
+
return "UNKNOWN";
|
|
84
|
+
return "UNKNOWN";
|
|
85
|
+
}
|
|
86
|
+
const DEBT_MARKER_RE = /coverage[- ]debt|--allow-coverage-debt/i;
|
|
87
|
+
/** True when title/body carry the mandatory debt markers (#2866). */
|
|
88
|
+
export function issueHasCoverageDebtMarkers(issue) {
|
|
89
|
+
const blob = `${issue.title ?? ""}\n${issue.body ?? ""}`;
|
|
90
|
+
return DEBT_MARKER_RE.test(blob);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Filter issues that count as unpaid coverage debt on the ledger.
|
|
94
|
+
*
|
|
95
|
+
* Accepts:
|
|
96
|
+
* - open issues with coverage-debt / --allow-coverage-debt markers
|
|
97
|
+
* - citation-only probes (`{ number }` only) from CHANGELOG scan after state check
|
|
98
|
+
*/
|
|
99
|
+
export function filterOpenCoverageDebtIssues(issues) {
|
|
100
|
+
const open = new Set();
|
|
101
|
+
for (const issue of issues) {
|
|
102
|
+
if (!issue.number || issue.number <= 0)
|
|
103
|
+
continue;
|
|
104
|
+
const state = (issue.state ?? "OPEN").toUpperCase();
|
|
105
|
+
if (state !== "OPEN")
|
|
106
|
+
continue;
|
|
107
|
+
const citationOnly = issue.title == null && issue.body == null;
|
|
108
|
+
if (citationOnly || issueHasCoverageDebtMarkers(issue)) {
|
|
109
|
+
open.add(issue.number);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return [...open].sort((a, b) => a - b);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Parse `--allow-coverage-debt=#N` / `allow-coverage-debt=#N` citations from
|
|
116
|
+
* CHANGELOG Unreleased + the last few version sections (legacy hatch ledger).
|
|
117
|
+
*/
|
|
118
|
+
export function extractCoverageDebtCitationsFromChangelog(changelog, maxVersionSections = 3) {
|
|
119
|
+
const versionHeader = /^## \[(?!Unreleased)/m;
|
|
120
|
+
const parts = changelog.split(versionHeader);
|
|
121
|
+
// parts[0] includes ## [Unreleased] and preamble; then each version section.
|
|
122
|
+
const windows = [parts[0] ?? "", ...parts.slice(1, maxVersionSections + 1)];
|
|
123
|
+
const found = new Set();
|
|
124
|
+
const re = /allow-coverage-debt=#?(\d+)/gi;
|
|
125
|
+
for (const section of windows) {
|
|
126
|
+
let m = re.exec(section);
|
|
127
|
+
while (m) {
|
|
128
|
+
const n = Number.parseInt(m[1] ?? "", 10);
|
|
129
|
+
if (Number.isFinite(n) && n > 0)
|
|
130
|
+
found.add(n);
|
|
131
|
+
m = re.exec(section);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return [...found].sort((a, b) => a - b);
|
|
135
|
+
}
|
|
136
|
+
/** Union marker-search hits with CHANGELOG-cited open issues. */
|
|
137
|
+
export function mergeOpenDebtLedger(markerHits, changelogCitedOpen) {
|
|
138
|
+
return [...new Set([...markerHits, ...changelogCitedOpen])].sort((a, b) => a - b);
|
|
139
|
+
}
|
|
140
|
+
/** Build a marker-compliant coverage-debt issue for auto-hatch (#3187). */
|
|
141
|
+
export function buildCoverageDebtIssueDraft(options) {
|
|
142
|
+
const { version, totals } = options;
|
|
143
|
+
const auto = options.autoHatched !== false;
|
|
144
|
+
const fmt = (n) => n.toFixed(2);
|
|
145
|
+
const title = `coverage-debt: restore branch coverage after v${version} hairline (${fmt(totals.branches)}%)`;
|
|
146
|
+
const body = [
|
|
147
|
+
`## coverage-debt`,
|
|
148
|
+
"",
|
|
149
|
+
auto
|
|
150
|
+
? "Auto-filed by `task release` Step 5 auto-hatch (#3187) after a branch-only hairline."
|
|
151
|
+
: "Filed for release Step 5 coverage-debt hatch (#2866).",
|
|
152
|
+
"",
|
|
153
|
+
"### Markers",
|
|
154
|
+
"",
|
|
155
|
+
"- `coverage-debt`",
|
|
156
|
+
"- `--allow-coverage-debt`",
|
|
157
|
+
"",
|
|
158
|
+
"### Measured (at hatch)",
|
|
159
|
+
"",
|
|
160
|
+
`| Metric | Measured | Goal |`,
|
|
161
|
+
`| --- | ---: | ---: |`,
|
|
162
|
+
`| branches | ${fmt(totals.branches)}% | ${COVERAGE_GOAL.branches}% |`,
|
|
163
|
+
`| lines | ${fmt(totals.lines)}% | ${COVERAGE_GOAL.lines}% |`,
|
|
164
|
+
`| functions | ${fmt(totals.functions)}% | ${COVERAGE_GOAL.functions}% |`,
|
|
165
|
+
`| statements | ${fmt(totals.statements)}% | ${COVERAGE_GOAL.statements}% |`,
|
|
166
|
+
"",
|
|
167
|
+
`### Cut`,
|
|
168
|
+
"",
|
|
169
|
+
`- Release version: \`v${version}\``,
|
|
170
|
+
"- Trigger: **branches** sole metric below 85% (lines / functions / statements all ≥ 85%)",
|
|
171
|
+
"",
|
|
172
|
+
"### Acceptance",
|
|
173
|
+
"",
|
|
174
|
+
"- [ ] All four coverage metrics (lines, functions, branches, statements) ≥ 85%",
|
|
175
|
+
"- [ ] Close this issue only after real coverage is restored (no consecutive soft-pass while open)",
|
|
176
|
+
"",
|
|
177
|
+
"Refs #3187, #2866, #2573.",
|
|
178
|
+
].join("\n");
|
|
179
|
+
return { title, body };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Decide whether Step 5 may soft-pass after one suite run (#3187).
|
|
183
|
+
* Never continues without a durable issue number (file-before-continue).
|
|
184
|
+
*/
|
|
185
|
+
export function evaluateAutoHatch(input) {
|
|
186
|
+
if (input.classification !== "BRANCH_HAIRLINE") {
|
|
187
|
+
return {
|
|
188
|
+
kind: "fail_closed",
|
|
189
|
+
class: input.classification,
|
|
190
|
+
reason: input.classification === "REAL_FAILURE"
|
|
191
|
+
? "real failure / hang / failed tests — use file-and-merge (#2859)"
|
|
192
|
+
: input.classification === "OTHER_COVERAGE"
|
|
193
|
+
? "non-hairline coverage miss — auto-hatch refused"
|
|
194
|
+
: "cannot classify Step 5 failure (UNKNOWN) — fail closed",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (input.noAutoCoverageDebt) {
|
|
198
|
+
return {
|
|
199
|
+
kind: "fail_closed",
|
|
200
|
+
class: "BRANCH_HAIRLINE",
|
|
201
|
+
reason: "auto-hatch disabled (--no-auto-coverage-debt)",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (!input.totals) {
|
|
205
|
+
return {
|
|
206
|
+
kind: "fail_closed",
|
|
207
|
+
class: "UNKNOWN",
|
|
208
|
+
reason: "BRANCH_HAIRLINE without measurable totals — refuse auto-hatch",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (input.openDebtIssues.length > 0) {
|
|
212
|
+
const listed = input.openDebtIssues.map((n) => `#${n}`).join(", ");
|
|
213
|
+
return {
|
|
214
|
+
kind: "fail_closed",
|
|
215
|
+
class: "BRANCH_HAIRLINE",
|
|
216
|
+
reason: `open coverage-debt ledger not empty (${listed}); restore coverage and close before soft-pass`,
|
|
217
|
+
openDebtIssues: input.openDebtIssues,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (input.existingDebtIssue != null && input.existingDebtIssue > 0) {
|
|
221
|
+
// Operator already bound a debt issue; treat as acknowledged hatch without create.
|
|
222
|
+
return {
|
|
223
|
+
kind: "pass_with_debt",
|
|
224
|
+
issue: input.existingDebtIssue,
|
|
225
|
+
created: false,
|
|
226
|
+
class: "BRANCH_HAIRLINE",
|
|
227
|
+
totals: input.totals,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (!input.createIssue) {
|
|
231
|
+
return {
|
|
232
|
+
kind: "fail_closed",
|
|
233
|
+
class: "BRANCH_HAIRLINE",
|
|
234
|
+
reason: "auto-hatch requires createIssue seam to file durable debt before continue",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const issue = input.createIssue();
|
|
238
|
+
if (!Number.isFinite(issue) || issue <= 0) {
|
|
239
|
+
return {
|
|
240
|
+
kind: "fail_closed",
|
|
241
|
+
class: "BRANCH_HAIRLINE",
|
|
242
|
+
reason: "auto-hatch issue create returned invalid number — refuse continue without ledger",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
kind: "pass_with_debt",
|
|
247
|
+
issue,
|
|
248
|
+
created: true,
|
|
249
|
+
class: "BRANCH_HAIRLINE",
|
|
250
|
+
totals: input.totals,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/** Loud stderr banner when Step 5 continues via auto-hatch. */
|
|
254
|
+
export function formatAutoHatchBanner(issue, totals) {
|
|
255
|
+
return (`*** AUTO-HATCH: coverage-debt #${issue} filed; Step 5 PASS_WITH_DEBT (no suite re-run) ***\n` +
|
|
256
|
+
` branches ${totals.branches.toFixed(2)}% (sole metric < ${COVERAGE_GOAL.branches}%); ` +
|
|
257
|
+
`lines ${totals.lines.toFixed(2)}% | functions ${totals.functions.toFixed(2)}% | ` +
|
|
258
|
+
`statements ${totals.statements.toFixed(2)}% all ≥ goal\n` +
|
|
259
|
+
` Cite #${issue} in CHANGELOG / release notes. Restore all four metrics before close.\n`);
|
|
260
|
+
}
|
|
261
|
+
/** Parse exit code from the default runReleaseCheck reason string when present. */
|
|
262
|
+
export function parseExitCodeFromReason(reason) {
|
|
263
|
+
const m = /exit\s+(\d+)/i.exec(reason);
|
|
264
|
+
if (!m)
|
|
265
|
+
return null;
|
|
266
|
+
const n = Number.parseInt(m[1] ?? "", 10);
|
|
267
|
+
return Number.isFinite(n) ? n : null;
|
|
268
|
+
}
|
|
269
|
+
export function reasonLooksLikeTimeout(reason) {
|
|
270
|
+
return /timed out|timeout/i.test(reason);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Refuse to trust a coverage-final.json older than the Step 5 wall budget (+buffer).
|
|
274
|
+
* Prevents a stale hairline report from masking a later non-coverage check failure (#3187 Greptile P1).
|
|
275
|
+
*/
|
|
276
|
+
export function isCoverageReportFresh(mtimeMs, nowMs = Date.now(), maxAgeMs = 25 * 60 * 1000) {
|
|
277
|
+
if (mtimeMs == null || !Number.isFinite(mtimeMs) || mtimeMs <= 0)
|
|
278
|
+
return false;
|
|
279
|
+
const age = nowMs - mtimeMs;
|
|
280
|
+
if (age < 0)
|
|
281
|
+
return true; // clock skew: treat as fresh
|
|
282
|
+
return age <= maxAgeMs;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* When totals come only from on-disk coverage-final and the failure reason is thin,
|
|
286
|
+
* require a fresh report before classifying BRANCH_HAIRLINE.
|
|
287
|
+
*/
|
|
288
|
+
export function classifyStep5FailureWithFreshness(input) {
|
|
289
|
+
const classified = classifyStep5Failure(input);
|
|
290
|
+
if (classified !== "BRANCH_HAIRLINE")
|
|
291
|
+
return classified;
|
|
292
|
+
const mtime = input.coverageReportMtimeMs;
|
|
293
|
+
// Explicit totals injected without mtime (unit tests / seams) remain trusted.
|
|
294
|
+
if (mtime === undefined)
|
|
295
|
+
return classified;
|
|
296
|
+
if (!isCoverageReportFresh(mtime, input.nowMs)) {
|
|
297
|
+
return "UNKNOWN";
|
|
298
|
+
}
|
|
299
|
+
return classified;
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=auto-hatch.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReleaseSeams } from "./types.js";
|
|
2
|
+
export interface CoverageDebtLedgerSeams {
|
|
3
|
+
readonly spawnText?: ReleaseSeams["spawnText"];
|
|
4
|
+
readonly whichGh?: ReleaseSeams["whichGh"];
|
|
5
|
+
readonly readFile?: ReleaseSeams["readFile"];
|
|
6
|
+
readonly fileExists?: ReleaseSeams["fileExists"];
|
|
7
|
+
/** Override full ledger probe (tests). */
|
|
8
|
+
readonly listOpenDebtIssues?: (repo: string, projectRoot: string) => number[];
|
|
9
|
+
/** Override issue create (tests). */
|
|
10
|
+
readonly createDebtIssue?: (repo: string, projectRoot: string, title: string, body: string) => number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Probe open coverage-debt ledger: marker searches + CHANGELOG citation scan.
|
|
14
|
+
* Citation numbers whose state is OPEN or UNKNOWN count as unpaid (fail closed).
|
|
15
|
+
*
|
|
16
|
+
* Fail closed: gh marker-search failures throw (do not treat as empty ledger).
|
|
17
|
+
* Missing gh falls through to CHANGELOG citations only (still fail-closed on UNKNOWN).
|
|
18
|
+
*/
|
|
19
|
+
export declare function probeOpenCoverageDebtLedger(repo: string, projectRoot: string, seams?: CoverageDebtLedgerSeams): number[];
|
|
20
|
+
/** Create a coverage-debt issue; returns issue number or throws. */
|
|
21
|
+
export declare function createCoverageDebtIssue(repo: string, projectRoot: string, title: string, body: string, seams?: CoverageDebtLedgerSeams): number;
|
|
22
|
+
//# sourceMappingURL=coverage-debt-ledger.d.ts.map
|