@deftai/directive-core 0.96.0 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/authz/classify.js +291 -0
  2. package/dist/cache/archive.js +10 -4
  3. package/dist/check/cached-orchestrator.d.ts +5 -0
  4. package/dist/check/cached-orchestrator.js +18 -1
  5. package/dist/check/gate-lists.d.ts +20 -0
  6. package/dist/check/gate-lists.js +54 -9
  7. package/dist/check/index.d.ts +1 -1
  8. package/dist/check/index.js +1 -1
  9. package/dist/check/orchestrator.d.ts +4 -0
  10. package/dist/check/orchestrator.js +4 -0
  11. package/dist/consumer-check-contract/evaluate.d.ts +124 -0
  12. package/dist/consumer-check-contract/evaluate.js +699 -0
  13. package/dist/consumer-check-contract/index.d.ts +5 -0
  14. package/dist/consumer-check-contract/index.js +5 -0
  15. package/dist/delivery-attempt/disk-begin.d.ts +51 -0
  16. package/dist/delivery-attempt/disk-begin.js +68 -0
  17. package/dist/delivery-attempt/evaluate.d.ts +26 -0
  18. package/dist/delivery-attempt/evaluate.js +443 -0
  19. package/dist/delivery-attempt/fingerprint.d.ts +32 -0
  20. package/dist/delivery-attempt/fingerprint.js +100 -0
  21. package/dist/delivery-attempt/handoff.d.ts +25 -0
  22. package/dist/delivery-attempt/handoff.js +102 -0
  23. package/dist/delivery-attempt/index.d.ts +17 -0
  24. package/dist/delivery-attempt/index.js +17 -0
  25. package/dist/delivery-attempt/ledger.d.ts +169 -0
  26. package/dist/delivery-attempt/ledger.js +758 -0
  27. package/dist/delivery-attempt/material-delta.d.ts +38 -0
  28. package/dist/delivery-attempt/material-delta.js +126 -0
  29. package/dist/delivery-attempt/types.d.ts +210 -0
  30. package/dist/delivery-attempt/types.js +77 -0
  31. package/dist/doctor/checks.d.ts +7 -0
  32. package/dist/doctor/checks.js +83 -0
  33. package/dist/doctor/index.d.ts +1 -0
  34. package/dist/doctor/index.js +1 -0
  35. package/dist/doctor/main.js +12 -0
  36. package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
  37. package/dist/doctor/openclaw-soft-rebind.js +164 -0
  38. package/dist/hooks/dispatcher.d.ts +7 -1
  39. package/dist/hooks/dispatcher.js +117 -13
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.js +4 -0
  42. package/dist/init-deposit/gitignore.js +7 -0
  43. package/dist/init-deposit/hygiene.d.ts +70 -1
  44. package/dist/init-deposit/hygiene.js +582 -8
  45. package/dist/init-deposit/init-deposit.js +5 -0
  46. package/dist/init-deposit/refresh.js +3 -0
  47. package/dist/init-deposit/scaffold.js +277 -4
  48. package/dist/init-deposit/skill-discovery-deposit.js +15 -0
  49. package/dist/policy/check-resume.d.ts +72 -0
  50. package/dist/policy/check-resume.js +253 -0
  51. package/dist/policy/coverage-check-resume-presets.d.ts +46 -0
  52. package/dist/policy/coverage-check-resume-presets.js +228 -0
  53. package/dist/policy/coverage-debt.d.ts +76 -0
  54. package/dist/policy/coverage-debt.js +262 -0
  55. package/dist/policy/index.d.ts +3 -0
  56. package/dist/policy/index.js +50 -21
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
  58. package/dist/pr-merge-readiness/ci-gate.js +191 -24
  59. package/dist/pr-merge-readiness/compute.js +10 -1
  60. package/dist/pr-merge-readiness/index.d.ts +2 -1
  61. package/dist/pr-merge-readiness/index.js +2 -1
  62. package/dist/pr-merge-readiness/output.js +14 -0
  63. package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
  64. package/dist/pr-merge-readiness/platform-status.js +49 -0
  65. package/dist/pr-watch/constants.d.ts +10 -0
  66. package/dist/pr-watch/constants.js +12 -1
  67. package/dist/pr-watch/main.js +16 -1
  68. package/dist/pr-watch/probe.js +13 -1
  69. package/dist/pr-watch/types.d.ts +3 -2
  70. package/dist/pr-watch/watch.js +14 -7
  71. package/dist/release/auto-hatch.d.ts +114 -0
  72. package/dist/release/auto-hatch.js +301 -0
  73. package/dist/release/coverage-debt-ledger.d.ts +22 -0
  74. package/dist/release/coverage-debt-ledger.js +157 -0
  75. package/dist/release/index.d.ts +3 -0
  76. package/dist/release/index.js +3 -0
  77. package/dist/release/pipeline.js +164 -12
  78. package/dist/release/suite-stamp.d.ts +44 -0
  79. package/dist/release/suite-stamp.js +133 -0
  80. package/dist/release/types.d.ts +19 -0
  81. package/dist/scope-provenance/digest.d.ts +67 -0
  82. package/dist/scope-provenance/digest.js +188 -0
  83. package/dist/scope-provenance/evaluate.d.ts +82 -0
  84. package/dist/scope-provenance/evaluate.js +528 -0
  85. package/dist/scope-provenance/index.d.ts +6 -0
  86. package/dist/scope-provenance/index.js +6 -0
  87. package/dist/session/compact-ritual.d.ts +96 -0
  88. package/dist/session/compact-ritual.js +237 -0
  89. package/dist/session/compact-ritual.spec.d.ts +2 -0
  90. package/dist/session/compact-ritual.spec.js +21 -0
  91. package/dist/session/coverage-check-resume-nudge.d.ts +34 -0
  92. package/dist/session/coverage-check-resume-nudge.js +66 -0
  93. package/dist/session/index.d.ts +3 -0
  94. package/dist/session/index.js +3 -0
  95. package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
  96. package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
  97. package/dist/session/session-start.js +21 -0
  98. package/dist/test-boundary/evaluate.d.ts +54 -0
  99. package/dist/test-boundary/evaluate.js +368 -0
  100. package/dist/test-boundary/index.d.ts +6 -0
  101. package/dist/test-boundary/index.js +6 -0
  102. package/dist/test-boundary/policy.d.ts +52 -0
  103. package/dist/test-boundary/policy.js +182 -0
  104. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  105. package/dist/triage/bootstrap/gitignore.js +15 -1
  106. package/dist/triage/classify/label-mirror.d.ts +31 -1
  107. package/dist/triage/classify/label-mirror.js +78 -6
  108. package/dist/triage/help/registry-data.d.ts +6 -6
  109. package/dist/triage/help/registry-data.js +12 -3
  110. package/dist/vbrief-activate/activate.js +22 -6
  111. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  112. package/dist/vbrief-validate/plan-hooks.js +54 -0
  113. package/dist/xbrief/styles.js +33 -17
  114. package/package.json +7 -3
@@ -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 === FIELD_STALENESS_TICKLER_CLI_ALIAS
426
- ? FIELD_STALENESS_TICKLER
427
- : name === FIELD_RUNTIME_AUTHORITY_CLI_ALIAS
428
- ? FIELD_RUNTIME_AUTHORITY
429
- : name === FIELD_HOST_HOOKS_CLI_ALIAS
430
- ? FIELD_HOST_HOOKS
431
- : name === FIELD_HOST_SLASH_COMMANDS_CLI_ALIAS
432
- ? FIELD_HOST_SLASH_COMMANDS
433
- : name === FIELD_OPENCLAW_PRODUCT_COMMANDS_CLI_ALIAS
434
- ? FIELD_OPENCLAW_PRODUCT_COMMANDS
435
- : name === FIELD_HOST_SKILL_DISCOVERY_CLI_ALIAS
436
- ? FIELD_HOST_SKILL_DISCOVERY
437
- : name === FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS
438
- ? FIELD_REQUIRE_HUMAN_MERGE
439
- : name === FIELD_HOTFIX_CRITERIA_CLI_ALIAS
440
- ? FIELD_HOTFIX_CRITERIA
441
- : name === FIELD_DELIVERY_BRANCH_CLI_ALIAS
442
- ? FIELD_DELIVERY_BRANCH
443
- : name === FIELD_MIN_GREPTILE_CONFIDENCE_CLI_ALIAS
444
- ? FIELD_MIN_GREPTILE_CONFIDENCE
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;
@@ -1,5 +1,21 @@
1
1
  import type { CheckRunRecord } from "./gh.js";
2
- export type CiReadyState = "ready" | "blocked" | "not_ready_yet" | "runner_capacity_stall" | "skipped";
2
+ /**
3
+ * Bot-reviewer check-runs are not CI workflow jobs. Presence of only these
4
+ * (or empty check-runs) is the `ci_never_scheduled` weather signal (#3167).
5
+ */
6
+ export declare function isBotReviewCheck(name: string): boolean;
7
+ /**
8
+ * Suite family for cancelled↔green sibling matching (#3167 / Greptile P1).
9
+ * Unrelated greens (CodeQL, Socket, …) must not clear a cancelled TypeScript/Go lane.
10
+ * Returns null when the check is not a TS/Go CI suite job.
11
+ */
12
+ export declare function suiteFamilyOf(name: string): "typescript" | "go" | null;
13
+ /**
14
+ * Branch-protection SoT aggregators only (#3168 map). Primary/failover lanes are
15
+ * not authoritative: a green primary must not clear a cancelled aggregator.
16
+ */
17
+ export declare function isAuthoritativeSuiteAggregator(name: string): boolean;
18
+ export type CiReadyState = "ready" | "blocked" | "ci_failures" | "not_ready_yet" | "runner_capacity_stall" | "ci_never_scheduled" | "ci_cancelled_no_failover" | "skipped";
3
19
  export interface CiGateOptions {
4
20
  readonly skipCi?: boolean;
5
21
  readonly ignoreCheckNames?: readonly string[];
@@ -23,6 +39,8 @@ export interface CiGateSummary {
23
39
  readonly pending_required: readonly string[];
24
40
  /** Required checks classified as capacity-stalled (#2672). */
25
41
  readonly capacity_stalled_required: readonly string[];
42
+ /** Required checks cancelled without a green failover sibling (#3167). */
43
+ readonly cancelled_required: readonly string[];
26
44
  readonly conclusions: readonly CiCheckConclusion[];
27
45
  }
28
46
  export interface CiGateResult {
@@ -30,5 +48,15 @@ export interface CiGateResult {
30
48
  readonly summary: CiGateSummary;
31
49
  }
32
50
  export declare function buildCiSummaryLine(summary: CiGateSummary): string;
51
+ /**
52
+ * Classify required CI weather for a HEAD check-run list (#2169 / #2672 / #3167).
53
+ *
54
+ * Distinct ready_state codes:
55
+ * - `ci_never_scheduled` — no CI workflow check-runs (bots alone / empty)
56
+ * - `runner_capacity_stall` — queued past budget, no runner claimed
57
+ * - `ci_failures` — completed failure / timed_out with product evidence
58
+ * - `ci_cancelled_no_failover` — only cancelled failures; no green failover
59
+ * - `not_ready_yet` / `ready` / `blocked` / `skipped` — legacy + residual
60
+ */
33
61
  export declare function evaluateCiGate(checkRuns: readonly CheckRunRecord[], options?: CiGateOptions): CiGateResult;
34
62
  //# sourceMappingURL=ci-gate.d.ts.map
@@ -1,45 +1,137 @@
1
1
  import { classifyCapacityStalledRequired, DEFAULT_CAPACITY_STALL_MS, } from "./runner-capacity-stall.js";
2
- const FAILED_CONCLUSIONS = new Set(["failure", "cancelled", "timed_out"]);
2
+ /** Real product/test failures (distinct from cancelled primaries, #3167). */
3
+ const FAILURE_CONCLUSIONS = new Set(["failure", "timed_out"]);
4
+ /** Capacity/outage cancels where failover may have been skipped (#3167). */
5
+ const CANCELLED_CONCLUSIONS = new Set(["cancelled"]);
3
6
  const PENDING_STATUSES = new Set(["queued", "in_progress"]);
7
+ /** Non-blocking completed conclusions for general ready/pending accounting. */
8
+ const SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
9
+ /**
10
+ * Only true success clears a cancelled suite lane. `skipped` / `neutral` are
11
+ * not executed green failover evidence (#3167 Greptile P1).
12
+ */
13
+ const AUTHORITATIVE_CLEAR_CONCLUSIONS = new Set(["success"]);
14
+ /**
15
+ * Bot-reviewer check-runs are not CI workflow jobs. Presence of only these
16
+ * (or empty check-runs) is the `ci_never_scheduled` weather signal (#3167).
17
+ */
18
+ export function isBotReviewCheck(name) {
19
+ const n = name.toLowerCase();
20
+ return n.includes("greptile") || n.includes("slizard") || n.includes("coderabbit");
21
+ }
22
+ /**
23
+ * Suite family for cancelled↔green sibling matching (#3167 / Greptile P1).
24
+ * Unrelated greens (CodeQL, Socket, …) must not clear a cancelled TypeScript/Go lane.
25
+ * Returns null when the check is not a TS/Go CI suite job.
26
+ */
27
+ export function suiteFamilyOf(name) {
28
+ const n = name.toLowerCase();
29
+ if (n.includes("typescript") || n.includes("type script")) {
30
+ return "typescript";
31
+ }
32
+ // "Go (…)" primary/failover/aggregator; avoid bare "go" false positives (e.g. CodeQL go analyze).
33
+ if (/\bgo\s*\(/.test(n) || n.startsWith("go ") || n === "go") {
34
+ return "go";
35
+ }
36
+ return null;
37
+ }
38
+ /**
39
+ * Branch-protection SoT aggregators only (#3168 map). Primary/failover lanes are
40
+ * not authoritative: a green primary must not clear a cancelled aggregator.
41
+ */
42
+ export function isAuthoritativeSuiteAggregator(name) {
43
+ const n = name.toLowerCase().trim();
44
+ if (n.includes("primary") || n.includes("failover") || n.includes("blacksmith")) {
45
+ return false;
46
+ }
47
+ if (n === "typescript (build + lint + test)") {
48
+ return true;
49
+ }
50
+ if (n === "go (test + build)") {
51
+ return true;
52
+ }
53
+ // Tolerate minor naming drift while excluding lane suffixes.
54
+ if (suiteFamilyOf(name) === "typescript" &&
55
+ n.includes("build") &&
56
+ n.includes("lint") &&
57
+ n.includes("test")) {
58
+ return true;
59
+ }
60
+ if (suiteFamilyOf(name) === "go" && n.includes("test") && n.includes("build")) {
61
+ return true;
62
+ }
63
+ return false;
64
+ }
4
65
  function isPending(status, conclusion) {
5
66
  if (PENDING_STATUSES.has(status)) {
6
67
  return true;
7
68
  }
8
69
  return status !== "completed" || conclusion === "none";
9
70
  }
71
+ function emptySummary(readyState, ignored = []) {
72
+ return {
73
+ ready_state: readyState,
74
+ checked_count: 0,
75
+ ignored_checks: ignored,
76
+ failed_required: [],
77
+ pending_required: [],
78
+ capacity_stalled_required: [],
79
+ cancelled_required: [],
80
+ conclusions: [],
81
+ };
82
+ }
10
83
  export function buildCiSummaryLine(summary) {
11
84
  const passed = summary.checked_count - summary.failed_required.length - summary.pending_required.length;
12
85
  const stallSuffix = summary.capacity_stalled_required.length > 0
13
86
  ? ` / ${summary.capacity_stalled_required.length} capacity-stalled`
14
87
  : "";
88
+ const cancelSuffix = summary.cancelled_required.length > 0
89
+ ? ` / ${summary.cancelled_required.length} cancelled`
90
+ : "";
91
+ const weather = summary.ready_state === "ci_never_scheduled"
92
+ ? " (ci_never_scheduled)"
93
+ : summary.ready_state === "ci_cancelled_no_failover"
94
+ ? " (ci_cancelled_no_failover)"
95
+ : summary.ready_state === "ci_failures"
96
+ ? " (ci_failures)"
97
+ : "";
15
98
  return (`CI check-runs: ${passed} passed / ` +
16
99
  `${summary.failed_required.length} failed / ${summary.pending_required.length} pending` +
17
- stallSuffix);
100
+ stallSuffix +
101
+ cancelSuffix +
102
+ weather);
18
103
  }
104
+ /**
105
+ * Classify required CI weather for a HEAD check-run list (#2169 / #2672 / #3167).
106
+ *
107
+ * Distinct ready_state codes:
108
+ * - `ci_never_scheduled` — no CI workflow check-runs (bots alone / empty)
109
+ * - `runner_capacity_stall` — queued past budget, no runner claimed
110
+ * - `ci_failures` — completed failure / timed_out with product evidence
111
+ * - `ci_cancelled_no_failover` — only cancelled failures; no green failover
112
+ * - `not_ready_yet` / `ready` / `blocked` / `skipped` — legacy + residual
113
+ */
19
114
  export function evaluateCiGate(checkRuns, options = {}) {
20
115
  if (options.skipCi === true) {
21
116
  return {
22
117
  failures: [],
23
- summary: {
24
- ready_state: "skipped",
25
- checked_count: 0,
26
- ignored_checks: [],
27
- failed_required: [],
28
- pending_required: [],
29
- capacity_stalled_required: [],
30
- conclusions: [],
31
- },
118
+ summary: emptySummary("skipped"),
32
119
  };
33
120
  }
34
121
  const ignoredSet = new Set(options.ignoreCheckNames ?? []);
35
122
  const ignoredChecks = [];
36
- const failedRequired = [];
123
+ const failedProduct = [];
124
+ const cancelledRequired = [];
37
125
  const pendingRequired = [];
38
126
  const pendingProbes = [];
39
127
  const conclusions = [];
128
+ let workflowCheckCount = 0;
129
+ let operatorIgnoredOnly = false;
40
130
  for (const run of checkRuns) {
41
- const ignored = ignoredSet.has(run.name);
42
- if (ignored) {
131
+ const botReview = isBotReviewCheck(run.name);
132
+ const operatorIgnored = ignoredSet.has(run.name);
133
+ const ignored = operatorIgnored || botReview;
134
+ if (operatorIgnored) {
43
135
  ignoredChecks.push(run.name);
44
136
  }
45
137
  const required = !ignored;
@@ -53,8 +145,16 @@ export function evaluateCiGate(checkRuns, options = {}) {
53
145
  if (!required) {
54
146
  continue;
55
147
  }
56
- if (FAILED_CONCLUSIONS.has(run.conclusion)) {
57
- failedRequired.push(`${run.name} (${run.conclusion})`);
148
+ workflowCheckCount += 1;
149
+ if (run.status === "completed" && SUCCESS_CONCLUSIONS.has(run.conclusion)) {
150
+ continue;
151
+ }
152
+ if (FAILURE_CONCLUSIONS.has(run.conclusion)) {
153
+ failedProduct.push(`${run.name} (${run.conclusion})`);
154
+ continue;
155
+ }
156
+ if (CANCELLED_CONCLUSIONS.has(run.conclusion)) {
157
+ cancelledRequired.push(`${run.name} (${run.conclusion})`);
58
158
  continue;
59
159
  }
60
160
  if (isPending(run.status, run.conclusion)) {
@@ -62,23 +162,48 @@ export function evaluateCiGate(checkRuns, options = {}) {
62
162
  pendingProbes.push(run);
63
163
  }
64
164
  }
165
+ // Operator --ci-ignore-check on every non-bot run is intentional, not weather.
166
+ operatorIgnoredOnly =
167
+ workflowCheckCount === 0 &&
168
+ checkRuns.some((r) => ignoredSet.has(r.name) && !isBotReviewCheck(r.name));
169
+ // #3167: no workflow CI check-runs at all (empty list or bots-only) → never scheduled.
170
+ // Distinct from "all remaining checks operator-ignored" (legacy ready).
171
+ if (workflowCheckCount === 0 && !operatorIgnoredOnly) {
172
+ return {
173
+ failures: [
174
+ "Required CI workflow check-runs never scheduled (ci_never_scheduled): " +
175
+ "no non-bot check-run for the current HEAD. " +
176
+ "Do not multi-hour empty-commit thrash; emit BLOCKED after thrash caps (#3167).",
177
+ ],
178
+ summary: {
179
+ ready_state: "ci_never_scheduled",
180
+ checked_count: 0,
181
+ ignored_checks: ignoredChecks,
182
+ failed_required: [],
183
+ pending_required: [],
184
+ capacity_stalled_required: [],
185
+ cancelled_required: [],
186
+ conclusions,
187
+ },
188
+ };
189
+ }
65
190
  const stallOpts = {
66
191
  budgetMs: options.capacityStallBudgetMs ?? DEFAULT_CAPACITY_STALL_MS,
67
192
  nowMs: options.nowMs,
68
193
  };
69
194
  const capacityStalledRequired = classifyCapacityStalledRequired(pendingProbes, stallOpts);
70
195
  const failures = [];
71
- if (failedRequired.length > 0) {
72
- failures.push(`Required CI check-runs failed: ${failedRequired.join(", ")}. ` +
73
- "Required checks fail closed by default (#2169).");
74
- }
75
196
  let readyState;
76
- if (failedRequired.length > 0) {
77
- readyState = "blocked";
197
+ // Blocking failures listed for merge-ready / clean-gate (not diagnostic-only cancels).
198
+ let failedRequired = [];
199
+ if (failedProduct.length > 0) {
200
+ readyState = "ci_failures";
201
+ failedRequired = [...failedProduct, ...cancelledRequired];
202
+ failures.push(`Required CI check-runs failed (ci_failures): ${failedProduct.join(", ")}. ` +
203
+ "Required checks fail closed by default (#2169 / #3167).");
78
204
  }
79
205
  else if (pendingRequired.length > 0) {
80
- // Capacity stall when every pending required check is a queue stall past
81
- // budget — distinct from ordinary not_ready_yet (under budget / in_progress).
206
+ // Pending wins over cancelled siblings (failover may still be arming).
82
207
  const allPendingStalled = capacityStalledRequired.length > 0 && capacityStalledRequired.length === pendingProbes.length;
83
208
  if (allPendingStalled) {
84
209
  readyState = "runner_capacity_stall";
@@ -93,6 +218,47 @@ export function evaluateCiGate(checkRuns, options = {}) {
93
218
  "Wait for required checks to finish before merge.");
94
219
  }
95
220
  }
221
+ else if (cancelledRequired.length > 0) {
222
+ // Cancelled suite jobs clear only when a NON-IGNORED AUTHORITATIVE aggregator
223
+ // for that suite family is green (#3167 Greptile P1s):
224
+ // - Unrelated greens (CodeQL, Socket) never clear
225
+ // - Green primary/failover never clear a cancelled aggregator
226
+ // - Operator-ignored aggregator never clears
227
+ // - Green aggregator clears cancelled primary/failover (failover path done)
228
+ // - Non-suite required cancellations always block (mixed cancel)
229
+ // Strip trailing " (conclusion)" without nested-paren regex (CodeQL js/polynomial-redos).
230
+ const stripConclusionParen = (label) => {
231
+ const open = label.lastIndexOf(" (");
232
+ if (open < 0 || !label.endsWith(")")) {
233
+ return label;
234
+ }
235
+ return label.slice(0, open);
236
+ };
237
+ const cancelledNonSuite = cancelledRequired.filter((label) => suiteFamilyOf(stripConclusionParen(label)) === null);
238
+ const cancelledFamilies = new Set(cancelledRequired
239
+ .map((label) => suiteFamilyOf(stripConclusionParen(label)))
240
+ .filter((f) => f !== null));
241
+ const greenAuthoritativeFamilies = new Set(conclusions
242
+ .filter((c) => c.required &&
243
+ !c.ignored &&
244
+ c.status === "completed" &&
245
+ AUTHORITATIVE_CLEAR_CONCLUSIONS.has(c.conclusion) &&
246
+ isAuthoritativeSuiteAggregator(c.name))
247
+ .map((c) => suiteFamilyOf(c.name)));
248
+ const unclearedFamilies = [...cancelledFamilies].filter((f) => !greenAuthoritativeFamilies.has(f));
249
+ if (unclearedFamilies.length > 0 || cancelledNonSuite.length > 0) {
250
+ readyState = "ci_cancelled_no_failover";
251
+ failedRequired = [...cancelledRequired];
252
+ failures.push(`Required CI check-runs cancelled without failover (ci_cancelled_no_failover): ` +
253
+ `${cancelledRequired.join(", ")}. ` +
254
+ "Cancelled required check with no green non-ignored authoritative suite aggregator (#3167 / #3168). " +
255
+ "Do not multi-hour re-push thrash; BLOCKED after thrash caps.");
256
+ }
257
+ else {
258
+ // Every cancelled suite family has a green non-ignored aggregator; no non-suite cancels.
259
+ readyState = "ready";
260
+ }
261
+ }
96
262
  else {
97
263
  readyState = "ready";
98
264
  }
@@ -105,6 +271,7 @@ export function evaluateCiGate(checkRuns, options = {}) {
105
271
  failed_required: failedRequired,
106
272
  pending_required: pendingRequired,
107
273
  capacity_stalled_required: capacityStalledRequired,
274
+ cancelled_required: cancelledRequired,
108
275
  conclusions,
109
276
  },
110
277
  };
@@ -6,6 +6,7 @@ import { fetchCheckRunsRest, fetchGreptileBodyRest, fetchGreptileCommentBody, fe
6
6
  import { fetchUnresolvedGreptileInlineFindings, inlineFindingsToDict, } from "./greptile-inline.js";
7
7
  import { fetchMergeability, isGithubMergeableClean, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
8
8
  import { emptyVerdict, parseGreptileBody } from "./parse.js";
9
+ import { attachPlatformStatusUrls } from "./platform-status.js";
9
10
  import { evaluateSlizardGate, isSlizardCheck } from "./slizard-gate.js";
10
11
  function buildGateResult(prNumber, repo, headSha, body, via, partialData = {}, error = null, inline = null, minConfidence) {
11
12
  const verdict = parseGreptileBody(body);
@@ -40,9 +41,15 @@ function evaluateCiAndSlizard(checkRuns, options) {
40
41
  ignoreCheckNames: [...(options.ignoreCheckNames ?? []), ...slizardNames],
41
42
  };
42
43
  const ciResult = evaluateCiGate(checkRuns, ciOptions);
44
+ const ci = {
45
+ ...ciResult.summary,
46
+ summary_line: buildCiSummaryLine(ciResult.summary),
47
+ };
48
+ // #3180: static status URLs when weather-class (no network fetch).
49
+ attachPlatformStatusUrls(ci, ciResult.summary.ready_state);
43
50
  return {
44
51
  failures: [...ciResult.failures, ...slizardResult.failures],
45
- ci: { ...ciResult.summary, summary_line: buildCiSummaryLine(ciResult.summary) },
52
+ ci,
46
53
  slizard: { ...slizardResult.summary },
47
54
  };
48
55
  }
@@ -85,6 +92,8 @@ function applyCiGateForHead(repo, headSha, runGh, options) {
85
92
  ignored_checks: [...(options.ignoreCheckNames ?? [])],
86
93
  failed_required: [],
87
94
  pending_required: [],
95
+ capacity_stalled_required: [],
96
+ cancelled_required: [],
88
97
  conclusions: [],
89
98
  },
90
99
  slizard: {
@@ -1,4 +1,4 @@
1
- export { buildCiSummaryLine, type CiCheckConclusion, type CiGateOptions, type CiGateResult, type CiGateSummary, type CiReadyState, evaluateCiGate, } from "./ci-gate.js";
1
+ export { buildCiSummaryLine, type CiCheckConclusion, type CiGateOptions, type CiGateResult, type CiGateSummary, type CiReadyState, evaluateCiGate, isAuthoritativeSuiteAggregator, isBotReviewCheck, suiteFamilyOf, } from "./ci-gate.js";
2
2
  export { type ComputeGateOptions, computeGateResult, type FetchMergeabilityFn } from "./compute.js";
3
3
  export * from "./constants.js";
4
4
  export { evaluateGates, isMergeReady } from "./evaluate.js";
@@ -8,6 +8,7 @@ export { cmdPrMergeReadiness, parseArgs, run } from "./main.js";
8
8
  export { fetchMergeability, isGithubMergeableClean, MERGE_STATE_CLEAN, type MergeabilitySignal, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
9
9
  export { emitJson, exitCodeFor, gateResultToDict, printHuman } from "./output.js";
10
10
  export { emptyVerdict, isInformalCleanMissingCanonicalFields, parseGreptileBody } from "./parse.js";
11
+ export { attachPlatformStatusUrls, CI_WEATHER_READY_STATES, type CiWeatherReadyState, isCiWeatherReadyState, PLATFORM_STATUS_BLACKSMITH_URL, PLATFORM_STATUS_GITHUB_URL, platformStatusUrlsForWeather, } from "./platform-status.js";
11
12
  export { type CapacityStallOptions, type CapacityStallProbe, classifyCapacityStalledRequired, DEFAULT_CAPACITY_STALL_MS, isRunnerCapacityStalled, } from "./runner-capacity-stall.js";
12
13
  export type { SlizardGateOptions, SlizardGateResult, SlizardGateSummary, SlizardVerdict, } from "./slizard-gate.js";
13
14
  export { evaluateSlizardGate, isSlizardCheck, parseSlizardVerdict, SLIZARD_CHECK_NAME, } from "./slizard-gate.js";
@@ -1,4 +1,4 @@
1
- export { buildCiSummaryLine, evaluateCiGate, } from "./ci-gate.js";
1
+ export { buildCiSummaryLine, evaluateCiGate, isAuthoritativeSuiteAggregator, isBotReviewCheck, suiteFamilyOf, } from "./ci-gate.js";
2
2
  export { computeGateResult } from "./compute.js";
3
3
  export * from "./constants.js";
4
4
  export { evaluateGates, isMergeReady } from "./evaluate.js";
@@ -8,6 +8,7 @@ export { cmdPrMergeReadiness, parseArgs, run } from "./main.js";
8
8
  export { fetchMergeability, isGithubMergeableClean, MERGE_STATE_CLEAN, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
9
9
  export { emitJson, exitCodeFor, gateResultToDict, printHuman } from "./output.js";
10
10
  export { emptyVerdict, isInformalCleanMissingCanonicalFields, parseGreptileBody } from "./parse.js";
11
+ export { attachPlatformStatusUrls, CI_WEATHER_READY_STATES, isCiWeatherReadyState, PLATFORM_STATUS_BLACKSMITH_URL, PLATFORM_STATUS_GITHUB_URL, platformStatusUrlsForWeather, } from "./platform-status.js";
11
12
  export { classifyCapacityStalledRequired, DEFAULT_CAPACITY_STALL_MS, isRunnerCapacityStalled, } from "./runner-capacity-stall.js";
12
13
  export { evaluateSlizardGate, isSlizardCheck, parseSlizardVerdict, SLIZARD_CHECK_NAME, } from "./slizard-gate.js";
13
14
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,6 @@
1
1
  import { EXIT_EXTERNAL_ERROR, EXIT_MERGE_BLOCKED, EXIT_OK, VIA_ERROR, VIA_FALLBACK2, } from "./constants.js";
2
2
  import { isMergeReady } from "./evaluate.js";
3
+ import { isCiWeatherReadyState, PLATFORM_STATUS_BLACKSMITH_URL, PLATFORM_STATUS_GITHUB_URL, } from "./platform-status.js";
3
4
  function verdictToDict(verdict) {
4
5
  return {
5
6
  found: verdict.found,
@@ -68,6 +69,19 @@ export function printHuman(result) {
68
69
  else if (typeof ci.ready_state === "string") {
69
70
  lines.push(` CI check-runs: ${ci.ready_state}`);
70
71
  }
72
+ // #3180: surface static status URLs on weather-class CI (human + JSON partial_data).
73
+ const readyState = typeof ci.ready_state === "string" ? ci.ready_state : null;
74
+ if (isCiWeatherReadyState(readyState)) {
75
+ const gh = typeof ci.platform_status_github === "string"
76
+ ? ci.platform_status_github
77
+ : PLATFORM_STATUS_GITHUB_URL;
78
+ const bs = typeof ci.platform_status_blacksmith === "string"
79
+ ? ci.platform_status_blacksmith
80
+ : PLATFORM_STATUS_BLACKSMITH_URL;
81
+ lines.push(` Platform status GH: ${gh}`);
82
+ lines.push(` Platform status BS: ${bs}`);
83
+ lines.push(" Probe status pages before workflow edits (#3180)");
84
+ }
71
85
  }
72
86
  const slizardBlock = result.partialData.slizard;
73
87
  if (slizardBlock !== null && typeof slizardBlock === "object" && !Array.isArray(slizardBlock)) {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Static platform status page URLs for CI weather attribution (#3180).
3
+ *
4
+ * v1 surfaces URLs only — no network fetch. Agents MUST open these pages when
5
+ * weather-class `ci_ready_state` fires (or CI never starts for HEAD) and apply
6
+ * the attribution table in scm/github.md + review-cycle.
7
+ */
8
+ /** Public GitHub Status (Actions / Webhooks components). */
9
+ export declare const PLATFORM_STATUS_GITHUB_URL = "https://www.githubstatus.com/";
10
+ /** Public Blacksmith Status (runner provider; may mirror GH Actions health). */
11
+ export declare const PLATFORM_STATUS_BLACKSMITH_URL = "https://status.blacksmith.sh/";
12
+ /**
13
+ * Weather-class ready_state values from evaluateCiGate (#3167).
14
+ * Status URLs are attached only for these (not ordinary ready / not_ready_yet).
15
+ */
16
+ export declare const CI_WEATHER_READY_STATES: readonly ["ci_never_scheduled", "runner_capacity_stall", "ci_failures", "ci_cancelled_no_failover"];
17
+ export type CiWeatherReadyState = (typeof CI_WEATHER_READY_STATES)[number];
18
+ export declare function isCiWeatherReadyState(state: string | null | undefined): state is CiWeatherReadyState;
19
+ /** Static status URL fields for weather-class states; null when not weather. */
20
+ export declare function platformStatusUrlsForWeather(state: string | null | undefined): {
21
+ platform_status_github: string;
22
+ platform_status_blacksmith: string;
23
+ } | null;
24
+ /**
25
+ * Attach static status URLs onto a CI partial/summary record when weather-class.
26
+ * Mutates and returns the same object for call-site chaining.
27
+ */
28
+ export declare function attachPlatformStatusUrls(ci: Record<string, unknown>, readyState?: string | null): Record<string, unknown>;
29
+ //# sourceMappingURL=platform-status.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Static platform status page URLs for CI weather attribution (#3180).
3
+ *
4
+ * v1 surfaces URLs only — no network fetch. Agents MUST open these pages when
5
+ * weather-class `ci_ready_state` fires (or CI never starts for HEAD) and apply
6
+ * the attribution table in scm/github.md + review-cycle.
7
+ */
8
+ /** Public GitHub Status (Actions / Webhooks components). */
9
+ export const PLATFORM_STATUS_GITHUB_URL = "https://www.githubstatus.com/";
10
+ /** Public Blacksmith Status (runner provider; may mirror GH Actions health). */
11
+ export const PLATFORM_STATUS_BLACKSMITH_URL = "https://status.blacksmith.sh/";
12
+ /**
13
+ * Weather-class ready_state values from evaluateCiGate (#3167).
14
+ * Status URLs are attached only for these (not ordinary ready / not_ready_yet).
15
+ */
16
+ export const CI_WEATHER_READY_STATES = [
17
+ "ci_never_scheduled",
18
+ "runner_capacity_stall",
19
+ "ci_failures",
20
+ "ci_cancelled_no_failover",
21
+ ];
22
+ const WEATHER_SET = new Set(CI_WEATHER_READY_STATES);
23
+ export function isCiWeatherReadyState(state) {
24
+ return state != null && WEATHER_SET.has(state);
25
+ }
26
+ /** Static status URL fields for weather-class states; null when not weather. */
27
+ export function platformStatusUrlsForWeather(state) {
28
+ if (!isCiWeatherReadyState(state)) {
29
+ return null;
30
+ }
31
+ return {
32
+ platform_status_github: PLATFORM_STATUS_GITHUB_URL,
33
+ platform_status_blacksmith: PLATFORM_STATUS_BLACKSMITH_URL,
34
+ };
35
+ }
36
+ /**
37
+ * Attach static status URLs onto a CI partial/summary record when weather-class.
38
+ * Mutates and returns the same object for call-site chaining.
39
+ */
40
+ export function attachPlatformStatusUrls(ci, readyState) {
41
+ const state = readyState ?? (typeof ci.ready_state === "string" ? ci.ready_state : null);
42
+ const urls = platformStatusUrlsForWeather(state);
43
+ if (urls !== null) {
44
+ ci.platform_status_github = urls.platform_status_github;
45
+ ci.platform_status_blacksmith = urls.platform_status_blacksmith;
46
+ }
47
+ return ci;
48
+ }
49
+ //# sourceMappingURL=platform-status.js.map
@@ -30,6 +30,16 @@ export declare const VERDICT_CI_BLOCKED = "CI_BLOCKED";
30
30
  * claimed (#2672). Exit 2 — wait for auto-failover; never --skip-ci.
31
31
  */
32
32
  export declare const VERDICT_RUNNER_CAPACITY_STALL = "RUNNER_CAPACITY_STALL";
33
+ /**
34
+ * No CI workflow check-run scheduled for HEAD (bots-only or empty) (#3167).
35
+ * Exit 2 — thrash-cap then BLOCKED; do not multi-hour empty-commit loops.
36
+ */
37
+ export declare const VERDICT_CI_NEVER_SCHEDULED = "CI_NEVER_SCHEDULED";
38
+ /**
39
+ * Primary CI cancelled and no green required sibling / failover (#3167).
40
+ * Exit 2 — thrash-cap then BLOCKED; workflow arming is sibling #3168.
41
+ */
42
+ export declare const VERDICT_CI_CANCELLED_NO_FAILOVER = "CI_CANCELLED_NO_FAILOVER";
33
43
  /** --one-shot only: a single probe with no terminal verdict yet. */
34
44
  export declare const VERDICT_PENDING = "PENDING";
35
45
  /** External/config fault mid-probe (unresolvable repo/HEAD, gh unavailable). */
@@ -30,6 +30,16 @@ export const VERDICT_CI_BLOCKED = "CI_BLOCKED";
30
30
  * claimed (#2672). Exit 2 — wait for auto-failover; never --skip-ci.
31
31
  */
32
32
  export const VERDICT_RUNNER_CAPACITY_STALL = "RUNNER_CAPACITY_STALL";
33
+ /**
34
+ * No CI workflow check-run scheduled for HEAD (bots-only or empty) (#3167).
35
+ * Exit 2 — thrash-cap then BLOCKED; do not multi-hour empty-commit loops.
36
+ */
37
+ export const VERDICT_CI_NEVER_SCHEDULED = "CI_NEVER_SCHEDULED";
38
+ /**
39
+ * Primary CI cancelled and no green required sibling / failover (#3167).
40
+ * Exit 2 — thrash-cap then BLOCKED; workflow arming is sibling #3168.
41
+ */
42
+ export const VERDICT_CI_CANCELLED_NO_FAILOVER = "CI_CANCELLED_NO_FAILOVER";
33
43
  /** --one-shot only: a single probe with no terminal verdict yet. */
34
44
  export const VERDICT_PENDING = "PENDING";
35
45
  /** External/config fault mid-probe (unresolvable repo/HEAD, gh unavailable). */
@@ -62,7 +72,8 @@ export const WATCH_HELP = "usage: task pr:watch -- <pr_number> [options]\n" +
62
72
  "exit codes:\n" +
63
73
  " 0 CLEAN SHA-matched review, confidence >= policy min (default 4; dogfood 5), no P0/P1, CI green\n" +
64
74
  " 1 NEW_P0_P1 Blocking findings on the current (SHA-matched) review\n" +
65
- " 2 ERRORED | STALL | TIMEOUT | CI_BLOCKED | RUNNER_CAPACITY_STALL | config / usage error\n";
75
+ " 2 ERRORED | STALL | TIMEOUT | CI_BLOCKED | RUNNER_CAPACITY_STALL |\n" +
76
+ " CI_NEVER_SCHEDULED | CI_CANCELLED_NO_FAILOVER | config / usage error\n";
66
77
  /**
67
78
  * Consecutive polls where the CLEAN gate is wedged on HEAD (!has_blocking &&
68
79
  * !is_clean with a holdout other than sha_match) before STALL (#1039). Stale-SHA