agentic-devtools 0.2.413__py3-none-any.whl → 0.2.415__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/checks/customization_quality.py +180 -4
- agentic_devtools/cli/ci/pipeline/actions/resolve_threads.py +17 -6
- agentic_devtools/cli/setup/pr_workflow.py +35 -5
- agentic_devtools/cli/setup/script_generators/required_setup.py +59 -20
- {agentic_devtools-0.2.413.dist-info → agentic_devtools-0.2.415.dist-info}/METADATA +1 -1
- {agentic_devtools-0.2.413.dist-info → agentic_devtools-0.2.415.dist-info}/RECORD +10 -10
- {agentic_devtools-0.2.413.dist-info → agentic_devtools-0.2.415.dist-info}/WHEEL +0 -0
- {agentic_devtools-0.2.413.dist-info → agentic_devtools-0.2.415.dist-info}/entry_points.txt +0 -0
- {agentic_devtools-0.2.413.dist-info → agentic_devtools-0.2.415.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.415'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 415)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -140,11 +140,20 @@ _MARKDOWN_LINK_PATTERN = re.compile(r"\[[^\]]*\]\(([^()]*(?:\([^()]*\)[^()]*)*)\
|
|
|
140
140
|
_REFERENCE_LINK_PATTERN = re.compile(r"\[([^\]]+)\]\[([^\]]*)\]")
|
|
141
141
|
_SHORTCUT_REFERENCE_PATTERN = re.compile(r"(?<![!\]])\[([^\]]+)\](?!\[)(?!\()(?!:)")
|
|
142
142
|
_SHORTCUT_IMAGE_PATTERN = re.compile(r"!\[([^\]]+)\](?!\[)(?!\()")
|
|
143
|
-
_REFERENCE_DEFINITION_PATTERN = re.compile(
|
|
144
|
-
|
|
143
|
+
_REFERENCE_DEFINITION_PATTERN = re.compile(
|
|
144
|
+
r"^[ \t]{0,3}\[([^\]]+)\]:[ \t]*(?:\r?\n[ \t]{0,3})?(.+)$",
|
|
145
|
+
re.MULTILINE,
|
|
146
|
+
)
|
|
145
147
|
_PATH_CANDIDATE_PATTERN = re.compile(r"^[A-Za-z0-9_.\-]+(?:/[A-Za-z0-9_.\-]+)+/?$")
|
|
146
148
|
_URI_SCHEME_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9+.\-]*:")
|
|
147
149
|
_FENCE_OPEN_PATTERN = re.compile(r"^[ \t]{0,3}(?P<fence>`{3,}|~{3,}).*$")
|
|
150
|
+
_ATX_HEADING_BOUNDARY_PATTERN = re.compile(r"^[ \t]{0,3}#{1,6}(?:[ \t]+|$)")
|
|
151
|
+
_BLOCK_QUOTE_BOUNDARY_PATTERN = re.compile(r"^[ \t]{0,3}>")
|
|
152
|
+
_UNORDERED_LIST_BOUNDARY_PATTERN = re.compile(r"^[ \t]{0,3}[-+*][ \t]+")
|
|
153
|
+
_ORDERED_LIST_BOUNDARY_PATTERN = re.compile(r"^[ \t]{0,3}1[.)][ \t]+")
|
|
154
|
+
_ORDERED_LIST_ITEM_PATTERN = re.compile(r"^[ \t]{0,3}\d+[.)][ \t]+")
|
|
155
|
+
_SETEXT_HEADING_BOUNDARY_PATTERN = re.compile(r"^[ \t]{0,3}(?:=+|-+)[ \t]*$")
|
|
156
|
+
_THEMATIC_BREAK_BOUNDARY_PATTERN = re.compile(r"^[ \t]{0,3}(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$")
|
|
148
157
|
|
|
149
158
|
_WORD_PATTERN = re.compile(r"[a-z0-9]+")
|
|
150
159
|
_STOPWORDS: frozenset[str] = frozenset(
|
|
@@ -697,8 +706,9 @@ def check_path_references(unit: CustomizationUnit, repo_root: Path | str) -> lis
|
|
|
697
706
|
entry_dir = Path(unit.path).parent
|
|
698
707
|
violations: list[Violation] = []
|
|
699
708
|
visible_body = _strip_fenced_code(unit.body)
|
|
709
|
+
link_scan_body = _strip_inline_code(visible_body)
|
|
700
710
|
|
|
701
|
-
for target in _iter_markdown_targets(
|
|
711
|
+
for target in _iter_markdown_targets(link_scan_body):
|
|
702
712
|
link = _link_target(target)
|
|
703
713
|
if not link or link.startswith("#") or _URI_SCHEME_PATTERN.match(link):
|
|
704
714
|
continue
|
|
@@ -711,7 +721,7 @@ def check_path_references(unit: CustomizationUnit, repo_root: Path | str) -> lis
|
|
|
711
721
|
Violation(RULE_PATHS, unit.path, f"linked resource is more than one level from its entry file: {link}")
|
|
712
722
|
)
|
|
713
723
|
|
|
714
|
-
for span in
|
|
724
|
+
for _, _, span in _iter_inline_code_spans(visible_body):
|
|
715
725
|
candidate = span.strip()
|
|
716
726
|
if not _PATH_CANDIDATE_PATTERN.match(candidate):
|
|
717
727
|
continue
|
|
@@ -769,14 +779,180 @@ def _strip_fenced_code(body: str) -> str:
|
|
|
769
779
|
inside = True
|
|
770
780
|
fence = match.group("fence")[0]
|
|
771
781
|
fence_len = len(match.group("fence"))
|
|
782
|
+
kept.append(_line_break_only(raw_line))
|
|
772
783
|
continue
|
|
773
784
|
kept.append(raw_line)
|
|
774
785
|
continue
|
|
786
|
+
kept.append(_line_break_only(raw_line))
|
|
775
787
|
if _is_fence_close(line, fence, fence_len):
|
|
776
788
|
inside = False
|
|
777
789
|
return "".join(kept)
|
|
778
790
|
|
|
779
791
|
|
|
792
|
+
def _strip_inline_code(body: str) -> str:
|
|
793
|
+
"""Return *body* with inline code spans replaced by spaces.
|
|
794
|
+
|
|
795
|
+
Inline code contents must not be parsed for Markdown link syntax because
|
|
796
|
+
they are literal text. The replacement preserves offsets and line
|
|
797
|
+
structure so that reference-definition lines immediately before or after a
|
|
798
|
+
span are still found at their original positions.
|
|
799
|
+
"""
|
|
800
|
+
parts: list[str] = []
|
|
801
|
+
last = 0
|
|
802
|
+
for start, end, _ in _iter_inline_code_spans(body):
|
|
803
|
+
parts.append(body[last:start])
|
|
804
|
+
span = body[start:end]
|
|
805
|
+
parts.append("".join(char if char in "\r\n`" else " " for char in span))
|
|
806
|
+
last = end
|
|
807
|
+
parts.append(body[last:])
|
|
808
|
+
return "".join(parts)
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
def _line_break_only(line: str) -> str:
|
|
812
|
+
"""Return only the line break from *line* (if present)."""
|
|
813
|
+
if line.endswith("\r\n"):
|
|
814
|
+
return "\r\n"
|
|
815
|
+
if line.endswith("\n"):
|
|
816
|
+
return "\n"
|
|
817
|
+
return ""
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def _iter_inline_code_spans(body: str) -> list[tuple[int, int, str]]:
|
|
821
|
+
"""Return Markdown inline-code spans as ``(start, end, content)`` tuples.
|
|
822
|
+
|
|
823
|
+
Matching is done per non-blank block so a code span may cross a line break
|
|
824
|
+
inside one paragraph, but an unmatched backtick run cannot consume later
|
|
825
|
+
paragraphs. Opening and closing delimiters must use the same backtick-run
|
|
826
|
+
length.
|
|
827
|
+
"""
|
|
828
|
+
spans: list[tuple[int, int, str]] = []
|
|
829
|
+
for block_start, block_end in _nonblank_block_ranges(body):
|
|
830
|
+
cursor = block_start
|
|
831
|
+
while cursor < block_end:
|
|
832
|
+
if body[cursor] != "`" or _is_escaped_backtick_run(body, cursor):
|
|
833
|
+
cursor += 1
|
|
834
|
+
continue
|
|
835
|
+
open_start = cursor
|
|
836
|
+
while cursor < block_end and body[cursor] == "`":
|
|
837
|
+
cursor += 1
|
|
838
|
+
delimiter_len = cursor - open_start
|
|
839
|
+
close = _find_inline_code_close(body, cursor, block_end, delimiter_len)
|
|
840
|
+
if close is None:
|
|
841
|
+
continue
|
|
842
|
+
spans.append((open_start, close + delimiter_len, body[cursor:close]))
|
|
843
|
+
cursor = close + delimiter_len
|
|
844
|
+
return spans
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def _nonblank_block_ranges(body: str) -> list[tuple[int, int]]:
|
|
848
|
+
"""Return ``[start, end)`` ranges for non-blank line blocks in *body*.
|
|
849
|
+
|
|
850
|
+
Consecutive block-quote lines (``> …``) at the same nesting depth are kept
|
|
851
|
+
in the same range so that a multiline code span within one block-quoted
|
|
852
|
+
paragraph is not split across separate ranges. A change in quote depth
|
|
853
|
+
starts a new range so that a code span cannot cross a nesting boundary.
|
|
854
|
+
A quote-only line (``>`` / ``> ``) is treated as blank and ends the current
|
|
855
|
+
paragraph inside the block quote.
|
|
856
|
+
|
|
857
|
+
Ordered list items (``2.``, ``3.``, …) that continue a list started by
|
|
858
|
+
any digit are treated as block boundaries so that an unterminated backtick
|
|
859
|
+
in one item cannot pair with a backtick in a later item.
|
|
860
|
+
"""
|
|
861
|
+
ranges: list[tuple[int, int]] = []
|
|
862
|
+
block_start: int | None = None
|
|
863
|
+
offset = 0
|
|
864
|
+
prev_quote_depth = 0
|
|
865
|
+
in_ordered_list = False
|
|
866
|
+
for line in body.splitlines(keepends=True):
|
|
867
|
+
# Count block-quote depth and strip all ``>`` markers so that nested
|
|
868
|
+
# quotes (``>>``, ``>>>``) do not leave a residual ``>`` that would look
|
|
869
|
+
# like a new block boundary to ``_starts_markdown_block_boundary``.
|
|
870
|
+
block_line = line
|
|
871
|
+
quote_depth = 0
|
|
872
|
+
while _BLOCK_QUOTE_BOUNDARY_PATTERN.match(block_line):
|
|
873
|
+
block_line = _BLOCK_QUOTE_BOUNDARY_PATTERN.sub("", block_line, count=1)
|
|
874
|
+
quote_depth += 1
|
|
875
|
+
is_block_quote = quote_depth > 0
|
|
876
|
+
if block_line.strip():
|
|
877
|
+
if block_start is None:
|
|
878
|
+
block_start = offset
|
|
879
|
+
if _ORDERED_LIST_ITEM_PATTERN.match(block_line):
|
|
880
|
+
in_ordered_list = True
|
|
881
|
+
elif (
|
|
882
|
+
_starts_markdown_block_boundary(block_line)
|
|
883
|
+
or (is_block_quote and prev_quote_depth == 0)
|
|
884
|
+
or (is_block_quote and prev_quote_depth > 0 and quote_depth != prev_quote_depth)
|
|
885
|
+
or (in_ordered_list and _ORDERED_LIST_ITEM_PATTERN.match(block_line))
|
|
886
|
+
):
|
|
887
|
+
ranges.append((block_start, offset))
|
|
888
|
+
block_start = offset
|
|
889
|
+
if _ORDERED_LIST_BOUNDARY_PATTERN.match(block_line):
|
|
890
|
+
in_ordered_list = True
|
|
891
|
+
elif not _ORDERED_LIST_ITEM_PATTERN.match(block_line):
|
|
892
|
+
in_ordered_list = False
|
|
893
|
+
prev_quote_depth = quote_depth
|
|
894
|
+
else:
|
|
895
|
+
if block_start is not None:
|
|
896
|
+
ranges.append((block_start, offset))
|
|
897
|
+
block_start = None
|
|
898
|
+
prev_quote_depth = 0
|
|
899
|
+
in_ordered_list = False
|
|
900
|
+
offset += len(line)
|
|
901
|
+
# ATX headings are self-terminating single-line blocks. Close the
|
|
902
|
+
# range immediately so that an unmatched backtick inside the heading
|
|
903
|
+
# cannot pair with a backtick in the following paragraph.
|
|
904
|
+
if block_start is not None and _ATX_HEADING_BOUNDARY_PATTERN.match(block_line):
|
|
905
|
+
ranges.append((block_start, offset))
|
|
906
|
+
block_start = None
|
|
907
|
+
prev_quote_depth = 0
|
|
908
|
+
in_ordered_list = False
|
|
909
|
+
if block_start is not None:
|
|
910
|
+
ranges.append((block_start, len(body)))
|
|
911
|
+
return ranges
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
def _starts_markdown_block_boundary(line: str) -> bool:
|
|
915
|
+
"""Return whether *line* starts a Markdown block that interrupts a paragraph."""
|
|
916
|
+
return bool(
|
|
917
|
+
_ATX_HEADING_BOUNDARY_PATTERN.match(line)
|
|
918
|
+
or _BLOCK_QUOTE_BOUNDARY_PATTERN.match(line)
|
|
919
|
+
or _UNORDERED_LIST_BOUNDARY_PATTERN.match(line)
|
|
920
|
+
or _ORDERED_LIST_BOUNDARY_PATTERN.match(line)
|
|
921
|
+
or _SETEXT_HEADING_BOUNDARY_PATTERN.match(line)
|
|
922
|
+
or _THEMATIC_BREAK_BOUNDARY_PATTERN.match(line)
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def _find_inline_code_close(body: str, start: int, end: int, delimiter_len: int) -> int | None:
|
|
927
|
+
"""Return the start offset of the matching inline-code closer, if any.
|
|
928
|
+
|
|
929
|
+
Backslash escaping does not apply inside a code span (CommonMark §6.1), so
|
|
930
|
+
the ``_is_escaped_backtick_run`` check is intentionally absent here. It is
|
|
931
|
+
only used by the opener scanner in ``_iter_inline_code_spans``.
|
|
932
|
+
"""
|
|
933
|
+
cursor = start
|
|
934
|
+
while cursor < end:
|
|
935
|
+
if body[cursor] != "`":
|
|
936
|
+
cursor += 1
|
|
937
|
+
continue
|
|
938
|
+
run_start = cursor
|
|
939
|
+
while cursor < end and body[cursor] == "`":
|
|
940
|
+
cursor += 1
|
|
941
|
+
if cursor - run_start == delimiter_len:
|
|
942
|
+
return run_start
|
|
943
|
+
return None
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def _is_escaped_backtick_run(body: str, start: int) -> bool:
|
|
947
|
+
"""Return whether the backtick at *start* is escaped by an odd backslash run."""
|
|
948
|
+
backslashes = 0
|
|
949
|
+
cursor = start - 1
|
|
950
|
+
while cursor >= 0 and body[cursor] == "\\":
|
|
951
|
+
backslashes += 1
|
|
952
|
+
cursor -= 1
|
|
953
|
+
return backslashes % 2 == 1
|
|
954
|
+
|
|
955
|
+
|
|
780
956
|
def _normalize_reference_label(label: str) -> str:
|
|
781
957
|
"""Return a Markdown reference label in its normalized lookup form."""
|
|
782
958
|
return " ".join(label.strip().split()).lower()
|
|
@@ -127,6 +127,7 @@ class ResolveThreadsAction:
|
|
|
127
127
|
suppressed = 0
|
|
128
128
|
skipped_reviews: list[str] = []
|
|
129
129
|
finalization_errors: list[str] = []
|
|
130
|
+
hard_failures: list[tuple[int, str]] = []
|
|
130
131
|
|
|
131
132
|
for prior_review in prior_reviews:
|
|
132
133
|
try:
|
|
@@ -138,13 +139,11 @@ class ResolveThreadsAction:
|
|
|
138
139
|
review_id=prior_review.id,
|
|
139
140
|
)
|
|
140
141
|
except Exception as exc:
|
|
141
|
-
logger.error(
|
|
142
|
-
|
|
143
|
-
name=self.name,
|
|
144
|
-
decision=ActionDecision.FAILED,
|
|
145
|
-
error=str(exc),
|
|
146
|
-
details="finalize_post_repair raised an exception",
|
|
142
|
+
logger.error(
|
|
143
|
+
"PR #%d: Thread resolution failed for review %s: %s", snapshot.pr_number, prior_review.id, exc
|
|
147
144
|
)
|
|
145
|
+
hard_failures.append((prior_review.id, str(exc)))
|
|
146
|
+
continue
|
|
148
147
|
|
|
149
148
|
finalization_errors.extend(result.errors)
|
|
150
149
|
|
|
@@ -156,6 +155,15 @@ class ResolveThreadsAction:
|
|
|
156
155
|
unresolved += result.unresolved_count
|
|
157
156
|
suppressed += result.suppressed_count
|
|
158
157
|
|
|
158
|
+
if len(hard_failures) == len(prior_reviews):
|
|
159
|
+
failure_msgs = [f"#{rid}: {err}" for rid, err in hard_failures]
|
|
160
|
+
return ActionResult(
|
|
161
|
+
name=self.name,
|
|
162
|
+
decision=ActionDecision.FAILED,
|
|
163
|
+
error="; ".join(failure_msgs),
|
|
164
|
+
details=f"All {len(prior_reviews)} prior reviews failed finalization: {', '.join(failure_msgs)}",
|
|
165
|
+
)
|
|
166
|
+
|
|
159
167
|
# Update derived state so downstream actions (approve, merge) see the
|
|
160
168
|
# post-resolution count within the same pipeline run.
|
|
161
169
|
#
|
|
@@ -192,6 +200,9 @@ class ResolveThreadsAction:
|
|
|
192
200
|
details = f"{details}; {suppressed} suppressed comment(s) not counted"
|
|
193
201
|
if skipped_reviews:
|
|
194
202
|
details = f"{details}; skipped {len(skipped_reviews)} prior review(s): {', '.join(skipped_reviews)}"
|
|
203
|
+
if hard_failures:
|
|
204
|
+
failure_msgs = [f"#{rid}: {err}" for rid, err in hard_failures]
|
|
205
|
+
details = f"{details}; hard failures: {', '.join(failure_msgs)}"
|
|
195
206
|
if finalization_errors:
|
|
196
207
|
details = f"{details}; finalization errors: {', '.join(finalization_errors)}"
|
|
197
208
|
|
|
@@ -249,6 +249,8 @@ def run_setup_with_pr_workflow(
|
|
|
249
249
|
pr_created = False
|
|
250
250
|
message = "No file changes detected."
|
|
251
251
|
emergency_stash_created = False
|
|
252
|
+
temporary_branch_name: str | None = None
|
|
253
|
+
should_delete_temporary_branch = False
|
|
252
254
|
branch_restored = False
|
|
253
255
|
|
|
254
256
|
try:
|
|
@@ -318,6 +320,7 @@ def run_setup_with_pr_workflow(
|
|
|
318
320
|
)
|
|
319
321
|
message = f"Failed to create branch '{branch_name}'."
|
|
320
322
|
else:
|
|
323
|
+
temporary_branch_name = branch_name
|
|
321
324
|
commit_msg = f"chore: agdt-setup v{version}"
|
|
322
325
|
add_result = run_git("add", ".", check=False)
|
|
323
326
|
if add_result.returncode != 0:
|
|
@@ -329,11 +332,31 @@ def run_setup_with_pr_workflow(
|
|
|
329
332
|
else:
|
|
330
333
|
commit_result = run_git("commit", "-m", commit_msg, check=False)
|
|
331
334
|
if commit_result.returncode != 0:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
diff_after = run_git("diff", "--cached", "--quiet", check=False)
|
|
336
|
+
if diff_after.returncode == 0:
|
|
337
|
+
status_after = run_git("status", "--porcelain", check=False)
|
|
338
|
+
if status_after.returncode == 0 and not status_after.stdout.strip():
|
|
339
|
+
print(
|
|
340
|
+
" ℹ Commit aborted by hooks or empty diff; treating as idempotent no-op.",
|
|
341
|
+
file=sys.stderr,
|
|
342
|
+
)
|
|
343
|
+
message = (
|
|
344
|
+
"No new changes — setup output matches origin/main "
|
|
345
|
+
"(already merged or formatted away)."
|
|
346
|
+
)
|
|
347
|
+
should_delete_temporary_branch = True
|
|
348
|
+
else:
|
|
349
|
+
print(
|
|
350
|
+
f"Error: 'git commit' failed: {commit_result.stderr.strip()}",
|
|
351
|
+
file=sys.stderr,
|
|
352
|
+
)
|
|
353
|
+
message = "Failed to commit changes."
|
|
354
|
+
else:
|
|
355
|
+
print(
|
|
356
|
+
f"Error: 'git commit' failed: {commit_result.stderr.strip()}",
|
|
357
|
+
file=sys.stderr,
|
|
358
|
+
)
|
|
359
|
+
message = "Failed to commit changes."
|
|
337
360
|
else:
|
|
338
361
|
branch_created = branch_name
|
|
339
362
|
|
|
@@ -461,6 +484,13 @@ def run_setup_with_pr_workflow(
|
|
|
461
484
|
f"Run 'git checkout {original_branch}' manually.",
|
|
462
485
|
file=sys.stderr,
|
|
463
486
|
)
|
|
487
|
+
if branch_restored and should_delete_temporary_branch and temporary_branch_name:
|
|
488
|
+
delete_branch = run_git("branch", "-D", temporary_branch_name, check=False)
|
|
489
|
+
if delete_branch.returncode != 0:
|
|
490
|
+
print(
|
|
491
|
+
f"Warning: Could not delete temporary branch '{temporary_branch_name}'.",
|
|
492
|
+
file=sys.stderr,
|
|
493
|
+
)
|
|
464
494
|
|
|
465
495
|
# Step 13 — pop the user's original auto-stash.
|
|
466
496
|
# Only pop when the original branch was successfully restored;
|
|
@@ -7,6 +7,7 @@ latest PyPI release of ``agentic-devtools``.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import os
|
|
10
11
|
import re
|
|
11
12
|
import site
|
|
12
13
|
import subprocess
|
|
@@ -29,9 +30,13 @@ SELF_UPGRADE_LOCK_MESSAGE = (
|
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
#: Actionable remediation printed alongside :data:`SELF_UPGRADE_LOCK_MESSAGE`.
|
|
32
|
-
|
|
33
|
+
#: The ``<sys.executable>`` placeholder stands for the Python interpreter path;
|
|
34
|
+
#: the generated script substitutes the real path via an f-string at runtime,
|
|
35
|
+
#: formats it for PowerShell with ``&`` and surrounds the executable with
|
|
36
|
+
#: double quotes so paths with spaces remain runnable.
|
|
37
|
+
SELF_UPGRADE_LOCK_REMEDY_TEMPLATE = (
|
|
33
38
|
" Continuing with the installed version. To upgrade, close every running "
|
|
34
|
-
|
|
39
|
+
'agdt-* process and run: & "<sys.executable>" -m pip install --upgrade agentic-devtools'
|
|
35
40
|
)
|
|
36
41
|
|
|
37
42
|
_SELF_UPGRADE_LOCK_EXECUTABLE = "agdt-setup.exe"
|
|
@@ -61,28 +66,30 @@ def is_self_upgrade_lock(output: str) -> bool:
|
|
|
61
66
|
|
|
62
67
|
def _is_agentic_devtools_tilde_backup(name: str) -> bool:
|
|
63
68
|
"""Return ``True`` for pip's ``~``-mangled agentic-devtools backups."""
|
|
69
|
+
name_lower = name.lower()
|
|
64
70
|
for candidate in ("agentic-devtools", "agentic_devtools"):
|
|
65
71
|
prefix = f"~{candidate[1:]}"
|
|
66
|
-
if
|
|
72
|
+
if name_lower == prefix:
|
|
67
73
|
return True
|
|
68
|
-
if not
|
|
74
|
+
if not name_lower.startswith(prefix):
|
|
69
75
|
continue
|
|
70
|
-
remainder =
|
|
76
|
+
remainder = name_lower[len(prefix) :]
|
|
71
77
|
return _has_distribution_suffix(remainder)
|
|
72
78
|
return False
|
|
73
79
|
|
|
74
80
|
|
|
75
81
|
def _is_agentic_devtools_distribution_name(name: str) -> bool:
|
|
76
82
|
"""Return ``True`` for agentic-devtools distribution names and pip backups."""
|
|
77
|
-
|
|
83
|
+
name_lower = name.lower()
|
|
84
|
+
if _is_agentic_devtools_tilde_backup(name_lower):
|
|
78
85
|
return True
|
|
79
86
|
|
|
80
87
|
for candidate in ("agentic-devtools", "agentic_devtools"):
|
|
81
|
-
if
|
|
88
|
+
if name_lower == candidate:
|
|
82
89
|
return True
|
|
83
|
-
if not
|
|
90
|
+
if not name_lower.startswith(candidate):
|
|
84
91
|
continue
|
|
85
|
-
remainder =
|
|
92
|
+
remainder = name_lower[len(candidate) :]
|
|
86
93
|
if _has_distribution_suffix(remainder):
|
|
87
94
|
return True
|
|
88
95
|
return False
|
|
@@ -178,8 +185,23 @@ def install_package() -> tuple[bool, str]:
|
|
|
178
185
|
"""Install/upgrade ``agentic-devtools`` from PyPI.
|
|
179
186
|
|
|
180
187
|
Returns ``(success, output)`` where *output* is the combined
|
|
181
|
-
stdout + stderr from pip
|
|
188
|
+
stdout + stderr from pip, or a synthetic lock message when the
|
|
189
|
+
Windows autorun short-circuit fires (see below).
|
|
190
|
+
|
|
191
|
+
On Windows when spawned by ``agdt-setup.exe`` (``AGDT_SETUP_AUTORUN``
|
|
192
|
+
is set), pip is **not** invoked — the function returns
|
|
193
|
+
``(False, <lock message>)`` immediately to prevent WinError 32 from
|
|
194
|
+
corrupting package metadata. Callers should treat that pair the same
|
|
195
|
+
as a real pip WinError 32 failure.
|
|
182
196
|
"""
|
|
197
|
+
if sys.platform == "win32" and os.environ.get("AGDT_SETUP_AUTORUN"):
|
|
198
|
+
# Proactively skip pip install when spawned by agdt-setup.exe to avoid WinError 32
|
|
199
|
+
return (
|
|
200
|
+
False,
|
|
201
|
+
"[WinError 32] The process cannot access the file because "
|
|
202
|
+
"it is being used by another process: agdt-setup.exe",
|
|
203
|
+
)
|
|
204
|
+
|
|
183
205
|
stdout_chunks: list[str] = []
|
|
184
206
|
stderr_chunks: list[str] = []
|
|
185
207
|
process = subprocess.Popen(
|
|
@@ -374,7 +396,7 @@ _SELF_UPGRADE_LOCK_MESSAGE = (
|
|
|
374
396
|
|
|
375
397
|
_SELF_UPGRADE_LOCK_REMEDY = (
|
|
376
398
|
" Continuing with the installed version. To upgrade, close every running "
|
|
377
|
-
|
|
399
|
+
f'agdt-* process and run: & "{sys.executable}" -m pip install --upgrade agentic-devtools'
|
|
378
400
|
)
|
|
379
401
|
|
|
380
402
|
_SELF_UPGRADE_LOCK_EXECUTABLE = "agdt-setup.exe"
|
|
@@ -397,28 +419,30 @@ def _site_packages_dirs():
|
|
|
397
419
|
|
|
398
420
|
def _is_agentic_devtools_tilde_backup(name):
|
|
399
421
|
"""Return True for pip's ~-mangled agentic-devtools backups."""
|
|
422
|
+
name_lower = name.lower()
|
|
400
423
|
for candidate in ("agentic-devtools", "agentic_devtools"):
|
|
401
424
|
prefix = f"~{candidate[1:]}"
|
|
402
|
-
if
|
|
425
|
+
if name_lower == prefix:
|
|
403
426
|
return True
|
|
404
|
-
if not
|
|
427
|
+
if not name_lower.startswith(prefix):
|
|
405
428
|
continue
|
|
406
|
-
remainder =
|
|
429
|
+
remainder = name_lower[len(prefix) :]
|
|
407
430
|
return _has_distribution_suffix(remainder)
|
|
408
431
|
return False
|
|
409
432
|
|
|
410
433
|
|
|
411
434
|
def _is_agentic_devtools_distribution_name(name):
|
|
412
435
|
"""Return True for agentic-devtools distribution names and pip backups."""
|
|
413
|
-
|
|
436
|
+
name_lower = name.lower()
|
|
437
|
+
if _is_agentic_devtools_tilde_backup(name_lower):
|
|
414
438
|
return True
|
|
415
439
|
|
|
416
440
|
for candidate in ("agentic-devtools", "agentic_devtools"):
|
|
417
|
-
if
|
|
441
|
+
if name_lower == candidate:
|
|
418
442
|
return True
|
|
419
|
-
if not
|
|
443
|
+
if not name_lower.startswith(candidate):
|
|
420
444
|
continue
|
|
421
|
-
remainder =
|
|
445
|
+
remainder = name_lower[len(candidate) :]
|
|
422
446
|
if _has_distribution_suffix(remainder):
|
|
423
447
|
return True
|
|
424
448
|
return False
|
|
@@ -482,9 +506,24 @@ def _install_package():
|
|
|
482
506
|
"""Install/upgrade agentic-devtools from PyPI.
|
|
483
507
|
|
|
484
508
|
Returns ``(success, self_upgrade_lock)``. ``self_upgrade_lock`` is True
|
|
485
|
-
|
|
486
|
-
running
|
|
509
|
+
in two cases:
|
|
510
|
+
1. **Proactive skip** — running on Windows as the ``agdt-setup.exe``
|
|
511
|
+
autorun executable (``AGDT_SETUP_AUTORUN`` is set). pip is not
|
|
512
|
+
invoked; the return is immediate to prevent WinError 32 from
|
|
513
|
+
corrupting package metadata.
|
|
514
|
+
2. **Reactive detection** — pip was invoked but failed with WinError 32
|
|
515
|
+
(Windows refused to replace the locked console-script executable).
|
|
516
|
+
In both cases ``success`` is False. Only the proactive skip guarantees
|
|
517
|
+
the installed version remains intact; the reactive path runs after pip
|
|
518
|
+
has already attempted the uninstall.
|
|
487
519
|
"""
|
|
520
|
+
import os
|
|
521
|
+
import sys
|
|
522
|
+
if sys.platform == "win32" and os.environ.get("AGDT_SETUP_AUTORUN"):
|
|
523
|
+
# Proactively skip pip install when spawned by agdt-setup.exe to avoid WinError 32
|
|
524
|
+
# which corrupts metadata before failing.
|
|
525
|
+
return False, True
|
|
526
|
+
|
|
488
527
|
stdout_chunks = []
|
|
489
528
|
stderr_chunks = []
|
|
490
529
|
process = subprocess.Popen(
|
|
@@ -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=yIi8ocqIsuh2cqu1EnYhgjeImc0-nP0OgjnFW9ZTHzw,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
|
|
@@ -127,7 +127,7 @@ agentic_devtools/cli/checks/__init__.py,sha256=TOccSqgwCTfvGAc39phAOJaAvRLn6AUUB
|
|
|
127
127
|
agentic_devtools/cli/checks/__main__.py,sha256=J5ilhApBdIVr2hZIB7gki42P0AOxLDVQJPoGi_-vTEo,201
|
|
128
128
|
agentic_devtools/cli/checks/changed_files.py,sha256=b3XjEBGVYcKQdXQtbYMBmEkT9uhKZGTXswESMBMjUzU,7255
|
|
129
129
|
agentic_devtools/cli/checks/commands.py,sha256=oLVwaOs_baFuzaj6ApIZEbQQwBOdeBYwxr-iF-BwabM,23421
|
|
130
|
-
agentic_devtools/cli/checks/customization_quality.py,sha256=
|
|
130
|
+
agentic_devtools/cli/checks/customization_quality.py,sha256=33kztmISilTk9w2PEj8YH9FTfVip20r0ZZh8K0WNshE,47638
|
|
131
131
|
agentic_devtools/cli/checks/lint.py,sha256=HP5lYUNzEX15QI_s7-q-C-s142CwySg4k4PoPtC97t4,7932
|
|
132
132
|
agentic_devtools/cli/checks/setup_drift.py,sha256=cGJBccYBiAEs6r6ER274fBwoN1FpL-EDwLsdoCcmU7o,7665
|
|
133
133
|
agentic_devtools/cli/checks/setup_drift_changed_files.py,sha256=EmWvKdOSATzz7f6cKMwxiEblYvJy_2lTy7ypYza8SoE,3721
|
|
@@ -187,7 +187,7 @@ agentic_devtools/cli/ci/pipeline/actions/merge.py,sha256=MPQxTMaDWpeZBzge2tx8htC
|
|
|
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=orY7RsqrMsvOmGDa7R-Z96itSFj9N5DICertZdekwOo,9687
|
|
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
|
|
@@ -340,7 +340,7 @@ agentic_devtools/cli/setup/phase_markers.py,sha256=cFiQoa8Ydbsb60RVu1chtW1FazOh3
|
|
|
340
340
|
agentic_devtools/cli/setup/phases.py,sha256=h_Czv3QH6rttoguVOFo5dnq1j1E7OfpvoNi0nvyEDnI,571
|
|
341
341
|
agentic_devtools/cli/setup/platform_detection.py,sha256=uEw6z44J1y85F1XLTWtnMIRRdZ-vWkI9iwWU_BzGA_4,15587
|
|
342
342
|
agentic_devtools/cli/setup/post_autorun_version_check.py,sha256=oqjp5Q5SnUfmrqubsYYnPD9gasviaoW7pry44Fpyxx0,3529
|
|
343
|
-
agentic_devtools/cli/setup/pr_workflow.py,sha256=
|
|
343
|
+
agentic_devtools/cli/setup/pr_workflow.py,sha256=PdgqhMMQu9YTGa0a1fWIsMffxbeUYrElafJP4vhaLkY,24083
|
|
344
344
|
agentic_devtools/cli/setup/property_change_detection.py,sha256=hjmgjy6OsX0z-1QPhNfQQ18PsW3ghAMgEzKu9ciPjnI,10588
|
|
345
345
|
agentic_devtools/cli/setup/refresh_outcome.py,sha256=FhMI-vmRl2LbEtbyKu8DtN98ix8IyA3eu8r1PXz-uPU,3436
|
|
346
346
|
agentic_devtools/cli/setup/registry.py,sha256=aSNcx4CtVEOl4qT8c7wu7iPG9YxrIatjFETK8JaUY-A,16692
|
|
@@ -360,7 +360,7 @@ agentic_devtools/cli/setup/script_generators/constants.py,sha256=BkpLmENaPkfYJUK
|
|
|
360
360
|
agentic_devtools/cli/setup/script_generators/gitignore_updater.py,sha256=RbpWdUCK7lAMfazVm6ytgVIZalbkMre6dzStQ7uEIoQ,4185
|
|
361
361
|
agentic_devtools/cli/setup/script_generators/legacy_migration.py,sha256=eFVKlkgQGVwYl8uXIJhxBB59sLQVeUAZi6Xsl8PNXY8,3075
|
|
362
362
|
agentic_devtools/cli/setup/script_generators/repo_specific.py,sha256=_xaZTOYNWKInValGMwJBqGvlRthip5R7oZBPsyWIU1I,1498
|
|
363
|
-
agentic_devtools/cli/setup/script_generators/required_setup.py,sha256=
|
|
363
|
+
agentic_devtools/cli/setup/script_generators/required_setup.py,sha256=0sdiCxEKSnEC2K1HYTcqI2SgIWFc87G7ya1hfa6fCGI,24546
|
|
364
364
|
agentic_devtools/cli/setup/script_generators/root_entry_point.py,sha256=UzW1N_ln_EvM28EmxOiBkxxis5QU2CiputAEZm2NwIE,4311
|
|
365
365
|
agentic_devtools/cli/setup/templates/README.md,sha256=Y_Kw_0CeHzUM6xcckVXT9UcNAybr9XM_BI1WkyW9WF4,869
|
|
366
366
|
agentic_devtools/cli/setup/templates/review-pr.py,sha256=XXZjDfJViByAy0RQP5WB1fUIK8qxNRDo_9ZlssKk10A,3456
|
|
@@ -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.415.dist-info/METADATA,sha256=0YFc2rqKU6aEqndgc0b1TPrbskguyu3vIIraNawyBCg,33466
|
|
953
|
+
agentic_devtools-0.2.415.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
954
|
+
agentic_devtools-0.2.415.dist-info/entry_points.txt,sha256=1xH6yqltFD5Gs3qbHN9zpKwwG3nXpWoC7x0w1Ip7LCw,11726
|
|
955
|
+
agentic_devtools-0.2.415.dist-info/licenses/LICENSE,sha256=yBEDdICksxhBYLWoERKp9MTqwGnUF6Ryj9BTLwXTc6k,1082
|
|
956
|
+
agentic_devtools-0.2.415.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|