@deftai/directive-core 0.108.0 → 0.109.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/cache/operations.js +1 -1
- package/dist/check/gate-lists.d.ts +14 -0
- package/dist/check/gate-lists.js +26 -3
- package/dist/check/session-completed-ac.d.ts +1 -1
- package/dist/check/session-completed-ac.js +1 -1
- package/dist/consumer-check-contract/evaluate.d.ts +47 -0
- package/dist/consumer-check-contract/evaluate.js +181 -15
- package/dist/delivery-attempt/evaluate.d.ts +9 -1
- package/dist/delivery-attempt/evaluate.js +69 -0
- package/dist/delivery-attempt/index.d.ts +1 -1
- package/dist/delivery-attempt/index.js +1 -1
- package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
- package/dist/deposit/live-procedure-exclusions.js +110 -0
- package/dist/deposit/live-procedure-targets.d.ts +45 -0
- package/dist/deposit/live-procedure-targets.js +274 -0
- package/dist/deposit/python-free.d.ts +6 -0
- package/dist/deposit/python-free.js +15 -0
- package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
- package/dist/deposit/rewrite-deposit-links.js +148 -0
- package/dist/deposit/run-stage-content-pack.d.ts +2 -0
- package/dist/deposit/run-stage-content-pack.js +3 -0
- package/dist/deposit/stage-content-pack.d.ts +17 -0
- package/dist/deposit/stage-content-pack.js +91 -0
- package/dist/design-critique/citation-grammar.d.ts +7 -0
- package/dist/design-critique/citation-grammar.js +1 -1
- package/dist/design-critique/completed-arc-record.d.ts +1 -1
- package/dist/design-critique/completed-arc-record.js +59 -14
- package/dist/hooks/classify/host-session-identity.d.ts +24 -11
- package/dist/hooks/classify/host-session-identity.js +46 -49
- package/dist/hooks/classify/index.d.ts +2 -2
- package/dist/hooks/classify/index.js +2 -2
- package/dist/hooks/classify/paths.d.ts +2 -0
- package/dist/hooks/classify/paths.js +8 -4
- package/dist/hooks/classify/stdin.js +69 -3
- package/dist/hooks/dest-form.d.ts +20 -1
- package/dist/hooks/dest-form.js +158 -21
- package/dist/hooks/dispatcher.d.ts +6 -2
- package/dist/hooks/dispatcher.js +258 -62
- package/dist/hooks/fixtures/cases.d.ts +6 -0
- package/dist/hooks/fixtures/cases.js +53 -0
- package/dist/hooks/git-destructive-log.d.ts +32 -0
- package/dist/hooks/git-destructive-log.js +46 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/shell-write-targets.d.ts +10 -0
- package/dist/hooks/shell-write-targets.js +274 -0
- package/dist/hooks/tools.d.ts +20 -2
- package/dist/hooks/tools.js +30 -1
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/init-deposit.js +3 -0
- package/dist/init-deposit/refresh.js +7 -0
- package/dist/init-deposit/runtime-writers.d.ts +14 -0
- package/dist/init-deposit/runtime-writers.js +33 -0
- package/dist/intake/issue-ingest.d.ts +6 -1
- package/dist/intake/issue-ingest.js +21 -2
- package/dist/intake/reconcile-issues.js +13 -13
- package/dist/lifecycle/brief-envelope.d.ts +25 -0
- package/dist/lifecycle/brief-envelope.js +42 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/evaluate.js +14 -5
- package/dist/literal-acceptance/index.d.ts +1 -1
- package/dist/literal-acceptance/index.js +1 -1
- package/dist/literal-acceptance/run.d.ts +2 -0
- package/dist/literal-acceptance/run.js +19 -1
- package/dist/orphan-active/candidate-scope.d.ts +53 -0
- package/dist/orphan-active/candidate-scope.js +157 -0
- package/dist/orphan-active/evaluate.d.ts +27 -0
- package/dist/orphan-active/evaluate.js +63 -6
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/policy/merge-approval-head.js +7 -6
- package/dist/pr-closing-keywords/gh.js +32 -9
- package/dist/pr-closing-keywords/main.d.ts +1 -0
- package/dist/pr-closing-keywords/main.js +68 -3
- package/dist/pr-closing-keywords/types.d.ts +2 -0
- package/dist/pr-merge-readiness/gh.js +32 -9
- package/dist/pr-protected-issues/gh.js +36 -9
- package/dist/pr-wait-mergeable/wrappers.js +3 -3
- package/dist/product-first-done-gate/evaluate.js +6 -2
- package/dist/product-first-done-gate/types.js +2 -0
- package/dist/release/consumer-hard-stops.d.ts +48 -0
- package/dist/release/consumer-hard-stops.js +140 -0
- package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
- package/dist/release/consumer-readiness-disclosure.js +51 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/pipeline.js +55 -0
- package/dist/release/run-consumer-readiness.d.ts +15 -0
- package/dist/release/run-consumer-readiness.js +24 -0
- package/dist/release/types.d.ts +6 -0
- package/dist/review-monitor/github-lease.js +4 -3
- package/dist/run-summary/types.d.ts +2 -2
- package/dist/scm/build-command.d.ts +2 -2
- package/dist/scm/build-command.js +2 -2
- package/dist/scm/call-shape.d.ts +25 -0
- package/dist/scm/call-shape.js +59 -0
- package/dist/scm/call.d.ts +7 -4
- package/dist/scm/call.js +41 -11
- package/dist/scm/gh-rest.d.ts +25 -12
- package/dist/scm/gh-rest.js +65 -15
- package/dist/scm/index.d.ts +2 -0
- package/dist/scm/index.js +2 -0
- package/dist/scm/spawn-status.d.ts +33 -0
- package/dist/scm/spawn-status.js +53 -0
- package/dist/scope/acceptance-evidence.d.ts +1 -1
- package/dist/scope/transition.js +4 -14
- package/dist/session/ac-pass-banking.d.ts +2 -2
- package/dist/session/ac-pass-banking.js +2 -2
- package/dist/session/host-session-owner.d.ts +53 -0
- package/dist/session/host-session-owner.js +89 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/occupancy.d.ts +21 -1
- package/dist/session/occupancy.js +70 -12
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/triage/evaluate/worktrees.js +153 -6
- package/dist/umbrella-current-shape/index.d.ts +51 -3
- package/dist/umbrella-current-shape/index.js +106 -18
- package/dist/validate-content/deposit-required.d.ts +39 -0
- package/dist/validate-content/deposit-required.js +147 -0
- package/dist/validate-content/index.d.ts +1 -0
- package/dist/validate-content/index.js +1 -0
- package/dist/validate-content/validate-links.d.ts +2 -3
- package/dist/validate-content/validate-links.js +29 -2
- package/dist/vbrief-activate/activate.d.ts +7 -2
- package/dist/vbrief-activate/activate.js +26 -13
- package/dist/verify-ac/evaluate.d.ts +9 -0
- package/dist/verify-ac/evaluate.js +32 -9
- package/dist/verify-source/deposit-closure.d.ts +23 -0
- package/dist/verify-source/deposit-closure.js +162 -0
- package/dist/verify-source/index.d.ts +2 -0
- package/dist/verify-source/index.js +2 -0
- package/dist/verify-source/semantic-single-source.d.ts +36 -0
- package/dist/verify-source/semantic-single-source.js +349 -0
- package/package.json +3 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check 4 -- consumer hard-stop census (#3900 / #3713 / #3969).
|
|
3
|
+
*
|
|
4
|
+
* Enumerates open issues by privileged label only.
|
|
5
|
+
* BLOCKER is the sole permitted consumer title classification (inbound flare).
|
|
6
|
+
* The adoption-blocker label is never derived from a title.
|
|
7
|
+
* Issue bodies are not read into the verdict.
|
|
8
|
+
*
|
|
9
|
+
* parseClosesSet composes citation-grammar classifyPosition; do not add a
|
|
10
|
+
* third position classifier (#3969).
|
|
11
|
+
*/
|
|
12
|
+
import { classifyPosition } from "../design-critique/citation-grammar.js";
|
|
13
|
+
export const CONSUMER_HARD_STOP_TITLE_RE = /^BLOCKER\b/i;
|
|
14
|
+
export const ADOPTION_BLOCKER_LABEL = "adoption-blocker";
|
|
15
|
+
export const BLOCKS_RELEASE_TAG_LABEL = "blocks-release-tag";
|
|
16
|
+
/** Maintainer-applied labels that can block a cut. Titles cannot. */
|
|
17
|
+
export const PRIVILEGED_HARD_STOP_LABELS = [
|
|
18
|
+
ADOPTION_BLOCKER_LABEL,
|
|
19
|
+
BLOCKS_RELEASE_TAG_LABEL,
|
|
20
|
+
];
|
|
21
|
+
const REMEDIATION = "Recovery: close those issues in this cut (or list them in the Unreleased Closes set). " +
|
|
22
|
+
"Census blocking classification is privileged labels (" +
|
|
23
|
+
PRIVILEGED_HARD_STOP_LABELS.join(", ") +
|
|
24
|
+
") only; BLOCKER in a title is an inbound flare and does not by itself block a cut (#3713 / #3969).";
|
|
25
|
+
function labelsOf(issue) {
|
|
26
|
+
return issue.labels.map((label) => label.trim()).filter((label) => label.length > 0);
|
|
27
|
+
}
|
|
28
|
+
function hasPrivilegedLabel(labels) {
|
|
29
|
+
return labels.some((label) => PRIVILEGED_HARD_STOP_LABELS.includes(label));
|
|
30
|
+
}
|
|
31
|
+
/** Privileged labels only. Title is recorded as a flare, never as a match. Never inspect a body field. */
|
|
32
|
+
export function classifyHardStop(issue) {
|
|
33
|
+
const viaTitle = CONSUMER_HARD_STOP_TITLE_RE.test(issue.title.trim());
|
|
34
|
+
const viaLabel = hasPrivilegedLabel(labelsOf(issue));
|
|
35
|
+
if (!viaLabel) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return { number: issue.number, title: issue.title, viaTitle, viaLabel };
|
|
39
|
+
}
|
|
40
|
+
export function enumerateConsumerHardStops(issues) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const issue of issues) {
|
|
43
|
+
const match = classifyHardStop(issue);
|
|
44
|
+
if (match !== null) {
|
|
45
|
+
out.push(match);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out.sort((a, b) => a.number - b.number);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract Closes/Fixes/Resolves #N from changelog Unreleased text.
|
|
52
|
+
* Never from issue bodies. Hits in refused markdown positions are not claims.
|
|
53
|
+
*/
|
|
54
|
+
export function parseClosesSet(changelogText) {
|
|
55
|
+
const after = changelogText.split(/^## \[Unreleased\]\s*$/m)[1] ?? "";
|
|
56
|
+
const unreleased = after.split(/^## \[/m)[0] ?? after;
|
|
57
|
+
const found = new Set();
|
|
58
|
+
const re = /\b(?:Closes|Fixes|Resolves)\s+#(\d+)/gi;
|
|
59
|
+
let match = re.exec(unreleased);
|
|
60
|
+
while (match !== null) {
|
|
61
|
+
const reason = classifyPosition(unreleased, match.index ?? 0);
|
|
62
|
+
if (reason === null) {
|
|
63
|
+
const n = Number(match[1]);
|
|
64
|
+
if (Number.isInteger(n) && n > 0) {
|
|
65
|
+
found.add(n);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
match = re.exec(unreleased);
|
|
69
|
+
}
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
72
|
+
export function evaluateConsumerHardStopCensus(options) {
|
|
73
|
+
const matches = enumerateConsumerHardStops(options.issues);
|
|
74
|
+
const shipsPast = matches.filter((entry) => !options.closesSet.has(entry.number));
|
|
75
|
+
if (matches.length === 0) {
|
|
76
|
+
return {
|
|
77
|
+
code: 0,
|
|
78
|
+
message: "Consumer hard-stops: ok -- no open privileged hard-stop labels (" +
|
|
79
|
+
PRIVILEGED_HARD_STOP_LABELS.join(", ") +
|
|
80
|
+
").",
|
|
81
|
+
stream: "stdout",
|
|
82
|
+
matches,
|
|
83
|
+
shipsPast,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (shipsPast.length === 0) {
|
|
87
|
+
return {
|
|
88
|
+
code: 0,
|
|
89
|
+
message: "Consumer hard-stops: ok -- " +
|
|
90
|
+
String(matches.length) +
|
|
91
|
+
" open hard-stop(s) are in this cut Closes set.",
|
|
92
|
+
stream: "stdout",
|
|
93
|
+
matches,
|
|
94
|
+
shipsPast,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const sample = shipsPast
|
|
98
|
+
.slice(0, 8)
|
|
99
|
+
.map((entry) => "#" + String(entry.number))
|
|
100
|
+
.join(", ");
|
|
101
|
+
return {
|
|
102
|
+
code: 1,
|
|
103
|
+
message: "Consumer hard-stops: fail -- " +
|
|
104
|
+
String(shipsPast.length) +
|
|
105
|
+
" open hard-stop(s) not in this cut Closes set: " +
|
|
106
|
+
sample +
|
|
107
|
+
". " +
|
|
108
|
+
REMEDIATION,
|
|
109
|
+
stream: "stderr",
|
|
110
|
+
matches,
|
|
111
|
+
shipsPast,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function issueFromInventoryRow(row) {
|
|
115
|
+
const number = row.number;
|
|
116
|
+
const title = row.title;
|
|
117
|
+
if (typeof number !== "number" || !Number.isInteger(number) || typeof title !== "string") {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if ("pull_request" in row) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const labelsRaw = row.labels;
|
|
124
|
+
const labels = [];
|
|
125
|
+
if (Array.isArray(labelsRaw)) {
|
|
126
|
+
for (const item of labelsRaw) {
|
|
127
|
+
if (typeof item === "string") {
|
|
128
|
+
labels.push(item);
|
|
129
|
+
}
|
|
130
|
+
else if (item !== null && typeof item === "object" && "name" in item) {
|
|
131
|
+
const name = item.name;
|
|
132
|
+
if (typeof name === "string") {
|
|
133
|
+
labels.push(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { number, title, labels };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=consumer-hard-stops.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks 5 and 6 -- operator disclosure at the release decision point (#3900).
|
|
3
|
+
*
|
|
4
|
+
* New-refusal census and net-posture are judgments. They are printed for the
|
|
5
|
+
* operator and never fail-closed, never dressed as a computed verdict.
|
|
6
|
+
*/
|
|
7
|
+
export interface RefusalHint {
|
|
8
|
+
readonly line: number;
|
|
9
|
+
readonly excerpt: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ConsumerReadinessDisclosure {
|
|
12
|
+
readonly blocking: false;
|
|
13
|
+
readonly newRefusalHints: readonly RefusalHint[];
|
|
14
|
+
readonly text: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function collectNewRefusalHints(changelogText: string): RefusalHint[];
|
|
17
|
+
export declare function formatConsumerReadinessDisclosure(changelogText: string): ConsumerReadinessDisclosure;
|
|
18
|
+
//# sourceMappingURL=consumer-readiness-disclosure.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks 5 and 6 -- operator disclosure at the release decision point (#3900).
|
|
3
|
+
*
|
|
4
|
+
* New-refusal census and net-posture are judgments. They are printed for the
|
|
5
|
+
* operator and never fail-closed, never dressed as a computed verdict.
|
|
6
|
+
*/
|
|
7
|
+
const FAIL_CLOSED_HINT = /fail-closed|fail closed|refuses? to|MUST NOT|must not|blocks? (?:the )?(?:tag|cut|release)|new refusal/i;
|
|
8
|
+
function unreleasedSection(changelogText) {
|
|
9
|
+
const rest = changelogText.split(/^## \[Unreleased\]\s*$/m)[1];
|
|
10
|
+
if (rest === undefined) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const next = rest.split(/^## \[/m)[0] ?? rest;
|
|
14
|
+
return next;
|
|
15
|
+
}
|
|
16
|
+
export function collectNewRefusalHints(changelogText) {
|
|
17
|
+
const section = unreleasedSection(changelogText);
|
|
18
|
+
const hints = [];
|
|
19
|
+
const lines = section.split("\n");
|
|
20
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
21
|
+
const line = lines[i] ?? "";
|
|
22
|
+
if (FAIL_CLOSED_HINT.test(line)) {
|
|
23
|
+
hints.push({ line: i + 1, excerpt: line.trim().slice(0, 160) });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return hints;
|
|
27
|
+
}
|
|
28
|
+
export function formatConsumerReadinessDisclosure(changelogText) {
|
|
29
|
+
const hints = collectNewRefusalHints(changelogText);
|
|
30
|
+
const lines = [
|
|
31
|
+
"Consumer-readiness disclosure (does not block the tag) -- #3900 checks 5 and 6.",
|
|
32
|
+
"",
|
|
33
|
+
"Check 5 -- new-refusal census (operator judgment):",
|
|
34
|
+
];
|
|
35
|
+
if (hints.length === 0) {
|
|
36
|
+
lines.push(" No Unreleased lines matched fail-closed / new-refusal hints.");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
lines.push(" Unreleased lines that may introduce a consumer-visible refusal:");
|
|
40
|
+
for (const hint of hints.slice(0, 20)) {
|
|
41
|
+
lines.push(" - " + hint.excerpt);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
lines.push(" Each new refusal needs remediation text and an escape hatch. This is not a computed verdict.");
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("Check 6 -- net-posture (operator judgment):");
|
|
47
|
+
lines.push(" Does this candidate fix more consumer-visible breakage than it introduces, and on which surfaces?");
|
|
48
|
+
lines.push(" Holding a release is only correct when that answer is negative. The operator decides.");
|
|
49
|
+
return { blocking: false, newRefusalHints: hints, text: lines.join("\n") };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=consumer-readiness-disclosure.js.map
|
package/dist/release/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from "./build-dist.js";
|
|
|
3
3
|
export * from "./cli-drift-report.js";
|
|
4
4
|
export * from "./closed-verb-gate.js";
|
|
5
5
|
export * from "./constants.js";
|
|
6
|
+
export * from "./consumer-hard-stops.js";
|
|
7
|
+
export * from "./consumer-readiness-disclosure.js";
|
|
6
8
|
export * from "./coverage-debt-ledger.js";
|
|
7
9
|
export * from "./flags.js";
|
|
8
10
|
export * from "./gh.js";
|
package/dist/release/index.js
CHANGED
|
@@ -4,6 +4,8 @@ export * from "./build-dist.js";
|
|
|
4
4
|
export * from "./cli-drift-report.js";
|
|
5
5
|
export * from "./closed-verb-gate.js";
|
|
6
6
|
export * from "./constants.js";
|
|
7
|
+
export * from "./consumer-hard-stops.js";
|
|
8
|
+
export * from "./consumer-readiness-disclosure.js";
|
|
7
9
|
export * from "./coverage-debt-ledger.js";
|
|
8
10
|
export * from "./flags.js";
|
|
9
11
|
export * from "./gh.js";
|
package/dist/release/pipeline.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
4
|
+
import { restIssueListOpenInventory } from "../scm/gh-rest.js";
|
|
4
5
|
import { readCoverageTotalsFromReport } from "../vitest-runner/coverage-debt.js";
|
|
5
6
|
import { buildCoverageDebtIssueDraft, classifyStep5FailureWithFreshness, evaluateAutoHatch, formatAutoHatchBanner, parseExitCodeFromReason, reasonLooksLikeTimeout, } from "./auto-hatch.js";
|
|
6
7
|
import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./changelog.js";
|
|
7
8
|
import { emitCliDriftReportBestEffort, shouldSkipRegistryPoll, } from "./cli-drift-report.js";
|
|
8
9
|
import { assertTagPushClosedVerb } from "./closed-verb-gate.js";
|
|
9
10
|
import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, RELEASE_CHECK_TIMEOUT_MINUTES, TOTAL_STEPS, VERIFY_DRAFT_INTERVAL_SECONDS, VERIFY_DRAFT_MAX_ATTEMPTS, } from "./constants.js";
|
|
11
|
+
import { formatConsumerReadinessDisclosure } from "./consumer-readiness-disclosure.js";
|
|
10
12
|
import { createCoverageDebtIssue, probeOpenCoverageDebtLedger } from "./coverage-debt-ledger.js";
|
|
11
13
|
import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
|
|
12
14
|
import { checkGitClean, commitReleaseArtifacts, createTag, currentBranch, pushRelease, releaseCommitSubject, runGit, } from "./git.js";
|
|
13
15
|
import { checkVbriefLifecycleSyncNative, refreshRoadmapNative, runBuildNative, } from "./native-steps.js";
|
|
14
16
|
import { todayIso } from "./paths.js";
|
|
15
17
|
import { runReleaseCheck } from "./preflight.js";
|
|
18
|
+
import { evaluateReleaseConsumerReadiness, issuesFromInventory } from "./run-consumer-readiness.js";
|
|
16
19
|
import { formatSkipCiIncidentWarning } from "./skip-ci-incident.js";
|
|
17
20
|
import { evaluateSuiteStamp, writeSuiteStamp } from "./suite-stamp.js";
|
|
18
21
|
import { isPrereleaseTag } from "./version.js";
|
|
@@ -290,6 +293,58 @@ export function runPipeline(config, seams = {}) {
|
|
|
290
293
|
}
|
|
291
294
|
}
|
|
292
295
|
}
|
|
296
|
+
// #3900: check 4 fail-closed (title/label census); checks 5-6 disclosure never block.
|
|
297
|
+
{
|
|
298
|
+
const crLabel = "Consumer-readiness census";
|
|
299
|
+
let changelogText = "";
|
|
300
|
+
try {
|
|
301
|
+
changelogText = readFile(changelogPath);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
changelogText = "";
|
|
305
|
+
}
|
|
306
|
+
const disclosure = formatConsumerReadinessDisclosure(changelogText);
|
|
307
|
+
if (config.dryRun) {
|
|
308
|
+
emit(5, crLabel, "DRYRUN (would enumerate open privileged hard-stop labels; disclosure does not block)");
|
|
309
|
+
process.stderr.write(disclosure.text + "\n");
|
|
310
|
+
}
|
|
311
|
+
else if (seams.consumerHardStops) {
|
|
312
|
+
const [ok, msg] = seams.consumerHardStops(config.repo, projectRoot);
|
|
313
|
+
if (!ok) {
|
|
314
|
+
emit(5, crLabel, "FAIL (" + msg + ")");
|
|
315
|
+
process.stderr.write(disclosure.text + "\n");
|
|
316
|
+
return EXIT_VIOLATION;
|
|
317
|
+
}
|
|
318
|
+
emit(5, crLabel, "OK (" + msg + ")");
|
|
319
|
+
process.stderr.write(disclosure.text + "\n");
|
|
320
|
+
}
|
|
321
|
+
else if (seams.runCi !== undefined || seams.spawnText !== undefined) {
|
|
322
|
+
emit(5, crLabel, "SKIP (injected seam; run task verify:consumer-hard-stops at a real cut)");
|
|
323
|
+
process.stderr.write(disclosure.text + "\n");
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
try {
|
|
327
|
+
const rows = restIssueListOpenInventory(config.repo);
|
|
328
|
+
const result = evaluateReleaseConsumerReadiness({
|
|
329
|
+
changelogText,
|
|
330
|
+
issues: issuesFromInventory(rows),
|
|
331
|
+
});
|
|
332
|
+
process.stderr.write(result.disclosure.text + "\n");
|
|
333
|
+
if (result.hardStops.code !== 0) {
|
|
334
|
+
emit(5, crLabel, "FAIL (" + result.hardStops.message + ")");
|
|
335
|
+
return result.hardStops.code === 2 ? EXIT_CONFIG_ERROR : EXIT_VIOLATION;
|
|
336
|
+
}
|
|
337
|
+
emit(5, crLabel, "OK (" + result.hardStops.message + ")");
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
341
|
+
emit(5, crLabel, "FAIL (could not list open issues. Recovery: check gh auth and REST quota. " +
|
|
342
|
+
reason +
|
|
343
|
+
")");
|
|
344
|
+
return EXIT_CONFIG_ERROR;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
293
348
|
// Step 6: CHANGELOG promotion.
|
|
294
349
|
label = "CHANGELOG promotion";
|
|
295
350
|
if (!fileExists(changelogPath)) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release pre-flight consumer-readiness: check 4 fail-closed, checks 5-6 disclosure (#3900).
|
|
3
|
+
*/
|
|
4
|
+
import { evaluateConsumerHardStopCensus, type HardStopIssue } from "./consumer-hard-stops.js";
|
|
5
|
+
import { formatConsumerReadinessDisclosure } from "./consumer-readiness-disclosure.js";
|
|
6
|
+
export interface ConsumerReadinessInput {
|
|
7
|
+
readonly changelogText: string;
|
|
8
|
+
readonly issues: readonly HardStopIssue[];
|
|
9
|
+
}
|
|
10
|
+
export declare function evaluateReleaseConsumerReadiness(input: ConsumerReadinessInput): {
|
|
11
|
+
readonly hardStops: ReturnType<typeof evaluateConsumerHardStopCensus>;
|
|
12
|
+
readonly disclosure: ReturnType<typeof formatConsumerReadinessDisclosure>;
|
|
13
|
+
};
|
|
14
|
+
export declare function issuesFromInventory(rows: readonly Record<string, unknown>[]): HardStopIssue[];
|
|
15
|
+
//# sourceMappingURL=run-consumer-readiness.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release pre-flight consumer-readiness: check 4 fail-closed, checks 5-6 disclosure (#3900).
|
|
3
|
+
*/
|
|
4
|
+
import { evaluateConsumerHardStopCensus, issueFromInventoryRow, parseClosesSet, } from "./consumer-hard-stops.js";
|
|
5
|
+
import { formatConsumerReadinessDisclosure } from "./consumer-readiness-disclosure.js";
|
|
6
|
+
export function evaluateReleaseConsumerReadiness(input) {
|
|
7
|
+
return {
|
|
8
|
+
hardStops: evaluateConsumerHardStopCensus({
|
|
9
|
+
issues: input.issues,
|
|
10
|
+
closesSet: parseClosesSet(input.changelogText),
|
|
11
|
+
}),
|
|
12
|
+
disclosure: formatConsumerReadinessDisclosure(input.changelogText),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function issuesFromInventory(rows) {
|
|
16
|
+
const issues = [];
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const issue = issueFromInventoryRow(row);
|
|
19
|
+
if (issue !== null)
|
|
20
|
+
issues.push(issue);
|
|
21
|
+
}
|
|
22
|
+
return issues;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=run-consumer-readiness.js.map
|
package/dist/release/types.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface ReleaseSeams {
|
|
|
54
54
|
readonly writeFile?: (path: string, content: string) => void;
|
|
55
55
|
readonly readFile?: (path: string) => string;
|
|
56
56
|
readonly fileExists?: (path: string) => boolean;
|
|
57
|
+
/**
|
|
58
|
+
* #3900 check 4 -- consumer hard-stop census. When omitted, production lists
|
|
59
|
+
* open issues via REST. Tests inject a [ok, message] tuple so they do not
|
|
60
|
+
* hit GitHub.
|
|
61
|
+
*/
|
|
62
|
+
readonly consumerHardStops?: (repo: string, projectRoot: string) => [boolean, string];
|
|
57
63
|
readonly runCi?: (projectRoot: string, allowCoverageDebtIssue: number | null) => [boolean, string];
|
|
58
64
|
readonly refreshRoadmap?: (projectRoot: string) => [boolean, string];
|
|
59
65
|
readonly checkVbriefLifecycleSync?: (projectRoot: string, repo: string) => [boolean, number, string];
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveBinaryForArgv } from "../scm/call-shape.js";
|
|
3
3
|
import { DEFAULT_TIMEOUT_S, restDeleteComment, restGetUser, restPostComment, restUpdateComment, } from "../scm/gh-rest.js";
|
|
4
4
|
import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
|
|
5
5
|
import { isMaintainerAuthored, parseCommentsFromGhStdout, } from "../umbrella-current-shape/index.js";
|
|
6
6
|
import { DEFAULT_PASS_STALE_MINUTES, PASS_MARKER_KIND } from "./constants.js";
|
|
7
7
|
import { computeExpiresAt, findActivePassComment, mapCommentEntry, mapPassCommentEntry, renderPassOpenComment, } from "./lease-comment.js";
|
|
8
8
|
function defaultFetchComments(repo, pr, seams) {
|
|
9
|
-
const binary = resolveBinary(seams.whichFn);
|
|
10
9
|
const path = `repos/${repo}/issues/${pr}/comments?per_page=100`;
|
|
11
|
-
const
|
|
10
|
+
const apiArgs = ["--paginate", path];
|
|
11
|
+
const binary = resolveBinaryForArgv("api", apiArgs, seams.whichFn);
|
|
12
|
+
const proc = spawnSync(binary, ["api", ...apiArgs], {
|
|
12
13
|
encoding: "utf8",
|
|
13
14
|
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
14
15
|
timeout: DEFAULT_TIMEOUT_S * 1000,
|
|
@@ -126,8 +126,8 @@ export interface AcceptanceRunSummaryPayload {
|
|
|
126
126
|
readonly none_stated?: boolean;
|
|
127
127
|
readonly clause_count?: number;
|
|
128
128
|
readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
|
|
129
|
-
/** bank | cache | executed — how verify:ac obtained this result (#3387). */
|
|
130
|
-
readonly served_from?: "bank" | "cache" | "executed";
|
|
129
|
+
/** bank | cache | executed | refused — how verify:ac obtained this result (#3387 / #3615). */
|
|
130
|
+
readonly served_from?: "bank" | "cache" | "executed" | "refused";
|
|
131
131
|
/** Config-error cause when outcome is config-error (#3559). */
|
|
132
132
|
readonly cause?: string;
|
|
133
133
|
/** Reuse-gate miss cause when served_from is executed (#3558). */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveBinaryForArgv } from "./call-shape.js";
|
|
2
2
|
export interface BuildCommandOptions {
|
|
3
3
|
readonly binary?: string;
|
|
4
|
-
readonly whichFn?: Parameters<typeof
|
|
4
|
+
readonly whichFn?: Parameters<typeof resolveBinaryForArgv>[2];
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Construct the underlying `[binary, namespace, verb, *extra]` argv.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveBinaryForArgv } from "./call-shape.js";
|
|
2
2
|
import { ALLOWED_ISSUE_VERBS, ALLOWED_NAMESPACES } from "./constants.js";
|
|
3
3
|
import { ScmStubError } from "./errors.js";
|
|
4
4
|
import { pyRepr, pyTuple } from "./py-format.js";
|
|
@@ -17,7 +17,7 @@ export function buildCommand(namespace, verb, extra, options = {}) {
|
|
|
17
17
|
`${pyTuple(ALLOWED_ISSUE_VERBS)}. The v1 stub only exposes these four; ` +
|
|
18
18
|
"additional scm:issue:* commands belong on #881.");
|
|
19
19
|
}
|
|
20
|
-
const resolved = options.binary ??
|
|
20
|
+
const resolved = options.binary ?? resolveBinaryForArgv(namespace, [verb, ...extra], options.whichFn);
|
|
21
21
|
return [resolved, namespace, verb, ...extra];
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=build-command.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type WhichFn } from "./binary.js";
|
|
2
|
+
/** Surfaces ghx may serve versus surfaces that must use live gh (#3737). */
|
|
3
|
+
export type ScmBinaryRole = "cached-get" | "live-gh";
|
|
4
|
+
/**
|
|
5
|
+
* Classify an SCM argv for ghx eligibility.
|
|
6
|
+
*
|
|
7
|
+
* ghx is a cached read-only GET proxy that accepts a single positional path.
|
|
8
|
+
* Extra positionals are rejected (`accepts 1 arg(s), received 2`). Flags
|
|
9
|
+
* (`--method`, `--paginate`, `--jq`, `--input`, `-X`, ...) are not the cache
|
|
10
|
+
* shape. Non-`api` verbs are live `gh`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function classifyScmArgv(verb: string, args?: readonly string[]): ScmBinaryRole;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a binary for a classified role.
|
|
15
|
+
*
|
|
16
|
+
* cached-get uses the PATH ladder (`resolveBinary`, ghx then gh).
|
|
17
|
+
* live-gh pins `gh` — the same preference as `resolveLiveGh`,
|
|
18
|
+
* `resolveAuthProbeBinary`, and `GH_ONLY_WHICH`. If `gh` is absent, throw
|
|
19
|
+
* rather than fall through to ghx (a mutation or flag-rich GET is not a
|
|
20
|
+
* cache-proxy shape).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveBinaryForRole(role: ScmBinaryRole, whichFn?: WhichFn): string;
|
|
23
|
+
/** Classify argv then resolve. Compose with `resolveBinary`; do not probe health. */
|
|
24
|
+
export declare function resolveBinaryForArgv(verb: string, args?: readonly string[], whichFn?: WhichFn): string;
|
|
25
|
+
//# sourceMappingURL=call-shape.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defaultWhich, resolveBinary } from "./binary.js";
|
|
2
|
+
import { ScmStubError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Classify an SCM argv for ghx eligibility.
|
|
5
|
+
*
|
|
6
|
+
* ghx is a cached read-only GET proxy that accepts a single positional path.
|
|
7
|
+
* Extra positionals are rejected (`accepts 1 arg(s), received 2`). Flags
|
|
8
|
+
* (`--method`, `--paginate`, `--jq`, `--input`, `-X`, ...) are not the cache
|
|
9
|
+
* shape. Non-`api` verbs are live `gh`.
|
|
10
|
+
*/
|
|
11
|
+
export function classifyScmArgv(verb, args = []) {
|
|
12
|
+
if (verb !== "api") {
|
|
13
|
+
return "live-gh";
|
|
14
|
+
}
|
|
15
|
+
const positionals = [];
|
|
16
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
17
|
+
const token = args[i];
|
|
18
|
+
if (token === undefined) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (token === "--") {
|
|
22
|
+
positionals.push(...args.slice(i + 1));
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
if (token.startsWith("-")) {
|
|
26
|
+
return "live-gh";
|
|
27
|
+
}
|
|
28
|
+
positionals.push(token);
|
|
29
|
+
}
|
|
30
|
+
if (positionals.length !== 1) {
|
|
31
|
+
return "live-gh";
|
|
32
|
+
}
|
|
33
|
+
return "cached-get";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a binary for a classified role.
|
|
37
|
+
*
|
|
38
|
+
* cached-get uses the PATH ladder (`resolveBinary`, ghx then gh).
|
|
39
|
+
* live-gh pins `gh` — the same preference as `resolveLiveGh`,
|
|
40
|
+
* `resolveAuthProbeBinary`, and `GH_ONLY_WHICH`. If `gh` is absent, throw
|
|
41
|
+
* rather than fall through to ghx (a mutation or flag-rich GET is not a
|
|
42
|
+
* cache-proxy shape).
|
|
43
|
+
*/
|
|
44
|
+
export function resolveBinaryForRole(role, whichFn = defaultWhich) {
|
|
45
|
+
if (role === "live-gh") {
|
|
46
|
+
if (whichFn("gh") !== null) {
|
|
47
|
+
return "gh";
|
|
48
|
+
}
|
|
49
|
+
throw new ScmStubError("gh not found on PATH; this call shape requires live gh, not the ghx cache proxy. " +
|
|
50
|
+
"install GitHub CLI (https://cli.github.com/) or pass GH_TOKEN into a matched env and re-run. " +
|
|
51
|
+
"Refs #3737 / #2275.");
|
|
52
|
+
}
|
|
53
|
+
return resolveBinary(whichFn);
|
|
54
|
+
}
|
|
55
|
+
/** Classify argv then resolve. Compose with `resolveBinary`; do not probe health. */
|
|
56
|
+
export function resolveBinaryForArgv(verb, args = [], whichFn = defaultWhich) {
|
|
57
|
+
return resolveBinaryForRole(classifyScmArgv(verb, args), whichFn);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=call-shape.js.map
|
package/dist/scm/call.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type WhichFn } from "./binary.js";
|
|
2
2
|
/** Mirrors Python `subprocess.CompletedProcess`. */
|
|
3
3
|
export interface CompletedProcess {
|
|
4
4
|
readonly args: readonly string[];
|
|
@@ -13,7 +13,7 @@ export interface CallOptions {
|
|
|
13
13
|
readonly timeout?: number;
|
|
14
14
|
readonly cwd?: string;
|
|
15
15
|
readonly binary?: string;
|
|
16
|
-
readonly whichFn?:
|
|
16
|
+
readonly whichFn?: WhichFn;
|
|
17
17
|
readonly env?: NodeJS.ProcessEnv;
|
|
18
18
|
readonly input?: string;
|
|
19
19
|
}
|
|
@@ -21,10 +21,13 @@ export interface CallOptions {
|
|
|
21
21
|
* Source-aware SCM invocation -- partial down-payment on #445 / #935 Workstream 6.
|
|
22
22
|
* Mirrors `scripts/scm.py::call`.
|
|
23
23
|
*
|
|
24
|
-
* Binary absence still throws ScmStubError via
|
|
25
|
-
* diagnostic. Full auth readiness is enforced at SCM CLI entry points
|
|
24
|
+
* Binary absence still throws ScmStubError via resolveBinaryForRole with the
|
|
25
|
+
* #2275 diagnostic. Full auth readiness is enforced at SCM CLI entry points
|
|
26
26
|
* (`scm/main`, issue-ingest, reconcile-issues) via requireScmReady — not on
|
|
27
27
|
* every call() — so unit tests that inject binary/seams stay hermetic.
|
|
28
|
+
*
|
|
29
|
+
* Call-shape selection (#3737): ghx only for single-path GET. An explicit
|
|
30
|
+
* `binary` override skips selection and spawn-failure fallback.
|
|
28
31
|
*/
|
|
29
32
|
export declare function call(source: string, verb: string, args?: readonly string[] | null, options?: CallOptions): CompletedProcess;
|
|
30
33
|
//# sourceMappingURL=call.d.ts.map
|
package/dist/scm/call.js
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { resolveCaptureFailureStderr, SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
|
|
3
|
-
import {
|
|
3
|
+
import { defaultWhich } from "./binary.js";
|
|
4
|
+
import { classifyScmArgv, resolveBinaryForRole } from "./call-shape.js";
|
|
4
5
|
import { SUPPORTED_CALL_SOURCES } from "./constants.js";
|
|
5
6
|
import { pyRepr } from "./py-format.js";
|
|
7
|
+
import { formatScmSpawnDiagnostic, isAvailabilitySpawnFailure } from "./spawn-status.js";
|
|
6
8
|
/**
|
|
7
9
|
* Source-aware SCM invocation -- partial down-payment on #445 / #935 Workstream 6.
|
|
8
10
|
* Mirrors `scripts/scm.py::call`.
|
|
9
11
|
*
|
|
10
|
-
* Binary absence still throws ScmStubError via
|
|
11
|
-
* diagnostic. Full auth readiness is enforced at SCM CLI entry points
|
|
12
|
+
* Binary absence still throws ScmStubError via resolveBinaryForRole with the
|
|
13
|
+
* #2275 diagnostic. Full auth readiness is enforced at SCM CLI entry points
|
|
12
14
|
* (`scm/main`, issue-ingest, reconcile-issues) via requireScmReady — not on
|
|
13
15
|
* every call() — so unit tests that inject binary/seams stay hermetic.
|
|
16
|
+
*
|
|
17
|
+
* Call-shape selection (#3737): ghx only for single-path GET. An explicit
|
|
18
|
+
* `binary` override skips selection and spawn-failure fallback.
|
|
14
19
|
*/
|
|
15
20
|
export function call(source, verb, args = null, options = {}) {
|
|
16
21
|
if (!SUPPORTED_CALL_SOURCES.includes(source)) {
|
|
17
22
|
throw new Error(`source=${pyRepr(source)} not yet supported; ` +
|
|
18
23
|
"see #445 / #935 Workstream 6 for the abstraction.");
|
|
19
24
|
}
|
|
20
|
-
const
|
|
21
|
-
const
|
|
25
|
+
const whichFn = options.whichFn ?? defaultWhich;
|
|
26
|
+
const role = classifyScmArgv(verb, args ?? []);
|
|
27
|
+
const explicitBinary = options.binary;
|
|
28
|
+
let resolved = explicitBinary ?? resolveBinaryForRole(role, whichFn);
|
|
29
|
+
const extra = args ?? [];
|
|
22
30
|
const captureOutput = options.captureOutput ?? true;
|
|
23
31
|
const timeoutMs = options.timeout !== undefined ? Math.round(options.timeout * 1000) : undefined;
|
|
24
|
-
const
|
|
32
|
+
const spawnOnce = (bin) => spawnSync(bin, [verb, ...extra], {
|
|
25
33
|
cwd: options.cwd,
|
|
26
34
|
env: options.env ?? process.env,
|
|
27
35
|
input: options.input,
|
|
@@ -30,14 +38,36 @@ export function call(source, verb, args = null, options = {}) {
|
|
|
30
38
|
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
31
39
|
stdio: captureOutput ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
32
40
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
let result = spawnOnce(resolved);
|
|
42
|
+
if (explicitBinary === undefined &&
|
|
43
|
+
role === "cached-get" &&
|
|
44
|
+
resolved === "ghx" &&
|
|
45
|
+
isAvailabilitySpawnFailure({
|
|
46
|
+
status: result.status,
|
|
47
|
+
error: result.error,
|
|
48
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
49
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
50
|
+
}) &&
|
|
51
|
+
whichFn("gh") !== null) {
|
|
52
|
+
resolved = "gh";
|
|
53
|
+
result = spawnOnce(resolved);
|
|
54
|
+
}
|
|
55
|
+
const argv = [resolved, verb, ...extra];
|
|
56
|
+
const captured = typeof result.stderr === "string" ? result.stderr : "";
|
|
57
|
+
let stderr = resolveCaptureFailureStderr({
|
|
58
|
+
captured,
|
|
38
59
|
status: result.status,
|
|
39
60
|
message: result.error?.message,
|
|
40
61
|
});
|
|
62
|
+
if (isAvailabilitySpawnFailure({
|
|
63
|
+
status: result.status,
|
|
64
|
+
error: result.error,
|
|
65
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
66
|
+
stderr: captured,
|
|
67
|
+
}) &&
|
|
68
|
+
stderr.trim().length === 0) {
|
|
69
|
+
stderr = formatScmSpawnDiagnostic(resolved, result.status, stderr, result.error);
|
|
70
|
+
}
|
|
41
71
|
if (options.check && result.status !== 0) {
|
|
42
72
|
const error = new Error(stderr || `Process exited with code ${result.status}`);
|
|
43
73
|
throw error;
|