@danmoisan/drm-copilot-mcp 0.0.6 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/mcp-server.js +10213 -398
- package/package.json +2 -2
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +127 -17
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +0 -12
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
- package/resources/config/orchestration-routing.json +3 -3
- package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +0 -5
- package/resources/scripts/dev_tools/__init__.py +0 -0
- package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +0 -127
- package/resources/scripts/dev_tools/_orchestrator_state_routing.py +0 -216
- package/resources/scripts/dev_tools/agentic_sync.py +0 -819
- package/resources/scripts/dev_tools/codex_native_converter/__init__.py +0 -11
- package/resources/scripts/dev_tools/codex_native_converter/__main__.py +0 -6
- package/resources/scripts/dev_tools/codex_native_converter/cli.py +0 -11
- package/resources/scripts/dev_tools/new_active_feature_folder.py +0 -79
- package/resources/scripts/dev_tools/new_active_feature_folder_docs.py +0 -268
- package/resources/scripts/dev_tools/new_active_feature_folder_flow.py +0 -366
- package/resources/scripts/dev_tools/new_active_feature_folder_io.py +0 -306
- package/resources/scripts/dev_tools/new_active_feature_folder_markdown.py +0 -252
- package/resources/scripts/dev_tools/new_active_feature_folder_models.py +0 -136
- package/resources/scripts/dev_tools/new_potential_bug_entry.py +0 -465
- package/resources/scripts/dev_tools/potential_to_issue.py +0 -421
- package/resources/scripts/dev_tools/potential_to_issue_content.py +0 -212
- package/resources/scripts/dev_tools/pr_context/__init__.py +0 -0
- package/resources/scripts/dev_tools/pr_context/collector.py +0 -619
- package/resources/scripts/dev_tools/pr_context/feature_docs.py +0 -349
- package/resources/scripts/dev_tools/pr_context/git.py +0 -153
- package/resources/scripts/dev_tools/pr_context/github.py +0 -549
- package/resources/scripts/dev_tools/pr_context/models.py +0 -198
- package/resources/scripts/dev_tools/pr_context/render.py +0 -342
- package/resources/scripts/dev_tools/pr_context/render_feature_excerpts.py +0 -256
- package/resources/scripts/dev_tools/pr_context/render_pr_helpers.py +0 -291
- package/resources/scripts/dev_tools/pr_context/summary_helpers.py +0 -386
- package/resources/scripts/dev_tools/pr_context/verification_evidence.py +0 -171
- package/resources/scripts/dev_tools/prompt_mode_contract.py +0 -152
- package/resources/scripts/dev_tools/push_down_claude_customizations.py +0 -403
- package/resources/scripts/dev_tools/push_down_claude_filesystem.py +0 -472
- package/resources/scripts/dev_tools/push_down_claude_pack_selection.py +0 -401
- package/resources/scripts/dev_tools/push_down_codex_and_agents_customizations.py +0 -139
- package/resources/scripts/dev_tools/push_down_copilot_customizations.py +0 -504
- package/resources/scripts/dev_tools/push_down_copilot_customizations_filesystem.py +0 -217
- package/resources/scripts/dev_tools/push_down_copilot_customizations_rewrites.py +0 -293
- package/resources/scripts/dev_tools/resolve_file_prompt.py +0 -457
- package/resources/scripts/dev_tools/resolve_hard_lock_prompt.py +0 -444
- package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +0 -246
- package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +0 -107
- package/resources/scripts/dev_tools/validate_orchestrator_state.py +0 -505
- package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -472
- package/resources/templates/codex_native_converter.py +0 -35
- package/resources/templates/collect_commit_context.py +0 -212
- package/resources/templates/collect_pr_context.py +0 -74
- package/resources/templates/hello_python.py +0 -11
- package/resources/templates/new_active_feature_folder.py +0 -67
- package/resources/templates/new_potential_bug_entry.py +0 -54
- package/resources/templates/potential_to_issue.py +0 -55
- package/resources/templates/push_down_claude_customizations.py +0 -233
- package/resources/templates/push_down_codex_and_agents_customizations.py +0 -95
- package/resources/templates/push_down_copilot_customizations.py +0 -124
- package/resources/templates/resolve_atomic_plan_prompt.py +0 -75
- package/resources/templates/resolve_hard_lock_prompt.py +0 -65
- package/resources/templates/validate_orchestration_artifacts.py +0 -55
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
"""Shared models and helpers for PR context collection."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import re
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
8
|
-
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from collections.abc import Iterable
|
|
11
|
-
|
|
12
|
-
SECTION_LINE = "===== {title} ====="
|
|
13
|
-
CONVENTIONAL_TYPES = (
|
|
14
|
-
"feat",
|
|
15
|
-
"fix",
|
|
16
|
-
"refactor",
|
|
17
|
-
"perf",
|
|
18
|
-
"docs",
|
|
19
|
-
"test",
|
|
20
|
-
"chore",
|
|
21
|
-
"build",
|
|
22
|
-
"ci",
|
|
23
|
-
"style",
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass
|
|
28
|
-
class CommandResult:
|
|
29
|
-
"""Represents the outcome of a shell command."""
|
|
30
|
-
|
|
31
|
-
stdout: str
|
|
32
|
-
stderr: str
|
|
33
|
-
code: int
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@dataclass
|
|
37
|
-
class IssueDetails:
|
|
38
|
-
"""Issue metadata, body, comments, and optional user story content."""
|
|
39
|
-
|
|
40
|
-
number: str
|
|
41
|
-
title: str
|
|
42
|
-
state: str
|
|
43
|
-
labels: list[str]
|
|
44
|
-
assignees: list[str]
|
|
45
|
-
author: str
|
|
46
|
-
created_at: str
|
|
47
|
-
updated_at: str
|
|
48
|
-
body: str
|
|
49
|
-
comments: list[str]
|
|
50
|
-
user_story_path: str | None = None
|
|
51
|
-
user_story_content: str | None = None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@dataclass
|
|
55
|
-
class PullRequestDetails:
|
|
56
|
-
"""Pull request metadata and body content."""
|
|
57
|
-
|
|
58
|
-
number: str
|
|
59
|
-
title: str
|
|
60
|
-
state: str
|
|
61
|
-
author: str
|
|
62
|
-
base_ref: str
|
|
63
|
-
head_ref: str
|
|
64
|
-
created_at: str
|
|
65
|
-
updated_at: str
|
|
66
|
-
merged_at: str | None
|
|
67
|
-
labels: list[str]
|
|
68
|
-
assignees: list[str]
|
|
69
|
-
body: str
|
|
70
|
-
closing_issues: list[str]
|
|
71
|
-
files_changed: list[str]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@dataclass
|
|
75
|
-
class FeatureDocExcerpt:
|
|
76
|
-
"""Feature documentation excerpts and referenced issues."""
|
|
77
|
-
|
|
78
|
-
feature: str
|
|
79
|
-
excerpt: str
|
|
80
|
-
issue_refs: list[str]
|
|
81
|
-
context_files: list[str]
|
|
82
|
-
primary_issue_ref: str | None = None
|
|
83
|
-
readiness_signal: str | None = None
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@dataclass
|
|
87
|
-
class PRContextResult:
|
|
88
|
-
"""Structured result from building the PR comparison section."""
|
|
89
|
-
|
|
90
|
-
text: str
|
|
91
|
-
referenced_issues: list[str]
|
|
92
|
-
referenced_prs: list[str]
|
|
93
|
-
verified_closing: list[str]
|
|
94
|
-
invalid_references: list[str]
|
|
95
|
-
base_ref: str | None
|
|
96
|
-
resolved_base: str | None
|
|
97
|
-
base_sha: str | None
|
|
98
|
-
head_ref: str | None
|
|
99
|
-
head_sha: str | None
|
|
100
|
-
merge_base: str | None
|
|
101
|
-
rev_range: str | None
|
|
102
|
-
gh_available: bool = True
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@dataclass
|
|
106
|
-
class GitHubCLIStatus:
|
|
107
|
-
"""Status snapshot for GitHub CLI availability."""
|
|
108
|
-
|
|
109
|
-
healthy: bool
|
|
110
|
-
message: str
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@dataclass
|
|
114
|
-
class CIStatusSnapshot:
|
|
115
|
-
"""Minimal CI status for a given commit."""
|
|
116
|
-
|
|
117
|
-
status: str | None
|
|
118
|
-
failing_jobs: list[str]
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@dataclass
|
|
122
|
-
class BaseHeadInfo:
|
|
123
|
-
"""Resolved base/head refs and merge-base provenance."""
|
|
124
|
-
|
|
125
|
-
requested_base: str | None
|
|
126
|
-
resolved_base: str
|
|
127
|
-
base_sha: str
|
|
128
|
-
head_ref: str
|
|
129
|
-
head_sha: str
|
|
130
|
-
merge_base: str
|
|
131
|
-
warning: str | None = None
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@dataclass
|
|
135
|
-
class ScopingDocChange:
|
|
136
|
-
"""Represents a scoping doc change and its materiality."""
|
|
137
|
-
|
|
138
|
-
path: str
|
|
139
|
-
additions: int
|
|
140
|
-
deletions: int
|
|
141
|
-
change_type: str
|
|
142
|
-
material: bool
|
|
143
|
-
reasons: list[str]
|
|
144
|
-
excerpt: str | None = None
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def section(title: str) -> str:
|
|
148
|
-
return "\n" + SECTION_LINE.format(title=title) + "\n"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def truncate(text: str, limit: int = 800) -> str:
|
|
152
|
-
if len(text) <= limit:
|
|
153
|
-
return text
|
|
154
|
-
return text[: limit - 3].rstrip() + "..."
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def truncate_lines(text: str, limit: int) -> str:
|
|
158
|
-
lines = text.splitlines()
|
|
159
|
-
if len(lines) <= limit:
|
|
160
|
-
return text
|
|
161
|
-
head = "\n".join(lines[:limit])
|
|
162
|
-
return f"{head}\n\nTRUNCATED: first {limit} lines shown"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def normalize_reference(ref: str) -> str:
|
|
166
|
-
return ref.lstrip("#").strip()
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def find_user_story_link(body: str) -> str | None:
|
|
170
|
-
if not body:
|
|
171
|
-
return None
|
|
172
|
-
|
|
173
|
-
match = re.search(r"\(([^)]+user-story\.md)\)", body, flags=re.IGNORECASE)
|
|
174
|
-
candidate = match.group(1) if match else None
|
|
175
|
-
|
|
176
|
-
if not candidate:
|
|
177
|
-
fallback = re.search(r"\b\S*user-story\.md\b", body, flags=re.IGNORECASE)
|
|
178
|
-
candidate = fallback.group(0) if fallback else None
|
|
179
|
-
|
|
180
|
-
if not candidate:
|
|
181
|
-
return None
|
|
182
|
-
|
|
183
|
-
github_blob = re.search(
|
|
184
|
-
r"github\.com/[^/]+/[^/]+/blob/[^/]+/(.+user-story\.md)",
|
|
185
|
-
candidate,
|
|
186
|
-
flags=re.IGNORECASE,
|
|
187
|
-
)
|
|
188
|
-
if github_blob:
|
|
189
|
-
return github_blob.group(1)
|
|
190
|
-
|
|
191
|
-
return candidate.lstrip("/")
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def format_list(values: Iterable[str], empty_text: str) -> str:
|
|
195
|
-
values_list = [value for value in values if value]
|
|
196
|
-
if not values_list:
|
|
197
|
-
return empty_text
|
|
198
|
-
return "\n".join(f"- {item}" for item in values_list)
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
"""Rendering and extraction helpers for PR context collection."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import re
|
|
6
|
-
import subprocess
|
|
7
|
-
from typing import TYPE_CHECKING, Protocol
|
|
8
|
-
|
|
9
|
-
from .models import PRContextResult, normalize_reference, section
|
|
10
|
-
from .render_feature_excerpts import (
|
|
11
|
-
build_excerpt_text,
|
|
12
|
-
completed_plan_tasks,
|
|
13
|
-
directory_exists,
|
|
14
|
-
extract_features_from_paths,
|
|
15
|
-
extract_plan_sections,
|
|
16
|
-
extract_spec_parts,
|
|
17
|
-
extract_story_parts,
|
|
18
|
-
gather_feature_excerpts,
|
|
19
|
-
parse_section,
|
|
20
|
-
read_text_file,
|
|
21
|
-
)
|
|
22
|
-
from .render_pr_helpers import (
|
|
23
|
-
build_close_candidates_section,
|
|
24
|
-
convert_numstat,
|
|
25
|
-
extension_summary,
|
|
26
|
-
extract_changed_paths,
|
|
27
|
-
extract_issue_references,
|
|
28
|
-
extract_merge_pr_numbers,
|
|
29
|
-
format_diff_path,
|
|
30
|
-
format_issue_details,
|
|
31
|
-
format_pr_details,
|
|
32
|
-
select_default_base,
|
|
33
|
-
summarize_conventional_commits,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
if TYPE_CHECKING:
|
|
37
|
-
from collections.abc import Iterable
|
|
38
|
-
from pathlib import Path
|
|
39
|
-
|
|
40
|
-
from .git import GitClient
|
|
41
|
-
from .models import PullRequestDetails
|
|
42
|
-
|
|
43
|
-
__all__ = [
|
|
44
|
-
"build_close_candidates_section",
|
|
45
|
-
"build_excerpt_text",
|
|
46
|
-
"build_pr_context",
|
|
47
|
-
"completed_plan_tasks",
|
|
48
|
-
"convert_numstat",
|
|
49
|
-
"directory_exists",
|
|
50
|
-
"extension_summary",
|
|
51
|
-
"extract_changed_paths",
|
|
52
|
-
"extract_features_from_paths",
|
|
53
|
-
"extract_issue_references",
|
|
54
|
-
"extract_merge_pr_numbers",
|
|
55
|
-
"extract_plan_sections",
|
|
56
|
-
"extract_spec_parts",
|
|
57
|
-
"extract_story_parts",
|
|
58
|
-
"format_diff_path",
|
|
59
|
-
"format_issue_details",
|
|
60
|
-
"format_pr_details",
|
|
61
|
-
"gather_feature_excerpts",
|
|
62
|
-
"parse_section",
|
|
63
|
-
"read_text_file",
|
|
64
|
-
"resolve_feature_dir",
|
|
65
|
-
"select_default_base",
|
|
66
|
-
"summarize_conventional_commits",
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class GhLike(Protocol):
|
|
71
|
-
def ensure_available(self) -> None: ...
|
|
72
|
-
|
|
73
|
-
def classify_entity(self, number: str) -> str | None: ...
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def available(self) -> bool: ...
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def resolve_feature_dir(base_dir: Path, feature: str) -> Path | None:
|
|
80
|
-
"""Resolve feature directory by exact, strong-pattern, then weak match."""
|
|
81
|
-
direct = base_dir / feature
|
|
82
|
-
if directory_exists(direct):
|
|
83
|
-
return direct
|
|
84
|
-
|
|
85
|
-
if not directory_exists(base_dir):
|
|
86
|
-
return None
|
|
87
|
-
|
|
88
|
-
pattern = re.compile(rf"(?:^|[-_]){re.escape(feature)}(?:[-_]|$)")
|
|
89
|
-
strong_matches: list[Path] = []
|
|
90
|
-
weak_matches: list[Path] = []
|
|
91
|
-
|
|
92
|
-
for candidate in sorted(base_dir.iterdir()):
|
|
93
|
-
if not candidate.is_dir():
|
|
94
|
-
continue
|
|
95
|
-
name = candidate.name
|
|
96
|
-
if pattern.search(name):
|
|
97
|
-
strong_matches.append(candidate)
|
|
98
|
-
elif feature in name:
|
|
99
|
-
weak_matches.append(candidate)
|
|
100
|
-
|
|
101
|
-
if strong_matches:
|
|
102
|
-
return strong_matches[0]
|
|
103
|
-
if weak_matches:
|
|
104
|
-
return weak_matches[0]
|
|
105
|
-
return None
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def build_pr_context(
|
|
109
|
-
*,
|
|
110
|
-
git: GitClient,
|
|
111
|
-
gh: GhLike,
|
|
112
|
-
base_ref: str | None,
|
|
113
|
-
head_ref: str | None,
|
|
114
|
-
include_untracked: bool,
|
|
115
|
-
feature_issue_refs: Iterable[str] | None = None,
|
|
116
|
-
current_pr: PullRequestDetails | None = None,
|
|
117
|
-
gh_available: bool | None = None,
|
|
118
|
-
) -> PRContextResult:
|
|
119
|
-
"""Build the full PR context payload from git/gh state and references."""
|
|
120
|
-
gh_available = (
|
|
121
|
-
gh.available
|
|
122
|
-
if gh_available is None and hasattr(gh, "available")
|
|
123
|
-
else gh_available
|
|
124
|
-
)
|
|
125
|
-
gh_available = True if gh_available is None else gh_available
|
|
126
|
-
if gh_available:
|
|
127
|
-
gh.ensure_available()
|
|
128
|
-
branch_name = git.branch_name()
|
|
129
|
-
upstream = git.upstream() or "(none)"
|
|
130
|
-
|
|
131
|
-
remotes = git.remote_verbose()
|
|
132
|
-
status_short = git.status_short()
|
|
133
|
-
untracked = git.untracked() if include_untracked else ""
|
|
134
|
-
untracked_display = untracked if untracked.strip() else "(none)"
|
|
135
|
-
|
|
136
|
-
feature_issue_list = list(feature_issue_refs or [])
|
|
137
|
-
referenced_issues: list[str] = []
|
|
138
|
-
referenced_prs: list[str] = []
|
|
139
|
-
verified_closing = (
|
|
140
|
-
current_pr.closing_issues if (current_pr and gh_available) else []
|
|
141
|
-
)
|
|
142
|
-
invalid_references: list[str] = []
|
|
143
|
-
|
|
144
|
-
resolved_base: str | None = None
|
|
145
|
-
base_sha: str | None = None
|
|
146
|
-
head_sha: str | None = None
|
|
147
|
-
head_ref_resolved: str | None = head_ref or branch_name
|
|
148
|
-
merge_base: str | None = None
|
|
149
|
-
rev_range: str | None = None
|
|
150
|
-
|
|
151
|
-
pr_block = ""
|
|
152
|
-
base_warning: str | None = None
|
|
153
|
-
try:
|
|
154
|
-
requested_base = base_ref
|
|
155
|
-
resolved_base = base_ref or select_default_base(git)
|
|
156
|
-
if not resolved_base:
|
|
157
|
-
raise RuntimeError("Failed to resolve base ref (tried common defaults)")
|
|
158
|
-
|
|
159
|
-
if not resolved_base.startswith("origin/"):
|
|
160
|
-
remote_candidate = f"origin/{resolved_base}"
|
|
161
|
-
remote_probe = git.run(
|
|
162
|
-
["rev-parse", "--verify", "--quiet", remote_candidate],
|
|
163
|
-
allow_error=True,
|
|
164
|
-
)
|
|
165
|
-
if remote_probe.code == 0 and remote_probe.stdout.strip():
|
|
166
|
-
resolved_base = remote_candidate
|
|
167
|
-
elif requested_base:
|
|
168
|
-
base_warning = (
|
|
169
|
-
"WARNING: Requested base is local and may be stale; prefer "
|
|
170
|
-
f"origin/{requested_base}"
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
base_sha = git.rev_parse(resolved_base)
|
|
174
|
-
head_ref_resolved = head_ref or branch_name
|
|
175
|
-
head_sha = git.rev_parse(head_ref_resolved or "HEAD")
|
|
176
|
-
merge_base = git.merge_base(base_sha, head_sha)
|
|
177
|
-
rev_range = f"{merge_base}..{head_sha}"
|
|
178
|
-
|
|
179
|
-
oneline = git.log("--pretty=format:%h %ad %an %s", rev_range)
|
|
180
|
-
subjects = git.log("--pretty=%s", rev_range)
|
|
181
|
-
authors = git.log("--format=%an <%ae>", rev_range)
|
|
182
|
-
authors_list = sorted(
|
|
183
|
-
{line.strip() for line in authors.splitlines() if line.strip()}
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
name_status = git.diff_range(["--name-status", merge_base, head_sha])
|
|
187
|
-
numstat = git.diff_range(["--numstat", merge_base, head_sha])
|
|
188
|
-
shortstat = git.diff_range(["--shortstat", merge_base, head_sha])
|
|
189
|
-
stat = git.diff_range(["--stat", merge_base, head_sha])
|
|
190
|
-
|
|
191
|
-
additions, deletions, files = convert_numstat(numstat)
|
|
192
|
-
ext_summary = extension_summary(files)
|
|
193
|
-
merge_prs = extract_merge_pr_numbers(oneline.splitlines())
|
|
194
|
-
|
|
195
|
-
issue_candidates = [
|
|
196
|
-
ref
|
|
197
|
-
for ref in extract_issue_references(oneline + "\n" + subjects)
|
|
198
|
-
if ref not in merge_prs
|
|
199
|
-
]
|
|
200
|
-
issues: list[str] = []
|
|
201
|
-
prs: list[str] = []
|
|
202
|
-
for ref in issue_candidates:
|
|
203
|
-
number = normalize_reference(ref)
|
|
204
|
-
if gh_available:
|
|
205
|
-
entity = gh.classify_entity(number)
|
|
206
|
-
else:
|
|
207
|
-
entity = None
|
|
208
|
-
formatted_ref = ref if ref.startswith("#") else f"#{ref}"
|
|
209
|
-
if entity == "issue":
|
|
210
|
-
issues.append(formatted_ref)
|
|
211
|
-
elif entity == "pull":
|
|
212
|
-
prs.append(formatted_ref)
|
|
213
|
-
else:
|
|
214
|
-
if gh_available:
|
|
215
|
-
invalid_references.append(formatted_ref)
|
|
216
|
-
else:
|
|
217
|
-
issues.append(formatted_ref)
|
|
218
|
-
referenced_issues = sorted(set(issues))
|
|
219
|
-
referenced_prs = sorted(set(prs + merge_prs))
|
|
220
|
-
|
|
221
|
-
issues_display = ", ".join(sorted(set(referenced_issues + feature_issue_list)))
|
|
222
|
-
if not issues_display:
|
|
223
|
-
issues_display = "(none)"
|
|
224
|
-
prs_display = ", ".join(referenced_prs) if referenced_prs else "(none)"
|
|
225
|
-
|
|
226
|
-
oneline_display = oneline if oneline.strip() else "(none)"
|
|
227
|
-
authors_display = "\n".join(authors_list) if authors_list else "(none)"
|
|
228
|
-
name_status_display = name_status if name_status.strip() else "(none)"
|
|
229
|
-
short_display = shortstat if shortstat.strip() else "(none)"
|
|
230
|
-
ext_display = ext_summary if ext_summary else "(none)"
|
|
231
|
-
stat_display = stat if stat.strip() else "(none)"
|
|
232
|
-
|
|
233
|
-
block_lines = [
|
|
234
|
-
section("PR Comparison"),
|
|
235
|
-
f"Base ref (requested): {requested_base or '(default)'}",
|
|
236
|
-
f"Base ref (resolved): {resolved_base} @ {base_sha}",
|
|
237
|
-
f"Head ref (resolved): {head_ref_resolved} @ {head_sha}",
|
|
238
|
-
f"Merge-base: {merge_base}",
|
|
239
|
-
]
|
|
240
|
-
if base_warning:
|
|
241
|
-
block_lines.append(f"Base warning: {base_warning}")
|
|
242
|
-
block_lines.extend(
|
|
243
|
-
[
|
|
244
|
-
f"Range: {rev_range}\n",
|
|
245
|
-
section("Commits in range"),
|
|
246
|
-
oneline_display,
|
|
247
|
-
"",
|
|
248
|
-
section("Conventional commit type summary"),
|
|
249
|
-
summarize_conventional_commits(subjects),
|
|
250
|
-
"",
|
|
251
|
-
section("Authors"),
|
|
252
|
-
authors_display,
|
|
253
|
-
"",
|
|
254
|
-
section("Changed files (name-status)"),
|
|
255
|
-
name_status_display,
|
|
256
|
-
"",
|
|
257
|
-
section("Diff shortstat"),
|
|
258
|
-
short_display,
|
|
259
|
-
"",
|
|
260
|
-
section("Additions/Deletions totals (from numstat)"),
|
|
261
|
-
f"Additions: {additions}\nDeletions: {deletions}\n",
|
|
262
|
-
section("Files by extension"),
|
|
263
|
-
ext_display,
|
|
264
|
-
"",
|
|
265
|
-
section("Referenced issues (detected)"),
|
|
266
|
-
issues_display,
|
|
267
|
-
"",
|
|
268
|
-
section("PRs in range"),
|
|
269
|
-
prs_display,
|
|
270
|
-
"",
|
|
271
|
-
section("Diff stat"),
|
|
272
|
-
stat_display,
|
|
273
|
-
]
|
|
274
|
-
)
|
|
275
|
-
pr_block = "\n".join(block_lines)
|
|
276
|
-
except (subprocess.CalledProcessError, RuntimeError, ValueError, OSError) as exc:
|
|
277
|
-
pr_block = section("PR Comparison") + f"(FAILED to compute PR context: {exc})\n"
|
|
278
|
-
referenced_issues = []
|
|
279
|
-
referenced_prs = []
|
|
280
|
-
verified_closing = []
|
|
281
|
-
invalid_references = []
|
|
282
|
-
resolved_base = None
|
|
283
|
-
base_sha = None
|
|
284
|
-
head_sha = None
|
|
285
|
-
head_ref_resolved = head_ref or branch_name
|
|
286
|
-
merge_base = None
|
|
287
|
-
rev_range = None
|
|
288
|
-
|
|
289
|
-
intent = "\n".join(
|
|
290
|
-
[
|
|
291
|
-
section("PR Intent (edit before generating PR body)"),
|
|
292
|
-
"Primary outcome:",
|
|
293
|
-
"Impact (user/developer):",
|
|
294
|
-
"Risks:",
|
|
295
|
-
"Author-asserted autoclose issues:",
|
|
296
|
-
]
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
combined_text = "\n".join(
|
|
300
|
-
[
|
|
301
|
-
intent,
|
|
302
|
-
section("Repository remotes"),
|
|
303
|
-
remotes,
|
|
304
|
-
"",
|
|
305
|
-
section("Current branch"),
|
|
306
|
-
branch_name,
|
|
307
|
-
"",
|
|
308
|
-
section("Upstream"),
|
|
309
|
-
upstream,
|
|
310
|
-
"",
|
|
311
|
-
section("Status (short)"),
|
|
312
|
-
status_short,
|
|
313
|
-
"",
|
|
314
|
-
section("Untracked files"),
|
|
315
|
-
untracked_display,
|
|
316
|
-
"",
|
|
317
|
-
section("Working tree diff (staged)"),
|
|
318
|
-
git.diff_name_status(staged=True),
|
|
319
|
-
git.diff_patch(staged=True),
|
|
320
|
-
"",
|
|
321
|
-
section("Working tree diff (unstaged)"),
|
|
322
|
-
git.diff_name_status(staged=False),
|
|
323
|
-
git.diff_patch(staged=False),
|
|
324
|
-
pr_block,
|
|
325
|
-
]
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
return PRContextResult(
|
|
329
|
-
text=combined_text,
|
|
330
|
-
referenced_issues=referenced_issues,
|
|
331
|
-
referenced_prs=referenced_prs,
|
|
332
|
-
verified_closing=sorted(set(verified_closing)),
|
|
333
|
-
invalid_references=sorted(set(invalid_references)),
|
|
334
|
-
base_ref=base_ref,
|
|
335
|
-
resolved_base=resolved_base,
|
|
336
|
-
base_sha=base_sha,
|
|
337
|
-
head_ref=head_ref_resolved,
|
|
338
|
-
head_sha=head_sha,
|
|
339
|
-
merge_base=merge_base,
|
|
340
|
-
rev_range=rev_range,
|
|
341
|
-
gh_available=gh_available,
|
|
342
|
-
)
|