agentic-devtools 0.2.415__py3-none-any.whl → 0.2.417__py3-none-any.whl
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.
- agentic_devtools/_version.py +2 -2
- agentic_devtools/cli/ci/pipeline/actions/apply_suggestions.py +3 -3
- agentic_devtools/cli/ci/pipeline/actions/dispatch_repair.py +166 -46
- agentic_devtools/cli/ci/pipeline/actions/resolve_threads.py +20 -7
- agentic_devtools/cli/ci/pipeline/gate_verdict.py +5 -5
- agentic_devtools/cli/ci/pipeline/snapshot.py +144 -51
- {agentic_devtools-0.2.415.dist-info → agentic_devtools-0.2.417.dist-info}/METADATA +1 -1
- {agentic_devtools-0.2.415.dist-info → agentic_devtools-0.2.417.dist-info}/RECORD +11 -11
- {agentic_devtools-0.2.415.dist-info → agentic_devtools-0.2.417.dist-info}/WHEEL +0 -0
- {agentic_devtools-0.2.415.dist-info → agentic_devtools-0.2.417.dist-info}/entry_points.txt +0 -0
- {agentic_devtools-0.2.415.dist-info → agentic_devtools-0.2.417.dist-info}/licenses/LICENSE +0 -0
agentic_devtools/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.417'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 417)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -87,10 +87,10 @@ class ApplySuggestionsAction:
|
|
|
87
87
|
snapshot.review_state == "CHANGES_REQUESTED"
|
|
88
88
|
or (snapshot.review_state == "COMMENTED" and snapshot.copilot_review_inline_count != 0)
|
|
89
89
|
)
|
|
90
|
-
|
|
91
|
-
preconditions["has_actionable_review"] = has_actionable_review or
|
|
90
|
+
has_repairable_threads = snapshot.repairable_threads > 0
|
|
91
|
+
preconditions["has_actionable_review"] = has_actionable_review or has_repairable_threads
|
|
92
92
|
|
|
93
|
-
if not has_actionable_review and not
|
|
93
|
+
if not has_actionable_review and not has_repairable_threads:
|
|
94
94
|
return ActionResult(
|
|
95
95
|
name=self.name,
|
|
96
96
|
decision=ActionDecision.SKIP,
|
|
@@ -9,7 +9,7 @@ from agentic_devtools.cli.ci.guards import (
|
|
|
9
9
|
check_deduplication,
|
|
10
10
|
is_duplicate_trigger,
|
|
11
11
|
)
|
|
12
|
-
from agentic_devtools.cli.ci.models import ReviewInfo
|
|
12
|
+
from agentic_devtools.cli.ci.models import ReviewCommentInfo, ReviewInfo
|
|
13
13
|
from agentic_devtools.cli.ci.pipeline.deferral import read_active_suppressed_deferral
|
|
14
14
|
from agentic_devtools.cli.ci.pipeline.exclusion import ExclusionContext
|
|
15
15
|
from agentic_devtools.cli.ci.pipeline.gate_verdict import (
|
|
@@ -17,10 +17,14 @@ from agentic_devtools.cli.ci.pipeline.gate_verdict import (
|
|
|
17
17
|
REASON_NEW_CCR_NOT_APPROVED,
|
|
18
18
|
REASON_SUPPRESSED_COMMENTS,
|
|
19
19
|
REASON_UNPARSED_SUPPRESSION,
|
|
20
|
-
|
|
20
|
+
is_copilot_or_synthetic_review,
|
|
21
21
|
)
|
|
22
22
|
from agentic_devtools.cli.ci.pipeline.models import ActionDecision, ActionResult
|
|
23
|
-
from agentic_devtools.cli.ci.pipeline.snapshot import
|
|
23
|
+
from agentic_devtools.cli.ci.pipeline.snapshot import (
|
|
24
|
+
REPAIRABLE_REVIEW_STATES,
|
|
25
|
+
DerivedState,
|
|
26
|
+
PRStateSnapshot,
|
|
27
|
+
)
|
|
24
28
|
from agentic_devtools.cli.ci.provider import CIPlatformProvider
|
|
25
29
|
from agentic_devtools.cli.github.ccr_review_format import parse_suppressed_count
|
|
26
30
|
|
|
@@ -98,16 +102,13 @@ def _is_copilot_review_actionable(snapshot: PRStateSnapshot) -> bool:
|
|
|
98
102
|
return _actionable_copilot_review_id(snapshot) > 0
|
|
99
103
|
|
|
100
104
|
|
|
101
|
-
def
|
|
102
|
-
"""Return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
verdict = snapshot.copilot_gate_verdict
|
|
109
|
-
verdict_review_id = verdict.review_id if verdict is not None else 0
|
|
110
|
-
return select_prior_actionable_reviews(snapshot.reviews, verdict_review_id)
|
|
105
|
+
def _list_repairable_copilot_reviews(snapshot: PRStateSnapshot) -> list[ReviewInfo]:
|
|
106
|
+
"""Return Copilot/synthetic reviews that can own repairable unresolved threads."""
|
|
107
|
+
return [
|
|
108
|
+
review
|
|
109
|
+
for review in snapshot.reviews
|
|
110
|
+
if is_copilot_or_synthetic_review(review) and review.state in REPAIRABLE_REVIEW_STATES
|
|
111
|
+
]
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
def _declared_suppressed_counts_by_review(snapshot: PRStateSnapshot, review_ids: list[int]) -> dict[int, int]:
|
|
@@ -134,11 +135,77 @@ def _has_stuck_prior_review_threads(snapshot: PRStateSnapshot) -> bool:
|
|
|
134
135
|
return (
|
|
135
136
|
snapshot.ci_status == "passing"
|
|
136
137
|
and snapshot.copilot_review_id == 0
|
|
137
|
-
and snapshot.
|
|
138
|
-
and bool(
|
|
138
|
+
and snapshot.repairable_threads > 0
|
|
139
|
+
and bool(_list_repairable_copilot_reviews(snapshot))
|
|
139
140
|
)
|
|
140
141
|
|
|
141
142
|
|
|
143
|
+
def _select_repairable_thread_owner_reviews(
|
|
144
|
+
provider: CIPlatformProvider,
|
|
145
|
+
snapshot: PRStateSnapshot,
|
|
146
|
+
repairable_reviews: list[ReviewInfo],
|
|
147
|
+
) -> tuple[list[ReviewInfo], dict[int, list[ReviewCommentInfo]]]:
|
|
148
|
+
"""Return reviews that own unresolved repairable threads, plus fetched comments."""
|
|
149
|
+
thread_lookup = getattr(provider, "list_review_threads_by_thread_id", None)
|
|
150
|
+
if not callable(thread_lookup):
|
|
151
|
+
return repairable_reviews, {}
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
thread_states = thread_lookup(snapshot.pr_number)
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.warning(
|
|
157
|
+
"PR #%d: Failed to derive repairable-thread ownership from thread state: %s",
|
|
158
|
+
snapshot.pr_number,
|
|
159
|
+
exc,
|
|
160
|
+
)
|
|
161
|
+
return repairable_reviews, {}
|
|
162
|
+
|
|
163
|
+
if not isinstance(thread_states, dict):
|
|
164
|
+
return repairable_reviews, {}
|
|
165
|
+
|
|
166
|
+
# Validate the entire mapping before processing: fall back to all candidates on any
|
|
167
|
+
# malformed entry so a mixed valid/malformed mapping never silently excludes an owning
|
|
168
|
+
# review from the repair payload.
|
|
169
|
+
for state in thread_states.values():
|
|
170
|
+
if not (
|
|
171
|
+
isinstance(state, tuple)
|
|
172
|
+
and len(state) == 2
|
|
173
|
+
and type(state[0]) is bool
|
|
174
|
+
and isinstance(state[1], tuple)
|
|
175
|
+
and all(type(c) is int and c >= 0 for c in state[1])
|
|
176
|
+
):
|
|
177
|
+
logger.warning(
|
|
178
|
+
"PR #%d: Malformed thread state entry detected; falling back to all repairable reviews.",
|
|
179
|
+
snapshot.pr_number,
|
|
180
|
+
)
|
|
181
|
+
return repairable_reviews, {}
|
|
182
|
+
|
|
183
|
+
unresolved_comment_ids = {comment_id for state in thread_states.values() if not state[0] for comment_id in state[1]}
|
|
184
|
+
if not unresolved_comment_ids:
|
|
185
|
+
return repairable_reviews, {}
|
|
186
|
+
|
|
187
|
+
owner_ids: set[int] = set()
|
|
188
|
+
comments_by_review_id: dict[int, list[ReviewCommentInfo]] = {}
|
|
189
|
+
for review in repairable_reviews:
|
|
190
|
+
try:
|
|
191
|
+
comments = provider.list_review_comments(snapshot.pr_number, review.id)
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
logger.warning(
|
|
194
|
+
"PR #%d: Failed to derive repairable-thread ownership from review %d comments: %s",
|
|
195
|
+
snapshot.pr_number,
|
|
196
|
+
review.id,
|
|
197
|
+
exc,
|
|
198
|
+
)
|
|
199
|
+
return repairable_reviews, {}
|
|
200
|
+
comments_by_review_id[review.id] = comments
|
|
201
|
+
if any(comment.id in unresolved_comment_ids for comment in comments if comment.id >= 0):
|
|
202
|
+
owner_ids.add(review.id)
|
|
203
|
+
|
|
204
|
+
if not owner_ids:
|
|
205
|
+
return repairable_reviews, comments_by_review_id
|
|
206
|
+
return [review for review in repairable_reviews if review.id in owner_ids], comments_by_review_id
|
|
207
|
+
|
|
208
|
+
|
|
142
209
|
class DispatchRepairAction:
|
|
143
210
|
"""Dispatch a repair when CI fails or actionable review feedback exists.
|
|
144
211
|
|
|
@@ -252,23 +319,74 @@ class DispatchRepairAction:
|
|
|
252
319
|
|
|
253
320
|
actionable_review_id = _actionable_copilot_review_id(snapshot)
|
|
254
321
|
review_actionable = actionable_review_id > 0
|
|
255
|
-
|
|
256
|
-
|
|
322
|
+
repairable_reviews = _list_repairable_copilot_reviews(snapshot)
|
|
323
|
+
repairable_reviews.sort(
|
|
257
324
|
key=lambda r: (
|
|
258
325
|
r.submitted_at if isinstance(r.submitted_at, str) else "",
|
|
259
326
|
r.id,
|
|
260
327
|
),
|
|
261
328
|
reverse=True,
|
|
262
329
|
)
|
|
330
|
+
repairable_owner_reviews = repairable_reviews
|
|
331
|
+
ownership_comments_cache: dict[int, list[ReviewCommentInfo]] = {}
|
|
263
332
|
stuck_prior_threads = _has_stuck_prior_review_threads(snapshot)
|
|
333
|
+
if stuck_prior_threads and repairable_reviews:
|
|
334
|
+
repairable_owner_reviews, ownership_comments_cache = _select_repairable_thread_owner_reviews(
|
|
335
|
+
provider,
|
|
336
|
+
snapshot,
|
|
337
|
+
repairable_reviews,
|
|
338
|
+
)
|
|
264
339
|
ci_failing = snapshot.ci_status == "failing"
|
|
265
340
|
ci_passing = snapshot.ci_status == "passing"
|
|
266
341
|
# Use the *effective* actionable review id (the review the gate evaluated),
|
|
267
342
|
# which may differ from ``copilot_review_id`` for a new-CCR review or a
|
|
268
343
|
# prior-commit review selected by diff-hash freshness.
|
|
269
|
-
review_context_id =
|
|
270
|
-
|
|
271
|
-
|
|
344
|
+
review_context_id = actionable_review_id
|
|
345
|
+
# True when the multi-owner loop below already checked is_duplicate_trigger for the
|
|
346
|
+
# chosen review_context_id; suppresses the redundant general check that follows.
|
|
347
|
+
_review_context_dedup_checked = False
|
|
348
|
+
if not review_actionable and stuck_prior_threads and repairable_owner_reviews:
|
|
349
|
+
# Find the first owner review that does not already have an active trigger
|
|
350
|
+
# marker. Picking [0] blindly stalls the pipeline permanently when [0] is
|
|
351
|
+
# already marked but a later owner has never been dispatched.
|
|
352
|
+
chosen_id = 0
|
|
353
|
+
all_owners_duplicated = True
|
|
354
|
+
for _candidate in repairable_owner_reviews:
|
|
355
|
+
try:
|
|
356
|
+
if not is_duplicate_trigger(provider, snapshot.pr_number, _candidate.id):
|
|
357
|
+
chosen_id = _candidate.id
|
|
358
|
+
all_owners_duplicated = False
|
|
359
|
+
_review_context_dedup_checked = True
|
|
360
|
+
break
|
|
361
|
+
except Exception as _exc:
|
|
362
|
+
logger.warning(
|
|
363
|
+
"PR #%d: Review-ID dedup check failed for candidate review %d: %s",
|
|
364
|
+
snapshot.pr_number,
|
|
365
|
+
_candidate.id,
|
|
366
|
+
_exc,
|
|
367
|
+
)
|
|
368
|
+
# Fail-open: use this candidate and proceed with dispatch.
|
|
369
|
+
# Mark as checked so the general dedup block below does not retry
|
|
370
|
+
# the same call on a transient API error.
|
|
371
|
+
chosen_id = _candidate.id
|
|
372
|
+
all_owners_duplicated = False
|
|
373
|
+
_review_context_dedup_checked = True
|
|
374
|
+
break
|
|
375
|
+
if all_owners_duplicated:
|
|
376
|
+
_all_ids = [r.id for r in repairable_owner_reviews]
|
|
377
|
+
logger.info(
|
|
378
|
+
"PR #%d: Trigger comment already exists for all repairable owner reviews %s — skipping",
|
|
379
|
+
snapshot.pr_number,
|
|
380
|
+
_all_ids,
|
|
381
|
+
)
|
|
382
|
+
return ActionResult(
|
|
383
|
+
name=self.name,
|
|
384
|
+
decision=ActionDecision.SKIP,
|
|
385
|
+
details=f"Repair already dispatched for all repairable owner reviews: {_all_ids}",
|
|
386
|
+
)
|
|
387
|
+
# Invariant: all_owners_duplicated is False only when a candidate break set chosen_id.
|
|
388
|
+
assert chosen_id > 0 # noqa: S101
|
|
389
|
+
review_context_id = chosen_id
|
|
272
390
|
if review_context_id > 0:
|
|
273
391
|
# Check for an active suppressed-comment deferral marker only when the
|
|
274
392
|
# deferred review is the sole repair reason. CI failures and stuck
|
|
@@ -295,25 +413,27 @@ class DispatchRepairAction:
|
|
|
295
413
|
exc,
|
|
296
414
|
)
|
|
297
415
|
|
|
298
|
-
# Check review-ID level deduplication
|
|
299
|
-
# Applies to
|
|
300
|
-
#
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
416
|
+
# Check review-ID level deduplication (FR-012).
|
|
417
|
+
# Applies to normal actionable reviews on HEAD and stuck prior-review thread
|
|
418
|
+
# repairs. Skipped when the multi-owner loop above already confirmed that
|
|
419
|
+
# review_context_id is not duplicated, to avoid a redundant double call.
|
|
420
|
+
if not _review_context_dedup_checked:
|
|
421
|
+
try:
|
|
422
|
+
if is_duplicate_trigger(provider, snapshot.pr_number, review_context_id):
|
|
423
|
+
logger.info(
|
|
424
|
+
"PR #%d: Trigger comment already exists for review_id=%d — skipping",
|
|
425
|
+
snapshot.pr_number,
|
|
426
|
+
review_context_id,
|
|
427
|
+
)
|
|
428
|
+
return ActionResult(
|
|
429
|
+
name=self.name,
|
|
430
|
+
decision=ActionDecision.SKIP,
|
|
431
|
+
details=f"Repair already dispatched for review_id={review_context_id}",
|
|
432
|
+
)
|
|
433
|
+
except Exception as exc:
|
|
434
|
+
logger.warning("PR #%d: Review-ID dedup check failed: %s", snapshot.pr_number, exc)
|
|
435
|
+
# Fail-open: proceed with dispatch on transient API failures; the
|
|
436
|
+
# review-ID dedup guard is best-effort and should not block repair.
|
|
317
437
|
|
|
318
438
|
dedup_kwargs = {"max_dispatches": 1} if ci_failing and not (review_actionable or stuck_prior_threads) else {}
|
|
319
439
|
|
|
@@ -388,12 +508,9 @@ class DispatchRepairAction:
|
|
|
388
508
|
declared_author_comment_counts_by_review: dict[int, int] = {}
|
|
389
509
|
if review_repair_needed and review_context_id:
|
|
390
510
|
if stuck_prior_threads:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
# review_context_id is already present in prior_ids.
|
|
395
|
-
extra = [review_context_id] if review_actionable and review_context_id not in prior_ids else []
|
|
396
|
-
review_ids = prior_ids + extra
|
|
511
|
+
review_ids = [r.id for r in repairable_owner_reviews]
|
|
512
|
+
if review_context_id not in review_ids:
|
|
513
|
+
review_ids.append(review_context_id)
|
|
397
514
|
else:
|
|
398
515
|
review_ids = [review_context_id]
|
|
399
516
|
declared_author_comment_counts_by_review = _declared_suppressed_counts_by_review(snapshot, review_ids)
|
|
@@ -401,7 +518,10 @@ class DispatchRepairAction:
|
|
|
401
518
|
seen_comment_keys: set[tuple[int, int]] = set()
|
|
402
519
|
for review_id in review_ids:
|
|
403
520
|
try:
|
|
404
|
-
|
|
521
|
+
comments = ownership_comments_cache.get(review_id)
|
|
522
|
+
if comments is None:
|
|
523
|
+
comments = provider.list_review_comments(snapshot.pr_number, review_id)
|
|
524
|
+
for comment in comments:
|
|
405
525
|
dedup_key = (review_id, comment.id) if comment.id < 0 else (0, comment.id)
|
|
406
526
|
if dedup_key in seen_comment_keys:
|
|
407
527
|
continue
|
|
@@ -111,7 +111,7 @@ class ResolveThreadsAction:
|
|
|
111
111
|
from agentic_devtools.cli.ci.pipeline.gate_verdict import select_prior_actionable_reviews
|
|
112
112
|
|
|
113
113
|
verdict = snapshot.copilot_gate_verdict
|
|
114
|
-
verdict_review_id = verdict.review_id if verdict is not None else
|
|
114
|
+
verdict_review_id = verdict.review_id if verdict is not None and verdict.review_id > 0 else None
|
|
115
115
|
prior_reviews = select_prior_actionable_reviews(snapshot.reviews, verdict_review_id)
|
|
116
116
|
|
|
117
117
|
if not prior_reviews:
|
|
@@ -175,23 +175,36 @@ class ResolveThreadsAction:
|
|
|
175
175
|
# same source the snapshot uses. The provider's thread-signals cache is
|
|
176
176
|
# invalidated by the resolve/unresolve mutations, so this observes the
|
|
177
177
|
# post-resolution state.
|
|
178
|
-
|
|
179
|
-
provider,
|
|
178
|
+
requeried_blocking, requeried_repairable, degraded, unknown_provenance = count_unresolved_prior_threads(
|
|
179
|
+
provider,
|
|
180
|
+
snapshot.pr_number,
|
|
181
|
+
snapshot.reviews,
|
|
182
|
+
verdict_review_id,
|
|
183
|
+
verdict=verdict,
|
|
180
184
|
)
|
|
181
185
|
if degraded:
|
|
182
186
|
# Thread state is unavailable — fail closed with the conservative
|
|
183
187
|
# pre-re-query estimate and the helper's degraded sentinel rather
|
|
184
188
|
# than trusting an unknown count.
|
|
185
|
-
unresolved_total = max(
|
|
189
|
+
unresolved_total = max(requeried_blocking, unresolved, snapshot.unresolved_threads - resolved, 0)
|
|
190
|
+
# ``resolved`` is a per-review aggregate and can double-count the same
|
|
191
|
+
# underlying thread across reviews. Preserve the previously measured
|
|
192
|
+
# repairable inventory when re-query is degraded instead of subtracting
|
|
193
|
+
# this non-deduplicated value.
|
|
194
|
+
repairable_total = max(requeried_repairable, snapshot.repairable_threads, 0)
|
|
186
195
|
else:
|
|
187
|
-
unresolved_total =
|
|
196
|
+
unresolved_total = requeried_blocking
|
|
197
|
+
repairable_total = requeried_repairable
|
|
198
|
+
|
|
188
199
|
derived.set("unresolved_threads", unresolved_total)
|
|
189
|
-
|
|
190
|
-
#
|
|
200
|
+
derived.set("repairable_threads", repairable_total)
|
|
201
|
+
# Propagate the re-query's degraded and unknown_provenance status so downstream
|
|
202
|
+
# actions (approve, merge) know whether the thread count is authoritative.
|
|
191
203
|
# A failed re-query after a healthy snapshot must mark state as degraded
|
|
192
204
|
# (not leave the snapshot's False in place); a successful re-query
|
|
193
205
|
# replaces whatever the snapshot held.
|
|
194
206
|
derived.set("unresolved_threads_degraded", degraded)
|
|
207
|
+
derived.set("unresolved_threads_unknown_provenance", unknown_provenance)
|
|
195
208
|
|
|
196
209
|
details = f"Resolved {resolved} thread(s), {unresolved_total} left open"
|
|
197
210
|
if suppressed:
|
|
@@ -503,7 +503,7 @@ def copilot_review_gate_passed(
|
|
|
503
503
|
)
|
|
504
504
|
|
|
505
505
|
|
|
506
|
-
def select_prior_actionable_reviews(reviews: list[ReviewInfo], verdict_review_id: int) -> list[ReviewInfo]:
|
|
506
|
+
def select_prior_actionable_reviews(reviews: list[ReviewInfo], verdict_review_id: int | None) -> list[ReviewInfo]:
|
|
507
507
|
"""Return actionable Copilot/synthetic reviews not already owned by the gate verdict.
|
|
508
508
|
|
|
509
509
|
This is the **single** definition of the "which reviews count as prior" predicate.
|
|
@@ -520,11 +520,11 @@ def select_prior_actionable_reviews(reviews: list[ReviewInfo], verdict_review_id
|
|
|
520
520
|
thread (e.g. ``squash``/``takeover`` minting a new commit SHA) does not change
|
|
521
521
|
the selection, unlike a HEAD-commit-SHA comparison.
|
|
522
522
|
|
|
523
|
-
Fails closed: when
|
|
524
|
-
|
|
525
|
-
|
|
523
|
+
Fails closed: when provenance is unknown (``verdict_review_id`` is ``None`` or
|
|
524
|
+
``<= 0``) every actionable review is treated as prior — i.e. this degenerates
|
|
525
|
+
to counting all of them, never fewer. Input order is preserved.
|
|
526
526
|
"""
|
|
527
|
-
if verdict_review_id <= 0:
|
|
527
|
+
if verdict_review_id is None or verdict_review_id <= 0:
|
|
528
528
|
return [r for r in reviews if is_copilot_or_synthetic_review(r) and r.state in _ACTIONABLE_PRIOR_REVIEW_STATES]
|
|
529
529
|
return [
|
|
530
530
|
r
|
|
@@ -61,6 +61,17 @@ class PRStateSnapshot:
|
|
|
61
61
|
mapping omitted selected comments, or a review's comments could not be
|
|
62
62
|
listed), so ``unresolved_threads`` is unknown. The gate fails closed on this signal and
|
|
63
63
|
the run summary reports "degraded / unknown" instead of a fabricated integer.
|
|
64
|
+
unresolved_threads_unknown_provenance: Whether the gate-verdict review ID could not be
|
|
65
|
+
resolved when ``unresolved_threads`` was computed, so the count covers *all*
|
|
66
|
+
actionable reviews (the conservative count-all floor) rather than being scoped
|
|
67
|
+
to reviews whose ID differs from the verdict's owner. The count is still
|
|
68
|
+
authoritative — it is the maximum possible value — but consumers can use this
|
|
69
|
+
flag to emit extra diagnostics. No additional gate blocking is required: the
|
|
70
|
+
count-all strategy is already the most conservative possible reading.
|
|
71
|
+
repairable_threads: Number of all unresolved Copilot review threads from any
|
|
72
|
+
actionable Copilot review (current or prior). Unlike ``unresolved_threads``,
|
|
73
|
+
this is independent of the gate verdict and is used solely to determine if
|
|
74
|
+
there is repairable work, decoupled from whether those threads are blocking.
|
|
64
75
|
labels: List of labels on the PR.
|
|
65
76
|
is_draft: Whether the PR is a draft.
|
|
66
77
|
mergeable: Whether the PR is mergeable (None if unknown).
|
|
@@ -134,6 +145,8 @@ class PRStateSnapshot:
|
|
|
134
145
|
copilot_review_pending: bool = False
|
|
135
146
|
unresolved_threads: int = 0
|
|
136
147
|
unresolved_threads_degraded: bool = False
|
|
148
|
+
repairable_threads: int = 0
|
|
149
|
+
unresolved_threads_unknown_provenance: bool = False
|
|
137
150
|
labels: list[str] = field(default_factory=list)
|
|
138
151
|
is_draft: bool = False
|
|
139
152
|
mergeable: bool | None = None
|
|
@@ -195,6 +208,7 @@ class DerivedState:
|
|
|
195
208
|
|
|
196
209
|
|
|
197
210
|
_DEFAULT_ACTIONABLE_CHECK_NAMES = DEFAULT_ACTIONABLE_CHECK_NAMES
|
|
211
|
+
REPAIRABLE_REVIEW_STATES = frozenset({"CHANGES_REQUESTED", "COMMENTED", "APPROVED"})
|
|
198
212
|
|
|
199
213
|
|
|
200
214
|
def build_pr_state_snapshot(
|
|
@@ -336,8 +350,19 @@ def build_pr_state_snapshot(
|
|
|
336
350
|
# as "prior". When the verdict evaluation above failed closed, its review_id
|
|
337
351
|
# defaults to 0, so this degrades explicitly to count-all — the same
|
|
338
352
|
# fail-closed direction the verdict itself takes on exception.
|
|
339
|
-
|
|
340
|
-
|
|
353
|
+
effective_verdict_review_id = (
|
|
354
|
+
copilot_gate_verdict.review_id
|
|
355
|
+
if copilot_gate_verdict is not None and copilot_gate_verdict.review_id > 0
|
|
356
|
+
else None
|
|
357
|
+
)
|
|
358
|
+
unresolved_threads, repairable_threads, unresolved_threads_degraded, unresolved_threads_unknown_provenance = (
|
|
359
|
+
count_unresolved_prior_threads(
|
|
360
|
+
provider,
|
|
361
|
+
pr_number,
|
|
362
|
+
reviews,
|
|
363
|
+
effective_verdict_review_id,
|
|
364
|
+
verdict=copilot_gate_verdict,
|
|
365
|
+
)
|
|
341
366
|
)
|
|
342
367
|
|
|
343
368
|
# Whether the Copilot review actually evaluated by the gate still matches HEAD.
|
|
@@ -404,6 +429,8 @@ def build_pr_state_snapshot(
|
|
|
404
429
|
copilot_review_pending=copilot_review_pending,
|
|
405
430
|
unresolved_threads=unresolved_threads,
|
|
406
431
|
unresolved_threads_degraded=unresolved_threads_degraded,
|
|
432
|
+
repairable_threads=repairable_threads,
|
|
433
|
+
unresolved_threads_unknown_provenance=unresolved_threads_unknown_provenance,
|
|
407
434
|
labels=list(pr_meta.labels),
|
|
408
435
|
is_draft=pr_meta.is_draft,
|
|
409
436
|
mergeable=pr_meta.mergeable,
|
|
@@ -472,38 +499,93 @@ def _is_copilot_review_pending(requested_reviewers: list[str]) -> bool:
|
|
|
472
499
|
return any(is_copilot_login(reviewer) for reviewer in requested_reviewers)
|
|
473
500
|
|
|
474
501
|
|
|
502
|
+
def _effective_thread_owner_verdict_id(
|
|
503
|
+
verdict_review_id: int | None,
|
|
504
|
+
verdict: CopilotGateVerdict | None,
|
|
505
|
+
) -> int | None:
|
|
506
|
+
"""Resolve the effective thread-owner review ID for merge-gate provenance.
|
|
507
|
+
|
|
508
|
+
``None`` represents unknown provenance, which is the fail-closed signal for
|
|
509
|
+
the count-all path. When a verdict object is present it is authoritative:
|
|
510
|
+
a positive ``verdict.review_id`` is used, otherwise provenance remains
|
|
511
|
+
unknown. The legacy integer is used only when no verdict object is supplied.
|
|
512
|
+
"""
|
|
513
|
+
if verdict is not None:
|
|
514
|
+
if verdict.review_id > 0:
|
|
515
|
+
return verdict.review_id
|
|
516
|
+
return None
|
|
517
|
+
if verdict_review_id is not None and verdict_review_id > 0:
|
|
518
|
+
return verdict_review_id
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
|
|
475
522
|
def count_unresolved_prior_threads(
|
|
476
523
|
provider: CIPlatformProvider,
|
|
477
524
|
pr_number: int,
|
|
478
525
|
reviews: list[ReviewInfo],
|
|
479
|
-
verdict_review_id: int,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
526
|
+
verdict_review_id: int | None,
|
|
527
|
+
*,
|
|
528
|
+
verdict: CopilotGateVerdict | None = None,
|
|
529
|
+
) -> tuple[int, int, bool, bool]:
|
|
530
|
+
"""Count unresolved Copilot or trusted synthetic review threads.
|
|
531
|
+
|
|
532
|
+
Returns a ``(blocking_count, repairable_count, degraded, unknown_provenance)`` tuple.
|
|
533
|
+
``blocking_count`` (unresolved_threads) is the number of distinct review threads
|
|
534
|
+
from genuine Copilot or trusted synthetic reviews — other than the one the gate
|
|
535
|
+
verdict already accounts for (``verdict_review_id``) — whose provider-reported
|
|
536
|
+
state is unresolved.
|
|
537
|
+
|
|
538
|
+
``repairable_count`` (repairable_threads) is the total number of unresolved
|
|
539
|
+
threads from ALL genuine or trusted Copilot reviews (including APPROVED reviews),
|
|
540
|
+
regardless of whether they are owned by the gate verdict. This decoupled count
|
|
541
|
+
allows the repair agent to detect genuinely repairable state even when a gate
|
|
542
|
+
block zeroes out the blocking count.
|
|
487
543
|
|
|
488
544
|
Provenance is scoped to ``verdict_review_id``, not to HEAD's commit SHA: this
|
|
489
545
|
keeps the count HEAD-independent, so a HEAD move that touches no review or
|
|
490
546
|
thread (e.g. ``squash``/``takeover`` minting a new commit) cannot change it.
|
|
491
547
|
When ``verdict_review_id <= 0`` (no verdict evaluated, or evaluation failed
|
|
492
|
-
closed), this explicitly degenerates to counting every actionable review
|
|
493
|
-
the same fail-closed direction the verdict itself takes.
|
|
548
|
+
closed), this explicitly degenerates to counting every actionable review for
|
|
549
|
+
the blocking count — the same fail-closed direction the verdict itself takes.
|
|
494
550
|
|
|
495
551
|
When the provider cannot report review-thread state (the capability is
|
|
496
552
|
missing, the lookup failed, the returned mapping omits selected comments, or
|
|
497
|
-
a review's comments could not be listed), ``degraded`` is True
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
553
|
+
a review's comments could not be listed), ``degraded`` is True. Degradation is
|
|
554
|
+
tracked separately for blocking and repairable inventories:
|
|
555
|
+
``blocking_count`` is floored to ``1`` only when blocking inventory is unknown,
|
|
556
|
+
while ``repairable_count`` is floored to ``1`` when repairable inventory is
|
|
557
|
+
unknown and no unresolved repairable threads were measured. This prevents
|
|
558
|
+
false-open merge gates and also prevents repair dispatch from stalling behind
|
|
559
|
+
an unknown-but-possible repairable inventory.
|
|
560
|
+
Every degradation is logged, so no thread is ever dropped from the count
|
|
561
|
+
without a diagnostic.
|
|
502
562
|
"""
|
|
503
|
-
|
|
504
|
-
|
|
563
|
+
effective_review_id = _effective_thread_owner_verdict_id(verdict_review_id, verdict)
|
|
564
|
+
unknown_provenance = effective_review_id is None
|
|
565
|
+
if unknown_provenance:
|
|
566
|
+
logger.warning(
|
|
567
|
+
"PR #%d: no concrete thread-owner provenance for prior actionable reviews; "
|
|
568
|
+
"falling back to count-all (conservative floor — not a query failure)",
|
|
569
|
+
pr_number,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# All genuine/trusted Copilot reviews are candidates for repairable-thread counting,
|
|
573
|
+
# regardless of their verdict state (APPROVED reviews can still have unresolved threads).
|
|
574
|
+
all_copilot_reviews = [
|
|
575
|
+
r for r in reviews if is_copilot_or_synthetic_review(r) and r.state in REPAIRABLE_REVIEW_STATES
|
|
576
|
+
]
|
|
577
|
+
if not all_copilot_reviews:
|
|
505
578
|
# Nothing to report on — no capability was needed, so this is not degraded.
|
|
506
|
-
return 0, False
|
|
579
|
+
return 0, 0, False, unknown_provenance
|
|
580
|
+
|
|
581
|
+
# Blocking classification still uses the filtered (CHANGES_REQUESTED/COMMENTED) set.
|
|
582
|
+
blocking_review_ids = {r.id for r in select_prior_actionable_reviews(reviews, effective_review_id)}
|
|
583
|
+
|
|
584
|
+
def _unknown_inventory_fallback(unknown_provenance: bool) -> tuple[int, int, bool, bool]:
|
|
585
|
+
blocking_floor = 1 if blocking_review_ids else 0
|
|
586
|
+
# We have at least one repairable review candidate, but inventory is
|
|
587
|
+
# unknown; fail closed so repair paths do not stall.
|
|
588
|
+
return blocking_floor, 1, True, unknown_provenance
|
|
507
589
|
|
|
508
590
|
thread_lookup = getattr(provider, "list_review_threads_by_thread_id", None)
|
|
509
591
|
if not callable(thread_lookup):
|
|
@@ -512,7 +594,7 @@ def count_unresolved_prior_threads(
|
|
|
512
594
|
" — failing closed with an unknown-thread count",
|
|
513
595
|
pr_number,
|
|
514
596
|
)
|
|
515
|
-
return
|
|
597
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
516
598
|
|
|
517
599
|
try:
|
|
518
600
|
thread_states = thread_lookup(pr_number)
|
|
@@ -522,14 +604,14 @@ def count_unresolved_prior_threads(
|
|
|
522
604
|
" — failing closed with an unknown-thread count",
|
|
523
605
|
pr_number,
|
|
524
606
|
)
|
|
525
|
-
return
|
|
607
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
526
608
|
except Exception as exc:
|
|
527
609
|
logger.warning(
|
|
528
610
|
"PR #%d: review-thread state unavailable (%s) — failing closed with an unknown-thread count",
|
|
529
611
|
pr_number,
|
|
530
612
|
str(exc)[:200],
|
|
531
613
|
)
|
|
532
|
-
return
|
|
614
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
533
615
|
|
|
534
616
|
if not isinstance(thread_states, dict):
|
|
535
617
|
logger.warning(
|
|
@@ -539,7 +621,7 @@ def count_unresolved_prior_threads(
|
|
|
539
621
|
pr_number,
|
|
540
622
|
type(thread_states).__name__,
|
|
541
623
|
)
|
|
542
|
-
return
|
|
624
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
543
625
|
|
|
544
626
|
comment_to_thread: dict[int, str] = {}
|
|
545
627
|
for thread_key, state in thread_states.items():
|
|
@@ -552,7 +634,7 @@ def count_unresolved_prior_threads(
|
|
|
552
634
|
pr_number,
|
|
553
635
|
thread_key,
|
|
554
636
|
)
|
|
555
|
-
return
|
|
637
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
556
638
|
if not isinstance(state, tuple) or len(state) != 2 or type(state[0]) is not bool:
|
|
557
639
|
logger.warning(
|
|
558
640
|
"PR #%d: review-thread state unavailable "
|
|
@@ -563,7 +645,7 @@ def count_unresolved_prior_threads(
|
|
|
563
645
|
thread_key,
|
|
564
646
|
state,
|
|
565
647
|
)
|
|
566
|
-
return
|
|
648
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
567
649
|
comment_ids = state[1]
|
|
568
650
|
if not isinstance(comment_ids, tuple):
|
|
569
651
|
logger.warning(
|
|
@@ -572,7 +654,7 @@ def count_unresolved_prior_threads(
|
|
|
572
654
|
" — failing closed with an unknown-thread count",
|
|
573
655
|
pr_number,
|
|
574
656
|
)
|
|
575
|
-
return
|
|
657
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
576
658
|
for comment_id in comment_ids:
|
|
577
659
|
if type(comment_id) is not int:
|
|
578
660
|
logger.warning(
|
|
@@ -584,7 +666,7 @@ def count_unresolved_prior_threads(
|
|
|
584
666
|
thread_key,
|
|
585
667
|
comment_id,
|
|
586
668
|
)
|
|
587
|
-
return
|
|
669
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
588
670
|
mapped_thread = comment_to_thread.get(comment_id)
|
|
589
671
|
if mapped_thread is not None and mapped_thread != thread_key:
|
|
590
672
|
logger.warning(
|
|
@@ -597,27 +679,34 @@ def count_unresolved_prior_threads(
|
|
|
597
679
|
mapped_thread,
|
|
598
680
|
thread_key,
|
|
599
681
|
)
|
|
600
|
-
return
|
|
682
|
+
return _unknown_inventory_fallback(unknown_provenance)
|
|
601
683
|
comment_to_thread[comment_id] = thread_key
|
|
602
684
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
685
|
+
total_blocking = 0
|
|
686
|
+
total_repairable = 0
|
|
687
|
+
blocking_inventory_unknown = False
|
|
688
|
+
repairable_inventory_unknown = False
|
|
689
|
+
counted_blocking_thread_ids: set[str] = set()
|
|
690
|
+
counted_repairable_thread_ids: set[str] = set()
|
|
691
|
+
|
|
692
|
+
for review in all_copilot_reviews:
|
|
693
|
+
is_blocking = review.id in blocking_review_ids
|
|
608
694
|
try:
|
|
609
|
-
comments = provider.list_review_comments(pr_number,
|
|
695
|
+
comments = provider.list_review_comments(pr_number, review.id)
|
|
610
696
|
for comment in comments:
|
|
611
697
|
if comment.id < 0:
|
|
612
698
|
continue # Skip synthetic review-body entries
|
|
613
699
|
if comment.id in comment_to_thread:
|
|
614
700
|
thread_id = comment_to_thread[comment.id]
|
|
615
|
-
if thread_id in counted_thread_ids:
|
|
616
|
-
continue
|
|
617
|
-
counted_thread_ids.add(thread_id)
|
|
618
701
|
is_resolved = thread_states[thread_id][0]
|
|
702
|
+
|
|
619
703
|
if not is_resolved:
|
|
620
|
-
|
|
704
|
+
if thread_id not in counted_repairable_thread_ids:
|
|
705
|
+
counted_repairable_thread_ids.add(thread_id)
|
|
706
|
+
total_repairable += 1
|
|
707
|
+
if is_blocking and thread_id not in counted_blocking_thread_ids:
|
|
708
|
+
counted_blocking_thread_ids.add(thread_id)
|
|
709
|
+
total_blocking += 1
|
|
621
710
|
continue
|
|
622
711
|
|
|
623
712
|
logger.warning(
|
|
@@ -625,28 +714,32 @@ def count_unresolved_prior_threads(
|
|
|
625
714
|
pr_number,
|
|
626
715
|
comment.id,
|
|
627
716
|
)
|
|
628
|
-
|
|
717
|
+
repairable_inventory_unknown = True
|
|
718
|
+
if is_blocking:
|
|
719
|
+
blocking_inventory_unknown = True
|
|
629
720
|
except Exception as exc:
|
|
630
721
|
logger.warning(
|
|
631
722
|
"PR #%d: failed to list review comments for review %d (%s) — failing closed",
|
|
632
723
|
pr_number,
|
|
633
|
-
|
|
724
|
+
review.id,
|
|
634
725
|
str(exc)[:200],
|
|
635
726
|
)
|
|
636
|
-
|
|
727
|
+
repairable_inventory_unknown = True
|
|
728
|
+
if is_blocking:
|
|
729
|
+
blocking_inventory_unknown = True
|
|
637
730
|
|
|
638
|
-
if
|
|
639
|
-
|
|
640
|
-
if comments_unavailable:
|
|
641
|
-
if total_unresolved == 0:
|
|
731
|
+
if repairable_inventory_unknown or blocking_inventory_unknown:
|
|
732
|
+
if repairable_inventory_unknown and total_repairable == 0:
|
|
642
733
|
logger.warning(
|
|
643
|
-
"PR #%d: no unresolved threads were measured and at least one review
|
|
644
|
-
" — applying the
|
|
734
|
+
"PR #%d: no unresolved repairable threads were measured and at least one review inventory "
|
|
735
|
+
"is unknown — applying the repairable floor of 1 unresolved thread",
|
|
645
736
|
pr_number,
|
|
646
737
|
)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
738
|
+
total_repairable = 1
|
|
739
|
+
if blocking_inventory_unknown:
|
|
740
|
+
total_blocking = max(1, total_blocking)
|
|
741
|
+
return total_blocking, total_repairable, True, unknown_provenance
|
|
742
|
+
return total_blocking, total_repairable, False, unknown_provenance
|
|
650
743
|
|
|
651
744
|
|
|
652
745
|
def _count_commits(provider: CIPlatformProvider, *, base_branch: str, head_sha: str) -> int:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
agentic_devtools/__init__.py,sha256=J_Zw_vWKghk-cLmqI83hXQmSiS8zMhGIHM5WPLDkZuo,242
|
|
2
|
-
agentic_devtools/_version.py,sha256=
|
|
2
|
+
agentic_devtools/_version.py,sha256=X7xzJ5LaWItIS7kP80cFtX-9DK9edflj5atOwis4Joo,524
|
|
3
3
|
agentic_devtools/agdt_gitignore.py,sha256=aBPBQe7M0GLH8NIp1NsyN9ZiO80fNGOi46IcT5A4SK4,1569
|
|
4
4
|
agentic_devtools/background_tasks.py,sha256=IVC1XJKQzPBP8wCRNCZX_iZCg9FJY_GBUzddSJj7xqw,17473
|
|
5
5
|
agentic_devtools/config.py,sha256=DEVxTVZhsQbVQwO9qdB_1h19aBpjMzF6xJY_ePYLGLs,15629
|
|
@@ -169,25 +169,25 @@ agentic_devtools/cli/ci/pipeline/command.py,sha256=RJ_QUhQjSyEobqDUQnPuVMl0-rBWK
|
|
|
169
169
|
agentic_devtools/cli/ci/pipeline/deferral.py,sha256=Dga-CQevmTe1PYeUpvptZxbvD3g8jl7Q5kqgZSAY2dg,18305
|
|
170
170
|
agentic_devtools/cli/ci/pipeline/exceptions.py,sha256=4UXy2weeQZex_lp4sILNxlYfGjetxbR-tjms2ReT4iw,451
|
|
171
171
|
agentic_devtools/cli/ci/pipeline/exclusion.py,sha256=mrOG5G1-m3U-GM4YgtMTuzmoj02TXB6GbNxemRoLtV4,931
|
|
172
|
-
agentic_devtools/cli/ci/pipeline/gate_verdict.py,sha256=
|
|
172
|
+
agentic_devtools/cli/ci/pipeline/gate_verdict.py,sha256=stMgkDzVmn4Tr0yoyfPs7RXe7BpAWkecWFbY1baXEQg,44021
|
|
173
173
|
agentic_devtools/cli/ci/pipeline/models.py,sha256=GVhAXMbIEhkuECYUdk6WgZ7-lOOb5sCFn9MfG50iSqY,2456
|
|
174
174
|
agentic_devtools/cli/ci/pipeline/runner.py,sha256=YfK9QBFQg0ri0kCvYBek3LiQm7Mxv4pXPxiaC5PmIjk,13805
|
|
175
175
|
agentic_devtools/cli/ci/pipeline/session_detector.py,sha256=dWJyZIv7NcJNUl7TIsYqNfzNmZu3vAxHaQp1wMqe70o,8926
|
|
176
|
-
agentic_devtools/cli/ci/pipeline/snapshot.py,sha256=
|
|
176
|
+
agentic_devtools/cli/ci/pipeline/snapshot.py,sha256=MQ_drAJKZQ24pLJ7ZqDJk-feeg2dZlykTZMe11RfX0o,39144
|
|
177
177
|
agentic_devtools/cli/ci/pipeline/suggestions.py,sha256=nM4w1hfDFkT5_DAMMJMDZnFIvKhcvK1ainxcnAU_FkM,26777
|
|
178
178
|
agentic_devtools/cli/ci/pipeline/summary.py,sha256=ILCNHE845oiqGM6CA8NjaVKBVyxLNu5xTG8GXdhOy_U,13321
|
|
179
179
|
agentic_devtools/cli/ci/pipeline/actions/__init__.py,sha256=rY6e3yk5ybMINMSdtWvcwZI8I9GGxQAQ90J_dSjipr8,1577
|
|
180
|
-
agentic_devtools/cli/ci/pipeline/actions/apply_suggestions.py,sha256=
|
|
180
|
+
agentic_devtools/cli/ci/pipeline/actions/apply_suggestions.py,sha256=Y71W3QliyyAHpVGvOvipEezPW3KliYAqu5SW2ux-HBk,21197
|
|
181
181
|
agentic_devtools/cli/ci/pipeline/actions/approve.py,sha256=AHTm8Mf1kMaQcrS303ZalMaKzpEfYrtMdWDrisWT6kQ,9080
|
|
182
182
|
agentic_devtools/cli/ci/pipeline/actions/defer_suppressed.py,sha256=EZZMXyopk6oBQCjTRi769EsFGfTUeCqwfzBNNgeREHA,16178
|
|
183
183
|
agentic_devtools/cli/ci/pipeline/actions/dispatch_conflict_resolution.py,sha256=Dl6EoagioHq74bLURRB_5Mi36Od6JHkbRuG5_za_goc,13754
|
|
184
|
-
agentic_devtools/cli/ci/pipeline/actions/dispatch_repair.py,sha256=
|
|
184
|
+
agentic_devtools/cli/ci/pipeline/actions/dispatch_repair.py,sha256=T_4Iz01GWyCm72h2BRlMW7vSfAh0NTFgkMqbBJMVzIA,26813
|
|
185
185
|
agentic_devtools/cli/ci/pipeline/actions/guards.py,sha256=UIkTgv5_aKqSVNRFlvl_JiAdFswm1uV5VJJfgu5FmFc,4101
|
|
186
186
|
agentic_devtools/cli/ci/pipeline/actions/merge.py,sha256=MPQxTMaDWpeZBzge2tx8htCNCjYDpF_bV5A0AofllBc,16778
|
|
187
187
|
agentic_devtools/cli/ci/pipeline/actions/publish.py,sha256=A00Lk-vZKnhOCN2O5mkks0tayr7NA5eZfUA95CUP7gs,3990
|
|
188
188
|
agentic_devtools/cli/ci/pipeline/actions/rebase.py,sha256=FGykjl5xi9Bg8nq2AE6IB7skdmKJ7Z4iOT4bd2K3B2U,5256
|
|
189
189
|
agentic_devtools/cli/ci/pipeline/actions/request_review.py,sha256=-BInEFbX7rHY_wKna-ksa5cQV_Arxi7w1Jg1kNdBltE,9173
|
|
190
|
-
agentic_devtools/cli/ci/pipeline/actions/resolve_threads.py,sha256=
|
|
190
|
+
agentic_devtools/cli/ci/pipeline/actions/resolve_threads.py,sha256=zMt9oZGQgYSBeAqYwOawt0UA0KY846D0jsOPMQetLus,10448
|
|
191
191
|
agentic_devtools/cli/ci/pipeline/actions/squash.py,sha256=woloutgBoaEkZMO6HcLoChCV3S-yeD7T22ygePciRx0,10894
|
|
192
192
|
agentic_devtools/cli/ci/pipeline/actions/takeover.py,sha256=ms0coVGuT7peZ3XKbYzqzwrAwqwyyvfPzCsPXLPXKws,6394
|
|
193
193
|
agentic_devtools/cli/ci/pipeline/discovery/__init__.py,sha256=QNdOYvl_wK6qcWOvNJR-wm8EtWD_DPn4barKF5RSmfU,752
|
|
@@ -949,8 +949,8 @@ agentic_devtools/_bundled_skills/prompts/speckit.plan.prompt.md,sha256=IJja5r2Sd
|
|
|
949
949
|
agentic_devtools/_bundled_skills/prompts/speckit.specify.prompt.md,sha256=eyzE3GRi2hyW30a6xPYOU7q6MJf0skrD-baEGURYqpg,31
|
|
950
950
|
agentic_devtools/_bundled_skills/prompts/speckit.tasks.prompt.md,sha256=iPxXwon5nV6dNcJV8-JoP3PssKUVXctNiG-C9SsRhB8,29
|
|
951
951
|
agentic_devtools/_bundled_skills/prompts/speckit.taskstoissues.prompt.md,sha256=L5Y21PMSoUcPAAdHy2Jnf-wGVdi04jV_pPvyOJZfpm0,37
|
|
952
|
-
agentic_devtools-0.2.
|
|
953
|
-
agentic_devtools-0.2.
|
|
954
|
-
agentic_devtools-0.2.
|
|
955
|
-
agentic_devtools-0.2.
|
|
956
|
-
agentic_devtools-0.2.
|
|
952
|
+
agentic_devtools-0.2.417.dist-info/METADATA,sha256=SHsp34rERnK4tBljYrlOjd_ChdoqFZzZdw7ua1FqsK0,33466
|
|
953
|
+
agentic_devtools-0.2.417.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
954
|
+
agentic_devtools-0.2.417.dist-info/entry_points.txt,sha256=1xH6yqltFD5Gs3qbHN9zpKwwG3nXpWoC7x0w1Ip7LCw,11726
|
|
955
|
+
agentic_devtools-0.2.417.dist-info/licenses/LICENSE,sha256=yBEDdICksxhBYLWoERKp9MTqwGnUF6Ryj9BTLwXTc6k,1082
|
|
956
|
+
agentic_devtools-0.2.417.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|