@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,252 +0,0 @@
1
- """Markdown transformation helpers for active feature folder creation."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- from typing import TYPE_CHECKING
7
-
8
- from dev_tools.new_active_feature_folder_models import PLACEHOLDERS
9
-
10
- if TYPE_CHECKING:
11
- from collections.abc import Callable
12
-
13
-
14
- def format_checklist(text: str) -> str:
15
- """Normalize freeform checklist text into markdown checkboxes."""
16
- lines: list[str] = []
17
- for raw_line in text.splitlines():
18
- trimmed = raw_line.strip()
19
- if not trimmed:
20
- continue
21
- if re.match(r"^-\s*\[?\s*\]", trimmed):
22
- lines.append(trimmed)
23
- elif trimmed.startswith("-"):
24
- lines.append(trimmed)
25
- else:
26
- lines.append(f"- [ ] {trimmed}")
27
- return "\n".join(lines)
28
-
29
-
30
- def get_section(content: str, name: str) -> str:
31
- """Extract a markdown section body by heading."""
32
- pattern = re.compile(
33
- rf"^\s*##\s+{re.escape(name)}\s*\r?\n(.*?)(?=^\s*##\s+|\Z)",
34
- re.DOTALL | re.MULTILINE,
35
- )
36
- match = pattern.search(content)
37
- if not match:
38
- return ""
39
- return match.group(1).strip()
40
-
41
-
42
- def upsert_work_mode_marker(content: str, mode: str) -> str:
43
- """Insert or update work-mode marker directly above first `##` heading."""
44
- marker_line = f"- Work Mode: {mode}"
45
- marker_pattern = re.compile(
46
- r"^- Work Mode:\s*(minor-audit|full-feature|full-bug|full)\s*$"
47
- )
48
- lines = [line for line in content.splitlines() if not marker_pattern.match(line)]
49
-
50
- for idx, line in enumerate(lines):
51
- if line.lstrip().startswith("## "):
52
- lines.insert(idx, "")
53
- lines.insert(idx, marker_line)
54
- return "\n".join(lines)
55
-
56
- if lines and lines[-1] != "":
57
- lines.append("")
58
- lines.append(marker_line)
59
- return "\n".join(lines)
60
-
61
-
62
- def set_section(content: str, name: str, body: str) -> str:
63
- """Set or append a markdown section body."""
64
- if not body or not body.strip():
65
- return content
66
-
67
- pattern = re.compile(
68
- rf"(^##\s+{re.escape(name)}\s*\r?\n)(.*?)(?=^\s*##\s+|\Z)",
69
- re.DOTALL | re.MULTILINE,
70
- )
71
- if pattern.search(content):
72
- return pattern.sub(
73
- lambda match: f"{match.group(1)}{body}\n\n",
74
- content,
75
- count=1,
76
- )
77
-
78
- trimmed = content.rstrip()
79
- if trimmed:
80
- trimmed += "\n\n"
81
- return f"{trimmed}## {name}\n{body}\n"
82
-
83
-
84
- def prepend_to_section_body(section_body: str, prefix: str) -> str:
85
- """Prepend text to an existing section body while preserving content."""
86
- trimmed_prefix = prefix.strip()
87
- if not trimmed_prefix:
88
- return section_body
89
-
90
- trimmed_body = section_body.strip()
91
- if not trimmed_body:
92
- return f"{trimmed_prefix}\n"
93
- return f"{trimmed_prefix}\n\n{trimmed_body}\n"
94
-
95
-
96
- def update_section_body(
97
- content: str,
98
- section_name: str,
99
- updater: Callable[[str], str],
100
- ) -> tuple[str, bool]:
101
- """Update a `##` section body using a transformation function."""
102
- pattern = re.compile(
103
- rf"(^##\s+{re.escape(section_name)}\s*\r?\n)(.*?)(?=^\s*##\s+|\Z)",
104
- re.DOTALL | re.MULTILINE,
105
- )
106
- match = pattern.search(content)
107
- if not match:
108
- return content, False
109
-
110
- header = match.group(1)
111
- body = match.group(2)
112
- updated_body = updater(body)
113
- if updated_body == body:
114
- return content, False
115
-
116
- return (
117
- pattern.sub(
118
- lambda _match: f"{header}{updated_body}\n",
119
- content,
120
- count=1,
121
- ),
122
- True,
123
- )
124
-
125
-
126
- def set_header_placeholder(
127
- content: str,
128
- feature_name: str,
129
- issue_field: str,
130
- owner_field: str,
131
- updated_field: str,
132
- status_field: str | None = None,
133
- parent_field: str | None = None,
134
- version_field: str | None = None,
135
- ) -> str:
136
- """Replace template placeholders in the frontmatter/header block."""
137
- result = content
138
- for placeholder in PLACEHOLDERS:
139
- result = result.replace(placeholder, feature_name)
140
- result = result.replace("<issue>", issue_field)
141
- if parent_field is not None:
142
- result = result.replace("<parent-id>", parent_field)
143
- if status_field is not None:
144
- result = result.replace("<status>", status_field)
145
- if version_field is not None:
146
- result = result.replace("<version_number>", version_field)
147
- result = re.sub(r"#`?<id>`?", issue_field, result)
148
- result = result.replace("<#id or TBD>", issue_field)
149
- result = result.replace("#<tracking-issue>", issue_field)
150
-
151
- result = re.sub(
152
- r"^-\s*\*\*Issue:\*\*\s+.*$",
153
- f"- **Issue:** {issue_field}",
154
- result,
155
- flags=re.MULTILINE,
156
- )
157
- result = re.sub(
158
- r"^-\s*Issue\s*:\s+.*$",
159
- f"- Issue: {issue_field}",
160
- result,
161
- flags=re.MULTILINE,
162
- )
163
-
164
- result = re.sub(
165
- r"^-\s*\*\*Owner:\*\*\s+(?:name|<name>|.*)$",
166
- f"- **Owner:** {owner_field}",
167
- result,
168
- flags=re.MULTILINE,
169
- )
170
- result = re.sub(
171
- r"^-\s*Owner\s*:\s+(?:name|<name>|.*)$",
172
- f"- Owner: {owner_field}",
173
- result,
174
- flags=re.MULTILINE,
175
- )
176
-
177
- if parent_field is not None:
178
- result = re.sub(
179
- r"^-\s*\*\*Parent \(optional\):\*\*\s+.*$",
180
- f"- **Parent (optional):** {parent_field}",
181
- result,
182
- flags=re.MULTILINE,
183
- )
184
- result = re.sub(
185
- r"^-\s*Parent \(optional\)\s*:\s+.*$",
186
- f"- Parent (optional): {parent_field}",
187
- result,
188
- flags=re.MULTILINE,
189
- )
190
-
191
- result = re.sub(
192
- r"^-\s*\*\*Last Updated:\*\*\s+.*$",
193
- f"- **Last Updated:** {updated_field}",
194
- result,
195
- flags=re.MULTILINE,
196
- )
197
- result = re.sub(
198
- r"^-\s*Last Updated\s*:\s+.*$",
199
- f"- Last Updated: {updated_field}",
200
- result,
201
- flags=re.MULTILINE,
202
- )
203
-
204
- result = re.sub(
205
- r"^-\s*\*\*Date:\*\*\s+.*$",
206
- f"- **Date:** {updated_field}",
207
- result,
208
- flags=re.MULTILINE,
209
- )
210
- result = re.sub(
211
- r"^-\s*Date\s*:\s+YYYY-MM-DD$",
212
- f"- Date: {updated_field}",
213
- result,
214
- flags=re.MULTILINE,
215
- )
216
- result = result.replace("<yyyy-MM-ddTHH-mm>", updated_field)
217
-
218
- if status_field is not None:
219
- result = re.sub(
220
- r"^-\s*\*\*Status:\*\*\s+.*$",
221
- f"- **Status:** {status_field}",
222
- result,
223
- flags=re.MULTILINE,
224
- )
225
- result = re.sub(
226
- r"^-\s*Status\s*:\s+.*$",
227
- f"- Status: {status_field}",
228
- result,
229
- flags=re.MULTILINE,
230
- )
231
-
232
- if version_field is not None:
233
- result = re.sub(
234
- r"^-\s*\*\*Version:\*\*\s+.*$",
235
- f"- **Version:** {version_field}",
236
- result,
237
- flags=re.MULTILINE,
238
- )
239
- result = re.sub(
240
- r"^-\s*Version\s*:\s+.*$",
241
- f"- Version: {version_field}",
242
- result,
243
- flags=re.MULTILINE,
244
- )
245
-
246
- if not re.search(
247
- r"^-\s*(?:\*\*Issue:\*\*\s*|Issue\s*:)\s*#?",
248
- result,
249
- flags=re.MULTILINE,
250
- ):
251
- result = f"- Issue: {issue_field}\n{result}"
252
- return result
@@ -1,136 +0,0 @@
1
- """Core models and basic utilities for active feature folder creation."""
2
-
3
- from __future__ import annotations
4
-
5
- import re
6
- import shutil
7
- from dataclasses import dataclass
8
- from datetime import datetime
9
- from pathlib import Path
10
- from typing import TYPE_CHECKING, Protocol
11
- from zoneinfo import ZoneInfo
12
-
13
- if TYPE_CHECKING:
14
- from collections.abc import Callable, Iterable
15
-
16
- NAME_PATTERN = re.compile(r"^[a-z0-9]+(?:[-_][a-z0-9]+)*$")
17
- EXCLUDED_POTENTIAL_NAMES = {"template.md", "README.md"}
18
- PLACEHOLDERS = [
19
- "<feature-name>",
20
- "<refactor-name>",
21
- "<epic-name>",
22
- "<name>",
23
- "<bug-name>",
24
- ]
25
-
26
- PLAN_TIMESTAMP_TEMPLATE_NAME = "plan.yyyy-MM-ddTHH-mm.md"
27
-
28
-
29
- @dataclass
30
- class IssueMeta:
31
- """Issue metadata container."""
32
-
33
- number: str
34
- author: str
35
- updated_date: str
36
-
37
-
38
- @dataclass
39
- class ActiveFolderResult:
40
- """Result payload for active-folder creation."""
41
-
42
- target: Path
43
- potential_issue_path: Path | None
44
-
45
-
46
- class FileSystem(Protocol):
47
- """Filesystem contract for folder creation workflows."""
48
-
49
- def exists(self, path: Path) -> bool: ...
50
-
51
- def ensure_dir(self, path: Path) -> None: ...
52
-
53
- def copy_file(self, src: Path, dest: Path) -> None: ...
54
-
55
- def copy_tree(self, src: Path, dest: Path) -> None: ...
56
-
57
- def list_files(self, path: Path) -> Iterable[Path]: ...
58
-
59
- def read_text(self, path: Path) -> str: ...
60
-
61
- def write_text(self, path: Path, content: str) -> None: ...
62
-
63
- def move(self, src: Path, dest: Path) -> None: ...
64
-
65
-
66
- @dataclass
67
- class RealFileSystem(FileSystem):
68
- """Disk-backed FileSystem implementation."""
69
-
70
- def exists(self, path: Path) -> bool:
71
- return path.exists()
72
-
73
- def ensure_dir(self, path: Path) -> None:
74
- path.mkdir(parents=True, exist_ok=True)
75
-
76
- def copy_file(self, src: Path, dest: Path) -> None:
77
- dest.parent.mkdir(parents=True, exist_ok=True)
78
- shutil.copyfile(src, dest)
79
-
80
- def copy_tree(self, src: Path, dest: Path) -> None:
81
- for file_path in src.rglob("*"):
82
- if file_path.is_dir():
83
- continue
84
- relative = file_path.relative_to(src)
85
- target_path = dest / relative
86
- self.copy_file(file_path, target_path)
87
-
88
- def list_files(self, path: Path) -> Iterable[Path]:
89
- if not path.exists():
90
- return []
91
- return [p for p in path.iterdir() if p.is_file()]
92
-
93
- def read_text(self, path: Path) -> str:
94
- return path.read_text(encoding="utf-8")
95
-
96
- def write_text(self, path: Path, content: str) -> None:
97
- path.parent.mkdir(parents=True, exist_ok=True)
98
- path.write_text(content, encoding="utf-8")
99
-
100
- def move(self, src: Path, dest: Path) -> None:
101
- dest.parent.mkdir(parents=True, exist_ok=True)
102
- if dest.exists() and dest.is_file():
103
- dest.unlink()
104
- src.replace(dest)
105
-
106
-
107
- def resolve_workspace() -> Path:
108
- """Resolve repository workspace root."""
109
- return Path.cwd()
110
-
111
-
112
- def get_est_timestamp(now_provider: Callable[[], datetime] | None = None) -> str:
113
- """Return a Windows-safe timestamp string for America/New_York."""
114
- now = (
115
- now_provider()
116
- if now_provider
117
- else datetime.now(tz=ZoneInfo("America/New_York"))
118
- )
119
- if now.tzinfo is None:
120
- raise ValueError("now_provider must return a timezone-aware datetime")
121
- localized = now.astimezone(ZoneInfo("America/New_York"))
122
- return localized.strftime("%Y-%m-%dT%H-%M")
123
-
124
-
125
- def extract_date_from_timestamp(timestamp: str) -> str:
126
- """Extract YYYY-MM-DD date component from a timestamp string."""
127
- return timestamp.split("T", 1)[0]
128
-
129
-
130
- def validate_feature_name(feature_name: str) -> None:
131
- """Validate feature-name slug format."""
132
- if not feature_name or not NAME_PATTERN.fullmatch(feature_name):
133
- raise ValueError(
134
- f"Aborted: '{feature_name}' is invalid. Use kebab/underscore-case "
135
- "letters/numbers (e.g., notes-feature or notes_feature)."
136
- )