@deftai/directive-core 0.97.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/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 +46 -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/doctor/checks.d.ts +7 -0
- package/dist/doctor/checks.js +83 -0
- package/dist/hooks/dispatcher.d.ts +5 -0
- package/dist/hooks/dispatcher.js +54 -4
- package/dist/init-deposit/hygiene.d.ts +70 -1
- package/dist/init-deposit/hygiene.js +582 -8
- 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/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/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 +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/session-start.js +21 -0
- 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-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +54 -0
- package/package.json +3 -3
package/dist/policy/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { FIELD_HOST_SKILL_DISCOVERY, FIELD_HOST_SKILL_DISCOVERY_CLI_ALIAS, inspectHostSkillDiscovery, } from "../init-deposit/skill-discovery-hosts.js";
|
|
2
2
|
import { FIELD_OPENCLAW_PRODUCT_COMMANDS, FIELD_OPENCLAW_PRODUCT_COMMANDS_CLI_ALIAS, inspectOpenClawProductCommands, } from "../slash/openclaw-deposit.js";
|
|
3
|
+
import { FIELD_CHECK_RESUME, FIELD_CHECK_RESUME_CLI_ALIAS, inspectCheckResume, } from "./check-resume.js";
|
|
4
|
+
import { FIELD_COVERAGE_DEBT, FIELD_COVERAGE_DEBT_CLI_ALIAS, inspectCoverageDebt, } from "./coverage-debt.js";
|
|
3
5
|
import { FIELD_DELIVERY_BRANCH, FIELD_DELIVERY_BRANCH_CLI_ALIAS, inspectDeliveryBranch, } from "./delivery-branch.js";
|
|
4
6
|
import { FIELD_HOST_HOOKS, FIELD_HOST_HOOKS_CLI_ALIAS, inspectHostHooks } from "./host-hooks.js";
|
|
5
7
|
import { FIELD_HOST_SLASH_COMMANDS, FIELD_HOST_SLASH_COMMANDS_CLI_ALIAS, inspectHostSlashCommands, } from "./host-slash-commands.js";
|
|
@@ -16,6 +18,9 @@ import { DEFAULT_WIP_CAP } from "./wip.js";
|
|
|
16
18
|
export * from "./agents-md-advisory.js";
|
|
17
19
|
export * from "./autonomy.js";
|
|
18
20
|
export * from "./capacity.js";
|
|
21
|
+
export * from "./check-resume.js";
|
|
22
|
+
export * from "./coverage-check-resume-presets.js";
|
|
23
|
+
export * from "./coverage-debt.js";
|
|
19
24
|
export * from "./decisions.js";
|
|
20
25
|
export * from "./deft-directive-disable.js";
|
|
21
26
|
export * from "./delivery-branch.js";
|
|
@@ -295,6 +300,24 @@ function inspectValueFeedbackField(data, projectRoot) {
|
|
|
295
300
|
source: field.source,
|
|
296
301
|
};
|
|
297
302
|
}
|
|
303
|
+
function inspectCoverageDebtField(data, projectRoot) {
|
|
304
|
+
const field = inspectCoverageDebt(data, projectRoot);
|
|
305
|
+
return {
|
|
306
|
+
name: field.name,
|
|
307
|
+
current: field.current,
|
|
308
|
+
default: field.default,
|
|
309
|
+
source: field.source,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function inspectCheckResumeField(data, projectRoot) {
|
|
313
|
+
const field = inspectCheckResume(data, projectRoot);
|
|
314
|
+
return {
|
|
315
|
+
name: field.name,
|
|
316
|
+
current: field.current,
|
|
317
|
+
default: field.default,
|
|
318
|
+
source: field.source,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
298
321
|
function inspectStalenessTicklerField(data) {
|
|
299
322
|
const field = inspectStalenessTickler(data);
|
|
300
323
|
return {
|
|
@@ -408,6 +431,8 @@ const REGISTERED_POLICIES = [
|
|
|
408
431
|
inspectRuntimeAuthorityField,
|
|
409
432
|
inspectProductSignalField,
|
|
410
433
|
inspectValueFeedbackField,
|
|
434
|
+
inspectCoverageDebtField,
|
|
435
|
+
inspectCheckResumeField,
|
|
411
436
|
inspectRequireHumanMergeField,
|
|
412
437
|
inspectHotfixCriteriaField,
|
|
413
438
|
];
|
|
@@ -422,27 +447,31 @@ export function inspectOnePolicy(name, projectRoot) {
|
|
|
422
447
|
? FIELD_VALUE_FEEDBACK
|
|
423
448
|
: name === FIELD_PRODUCT_SIGNAL_CLI_ALIAS
|
|
424
449
|
? FIELD_PRODUCT_SIGNAL
|
|
425
|
-
: name ===
|
|
426
|
-
?
|
|
427
|
-
: name ===
|
|
428
|
-
?
|
|
429
|
-
: name ===
|
|
430
|
-
?
|
|
431
|
-
: name ===
|
|
432
|
-
?
|
|
433
|
-
: name ===
|
|
434
|
-
?
|
|
435
|
-
: name ===
|
|
436
|
-
?
|
|
437
|
-
: name ===
|
|
438
|
-
?
|
|
439
|
-
: name ===
|
|
440
|
-
?
|
|
441
|
-
: name ===
|
|
442
|
-
?
|
|
443
|
-
: name ===
|
|
444
|
-
?
|
|
445
|
-
: name
|
|
450
|
+
: name === FIELD_COVERAGE_DEBT_CLI_ALIAS
|
|
451
|
+
? FIELD_COVERAGE_DEBT
|
|
452
|
+
: name === FIELD_CHECK_RESUME_CLI_ALIAS
|
|
453
|
+
? FIELD_CHECK_RESUME
|
|
454
|
+
: name === FIELD_STALENESS_TICKLER_CLI_ALIAS
|
|
455
|
+
? FIELD_STALENESS_TICKLER
|
|
456
|
+
: name === FIELD_RUNTIME_AUTHORITY_CLI_ALIAS
|
|
457
|
+
? FIELD_RUNTIME_AUTHORITY
|
|
458
|
+
: name === FIELD_HOST_HOOKS_CLI_ALIAS
|
|
459
|
+
? FIELD_HOST_HOOKS
|
|
460
|
+
: name === FIELD_HOST_SLASH_COMMANDS_CLI_ALIAS
|
|
461
|
+
? FIELD_HOST_SLASH_COMMANDS
|
|
462
|
+
: name === FIELD_OPENCLAW_PRODUCT_COMMANDS_CLI_ALIAS
|
|
463
|
+
? FIELD_OPENCLAW_PRODUCT_COMMANDS
|
|
464
|
+
: name === FIELD_HOST_SKILL_DISCOVERY_CLI_ALIAS
|
|
465
|
+
? FIELD_HOST_SKILL_DISCOVERY
|
|
466
|
+
: name === FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS
|
|
467
|
+
? FIELD_REQUIRE_HUMAN_MERGE
|
|
468
|
+
: name === FIELD_HOTFIX_CRITERIA_CLI_ALIAS
|
|
469
|
+
? FIELD_HOTFIX_CRITERIA
|
|
470
|
+
: name === FIELD_DELIVERY_BRANCH_CLI_ALIAS
|
|
471
|
+
? FIELD_DELIVERY_BRANCH
|
|
472
|
+
: name === FIELD_MIN_GREPTILE_CONFIDENCE_CLI_ALIAS
|
|
473
|
+
? FIELD_MIN_GREPTILE_CONFIDENCE
|
|
474
|
+
: name;
|
|
446
475
|
for (const field of inspectAllPolicies(projectRoot)) {
|
|
447
476
|
if (field.name === normalized)
|
|
448
477
|
return field;
|
|
@@ -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
|