@deftai/directive-core 0.79.0 → 0.79.2

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 (43) hide show
  1. package/dist/cache/fetch.js +14 -4
  2. package/dist/capacity/backfill.js +4 -2
  3. package/dist/category-b-namespace/index.js +31 -6
  4. package/dist/content-contracts/skills/skill-frontmatter.js +6 -2
  5. package/dist/eval/health.js +8 -1
  6. package/dist/eval/run.js +2 -0
  7. package/dist/fs/projection-containment.d.ts +11 -0
  8. package/dist/fs/projection-containment.js +62 -1
  9. package/dist/hooks/dispatcher.d.ts +14 -2
  10. package/dist/hooks/dispatcher.js +121 -7
  11. package/dist/init-deposit/hygiene.js +3 -0
  12. package/dist/intake/reconcile-issues.d.ts +1 -1
  13. package/dist/intake/reconcile-issues.js +31 -3
  14. package/dist/issue-sync/sync-from-xbrief-cli.d.ts +1 -0
  15. package/dist/issue-sync/sync-from-xbrief-cli.js +3 -0
  16. package/dist/issue-sync/sync-from-xbrief.d.ts +4 -0
  17. package/dist/issue-sync/sync-from-xbrief.js +12 -0
  18. package/dist/lifecycle/event.d.ts +15 -0
  19. package/dist/lifecycle/event.js +163 -0
  20. package/dist/lifecycle/index.d.ts +1 -0
  21. package/dist/lifecycle/index.js +1 -0
  22. package/dist/orchestration/verify-judgment-gates.js +18 -2
  23. package/dist/pr-merge-readiness/compute.d.ts +2 -1
  24. package/dist/pr-merge-readiness/compute.js +23 -6
  25. package/dist/pr-merge-readiness/evaluate.d.ts +2 -1
  26. package/dist/pr-merge-readiness/evaluate.js +46 -31
  27. package/dist/pr-merge-readiness/greptile-inline.d.ts +27 -0
  28. package/dist/pr-merge-readiness/greptile-inline.js +239 -0
  29. package/dist/pr-merge-readiness/index.d.ts +1 -0
  30. package/dist/pr-merge-readiness/index.js +1 -0
  31. package/dist/pr-merge-readiness/mergeability.d.ts +2 -1
  32. package/dist/pr-merge-readiness/mergeability.js +7 -1
  33. package/dist/pr-wait-mergeable/wrappers.d.ts +9 -0
  34. package/dist/pr-wait-mergeable/wrappers.js +31 -2
  35. package/dist/release/constants.js +2 -0
  36. package/dist/scope/demote.js +20 -0
  37. package/dist/swarm/worktrees.d.ts +3 -0
  38. package/dist/swarm/worktrees.js +18 -2
  39. package/dist/task-surface/index.js +3 -2
  40. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +16 -8
  41. package/dist/vitest-runner/win32-coverage-tmp-setup.js +41 -14
  42. package/dist/xbrief-migrate/migration-containment.js +9 -25
  43. package/package.json +3 -3
@@ -0,0 +1,15 @@
1
+ interface ParsedEmitInvocation {
2
+ readonly name: string;
3
+ readonly payload: Record<string, unknown>;
4
+ readonly log?: string;
5
+ }
6
+ /** Parse `emit <event> ...` argv for lifecycle:event approval recording. */
7
+ export declare function parseEmitInvocation(args: readonly string[]): ParsedEmitInvocation;
8
+ /** CLI entry for `lifecycle:event` / review-cycle approval recorder (#2631). */
9
+ export declare function runLifecycleEvent(argv: readonly string[], options?: {
10
+ projectRoot?: string;
11
+ }): number;
12
+ /** Process entrypoint mirroring other lifecycle CLI modules. */
13
+ export declare function main(argv?: readonly string[]): number;
14
+ export {};
15
+ //# sourceMappingURL=event.d.ts.map
@@ -0,0 +1,163 @@
1
+ import { resolve } from "node:path";
2
+ import { emit, main as eventsMain, readEvents } from "./events.js";
3
+ const APPROVAL_PHRASES = new Set(["yes", "confirmed", "approve", "other"]);
4
+ function parseBooleanFlag(value) {
5
+ return ["1", "true", "yes"].includes(value.toLowerCase());
6
+ }
7
+ function repositoryFromPlanRef(planRef) {
8
+ const match = /^https?:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+/i.exec(planRef.trim());
9
+ return match?.[1];
10
+ }
11
+ function dedupeKey(payload) {
12
+ const planRef = payload.plan_ref;
13
+ const approver = payload.approver;
14
+ if (typeof planRef !== "string" || typeof approver !== "string") {
15
+ return null;
16
+ }
17
+ const parts = [planRef, approver];
18
+ if (typeof payload.pr_number === "number") {
19
+ parts.push(String(payload.pr_number));
20
+ }
21
+ if (typeof payload.head_sha === "string" && payload.head_sha.length > 0) {
22
+ parts.push(payload.head_sha);
23
+ }
24
+ return parts.join("|");
25
+ }
26
+ function findExistingPlanApproved(key, logPath) {
27
+ for (const record of readEvents(logPath)) {
28
+ if (record.event !== "plan:approved") {
29
+ continue;
30
+ }
31
+ if (dedupeKey(record.payload) === key) {
32
+ return record;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ function validatePlanApprovedPayload(payload) {
38
+ const phrase = payload.approval_phrase;
39
+ if (phrase === undefined) {
40
+ return;
41
+ }
42
+ if (typeof phrase !== "string") {
43
+ throw new Error("approval_phrase must be a string");
44
+ }
45
+ const normalized = phrase.toLowerCase();
46
+ if (!APPROVAL_PHRASES.has(normalized)) {
47
+ throw new Error(`invalid approval_phrase '${phrase}'; expected one of yes, confirmed, approve, other`);
48
+ }
49
+ }
50
+ /** Parse `emit <event> ...` argv for lifecycle:event approval recording. */
51
+ export function parseEmitInvocation(args) {
52
+ const payload = {};
53
+ let name;
54
+ let log;
55
+ let i = 0;
56
+ if (args.length > 0 && !args[0]?.startsWith("--")) {
57
+ name = args[0];
58
+ i = 1;
59
+ }
60
+ if (name === undefined) {
61
+ throw new Error("event name required");
62
+ }
63
+ while (i < args.length) {
64
+ const arg = args[i];
65
+ if (arg === "--payload") {
66
+ const raw = args[i + 1];
67
+ if (raw === undefined) {
68
+ throw new Error("--payload requires a value");
69
+ }
70
+ const data = JSON.parse(raw);
71
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
72
+ throw new Error("--payload must be a JSON object");
73
+ }
74
+ Object.assign(payload, data);
75
+ i += 2;
76
+ continue;
77
+ }
78
+ if (arg === "--log") {
79
+ log = args[i + 1];
80
+ i += 2;
81
+ continue;
82
+ }
83
+ const flagMap = {
84
+ "--plan-ref": "plan_ref",
85
+ "--approver": "approver",
86
+ "--approval-phrase": "approval_phrase",
87
+ "--head-sha": "head_sha",
88
+ };
89
+ if (arg === "--pr-number") {
90
+ payload.pr_number = Number.parseInt(args[i + 1] ?? "", 10);
91
+ i += 2;
92
+ continue;
93
+ }
94
+ const field = flagMap[arg ?? ""];
95
+ if (field !== undefined) {
96
+ payload[field] = args[i + 1];
97
+ i += 2;
98
+ continue;
99
+ }
100
+ if (arg === "--inline") {
101
+ payload.inline = parseBooleanFlag(args[i + 1] ?? "");
102
+ i += 2;
103
+ continue;
104
+ }
105
+ throw new Error(`unrecognized arguments: ${arg}`);
106
+ }
107
+ return { name, payload, log };
108
+ }
109
+ function enrichPlanApprovedPayload(payload) {
110
+ const next = { ...payload };
111
+ if (typeof next.plan_ref === "string" && next.repository === undefined) {
112
+ const repository = repositoryFromPlanRef(next.plan_ref);
113
+ if (repository !== undefined) {
114
+ next.repository = repository;
115
+ }
116
+ }
117
+ return next;
118
+ }
119
+ function emitPlanApprovedIdempotent(payload, options = {}) {
120
+ const enriched = enrichPlanApprovedPayload(payload);
121
+ validatePlanApprovedPayload(enriched);
122
+ const key = dedupeKey(enriched);
123
+ if (key !== null) {
124
+ const existing = findExistingPlanApproved(key, options.logPath ?? null);
125
+ if (existing !== null) {
126
+ return existing;
127
+ }
128
+ }
129
+ return emit("plan:approved", enriched, options);
130
+ }
131
+ /** CLI entry for `lifecycle:event` / review-cycle approval recorder (#2631). */
132
+ export function runLifecycleEvent(argv, options = {}) {
133
+ if (options.projectRoot !== undefined) {
134
+ process.chdir(resolve(options.projectRoot));
135
+ }
136
+ if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help" || argv[0] === "help") {
137
+ process.stderr.write("usage: lifecycle:event emit plan:approved --plan-ref <url> --approver <login> " +
138
+ "[--approval-phrase yes|confirmed|approve] [--pr-number N] [--head-sha SHA]\n");
139
+ return argv.length === 0 ? 2 : 0;
140
+ }
141
+ if (argv[0] !== "emit") {
142
+ return eventsMain(argv);
143
+ }
144
+ if (argv[1] !== "plan:approved") {
145
+ return eventsMain(argv);
146
+ }
147
+ try {
148
+ const parsed = parseEmitInvocation(argv.slice(1));
149
+ const record = emitPlanApprovedIdempotent(parsed.payload, { logPath: parsed.log ?? null });
150
+ process.stdout.write(`${JSON.stringify(record)}\n`);
151
+ return 0;
152
+ }
153
+ catch (error) {
154
+ const message = error instanceof Error ? error.message : String(error);
155
+ process.stderr.write(`lifecycle:event failed: ${message}\n`);
156
+ return 2;
157
+ }
158
+ }
159
+ /** Process entrypoint mirroring other lifecycle CLI modules. */
160
+ export function main(argv = process.argv.slice(2)) {
161
+ return runLifecycleEvent(argv);
162
+ }
163
+ //# sourceMappingURL=event.js.map
@@ -1,3 +1,4 @@
1
+ export * from "./event.js";
1
2
  export * as eventDetect from "./event-detect.js";
2
3
  export * as events from "./events.js";
3
4
  export * as lifecycleHygiene from "./lifecycle-hygiene.js";
@@ -1,3 +1,4 @@
1
+ export * from "./event.js";
1
2
  export * as eventDetect from "./event-detect.js";
2
3
  export * as events from "./events.js";
3
4
  export * as lifecycleHygiene from "./lifecycle-hygiene.js";
@@ -3,8 +3,9 @@
3
3
  */
4
4
  import { execFileSync } from "node:child_process";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
- import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
6
+ import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, statSync } from "node:fs";
7
7
  import { join, resolve } from "node:path";
8
+ import { assertProjectionContained, assertWriteTargetSafe, ProjectionContainmentError, } from "../fs/projection-containment.js";
8
9
  import { resolveAuditDir } from "../layout/resolve.js";
9
10
  import { resolveJudgmentGates } from "./judgment-policy.js";
10
11
  import { matchAny } from "./pathspec.js";
@@ -163,7 +164,22 @@ export function readClearances(projectRoot, logPath) {
163
164
  }
164
165
  export function recordClearance(projectRoot, options) {
165
166
  const path = options.log_path ?? clearanceLogPath(projectRoot);
166
- mkdirSync(resolveAuditDir(projectRoot), { recursive: true });
167
+ const auditDir = resolveAuditDir(projectRoot);
168
+ assertProjectionContained(projectRoot, auditDir);
169
+ assertWriteTargetSafe(projectRoot, path);
170
+ try {
171
+ const info = lstatSync(auditDir);
172
+ if (info.isSymbolicLink()) {
173
+ throw new ProjectionContainmentError(`projection write refused: audit directory ${auditDir} must not be a symlink`, { projectDir: resolve(projectRoot), targetPath: auditDir, offendingPath: auditDir });
174
+ }
175
+ }
176
+ catch (err) {
177
+ if (err instanceof ProjectionContainmentError) {
178
+ throw err;
179
+ }
180
+ // Audit dir does not exist yet; mkdirSync below will create it under containment.
181
+ }
182
+ mkdirSync(auditDir, { recursive: true });
167
183
  const entry = {
168
184
  clearance_id: randomUUID(),
169
185
  timestamp: utcNowIso(options.now),
@@ -1,9 +1,10 @@
1
1
  import type { CiGateOptions } from "./ci-gate.js";
2
2
  import { isMergeReady } from "./evaluate.js";
3
+ import { type InlineGreptileFindings } from "./greptile-inline.js";
3
4
  import { type MergeabilitySignal } from "./mergeability.js";
4
5
  import type { SlizardGateOptions } from "./slizard-gate.js";
5
6
  import type { GateResult, RunGhFn } from "./types.js";
6
- declare function buildGateResult(prNumber: number, repo: string | null, headSha: string | null, body: string, via: string, partialData?: Record<string, unknown>, error?: string | null): GateResult;
7
+ declare function buildGateResult(prNumber: number, repo: string | null, headSha: string | null, body: string, via: string, partialData?: Record<string, unknown>, error?: string | null, inline?: InlineGreptileFindings | null): GateResult;
7
8
  /** Injectable GitHub-mergeability read seam (#2260) — keeps unit tests hermetic. */
8
9
  export type FetchMergeabilityFn = (prNumber: number, repo: string, runGh: RunGhFn) => MergeabilitySignal;
9
10
  export interface ComputeGateOptions extends CiGateOptions, SlizardGateOptions {
@@ -2,12 +2,13 @@ import { buildCiSummaryLine, evaluateCiGate } from "./ci-gate.js";
2
2
  import { FALLBACK2_NOT_CLEAN_MSG, VIA_ERROR, VIA_FALLBACK1, VIA_FALLBACK2, VIA_PRIMARY, } from "./constants.js";
3
3
  import { evaluateGates, isMergeReady } from "./evaluate.js";
4
4
  import { fetchCheckRunsRest, fetchGreptileBodyRest, fetchGreptileCommentBody, fetchPrHeadSha, fetchPrHeadShaRest, resolveRepo, } from "./gh.js";
5
+ import { fetchUnresolvedGreptileInlineFindings, inlineFindingsToDict, } from "./greptile-inline.js";
5
6
  import { fetchMergeability, isGithubMergeableClean, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
6
7
  import { emptyVerdict, parseGreptileBody } from "./parse.js";
7
8
  import { evaluateSlizardGate, isSlizardCheck } from "./slizard-gate.js";
8
- function buildGateResult(prNumber, repo, headSha, body, via, partialData = {}, error = null) {
9
+ function buildGateResult(prNumber, repo, headSha, body, via, partialData = {}, error = null, inline = null) {
9
10
  const verdict = parseGreptileBody(body);
10
- const failures = evaluateGates(prNumber, headSha, verdict);
11
+ const failures = evaluateGates(prNumber, headSha, verdict, inline);
11
12
  return {
12
13
  prNumber,
13
14
  repo,
@@ -92,10 +93,24 @@ function applyCiGateForHead(repo, headSha, runGh, options) {
92
93
  * is only soft-blocked -- absent or stale for the current head) reconcile
93
94
  * against GitHub's own mergeability (#2260). Shared by primary + fallback1.
94
95
  */
96
+ function loadInlineGreptileFindings(prNumber, repo, headSha, runGh) {
97
+ const resolved = resolveRepo(repo, runGh);
98
+ if (resolved.repo === null) {
99
+ return {
100
+ p0Count: 0,
101
+ p1Count: 0,
102
+ unresolvedThreadCount: 0,
103
+ error: resolved.error || "repo unresolved for inline reviewThreads lookup; pass --repo OWNER/REPO",
104
+ };
105
+ }
106
+ return fetchUnresolvedGreptileInlineFindings(prNumber, resolved.repo, headSha, runGh);
107
+ }
95
108
  function finalizeVerdictGate(prNumber, repo, headSha, verdict, runGh, options) {
96
- const failures = evaluateGates(prNumber, headSha, verdict);
97
109
  const partialData = {};
98
110
  const resolved = resolveRepo(repo, runGh);
111
+ const inline = loadInlineGreptileFindings(prNumber, repo, headSha, runGh);
112
+ partialData.greptile_inline = inlineFindingsToDict(inline);
113
+ const failures = evaluateGates(prNumber, headSha, verdict, inline);
99
114
  if (failures.length === 0) {
100
115
  const ci = applyCiGateForHead(resolved.repo, headSha, runGh, options);
101
116
  failures.push(...ci.failures);
@@ -108,7 +123,7 @@ function finalizeVerdictGate(prNumber, repo, headSha, verdict, runGh, options) {
108
123
  // blocking. Only reconcile a SOFT block (verdict absent / stale head SHA).
109
124
  if (options.disableMergeabilityReconcile === true ||
110
125
  resolved.repo === null ||
111
- !verdictBlockIsSoftOnly(verdict, headSha)) {
126
+ !verdictBlockIsSoftOnly(verdict, headSha, inline)) {
112
127
  return { failures, partialData };
113
128
  }
114
129
  const ci = applyCiGateForHead(resolved.repo, headSha, runGh, options);
@@ -143,17 +158,19 @@ function computePrimary(prNumber, repo, runGh, options) {
143
158
  return { result: null, partial };
144
159
  }
145
160
  partial.head_sha = headSha;
161
+ const resolved = resolveRepo(repo, runGh);
162
+ const effectiveRepo = resolved.repo ?? repo;
146
163
  const body = fetchGreptileCommentBody(prNumber, repo, runGh);
147
164
  if (body === null) {
148
165
  partial.primary_error = "gh api /issues/<N>/comments --jq returned non-zero";
149
166
  return { result: null, partial };
150
167
  }
151
168
  const verdict = parseGreptileBody(body);
152
- const { failures, partialData } = finalizeVerdictGate(prNumber, repo, headSha, verdict, runGh, options);
169
+ const { failures, partialData } = finalizeVerdictGate(prNumber, effectiveRepo, headSha, verdict, runGh, options);
153
170
  return {
154
171
  result: {
155
172
  prNumber,
156
- repo,
173
+ repo: effectiveRepo,
157
174
  headSha,
158
175
  verdict,
159
176
  failures,
@@ -1,5 +1,6 @@
1
+ import type { InlineGreptileFindings } from "./greptile-inline.js";
1
2
  import type { GreptileVerdict } from "./types.js";
2
3
  /** Return failure messages (empty list == merge-ready). */
3
- export declare function evaluateGates(_prNumber: number, headSha: string | null, verdict: GreptileVerdict): string[];
4
+ export declare function evaluateGates(_prNumber: number, headSha: string | null, verdict: GreptileVerdict, inline?: InlineGreptileFindings | null): string[];
4
5
  export declare function isMergeReady(failures: readonly string[]): boolean;
5
6
  //# sourceMappingURL=evaluate.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { INFORMAL_CLEAN_DIAGNOSTIC } from "./constants.js";
2
2
  /** Return failure messages (empty list == merge-ready). */
3
- export function evaluateGates(_prNumber, headSha, verdict) {
3
+ export function evaluateGates(_prNumber, headSha, verdict, inline = null) {
4
4
  const failures = [];
5
5
  if (!verdict.found) {
6
6
  failures.push("No Greptile rolling-summary comment found on the PR. " +
@@ -8,40 +8,55 @@ export function evaluateGates(_prNumber, headSha, verdict) {
8
8
  "Wait for the review to land before merging (see #796 late-bot-review re-check).");
9
9
  return failures;
10
10
  }
11
- if (verdict.excludedAuthor) {
12
- return failures;
13
- }
14
- if (verdict.errored) {
15
- failures.push("Greptile review is in the ERRORED state on the current HEAD (#526). " +
16
- "Retry via @greptileai or escalate per " +
17
- "skills/deft-directive-swarm/SKILL.md Phase 6 Step 1.");
11
+ if (!verdict.excludedAuthor) {
12
+ if (verdict.errored) {
13
+ failures.push("Greptile review is in the ERRORED state on the current HEAD (#526). " +
14
+ "Retry via @greptileai or escalate per " +
15
+ "skills/deft-directive-swarm/SKILL.md Phase 6 Step 1.");
16
+ }
17
+ if (verdict.informalClean) {
18
+ failures.push(INFORMAL_CLEAN_DIAGNOSTIC);
19
+ return appendInlineFailures(failures, inline);
20
+ }
21
+ if (verdict.lastReviewedSha === null) {
22
+ failures.push("Could not parse `Last reviewed commit:` from Greptile body. " +
23
+ "The comment may be malformed or Greptile may still be writing it -- re-fetch.");
24
+ }
25
+ else if (headSha &&
26
+ !(headSha.startsWith(verdict.lastReviewedSha) || verdict.lastReviewedSha.startsWith(headSha))) {
27
+ failures.push(`Greptile last reviewed ${verdict.lastReviewedSha} but PR HEAD is ${headSha}. ` +
28
+ "Review is stale -- wait for Greptile to re-review the latest commit.");
29
+ }
30
+ if (verdict.confidence === null) {
31
+ failures.push("Could not parse `Confidence Score: X/5` from Greptile body. " +
32
+ "Confidence is a required exit-condition input per " +
33
+ "skills/deft-directive-review-cycle/SKILL.md Phase 2 Step 6.");
34
+ }
35
+ else if (verdict.confidence <= 3) {
36
+ failures.push(`Greptile confidence is ${verdict.confidence}/5; exit condition requires > 3. ` +
37
+ "Address remaining findings or push clarifying changes.");
38
+ }
39
+ if (verdict.p0Count > 0 || verdict.p1Count > 0) {
40
+ failures.push(`Greptile reports ${verdict.p0Count} P0 and ${verdict.p1Count} P1 findings ` +
41
+ "on the current HEAD. All P0 / P1 findings MUST be addressed before merge " +
42
+ "(P2 findings are non-blocking).");
43
+ }
18
44
  }
19
- if (verdict.informalClean) {
20
- failures.push(INFORMAL_CLEAN_DIAGNOSTIC);
45
+ return appendInlineFailures(failures, inline);
46
+ }
47
+ function appendInlineFailures(failures, inline) {
48
+ if (inline === null) {
21
49
  return failures;
22
50
  }
23
- if (verdict.lastReviewedSha === null) {
24
- failures.push("Could not parse `Last reviewed commit:` from Greptile body. " +
25
- "The comment may be malformed or Greptile may still be writing it -- re-fetch.");
26
- }
27
- else if (headSha &&
28
- !(headSha.startsWith(verdict.lastReviewedSha) || verdict.lastReviewedSha.startsWith(headSha))) {
29
- failures.push(`Greptile last reviewed ${verdict.lastReviewedSha} but PR HEAD is ${headSha}. ` +
30
- "Review is stale -- wait for Greptile to re-review the latest commit.");
31
- }
32
- if (verdict.confidence === null) {
33
- failures.push("Could not parse `Confidence Score: X/5` from Greptile body. " +
34
- "Confidence is a required exit-condition input per " +
35
- "skills/deft-directive-review-cycle/SKILL.md Phase 2 Step 6.");
36
- }
37
- else if (verdict.confidence <= 3) {
38
- failures.push(`Greptile confidence is ${verdict.confidence}/5; exit condition requires > 3. ` +
39
- "Address remaining findings or push clarifying changes.");
51
+ if (inline.error !== null) {
52
+ failures.push("Could not verify Greptile inline review comments on the current HEAD (#2620). " +
53
+ `Root cause: ${inline.error}`);
40
54
  }
41
- if (verdict.p0Count > 0 || verdict.p1Count > 0) {
42
- failures.push(`Greptile reports ${verdict.p0Count} P0 and ${verdict.p1Count} P1 findings ` +
43
- "on the current HEAD. All P0 / P1 findings MUST be addressed before merge " +
44
- "(P2 findings are non-blocking).");
55
+ else if (inline.p0Count > 0 || inline.p1Count > 0) {
56
+ failures.push(`Greptile has ${inline.p0Count} unresolved inline P0 and ${inline.p1Count} unresolved inline P1 ` +
57
+ `review comment(s) on the current HEAD across ${inline.unresolvedThreadCount} open thread(s). ` +
58
+ "Resolve or address all blocking inline threads before merge -- rolling-summary badge counts alone " +
59
+ "are insufficient (#2620).");
45
60
  }
46
61
  return failures;
47
62
  }
@@ -0,0 +1,27 @@
1
+ import type { RunGhFn } from "./types.js";
2
+ /** Unresolved Greptile inline P0/P1 on the current PR HEAD (#2620). */
3
+ export interface InlineGreptileFindings {
4
+ readonly p0Count: number;
5
+ readonly p1Count: number;
6
+ readonly unresolvedThreadCount: number;
7
+ readonly error: string | null;
8
+ }
9
+ export interface InlineReviewComment {
10
+ readonly authorLogin: string;
11
+ readonly body: string;
12
+ readonly path: string | null;
13
+ readonly commitOid: string | null;
14
+ }
15
+ export interface InlineReviewThread {
16
+ readonly isResolved: boolean;
17
+ readonly isOutdated: boolean;
18
+ readonly comments: readonly InlineReviewComment[];
19
+ }
20
+ /** True when a review comment commit SHA matches the current PR head (#2620 AC-2). */
21
+ export declare function headShaMatches(commentSha: string, headSha: string): boolean;
22
+ export declare function inlineFindingsToDict(findings: InlineGreptileFindings): Record<string, unknown>;
23
+ /** Score unresolved Greptile inline P0/P1 threads pinned to the current HEAD (#2620). */
24
+ export declare function evaluateInlineReviewThreads(threads: readonly InlineReviewThread[], headSha: string): InlineGreptileFindings;
25
+ /** Fetch unresolved Greptile inline P0/P1 on the current HEAD via reviewThreads GraphQL (#2620). */
26
+ export declare function fetchUnresolvedGreptileInlineFindings(prNumber: number, repo: string, headSha: string, runGh: RunGhFn): InlineGreptileFindings;
27
+ //# sourceMappingURL=greptile-inline.d.ts.map