@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.
Files changed (62) hide show
  1. package/out/mcp-server.js +10213 -398
  2. package/package.json +2 -2
  3. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +127 -17
  4. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
  5. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +0 -12
  6. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
  7. package/resources/config/orchestration-routing.json +3 -3
  8. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +0 -5
  9. package/resources/scripts/dev_tools/__init__.py +0 -0
  10. package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +0 -127
  11. package/resources/scripts/dev_tools/_orchestrator_state_routing.py +0 -216
  12. package/resources/scripts/dev_tools/agentic_sync.py +0 -819
  13. package/resources/scripts/dev_tools/codex_native_converter/__init__.py +0 -11
  14. package/resources/scripts/dev_tools/codex_native_converter/__main__.py +0 -6
  15. package/resources/scripts/dev_tools/codex_native_converter/cli.py +0 -11
  16. package/resources/scripts/dev_tools/new_active_feature_folder.py +0 -79
  17. package/resources/scripts/dev_tools/new_active_feature_folder_docs.py +0 -268
  18. package/resources/scripts/dev_tools/new_active_feature_folder_flow.py +0 -366
  19. package/resources/scripts/dev_tools/new_active_feature_folder_io.py +0 -306
  20. package/resources/scripts/dev_tools/new_active_feature_folder_markdown.py +0 -252
  21. package/resources/scripts/dev_tools/new_active_feature_folder_models.py +0 -136
  22. package/resources/scripts/dev_tools/new_potential_bug_entry.py +0 -465
  23. package/resources/scripts/dev_tools/potential_to_issue.py +0 -421
  24. package/resources/scripts/dev_tools/potential_to_issue_content.py +0 -212
  25. package/resources/scripts/dev_tools/pr_context/__init__.py +0 -0
  26. package/resources/scripts/dev_tools/pr_context/collector.py +0 -619
  27. package/resources/scripts/dev_tools/pr_context/feature_docs.py +0 -349
  28. package/resources/scripts/dev_tools/pr_context/git.py +0 -153
  29. package/resources/scripts/dev_tools/pr_context/github.py +0 -549
  30. package/resources/scripts/dev_tools/pr_context/models.py +0 -198
  31. package/resources/scripts/dev_tools/pr_context/render.py +0 -342
  32. package/resources/scripts/dev_tools/pr_context/render_feature_excerpts.py +0 -256
  33. package/resources/scripts/dev_tools/pr_context/render_pr_helpers.py +0 -291
  34. package/resources/scripts/dev_tools/pr_context/summary_helpers.py +0 -386
  35. package/resources/scripts/dev_tools/pr_context/verification_evidence.py +0 -171
  36. package/resources/scripts/dev_tools/prompt_mode_contract.py +0 -152
  37. package/resources/scripts/dev_tools/push_down_claude_customizations.py +0 -403
  38. package/resources/scripts/dev_tools/push_down_claude_filesystem.py +0 -472
  39. package/resources/scripts/dev_tools/push_down_claude_pack_selection.py +0 -401
  40. package/resources/scripts/dev_tools/push_down_codex_and_agents_customizations.py +0 -139
  41. package/resources/scripts/dev_tools/push_down_copilot_customizations.py +0 -504
  42. package/resources/scripts/dev_tools/push_down_copilot_customizations_filesystem.py +0 -217
  43. package/resources/scripts/dev_tools/push_down_copilot_customizations_rewrites.py +0 -293
  44. package/resources/scripts/dev_tools/resolve_file_prompt.py +0 -457
  45. package/resources/scripts/dev_tools/resolve_hard_lock_prompt.py +0 -444
  46. package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +0 -246
  47. package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +0 -107
  48. package/resources/scripts/dev_tools/validate_orchestrator_state.py +0 -505
  49. package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -472
  50. package/resources/templates/codex_native_converter.py +0 -35
  51. package/resources/templates/collect_commit_context.py +0 -212
  52. package/resources/templates/collect_pr_context.py +0 -74
  53. package/resources/templates/hello_python.py +0 -11
  54. package/resources/templates/new_active_feature_folder.py +0 -67
  55. package/resources/templates/new_potential_bug_entry.py +0 -54
  56. package/resources/templates/potential_to_issue.py +0 -55
  57. package/resources/templates/push_down_claude_customizations.py +0 -233
  58. package/resources/templates/push_down_codex_and_agents_customizations.py +0 -95
  59. package/resources/templates/push_down_copilot_customizations.py +0 -124
  60. package/resources/templates/resolve_atomic_plan_prompt.py +0 -75
  61. package/resources/templates/resolve_hard_lock_prompt.py +0 -65
  62. 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
- )