@deftai/directive-core 0.66.2 → 0.68.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.
Files changed (147) hide show
  1. package/dist/agents-md-advisory/evaluate.d.ts +44 -0
  2. package/dist/agents-md-advisory/evaluate.js +115 -0
  3. package/dist/agents-md-advisory/index.d.ts +2 -0
  4. package/dist/agents-md-advisory/index.js +2 -0
  5. package/dist/agents-md-budget/evaluate.d.ts +38 -0
  6. package/dist/agents-md-budget/evaluate.js +152 -0
  7. package/dist/agents-md-budget/index.d.ts +2 -0
  8. package/dist/agents-md-budget/index.js +2 -0
  9. package/dist/check/index.d.ts +1 -1
  10. package/dist/check/index.js +1 -1
  11. package/dist/check/orchestrator.d.ts +14 -4
  12. package/dist/check/orchestrator.js +30 -6
  13. package/dist/codebase/map.d.ts +1 -0
  14. package/dist/codebase/map.js +5 -2
  15. package/dist/doctor/checks.js +11 -11
  16. package/dist/doctor/constants.d.ts +7 -2
  17. package/dist/doctor/constants.js +17 -1
  18. package/dist/doctor/flags.js +5 -1
  19. package/dist/doctor/main.js +99 -8
  20. package/dist/doctor/payload-staleness.js +15 -6
  21. package/dist/doctor/types.d.ts +7 -0
  22. package/dist/forward-coverage/evaluate.d.ts +55 -0
  23. package/dist/forward-coverage/evaluate.js +254 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/init-deposit/constants.d.ts +14 -0
  27. package/dist/init-deposit/constants.js +10 -0
  28. package/dist/init-deposit/hygiene.d.ts +50 -0
  29. package/dist/init-deposit/hygiene.js +176 -0
  30. package/dist/init-deposit/init-deposit.d.ts +1 -0
  31. package/dist/init-deposit/init-deposit.js +7 -1
  32. package/dist/init-deposit/refresh.d.ts +2 -0
  33. package/dist/init-deposit/refresh.js +48 -30
  34. package/dist/init-deposit/scaffold.d.ts +3 -4
  35. package/dist/init-deposit/scaffold.js +3 -24
  36. package/dist/intake/github-auth-modes.d.ts +1 -0
  37. package/dist/intake/github-auth-modes.js +1 -0
  38. package/dist/intake/issue-ingest.d.ts +6 -2
  39. package/dist/intake/issue-ingest.js +65 -9
  40. package/dist/integration-e2e/helpers.d.ts +0 -1
  41. package/dist/integration-e2e/helpers.js +3 -7
  42. package/dist/layout/lifecycle-ref.d.ts +15 -0
  43. package/dist/layout/lifecycle-ref.js +55 -0
  44. package/dist/platform/agents-consumer-header.d.ts +13 -0
  45. package/dist/platform/agents-consumer-header.js +57 -0
  46. package/dist/platform/agents-md.d.ts +28 -0
  47. package/dist/platform/agents-md.js +38 -1
  48. package/dist/platform/index.d.ts +1 -0
  49. package/dist/platform/index.js +1 -0
  50. package/dist/policy/agents-md-advisory.d.ts +52 -0
  51. package/dist/policy/agents-md-advisory.js +63 -0
  52. package/dist/policy/agents-md-budget.d.ts +24 -0
  53. package/dist/policy/agents-md-budget.js +89 -0
  54. package/dist/policy/index.d.ts +1 -0
  55. package/dist/policy/index.js +1 -0
  56. package/dist/policy/resolve.js +56 -46
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
  58. package/dist/pr-merge-readiness/ci-gate.js +79 -0
  59. package/dist/pr-merge-readiness/compute.d.ts +15 -1
  60. package/dist/pr-merge-readiness/compute.js +129 -9
  61. package/dist/pr-merge-readiness/gh.d.ts +8 -0
  62. package/dist/pr-merge-readiness/gh.js +30 -5
  63. package/dist/pr-merge-readiness/index.d.ts +5 -1
  64. package/dist/pr-merge-readiness/index.js +3 -0
  65. package/dist/pr-merge-readiness/main.d.ts +3 -0
  66. package/dist/pr-merge-readiness/main.js +73 -6
  67. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  68. package/dist/pr-merge-readiness/mergeability.js +105 -0
  69. package/dist/pr-merge-readiness/output.js +20 -0
  70. package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
  71. package/dist/pr-merge-readiness/slizard-gate.js +119 -0
  72. package/dist/pr-monitor/index.d.ts +1 -1
  73. package/dist/pr-monitor/index.js +1 -1
  74. package/dist/pr-monitor/monitor.d.ts +8 -2
  75. package/dist/pr-monitor/monitor.js +35 -6
  76. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  77. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  78. package/dist/pr-watch/constants.d.ts +33 -0
  79. package/dist/pr-watch/constants.js +33 -0
  80. package/dist/pr-watch/index.d.ts +6 -0
  81. package/dist/pr-watch/index.js +6 -0
  82. package/dist/pr-watch/main.d.ts +21 -0
  83. package/dist/pr-watch/main.js +206 -0
  84. package/dist/pr-watch/probe.d.ts +13 -0
  85. package/dist/pr-watch/probe.js +100 -0
  86. package/dist/pr-watch/types.d.ts +47 -0
  87. package/dist/pr-watch/types.js +2 -0
  88. package/dist/pr-watch/watch.d.ts +12 -0
  89. package/dist/pr-watch/watch.js +122 -0
  90. package/dist/preflight-cache/evaluate.d.ts +1 -0
  91. package/dist/preflight-cache/evaluate.js +17 -10
  92. package/dist/render/project-render.d.ts +16 -2
  93. package/dist/render/project-render.js +155 -97
  94. package/dist/scope/capacity-stamp.d.ts +24 -2
  95. package/dist/scope/capacity-stamp.js +80 -28
  96. package/dist/scope/decomposed-refs.js +2 -2
  97. package/dist/scope/project-definition-sync.js +3 -3
  98. package/dist/scope/vbrief-ref.d.ts +2 -1
  99. package/dist/scope/vbrief-ref.js +3 -2
  100. package/dist/spec-authority/constants.d.ts +6 -0
  101. package/dist/spec-authority/constants.js +11 -0
  102. package/dist/spec-authority/resolver.js +3 -3
  103. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  104. package/dist/swarm/finalize-cohort-cli.js +100 -0
  105. package/dist/swarm/finalize-cohort.d.ts +39 -0
  106. package/dist/swarm/finalize-cohort.js +589 -0
  107. package/dist/swarm/index.d.ts +2 -0
  108. package/dist/swarm/index.js +2 -0
  109. package/dist/swarm/launch.d.ts +13 -0
  110. package/dist/swarm/launch.js +19 -7
  111. package/dist/swarm/routing-verify.d.ts +1 -1
  112. package/dist/swarm/routing-verify.js +11 -10
  113. package/dist/swarm/routing.d.ts +9 -0
  114. package/dist/swarm/routing.js +46 -0
  115. package/dist/swarm/verify-review-clean-cli.js +25 -1
  116. package/dist/swarm/verify-review-clean.d.ts +9 -1
  117. package/dist/swarm/verify-review-clean.js +76 -3
  118. package/dist/triage/queue/index.d.ts +1 -0
  119. package/dist/triage/queue/index.js +1 -0
  120. package/dist/triage/queue/project.d.ts +6 -1
  121. package/dist/triage/queue/project.js +9 -4
  122. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  123. package/dist/triage/queue/reconcile-live-state.js +54 -0
  124. package/dist/triage/scope/mutations-core.js +91 -81
  125. package/dist/triage/scope/resolve.js +4 -3
  126. package/dist/triage/scope-drift/add-ignore.js +33 -32
  127. package/dist/triage/scope-drift/scope-rules.js +3 -3
  128. package/dist/triage/subscribe/index.js +51 -45
  129. package/dist/triage/welcome/writers.js +79 -64
  130. package/dist/vbrief-build/project-definition-io.js +8 -3
  131. package/dist/vbrief-validate/paths.js +8 -4
  132. package/dist/vbrief-validate/precutover.d.ts +1 -1
  133. package/dist/vbrief-validate/precutover.js +12 -10
  134. package/dist/verify-source/biome-config.d.ts +41 -0
  135. package/dist/verify-source/biome-config.js +97 -0
  136. package/dist/verify-source/index.d.ts +1 -0
  137. package/dist/verify-source/index.js +1 -0
  138. package/dist/xbrief-migrate/agents-header.d.ts +69 -0
  139. package/dist/xbrief-migrate/agents-header.js +179 -0
  140. package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
  141. package/dist/xbrief-migrate/fs-helpers.js +11 -0
  142. package/dist/xbrief-migrate/index.d.ts +1 -0
  143. package/dist/xbrief-migrate/index.js +1 -0
  144. package/dist/xbrief-migrate/migrate-project.js +29 -10
  145. package/package.json +15 -7
  146. package/dist/install-upgrade/index.d.ts +0 -13
  147. package/dist/install-upgrade/index.js +0 -251
@@ -0,0 +1,105 @@
1
+ /** REST `mergeable_state` value that mirrors GraphQL `mergeStateStatus: CLEAN`. */
2
+ export const MERGE_STATE_CLEAN = "clean";
3
+ /**
4
+ * Read GitHub's own mergeability verdict for a PR over REST.
5
+ *
6
+ * Injectable via `runGh` so unit tests stay hermetic (no live network).
7
+ */
8
+ export function fetchMergeability(prNumber, repo, runGh) {
9
+ const rc = runGh(["gh", "api", `repos/${repo}/pulls/${prNumber}`]);
10
+ if (rc.returncode !== 0) {
11
+ return {
12
+ mergeableState: null,
13
+ mergeable: null,
14
+ error: `gh api /pulls/${prNumber} failed: ${rc.stderr.trim()}`,
15
+ };
16
+ }
17
+ if (!rc.stdout.trim()) {
18
+ return { mergeableState: null, mergeable: null, error: "empty body from gh api /pulls/<N>" };
19
+ }
20
+ let payload;
21
+ try {
22
+ payload = JSON.parse(rc.stdout);
23
+ }
24
+ catch (exc) {
25
+ const message = exc instanceof Error ? exc.message : String(exc);
26
+ return { mergeableState: null, mergeable: null, error: `could not parse PR JSON: ${message}` };
27
+ }
28
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
29
+ return {
30
+ mergeableState: null,
31
+ mergeable: null,
32
+ error: "unexpected PR JSON shape (not a dict)",
33
+ };
34
+ }
35
+ const pr = payload;
36
+ const rawState = pr.mergeable_state;
37
+ const rawMergeable = pr.mergeable;
38
+ return {
39
+ mergeableState: typeof rawState === "string" ? rawState : null,
40
+ mergeable: typeof rawMergeable === "boolean" ? rawMergeable : null,
41
+ error: null,
42
+ };
43
+ }
44
+ /** True when GitHub itself reports the PR as CLEAN + MERGEABLE (#2260). */
45
+ export function isGithubMergeableClean(signal) {
46
+ return signal.mergeable === true && signal.mergeableState === MERGE_STATE_CLEAN;
47
+ }
48
+ /** Serialise the mergeability signal into the partial_data envelope shape. */
49
+ export function mergeabilityToDict(signal) {
50
+ return {
51
+ mergeable_state: signal.mergeableState,
52
+ mergeable: signal.mergeable,
53
+ error: signal.error,
54
+ };
55
+ }
56
+ /** True when a parsed verdict SHA does not match the current PR head SHA. */
57
+ export function verdictShaIsStale(verdict, headSha) {
58
+ if (!verdict.found || verdict.lastReviewedSha === null || !headSha) {
59
+ return false;
60
+ }
61
+ const reviewed = verdict.lastReviewedSha;
62
+ return !(headSha.startsWith(reviewed) || reviewed.startsWith(headSha));
63
+ }
64
+ /**
65
+ * Classify whether the verdict-based merge block is ONLY "soft" (#2260).
66
+ *
67
+ * A soft block means the review verdict is absent or pinned to a prior head
68
+ * SHA (rebased staleness) -- i.e. the review has not spoken about the CURRENT
69
+ * head. It is safe to reconcile a soft block against GitHub mergeability.
70
+ *
71
+ * A HARD block -- a genuine P0/P1 finding, an ERRORED review, or a low
72
+ * confidence score on the current head -- is NEVER soft; those must keep
73
+ * blocking regardless of GitHub mergeability (guardrail: do not merge a PR
74
+ * with a real P0/P1 review finding).
75
+ */
76
+ export function verdictBlockIsSoftOnly(verdict, headSha) {
77
+ // Absent: no Greptile rolling-summary comment at all.
78
+ if (!verdict.found) {
79
+ return true;
80
+ }
81
+ // informal-clean (#1543) is a present-but-noncanonical verdict with its own
82
+ // remediation path; it is out of scope for the #2260 mergeability override.
83
+ if (verdict.informalClean) {
84
+ return false;
85
+ }
86
+ // Stale: verdict pinned to a prior head SHA -> its findings pertain to old code.
87
+ if (verdictShaIsStale(verdict, headSha)) {
88
+ return true;
89
+ }
90
+ // Verdict is current (or its SHA is unparseable). A genuine current-head
91
+ // finding is a hard block and must not be overridden.
92
+ if (verdict.errored) {
93
+ return false;
94
+ }
95
+ if (verdict.confidence !== null && verdict.confidence <= 3) {
96
+ return false;
97
+ }
98
+ if (verdict.p0Count > 0 || verdict.p1Count > 0) {
99
+ return false;
100
+ }
101
+ // No genuine finding: the block is a missing-canonical-field / not-yet-posted
102
+ // wait, which GitHub mergeability may resolve.
103
+ return true;
104
+ }
105
+ //# sourceMappingURL=mergeability.js.map
@@ -53,6 +53,26 @@ export function printHuman(result) {
53
53
  lines.push(` Findings: P0=${result.verdict.p0Count} ` +
54
54
  `P1=${result.verdict.p1Count} P2=${result.verdict.p2Count}`);
55
55
  lines.push(` Errored sentinel: ${result.verdict.errored ? "True" : "False"}`);
56
+ const ciBlock = result.partialData.ci;
57
+ if (ciBlock !== null && typeof ciBlock === "object" && !Array.isArray(ciBlock)) {
58
+ const ci = ciBlock;
59
+ if (typeof ci.summary_line === "string") {
60
+ lines.push(` ${ci.summary_line}`);
61
+ }
62
+ else if (typeof ci.ready_state === "string") {
63
+ lines.push(` CI check-runs: ${ci.ready_state}`);
64
+ }
65
+ }
66
+ const slizardBlock = result.partialData.slizard;
67
+ if (slizardBlock !== null && typeof slizardBlock === "object" && !Array.isArray(slizardBlock)) {
68
+ const slizard = slizardBlock;
69
+ if (typeof slizard.summary_line === "string") {
70
+ lines.push(` ${slizard.summary_line}`);
71
+ }
72
+ else if (typeof slizard.ready_state === "string") {
73
+ lines.push(` SLizard review: ${slizard.ready_state}`);
74
+ }
75
+ }
56
76
  if (result.via === VIA_FALLBACK2 && Object.keys(result.partialData).length > 0) {
57
77
  lines.push(" Fallback2 signal:");
58
78
  for (const key of ["pr_state", "merged", "mergeable", "mergeable_state"]) {
@@ -0,0 +1,47 @@
1
+ import type { CheckRunRecord } from "./gh.js";
2
+ /**
3
+ * Dedicated gate for the SLizard second-reviewer verdict (#2189).
4
+ *
5
+ * SLizard posts a check-run whose `output.summary` carries a structured verdict:
6
+ *
7
+ * Decision: request_changes
8
+ * Merge impact: blocking
9
+ * Findings: 2 (P0: 0, P1: 1, P2: 0, P3: 0)
10
+ *
11
+ * The generic CI check-run gate (#2169) only fails closed on a check-run
12
+ * `conclusion` in the failed set, so a blocking *decision* carried on a
13
+ * non-`failure` conclusion (e.g. `neutral`) would slip through and the review
14
+ * is surfaced indistinctly among build/test checks. This gate parses the
15
+ * structured verdict and fails merge-readiness on a blocking decision.
16
+ */
17
+ /** Canonical SLizard check-run name; matching is case-insensitive substring for resilience. */
18
+ export declare const SLIZARD_CHECK_NAME = "SLizard";
19
+ export type SlizardReadyState = "ready" | "blocked" | "not_ready_yet" | "skipped";
20
+ export interface SlizardGateOptions {
21
+ readonly skipSlizard?: boolean;
22
+ }
23
+ export interface SlizardVerdict {
24
+ readonly decision: string | null;
25
+ readonly mergeImpact: string | null;
26
+ readonly p0Count: number | null;
27
+ readonly p1Count: number | null;
28
+ readonly p2Count: number | null;
29
+ }
30
+ export interface SlizardGateSummary {
31
+ readonly ready_state: SlizardReadyState;
32
+ readonly present: boolean;
33
+ readonly check_name: string | null;
34
+ readonly status: string | null;
35
+ readonly conclusion: string | null;
36
+ readonly verdict: SlizardVerdict | null;
37
+ readonly summary_line: string;
38
+ }
39
+ export interface SlizardGateResult {
40
+ readonly failures: readonly string[];
41
+ readonly summary: SlizardGateSummary;
42
+ }
43
+ export declare function isSlizardCheck(name: string): boolean;
44
+ /** Parse a SLizard check-run `output.summary` into a structured verdict. */
45
+ export declare function parseSlizardVerdict(summary: string | undefined | null): SlizardVerdict;
46
+ export declare function evaluateSlizardGate(checkRuns: readonly CheckRunRecord[], options?: SlizardGateOptions): SlizardGateResult;
47
+ //# sourceMappingURL=slizard-gate.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Dedicated gate for the SLizard second-reviewer verdict (#2189).
3
+ *
4
+ * SLizard posts a check-run whose `output.summary` carries a structured verdict:
5
+ *
6
+ * Decision: request_changes
7
+ * Merge impact: blocking
8
+ * Findings: 2 (P0: 0, P1: 1, P2: 0, P3: 0)
9
+ *
10
+ * The generic CI check-run gate (#2169) only fails closed on a check-run
11
+ * `conclusion` in the failed set, so a blocking *decision* carried on a
12
+ * non-`failure` conclusion (e.g. `neutral`) would slip through and the review
13
+ * is surfaced indistinctly among build/test checks. This gate parses the
14
+ * structured verdict and fails merge-readiness on a blocking decision.
15
+ */
16
+ /** Canonical SLizard check-run name; matching is case-insensitive substring for resilience. */
17
+ export const SLIZARD_CHECK_NAME = "SLizard";
18
+ const FAILED_CONCLUSIONS = new Set(["failure", "cancelled", "timed_out"]);
19
+ const PENDING_STATUSES = new Set(["queued", "in_progress"]);
20
+ const BLOCKING_DECISIONS = new Set(["request_changes", "changes_requested", "reject"]);
21
+ export function isSlizardCheck(name) {
22
+ return name.toLowerCase().includes("slizard");
23
+ }
24
+ function firstMatch(text, re) {
25
+ const m = re.exec(text);
26
+ return m?.[1] !== undefined ? m[1].trim() : null;
27
+ }
28
+ function countFor(text, sev) {
29
+ const m = new RegExp(`${sev}\\s*:\\s*(\\d+)`, "i").exec(text);
30
+ return m?.[1] !== undefined ? Number.parseInt(m[1], 10) : null;
31
+ }
32
+ /** Parse a SLizard check-run `output.summary` into a structured verdict. */
33
+ export function parseSlizardVerdict(summary) {
34
+ const text = summary ?? "";
35
+ const decision = firstMatch(text, /Decision\s*:\s*([A-Za-z_]+)/i);
36
+ const mergeImpact = firstMatch(text, /Merge impact\s*:\s*([A-Za-z_-]+)/i);
37
+ return {
38
+ decision: decision ? decision.toLowerCase() : null,
39
+ mergeImpact: mergeImpact ? mergeImpact.toLowerCase() : null,
40
+ p0Count: countFor(text, "P0"),
41
+ p1Count: countFor(text, "P1"),
42
+ p2Count: countFor(text, "P2"),
43
+ };
44
+ }
45
+ function skippedSummary(reason) {
46
+ return {
47
+ ready_state: "skipped",
48
+ present: false,
49
+ check_name: null,
50
+ status: null,
51
+ conclusion: null,
52
+ verdict: null,
53
+ summary_line: `SLizard review: skipped (${reason})`,
54
+ };
55
+ }
56
+ function isPending(status, conclusion) {
57
+ if (PENDING_STATUSES.has(status)) {
58
+ return true;
59
+ }
60
+ return status !== "completed" || conclusion === "none";
61
+ }
62
+ export function evaluateSlizardGate(checkRuns, options = {}) {
63
+ if (options.skipSlizard === true) {
64
+ return { failures: [], summary: skippedSummary("--skip-slizard") };
65
+ }
66
+ const run = checkRuns.find((r) => isSlizardCheck(r.name));
67
+ if (run === undefined) {
68
+ // SLizard is an optional second reviewer; its absence does not block merge.
69
+ return { failures: [], summary: skippedSummary("no SLizard check-run on this commit") };
70
+ }
71
+ const verdict = parseSlizardVerdict(run.summary);
72
+ const blockingDecision = verdict.decision !== null && BLOCKING_DECISIONS.has(verdict.decision);
73
+ const blockingImpact = verdict.mergeImpact === "blocking";
74
+ const failedConclusion = FAILED_CONCLUSIONS.has(run.conclusion);
75
+ const failures = [];
76
+ let readyState;
77
+ if (blockingDecision || blockingImpact || failedConclusion) {
78
+ readyState = "blocked";
79
+ const reasons = [];
80
+ if (verdict.decision !== null) {
81
+ reasons.push(`decision=${verdict.decision}`);
82
+ }
83
+ if (verdict.mergeImpact !== null) {
84
+ reasons.push(`merge impact=${verdict.mergeImpact}`);
85
+ }
86
+ if (failedConclusion) {
87
+ reasons.push(`conclusion=${run.conclusion}`);
88
+ }
89
+ const findings = verdict.p0Count !== null || verdict.p1Count !== null || verdict.p2Count !== null
90
+ ? ` (P0=${verdict.p0Count ?? 0} P1=${verdict.p1Count ?? 0} P2=${verdict.p2Count ?? 0})`
91
+ : "";
92
+ failures.push(`SLizard review is blocking: ${reasons.join(", ")}${findings}. ` +
93
+ "Resolve the SLizard findings or pass --skip-slizard to override (#2189).");
94
+ }
95
+ else if (isPending(run.status, run.conclusion)) {
96
+ readyState = "not_ready_yet";
97
+ failures.push(`SLizard review still in progress (${run.status}); wait for the verdict before merge (#2189).`);
98
+ }
99
+ else {
100
+ readyState = "ready";
101
+ }
102
+ const parts = [
103
+ `decision=${verdict.decision ?? "?"}`,
104
+ `impact=${verdict.mergeImpact ?? "?"}`,
105
+ ];
106
+ return {
107
+ failures,
108
+ summary: {
109
+ ready_state: readyState,
110
+ present: true,
111
+ check_name: run.name,
112
+ status: run.status,
113
+ conclusion: run.conclusion,
114
+ verdict,
115
+ summary_line: `SLizard review: ${readyState} (${parts.join(", ")})`,
116
+ },
117
+ };
118
+ }
119
+ //# sourceMappingURL=slizard-gate.js.map
@@ -1,7 +1,7 @@
1
1
  export { cadenceIntervals } from "./cadence.js";
2
2
  export { DEFAULT_CADENCE, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
3
  export { cmdPrMonitor, parseMonitorArgs, runMonitor } from "./main.js";
4
- export { formatPollStatus, isTerminalPrState, monitor, summaryLabelForExit } from "./monitor.js";
4
+ export { formatPollStatus, isTerminalPrState, mergeStateFromPayload, monitor, summaryLabelForExit, } from "./monitor.js";
5
5
  export { callReadiness } from "./readiness.js";
6
6
  export type { CallReadinessFn, CallReadinessOptions, MonitorOptions, MonitorRunResult, MonotonicClock, PollResult, SleepFn, } from "./types.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,6 @@
1
1
  export { cadenceIntervals } from "./cadence.js";
2
2
  export { DEFAULT_CADENCE, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
3
  export { cmdPrMonitor, parseMonitorArgs, runMonitor } from "./main.js";
4
- export { formatPollStatus, isTerminalPrState, monitor, summaryLabelForExit } from "./monitor.js";
4
+ export { formatPollStatus, isTerminalPrState, mergeStateFromPayload, monitor, summaryLabelForExit, } from "./monitor.js";
5
5
  export { callReadiness } from "./readiness.js";
6
6
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,12 @@
1
1
  import type { MonitorOptions, MonitorRunResult, PollResult } from "./types.js";
2
- /** One-line stderr status mirror per poll. */
3
- export declare function formatPollStatus(pollIndex: number, pollResult: PollResult): string;
2
+ /** Pull GitHub's merge-state signal out of a readiness payload, if present. */
3
+ export declare function mergeStateFromPayload(payload: Record<string, unknown>): string;
4
+ /**
5
+ * One-line stderr heartbeat per poll (#2260): elapsed, poll#, via, head,
6
+ * GitHub merge-state, CLEAN/BLOCKED, and what the poll is blocked on. Emitting
7
+ * this every poll makes a live poll visibly distinct from a hung process.
8
+ */
9
+ export declare function formatPollStatus(pollIndex: number, pollResult: PollResult, elapsedSeconds?: number): string;
4
10
  /** Detect merged / closed PR via fallback2 partial_data. */
5
11
  export declare function isTerminalPrState(payload: Record<string, unknown>): boolean;
6
12
  /** Loop readiness with adaptive cadence until CLEAN / cap / terminal. */
@@ -13,8 +13,34 @@ function defaultSleep(seconds) {
13
13
  // busy-wait fallback when no injectable sleep in production CLI path
14
14
  }
15
15
  }
16
- /** One-line stderr status mirror per poll. */
17
- export function formatPollStatus(pollIndex, pollResult) {
16
+ /** Pull GitHub's merge-state signal out of a readiness payload, if present. */
17
+ export function mergeStateFromPayload(payload) {
18
+ const partialRaw = payload.partial_data;
19
+ const partial = partialRaw !== null && typeof partialRaw === "object" && !Array.isArray(partialRaw)
20
+ ? partialRaw
21
+ : {};
22
+ const mergeabilityRaw = partial.mergeability;
23
+ if (mergeabilityRaw !== null &&
24
+ typeof mergeabilityRaw === "object" &&
25
+ !Array.isArray(mergeabilityRaw)) {
26
+ const state = mergeabilityRaw.mergeable_state;
27
+ if (typeof state === "string" && state.length > 0) {
28
+ return state;
29
+ }
30
+ }
31
+ // fallback2 surfaces mergeable_state directly on partial_data.
32
+ const flatState = partial.mergeable_state;
33
+ if (typeof flatState === "string" && flatState.length > 0) {
34
+ return flatState;
35
+ }
36
+ return "?";
37
+ }
38
+ /**
39
+ * One-line stderr heartbeat per poll (#2260): elapsed, poll#, via, head,
40
+ * GitHub merge-state, CLEAN/BLOCKED, and what the poll is blocked on. Emitting
41
+ * this every poll makes a live poll visibly distinct from a hung process.
42
+ */
43
+ export function formatPollStatus(pollIndex, pollResult, elapsedSeconds = 0) {
18
44
  const payload = pollResult.payload;
19
45
  const via = typeof payload.via === "string" ? payload.via : "?";
20
46
  const mergeReady = payload.merge_ready === true;
@@ -30,10 +56,12 @@ export function formatPollStatus(pollIndex, pollResult) {
30
56
  const failures = Array.isArray(failuresRaw) ? failuresRaw.map(String) : [];
31
57
  const firstFailure = failures[0] ?? "";
32
58
  const label = mergeReady ? "CLEAN" : "BLOCKED";
33
- let line = `[monitor_pr] poll #${pollIndex} via=${via} head=${headDisplay} ` +
34
- `${label} (${failures.length} failures)`;
59
+ const elapsed = Math.max(0, Math.round(elapsedSeconds));
60
+ const mergeState = mergeStateFromPayload(payload);
61
+ let line = `[monitor_pr] poll #${pollIndex} t=${elapsed}s via=${via} head=${headDisplay} ` +
62
+ `mergeState=${mergeState} ${label} (${failures.length} failures)`;
35
63
  if (firstFailure.length > 0) {
36
- line += ` -- ${firstFailure.slice(0, 80)}`;
64
+ line += ` -- blocked-on: ${firstFailure.slice(0, 80)}`;
37
65
  }
38
66
  return line;
39
67
  }
@@ -65,7 +93,8 @@ export function monitor(prNumber, repo, options = {}) {
65
93
  const pollResult = callReadinessFn(prNumber, repo);
66
94
  lastPayload = pollResult.payload;
67
95
  lastExit = pollResult.exitCode;
68
- process.stderr.write(`${formatPollStatus(pollIndex, pollResult)}\n`);
96
+ const elapsedAtPoll = clockFn.now() - startedAt;
97
+ process.stderr.write(`${formatPollStatus(pollIndex, pollResult, elapsedAtPoll)}\n`);
69
98
  if (pollResult.rawStderr.trim().length > 0) {
70
99
  process.stderr.write(pollResult.rawStderr);
71
100
  }
@@ -4,8 +4,19 @@ export interface CaptureExecResult {
4
4
  readonly stdout: string;
5
5
  readonly stderr: string;
6
6
  }
7
+ export interface CaptureExecOptions {
8
+ /**
9
+ * Stream the child's stderr straight to the parent's stderr (live) instead of
10
+ * capturing it. Used for long polls so the per-poll heartbeat is visible in
11
+ * real time rather than buffered until the subprocess exits (#2260). When set,
12
+ * the returned `stderr` is empty because it was not captured.
13
+ */
14
+ readonly inheritStderr?: boolean;
15
+ /** Environment for the child (defaults to `process.env`). */
16
+ readonly env?: NodeJS.ProcessEnv;
17
+ }
7
18
  /** UTF-8-safe subprocess capture via spawnSync (no shell) — mirrors #1366. */
8
- export declare function captureExec(executable: string, args: readonly string[], timeoutMs: number): CaptureExecResult;
19
+ export declare function captureExec(executable: string, args: readonly string[], timeoutMs: number, options?: CaptureExecOptions): CaptureExecResult;
9
20
  export interface RunProtectedCheckOptions {
10
21
  readonly nodeExecutable?: string;
11
22
  readonly timeout?: number;
@@ -4,13 +4,13 @@ import { fileURLToPath } from "node:url";
4
4
  import { resolveBinary } from "../scm/binary.js";
5
5
  import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
6
6
  /** UTF-8-safe subprocess capture via spawnSync (no shell) — mirrors #1366. */
7
- export function captureExec(executable, args, timeoutMs) {
7
+ export function captureExec(executable, args, timeoutMs, options = {}) {
8
8
  const result = spawnSync(executable, args, {
9
9
  encoding: "utf8",
10
10
  timeout: timeoutMs,
11
- stdio: ["ignore", "pipe", "pipe"],
11
+ stdio: ["ignore", "pipe", options.inheritStderr === true ? "inherit" : "pipe"],
12
12
  maxBuffer: SUBPROCESS_MAX_BUFFER,
13
- env: process.env,
13
+ env: options.env ?? process.env,
14
14
  });
15
15
  if (result.error !== undefined) {
16
16
  const code = result.error.code;
@@ -56,7 +56,9 @@ export function runProtectedCheck(prNumber, repo, protectedIssues, options = {})
56
56
  if (repo) {
57
57
  cmd.push("--repo", repo);
58
58
  }
59
- const result = captureExec(node, cmd, (options.timeout ?? 60) * 1000);
59
+ const result = captureExec(node, cmd, (options.timeout ?? 60) * 1000, {
60
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
61
+ });
60
62
  return [result.returncode, result.stdout, result.stderr];
61
63
  }
62
64
  /** Invoke pr-monitor CLI with --json and return (returncode, stdout, stderr). */
@@ -72,7 +74,13 @@ export function runMonitor(prNumber, repo, capMinutes, options = {}) {
72
74
  String(capMinutes),
73
75
  "--json",
74
76
  ];
75
- const result = captureExec(node, cmd, timeoutSec * 1000);
77
+ // Stream the monitor's per-poll heartbeat live (a buffered poll looks like a
78
+ // hang, #2260) and suppress Node deprecation/engine warning noise so it never
79
+ // leaks onto the captured JSON stream or the final result line (#2240-class).
80
+ const result = captureExec(node, cmd, timeoutSec * 1000, {
81
+ inheritStderr: true,
82
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
83
+ });
76
84
  return [result.returncode, result.stdout, result.stderr];
77
85
  }
78
86
  /** Invoke ``gh pr merge --squash --delete-branch --admin``. */
@@ -0,0 +1,33 @@
1
+ /**
2
+ * task pr:watch -- deterministic PR-verdict polling surface (#1056).
3
+ *
4
+ * Three-state exit contract (AC-1): mirrors scripts/preflight_branch.py (#747)
5
+ * and pr:merge-ready (#796) -- the invocation IS the wait, so an orchestrator
6
+ * cannot promise to poll and then silently forget (2026-05-11 three-strikes on
7
+ * #1051 / #1054).
8
+ */
9
+ /** CLEAN: SHA-matched, non-errored, no P0/P1, confidence > 3, CI green. */
10
+ export declare const EXIT_CLEAN = 0;
11
+ /** NEW_P0_P1: blocking findings on the CURRENT (SHA-matched) review. */
12
+ export declare const EXIT_NEW_P0_P1 = 1;
13
+ /** ERRORED | STALL | TIMEOUT | config-error all collapse to a single non-zero. */
14
+ export declare const EXIT_TERMINAL_ERROR = 2;
15
+ export declare const VERDICT_CLEAN = "CLEAN";
16
+ export declare const VERDICT_NEW_P0_P1 = "NEW_P0_P1";
17
+ export declare const VERDICT_ERRORED = "ERRORED";
18
+ export declare const VERDICT_STALL = "STALL";
19
+ export declare const VERDICT_TIMEOUT = "TIMEOUT";
20
+ /** --one-shot only: a single probe with no terminal verdict yet. */
21
+ export declare const VERDICT_PENDING = "PENDING";
22
+ /** External/config fault mid-probe (unresolvable repo/HEAD, gh unavailable). */
23
+ export declare const VERDICT_CONFIG = "CONFIG";
24
+ export declare const DEFAULT_MAX_WAIT_MINUTES = 30;
25
+ export declare const DEFAULT_POLL_SECONDS = 90;
26
+ /**
27
+ * Consecutive polls with a review PRESENT but stuck on a stale (non-HEAD)
28
+ * commit before the loop surfaces STALL instead of waiting the full cap. Keeps
29
+ * a Greptile review that never re-reviews the new HEAD from silently eating the
30
+ * whole 30-minute budget (#1039 STALL terminal).
31
+ */
32
+ export declare const DEFAULT_STALL_THRESHOLD = 3;
33
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * task pr:watch -- deterministic PR-verdict polling surface (#1056).
3
+ *
4
+ * Three-state exit contract (AC-1): mirrors scripts/preflight_branch.py (#747)
5
+ * and pr:merge-ready (#796) -- the invocation IS the wait, so an orchestrator
6
+ * cannot promise to poll and then silently forget (2026-05-11 three-strikes on
7
+ * #1051 / #1054).
8
+ */
9
+ /** CLEAN: SHA-matched, non-errored, no P0/P1, confidence > 3, CI green. */
10
+ export const EXIT_CLEAN = 0;
11
+ /** NEW_P0_P1: blocking findings on the CURRENT (SHA-matched) review. */
12
+ export const EXIT_NEW_P0_P1 = 1;
13
+ /** ERRORED | STALL | TIMEOUT | config-error all collapse to a single non-zero. */
14
+ export const EXIT_TERMINAL_ERROR = 2;
15
+ export const VERDICT_CLEAN = "CLEAN";
16
+ export const VERDICT_NEW_P0_P1 = "NEW_P0_P1";
17
+ export const VERDICT_ERRORED = "ERRORED";
18
+ export const VERDICT_STALL = "STALL";
19
+ export const VERDICT_TIMEOUT = "TIMEOUT";
20
+ /** --one-shot only: a single probe with no terminal verdict yet. */
21
+ export const VERDICT_PENDING = "PENDING";
22
+ /** External/config fault mid-probe (unresolvable repo/HEAD, gh unavailable). */
23
+ export const VERDICT_CONFIG = "CONFIG";
24
+ export const DEFAULT_MAX_WAIT_MINUTES = 30;
25
+ export const DEFAULT_POLL_SECONDS = 90;
26
+ /**
27
+ * Consecutive polls with a review PRESENT but stuck on a stale (non-HEAD)
28
+ * commit before the loop surfaces STALL instead of waiting the full cap. Keeps
29
+ * a Greptile review that never re-reviews the new HEAD from silently eating the
30
+ * whole 30-minute budget (#1039 STALL terminal).
31
+ */
32
+ export const DEFAULT_STALL_THRESHOLD = 3;
33
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,6 @@
1
+ export * from "./constants.js";
2
+ export { cmdPrWatch, emitWatchJson, type ParsedWatchArgs, parseWatchArgs, printWatchHuman, type RunWatchOptions, runWatch, watchResultToJson, } from "./main.js";
3
+ export { probeOnce } from "./probe.js";
4
+ export * from "./types.js";
5
+ export { formatWatchStatus, watch } from "./watch.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ export * from "./constants.js";
2
+ export { cmdPrWatch, emitWatchJson, parseWatchArgs, printWatchHuman, runWatch, watchResultToJson, } from "./main.js";
3
+ export { probeOnce } from "./probe.js";
4
+ export * from "./types.js";
5
+ export { formatWatchStatus, watch } from "./watch.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ import type { WatchOptions, WatchResult } from "./types.js";
2
+ export interface ParsedWatchArgs {
3
+ readonly prNumber: number | null;
4
+ readonly repo: string | null;
5
+ readonly maxWaitMinutes: number;
6
+ readonly pollSeconds: number;
7
+ readonly oneShot: boolean;
8
+ readonly emitJson: boolean;
9
+ readonly projectRoot: string | null;
10
+ readonly error?: string;
11
+ }
12
+ export declare function parseWatchArgs(argv: readonly string[]): ParsedWatchArgs;
13
+ /** AC-4 stable --json shape (same field set as the #1039 Tier-1 instrumentation line). */
14
+ export declare function watchResultToJson(result: WatchResult): Record<string, unknown>;
15
+ export declare function emitWatchJson(result: WatchResult): string;
16
+ export declare function printWatchHuman(result: WatchResult): string;
17
+ export interface RunWatchOptions extends WatchOptions {
18
+ }
19
+ export declare function runWatch(argv: readonly string[], options?: RunWatchOptions): number;
20
+ export declare function cmdPrWatch(argv: readonly string[], options?: RunWatchOptions): number;
21
+ //# sourceMappingURL=main.d.ts.map