@danmoisan/drm-copilot-mcp 0.0.5 → 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 +10268 -380
- package/package.json +2 -2
- package/resources/claude-customizations/.claude/agents/orchestrator.md +6 -2
- package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
- package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +68 -10
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +148 -11
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +198 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +153 -10
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +136 -0
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +22 -6
- package/resources/claude-customizations/.claude/settings.json +22 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +0 -1
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +76 -2
- package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +5 -3
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
- package/resources/claude-customizations/pack-manifests/core.json +65 -0
- package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
- package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
- package/resources/claude-customizations/pack-manifests/python.json +14 -0
- package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +30 -13
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +113 -10
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +33 -8
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +12 -7
- package/resources/codex-and-agents-customizations/.codex/config.toml +65 -6
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +83 -17
- package/resources/config/orchestration-routing.json +5 -13
- package/resources/customizations/.github/agents/pr-author.agent.md +25 -0
- package/resources/customizations/.github/agents/task-researcher.agent.md +4 -4
- package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
- package/resources/customizations/.github/prompts/research-issue.prompt.md +4 -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 -374
- 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 -428
- package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -448
- 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 -409
- 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,74 +0,0 @@
|
|
|
1
|
-
"""Compatibility wrapper for extension-side PR context collection.
|
|
2
|
-
|
|
3
|
-
Purpose:
|
|
4
|
-
Preserve the historical bundled-script entry point while delegating all
|
|
5
|
-
collection/rendering behavior to the bundled package implementation.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python collect_pr_context.py --base <ref> --repo-root <path> \
|
|
9
|
-
--out <path> --appendix-out <path>
|
|
10
|
-
|
|
11
|
-
Flow:
|
|
12
|
-
1. Ensure `resources/scripts/` is importable at runtime.
|
|
13
|
-
2. Import `dev_tools.pr_context.collector.main` from bundled sources.
|
|
14
|
-
3. Invoke collector entrypoint in-process with unmodified CLI args.
|
|
15
|
-
|
|
16
|
-
Invariants / Constraints:
|
|
17
|
-
- No PR context rendering logic is implemented in this wrapper.
|
|
18
|
-
- Argument contract is owned by the collector module and forwarded unchanged.
|
|
19
|
-
|
|
20
|
-
Side Effects:
|
|
21
|
-
Imports bundled collector code and executes it in the current Python process.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
from __future__ import annotations
|
|
25
|
-
|
|
26
|
-
import importlib
|
|
27
|
-
import sys
|
|
28
|
-
from pathlib import Path
|
|
29
|
-
from typing import TYPE_CHECKING, cast
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import Callable
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _ensure_bundled_scripts_import_path() -> None:
|
|
36
|
-
"""Prepend bundled `resources/scripts` directory to ``sys.path``.
|
|
37
|
-
|
|
38
|
-
Purpose:
|
|
39
|
-
Make extension-bundled Python packages importable regardless of the
|
|
40
|
-
destination workspace layout.
|
|
41
|
-
|
|
42
|
-
Side Effects:
|
|
43
|
-
Mutates ``sys.path`` by inserting the bundled scripts directory at
|
|
44
|
-
index 0 when not already present.
|
|
45
|
-
"""
|
|
46
|
-
scripts_dir = Path(__file__).resolve().parent.parent / "scripts"
|
|
47
|
-
scripts_dir_str = str(scripts_dir)
|
|
48
|
-
|
|
49
|
-
if scripts_dir_str not in sys.path:
|
|
50
|
-
sys.path.insert(0, scripts_dir_str)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def main() -> int:
|
|
54
|
-
"""Execute bundled PR-context collector entrypoint in-process.
|
|
55
|
-
|
|
56
|
-
Purpose:
|
|
57
|
-
Keep the bundled extension script as a thin adapter and avoid duplicated
|
|
58
|
-
PR-context business logic while preserving collector-owned CLI parsing.
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Exit code from bundled collector main function.
|
|
62
|
-
|
|
63
|
-
Side Effects:
|
|
64
|
-
Imports the bundled collector module and executes its CLI flow.
|
|
65
|
-
"""
|
|
66
|
-
_ensure_bundled_scripts_import_path()
|
|
67
|
-
collector_module = importlib.import_module("dev_tools.pr_context.collector")
|
|
68
|
-
collector_main = cast("Callable[[], int]", collector_module.main)
|
|
69
|
-
|
|
70
|
-
return collector_main()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if __name__ == "__main__":
|
|
74
|
-
raise SystemExit(main())
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def main() -> None:
|
|
5
|
-
output_file = Path("artifacts/hello_python.txt")
|
|
6
|
-
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
7
|
-
output_file.write_text("hello_python:ok\n", encoding="utf-8")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if __name__ == "__main__":
|
|
11
|
-
main()
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"""Compatibility wrapper for extension-side active feature folder creation.
|
|
2
|
-
|
|
3
|
-
Purpose:
|
|
4
|
-
Preserve the bundled-script entry point while delegating folder-creation
|
|
5
|
-
behavior to the bundled package implementation.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python new_active_feature_folder.py --feature-name <name> --type <type> \
|
|
9
|
-
[--issue-number <number>] --work-mode <mode>
|
|
10
|
-
|
|
11
|
-
Flow:
|
|
12
|
-
1. Ensure `resources/scripts/` is importable at runtime.
|
|
13
|
-
2. Import `dev_tools.new_active_feature_folder` from bundled sources.
|
|
14
|
-
3. Invoke the bundled CLI entrypoint in-process with unmodified CLI args.
|
|
15
|
-
|
|
16
|
-
Invariants / Constraints:
|
|
17
|
-
- No feature-folder business logic is implemented in this wrapper.
|
|
18
|
-
- Argument contract is owned by the bundled folder-creation module and
|
|
19
|
-
forwarded unchanged.
|
|
20
|
-
|
|
21
|
-
Side Effects:
|
|
22
|
-
Imports bundled folder-creation code and executes it in the current Python
|
|
23
|
-
process.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
from __future__ import annotations
|
|
27
|
-
|
|
28
|
-
import importlib
|
|
29
|
-
import sys
|
|
30
|
-
from pathlib import Path
|
|
31
|
-
from typing import TYPE_CHECKING, cast
|
|
32
|
-
|
|
33
|
-
if TYPE_CHECKING:
|
|
34
|
-
from collections.abc import Callable
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _ensure_bundled_scripts_import_path() -> None:
|
|
38
|
-
"""Prepend bundled `resources/scripts` directory to ``sys.path``."""
|
|
39
|
-
scripts_dir = Path(__file__).resolve().parent.parent / "scripts"
|
|
40
|
-
scripts_dir_str = str(scripts_dir)
|
|
41
|
-
|
|
42
|
-
if scripts_dir_str not in sys.path:
|
|
43
|
-
sys.path.insert(0, scripts_dir_str)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def main() -> int:
|
|
47
|
-
"""Execute bundled active-feature-folder entrypoint in-process.
|
|
48
|
-
|
|
49
|
-
Injects ``--template-root`` pointing to the extension's bundled
|
|
50
|
-
``feature-templates`` directory so the downstream module resolves
|
|
51
|
-
templates from the extension rather than the workspace.
|
|
52
|
-
"""
|
|
53
|
-
_ensure_bundled_scripts_import_path()
|
|
54
|
-
|
|
55
|
-
# Compute the bundled template root relative to this wrapper script.
|
|
56
|
-
template_root = str(Path(__file__).resolve().parent.parent / "feature-templates")
|
|
57
|
-
if "--template-root" not in sys.argv:
|
|
58
|
-
sys.argv.extend(["--template-root", template_root])
|
|
59
|
-
|
|
60
|
-
module = importlib.import_module("dev_tools.new_active_feature_folder")
|
|
61
|
-
module_main = cast("Callable[[], None]", module.main)
|
|
62
|
-
module_main()
|
|
63
|
-
return 0
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if __name__ == "__main__":
|
|
67
|
-
raise SystemExit(main())
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""Compatibility wrapper for extension-side potential bug entry creation.
|
|
2
|
-
|
|
3
|
-
Purpose:
|
|
4
|
-
Preserve the bundled-script entry point while delegating bug-entry creation
|
|
5
|
-
behavior to the bundled package implementation.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python new_potential_bug_entry.py --short-name <name> [--template-root <path>]
|
|
9
|
-
|
|
10
|
-
Flow:
|
|
11
|
-
1. Ensure `resources/scripts/` is importable at runtime.
|
|
12
|
-
2. Import `dev_tools.new_potential_bug_entry` from bundled sources.
|
|
13
|
-
3. Invoke the bundled CLI entrypoint in-process with unmodified CLI args.
|
|
14
|
-
|
|
15
|
-
Invariants / Constraints:
|
|
16
|
-
- No bug-entry business logic is implemented in this wrapper.
|
|
17
|
-
- Argument contract is owned by the bundled bug-entry module and forwarded
|
|
18
|
-
unchanged.
|
|
19
|
-
|
|
20
|
-
Side Effects:
|
|
21
|
-
Imports bundled bug-entry code and executes it in the current Python process.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
from __future__ import annotations
|
|
25
|
-
|
|
26
|
-
import importlib
|
|
27
|
-
import sys
|
|
28
|
-
from pathlib import Path
|
|
29
|
-
from typing import TYPE_CHECKING, cast
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import Callable
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _ensure_bundled_scripts_import_path() -> None:
|
|
36
|
-
"""Prepend bundled `resources/scripts` directory to ``sys.path``."""
|
|
37
|
-
scripts_dir = Path(__file__).resolve().parent.parent / "scripts"
|
|
38
|
-
scripts_dir_str = str(scripts_dir)
|
|
39
|
-
|
|
40
|
-
if scripts_dir_str not in sys.path:
|
|
41
|
-
sys.path.insert(0, scripts_dir_str)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def main() -> int:
|
|
45
|
-
"""Execute bundled potential-bug-entry entrypoint in-process."""
|
|
46
|
-
_ensure_bundled_scripts_import_path()
|
|
47
|
-
module = importlib.import_module("dev_tools.new_potential_bug_entry")
|
|
48
|
-
module_main = cast("Callable[[], None]", module.main)
|
|
49
|
-
module_main()
|
|
50
|
-
return 0
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if __name__ == "__main__":
|
|
54
|
-
raise SystemExit(main())
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""Compatibility wrapper for extension-side potential-to-issue promotion.
|
|
2
|
-
|
|
3
|
-
Purpose:
|
|
4
|
-
Preserve the bundled-script entry point while delegating promotion behavior
|
|
5
|
-
to the bundled package implementation.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python potential_to_issue.py --potential-path <path> --promotion-type <type> \
|
|
9
|
-
--work-mode <mode>
|
|
10
|
-
|
|
11
|
-
Flow:
|
|
12
|
-
1. Ensure `resources/scripts/` is importable at runtime.
|
|
13
|
-
2. Import `dev_tools.potential_to_issue` from bundled sources.
|
|
14
|
-
3. Invoke the bundled CLI entrypoint in-process with unmodified CLI args.
|
|
15
|
-
|
|
16
|
-
Invariants / Constraints:
|
|
17
|
-
- No promotion business logic is implemented in this wrapper.
|
|
18
|
-
- Argument contract is owned by the bundled promotion module and forwarded
|
|
19
|
-
unchanged.
|
|
20
|
-
|
|
21
|
-
Side Effects:
|
|
22
|
-
Imports bundled promotion code and executes it in the current Python process.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import importlib
|
|
28
|
-
import sys
|
|
29
|
-
from pathlib import Path
|
|
30
|
-
from typing import TYPE_CHECKING, cast
|
|
31
|
-
|
|
32
|
-
if TYPE_CHECKING:
|
|
33
|
-
from collections.abc import Callable
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _ensure_bundled_scripts_import_path() -> None:
|
|
37
|
-
"""Prepend bundled `resources/scripts` directory to ``sys.path``."""
|
|
38
|
-
scripts_dir = Path(__file__).resolve().parent.parent / "scripts"
|
|
39
|
-
scripts_dir_str = str(scripts_dir)
|
|
40
|
-
|
|
41
|
-
if scripts_dir_str not in sys.path:
|
|
42
|
-
sys.path.insert(0, scripts_dir_str)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def main() -> int:
|
|
46
|
-
"""Execute bundled potential-to-issue entrypoint in-process."""
|
|
47
|
-
_ensure_bundled_scripts_import_path()
|
|
48
|
-
module = importlib.import_module("dev_tools.potential_to_issue")
|
|
49
|
-
module_main = cast("Callable[[], None]", module.main)
|
|
50
|
-
module_main()
|
|
51
|
-
return 0
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if __name__ == "__main__":
|
|
55
|
-
raise SystemExit(main())
|
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
"""Publish bundled `.claude` content into a destination workspace.
|
|
2
|
-
|
|
3
|
-
Purpose:
|
|
4
|
-
Provide a dedicated public entry point for the Claude customization push-down
|
|
5
|
-
workflow while reusing the shared publisher engine behind the existing
|
|
6
|
-
`.github` customization flow. Settings-local configuration is excluded from
|
|
7
|
-
push-down because it holds host-specific overrides that must not propagate.
|
|
8
|
-
|
|
9
|
-
Agent-memory files under `.claude/agent-memory/` are filtered by a
|
|
10
|
-
content-based scope check: only memories whose frontmatter declares
|
|
11
|
-
`metadata.scope: general` are distributed to a destination workspace. A
|
|
12
|
-
memory with an absent, malformed, or unrecognized scope is treated as
|
|
13
|
-
`repo` and excluded (fail-safe default), so repository-specific memories do
|
|
14
|
-
not leak into consumer workspaces. Files outside `.claude/agent-memory/`
|
|
15
|
-
are copied verbatim and are never affected by the scope filter.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import argparse
|
|
21
|
-
import re
|
|
22
|
-
import sys
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
|
|
25
|
-
AGENT_MEMORY_RELATIVE_ROOT = Path(".claude/agent-memory")
|
|
26
|
-
GENERAL_MEMORY_SCOPE = "general"
|
|
27
|
-
REPO_MEMORY_SCOPE = "repo"
|
|
28
|
-
|
|
29
|
-
# Match a leading YAML frontmatter block: the first `---` line, the block body,
|
|
30
|
-
# and the closing `---` line. DOTALL lets the body span multiple lines.
|
|
31
|
-
_FRONTMATTER_PATTERN = re.compile(
|
|
32
|
-
r"\A---[ \t]*\r?\n(.*?)\r?\n---[ \t]*(?:\r?\n|\Z)", re.DOTALL
|
|
33
|
-
)
|
|
34
|
-
# Match a `metadata:` mapping key at column zero, then capture the indented
|
|
35
|
-
# block lines that belong to it (lines that are more-indented or blank) until
|
|
36
|
-
# the next column-zero key or end of the frontmatter body.
|
|
37
|
-
_METADATA_BLOCK_PATTERN = re.compile(
|
|
38
|
-
r"^metadata:[ \t]*\r?\n((?:[ \t]+.*(?:\r?\n|\Z)|\r?\n)*)",
|
|
39
|
-
re.MULTILINE,
|
|
40
|
-
)
|
|
41
|
-
# Match a `scope:` leaf inside the metadata block, capturing its scalar value
|
|
42
|
-
# up to an optional inline comment. Surrounding quotes are stripped later.
|
|
43
|
-
_SCOPE_LEAF_PATTERN = re.compile(
|
|
44
|
-
r"^[ \t]+scope:[ \t]*([^\r\n#]*)",
|
|
45
|
-
re.MULTILINE,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _ensure_bundled_scripts_import_path() -> None:
|
|
50
|
-
"""Prepend bundled ``resources/scripts`` directory to ``sys.path``.
|
|
51
|
-
|
|
52
|
-
Purpose:
|
|
53
|
-
Make extension-bundled Python packages importable when this template is
|
|
54
|
-
invoked directly from ``resources/templates/`` rather than from the
|
|
55
|
-
repository root. Without this bootstrap, neither
|
|
56
|
-
``scripts.dev_tools.push_down_copilot_customizations`` nor the fallback
|
|
57
|
-
``dev_tools.push_down_copilot_customizations`` import resolves because
|
|
58
|
-
``resources/scripts`` is not on ``sys.path`` by default.
|
|
59
|
-
|
|
60
|
-
Side Effects:
|
|
61
|
-
Mutates ``sys.path`` by inserting the bundled scripts directory at
|
|
62
|
-
index 0 when not already present.
|
|
63
|
-
"""
|
|
64
|
-
scripts_dir = Path(__file__).resolve().parent.parent / "scripts"
|
|
65
|
-
scripts_dir_str = str(scripts_dir)
|
|
66
|
-
|
|
67
|
-
if scripts_dir_str not in sys.path:
|
|
68
|
-
sys.path.insert(0, scripts_dir_str)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
_ensure_bundled_scripts_import_path()
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
from scripts.dev_tools.push_down_copilot_customizations import (
|
|
75
|
-
PushDownFileSystem,
|
|
76
|
-
PushDownSummary,
|
|
77
|
-
RealPushDownFileSystem,
|
|
78
|
-
resolve_cli_path,
|
|
79
|
-
)
|
|
80
|
-
from scripts.dev_tools.push_down_copilot_customizations import (
|
|
81
|
-
push_down_customizations as push_down_scoped_customizations,
|
|
82
|
-
)
|
|
83
|
-
except ModuleNotFoundError as error:
|
|
84
|
-
if error.name is None or not error.name.startswith("scripts"):
|
|
85
|
-
raise
|
|
86
|
-
from dev_tools.push_down_copilot_customizations import (
|
|
87
|
-
PushDownFileSystem,
|
|
88
|
-
PushDownSummary,
|
|
89
|
-
RealPushDownFileSystem,
|
|
90
|
-
resolve_cli_path,
|
|
91
|
-
)
|
|
92
|
-
from dev_tools.push_down_copilot_customizations import (
|
|
93
|
-
push_down_customizations as push_down_scoped_customizations,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
ARTIFACT_DIRECTORY = "artifacts/claude-customizations"
|
|
97
|
-
MODULE_ENTRY_POINT = "scripts.dev_tools.push_down_claude_customizations"
|
|
98
|
-
ROOT_FOLDERS: tuple[Path, ...] = (Path(".claude"),)
|
|
99
|
-
EXCLUDED_RELATIVE_PATHS: tuple[Path, ...] = (Path(".claude/settings.local.json"),)
|
|
100
|
-
|
|
101
|
-
__all__ = [
|
|
102
|
-
"AGENT_MEMORY_RELATIVE_ROOT",
|
|
103
|
-
"ARTIFACT_DIRECTORY",
|
|
104
|
-
"EXCLUDED_RELATIVE_PATHS",
|
|
105
|
-
"GENERAL_MEMORY_SCOPE",
|
|
106
|
-
"PushDownSummary",
|
|
107
|
-
"REPO_MEMORY_SCOPE",
|
|
108
|
-
"ROOT_FOLDERS",
|
|
109
|
-
"main",
|
|
110
|
-
"parse_args",
|
|
111
|
-
"push_down_customizations",
|
|
112
|
-
]
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _read_memory_scope(content: str) -> str:
|
|
116
|
-
"""Return the declared memory scope from a file's YAML frontmatter.
|
|
117
|
-
|
|
118
|
-
Purpose:
|
|
119
|
-
Extract the `metadata.scope` leaf from the leading YAML frontmatter
|
|
120
|
-
block using a narrow `re`-based parser. This avoids adding a runtime
|
|
121
|
-
YAML dependency (PyYAML) while reading only the single leaf the
|
|
122
|
-
push-down scope filter requires.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
content (str): The full text of a candidate memory file, including any
|
|
126
|
-
leading `---` frontmatter block.
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
str: ``"general"`` only when the frontmatter contains a `metadata:`
|
|
130
|
-
mapping whose `scope:` leaf is exactly ``general`` (quotes and inline
|
|
131
|
-
comments stripped). In every other case — missing frontmatter, no
|
|
132
|
-
closing `---`, no `metadata:` block, no `scope:` leaf, or any value
|
|
133
|
-
other than exactly ``general`` — it returns ``"repo"`` as the fail-safe
|
|
134
|
-
default so nothing leaks by accident.
|
|
135
|
-
|
|
136
|
-
Raises:
|
|
137
|
-
None.
|
|
138
|
-
|
|
139
|
-
Side Effects:
|
|
140
|
-
None.
|
|
141
|
-
"""
|
|
142
|
-
|
|
143
|
-
# Isolate the leading frontmatter block; absent or unterminated frontmatter
|
|
144
|
-
# fails safe to the repo scope so unmarked files are never distributed.
|
|
145
|
-
frontmatter_match = _FRONTMATTER_PATTERN.match(content)
|
|
146
|
-
if frontmatter_match is None:
|
|
147
|
-
return REPO_MEMORY_SCOPE
|
|
148
|
-
frontmatter_body = frontmatter_match.group(1)
|
|
149
|
-
|
|
150
|
-
# Locate the metadata mapping; without it there is no scope leaf to read.
|
|
151
|
-
metadata_match = _METADATA_BLOCK_PATTERN.search(frontmatter_body)
|
|
152
|
-
if metadata_match is None:
|
|
153
|
-
return REPO_MEMORY_SCOPE
|
|
154
|
-
metadata_block = metadata_match.group(1)
|
|
155
|
-
|
|
156
|
-
# Read the scope leaf from within the metadata block only; a top-level
|
|
157
|
-
# `scope:` outside `metadata:` is intentionally ignored.
|
|
158
|
-
scope_match = _SCOPE_LEAF_PATTERN.search(metadata_block)
|
|
159
|
-
if scope_match is None:
|
|
160
|
-
return REPO_MEMORY_SCOPE
|
|
161
|
-
|
|
162
|
-
# Strip surrounding whitespace and optional matching quotes before the
|
|
163
|
-
# exact-match comparison; only an exact `general` is treated as general.
|
|
164
|
-
scope_value = scope_match.group(1).strip()
|
|
165
|
-
if (
|
|
166
|
-
len(scope_value) >= 2
|
|
167
|
-
and scope_value[0] == scope_value[-1]
|
|
168
|
-
and scope_value[0] in {'"', "'"}
|
|
169
|
-
):
|
|
170
|
-
scope_value = scope_value[1:-1].strip()
|
|
171
|
-
if scope_value == GENERAL_MEMORY_SCOPE:
|
|
172
|
-
return GENERAL_MEMORY_SCOPE
|
|
173
|
-
return REPO_MEMORY_SCOPE
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _is_general_memory_file(relative_path: Path, content: str) -> bool:
|
|
177
|
-
"""Return whether a candidate file may be distributed by push-down.
|
|
178
|
-
|
|
179
|
-
Purpose:
|
|
180
|
-
Decide inclusion for one source file. Files under
|
|
181
|
-
`.claude/agent-memory/` are distributed only when general-scoped;
|
|
182
|
-
every other file is always distributed and is unaffected by the scope
|
|
183
|
-
filter.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
relative_path (Path): The file path relative to the repository root.
|
|
187
|
-
content (str): The full text of the file, used to read the memory
|
|
188
|
-
scope when the path is under the agent-memory subtree.
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
bool: ``True`` when the path is outside `.claude/agent-memory/`, or when
|
|
192
|
-
the path is under that subtree and `_read_memory_scope(content)` is
|
|
193
|
-
exactly ``general``. ``False`` only for an agent-memory file whose
|
|
194
|
-
scope is not general (the fail-safe exclusion).
|
|
195
|
-
|
|
196
|
-
Raises:
|
|
197
|
-
None.
|
|
198
|
-
|
|
199
|
-
Side Effects:
|
|
200
|
-
None.
|
|
201
|
-
"""
|
|
202
|
-
|
|
203
|
-
# Files outside the agent-memory subtree are always copied; the scope
|
|
204
|
-
# filter never applies to rules, skills, agents, hooks, or settings.
|
|
205
|
-
try:
|
|
206
|
-
relative_path.relative_to(AGENT_MEMORY_RELATIVE_ROOT)
|
|
207
|
-
except ValueError:
|
|
208
|
-
return True
|
|
209
|
-
return _read_memory_scope(content) == GENERAL_MEMORY_SCOPE
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
class _ExcludingFileSystem:
|
|
213
|
-
"""Wrap a PushDownFileSystem and filter specified paths from list_files.
|
|
214
|
-
|
|
215
|
-
Purpose:
|
|
216
|
-
Prevent host-specific files (e.g. `settings.local.json`) from being
|
|
217
|
-
included when the publisher enumerates source files, and exclude
|
|
218
|
-
repository-specific agent memories so only general-scoped memories are
|
|
219
|
-
distributed. Chosen over a post-enumeration filter so the exclusion
|
|
220
|
-
travels with the filesystem contract and is transparent to the shared
|
|
221
|
-
engine.
|
|
222
|
-
|
|
223
|
-
Usage:
|
|
224
|
-
Instantiate with the inner adapter and exclusion paths relative to
|
|
225
|
-
the repo root; pass the result as the `fs` argument to the engine.
|
|
226
|
-
|
|
227
|
-
Invariants / Constraints:
|
|
228
|
-
Excluded paths are resolved once at construction time for O(1) checks.
|
|
229
|
-
The content-based scope filter reads file content only for candidates
|
|
230
|
-
under `.claude/agent-memory/`; all other files skip the read.
|
|
231
|
-
|
|
232
|
-
Side Effects:
|
|
233
|
-
Delegates all I/O to the inner adapter.
|
|
234
|
-
"""
|
|
235
|
-
|
|
236
|
-
def __init__(
|
|
237
|
-
self, inner: PushDownFileSystem, repo_root: Path, excluded: tuple[Path, ...]
|
|
238
|
-
) -> None:
|
|
239
|
-
"""Set up the adapter; resolve exclusion paths relative to repo_root."""
|
|
240
|
-
self._inner = inner
|
|
241
|
-
# Retain the resolved repo root so per-file scope checks can derive the
|
|
242
|
-
# repo-relative path needed by the agent-memory scope filter.
|
|
243
|
-
self._repo_root = repo_root.resolve()
|
|
244
|
-
# Resolve once at init so list_files per-path checks are O(1).
|
|
245
|
-
self._excluded: frozenset[Path] = frozenset(
|
|
246
|
-
(repo_root / p).resolve() for p in excluded
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
def _is_scope_included(self, path: Path) -> bool:
|
|
250
|
-
"""Return whether a candidate file passes the agent-memory scope filter.
|
|
251
|
-
|
|
252
|
-
Purpose:
|
|
253
|
-
Apply the general-vs-repo memory scope decision to one enumerated
|
|
254
|
-
file, reading its content only when the path is under the
|
|
255
|
-
agent-memory subtree.
|
|
256
|
-
|
|
257
|
-
Args:
|
|
258
|
-
path (Path): The absolute candidate path returned by the inner
|
|
259
|
-
adapter's ``list_files``.
|
|
260
|
-
|
|
261
|
-
Returns:
|
|
262
|
-
bool: ``True`` when the file is outside `.claude/agent-memory/` or
|
|
263
|
-
is a general-scoped memory; ``False`` for a non-general memory.
|
|
264
|
-
|
|
265
|
-
Raises:
|
|
266
|
-
None.
|
|
267
|
-
|
|
268
|
-
Side Effects:
|
|
269
|
-
Reads file content via the inner adapter for agent-memory
|
|
270
|
-
candidates only.
|
|
271
|
-
"""
|
|
272
|
-
|
|
273
|
-
# Derive the repo-relative path so the agent-memory check matches the
|
|
274
|
-
# `.claude/agent-memory/` prefix regardless of the absolute location.
|
|
275
|
-
try:
|
|
276
|
-
relative_path = path.resolve().relative_to(self._repo_root)
|
|
277
|
-
except ValueError:
|
|
278
|
-
# A path outside the repo root cannot be an agent memory; include it.
|
|
279
|
-
return True
|
|
280
|
-
|
|
281
|
-
# Skip the content read entirely for files outside the memory subtree.
|
|
282
|
-
try:
|
|
283
|
-
relative_path.relative_to(AGENT_MEMORY_RELATIVE_ROOT)
|
|
284
|
-
except ValueError:
|
|
285
|
-
return True
|
|
286
|
-
|
|
287
|
-
content = self._inner.read_text(path)
|
|
288
|
-
return _is_general_memory_file(relative_path, content)
|
|
289
|
-
|
|
290
|
-
def list_files(self, root: Path) -> list[Path]:
|
|
291
|
-
"""Return inner list_files output with excluded paths removed.
|
|
292
|
-
|
|
293
|
-
Drops paths in ``EXCLUDED_RELATIVE_PATHS`` and any agent-memory file
|
|
294
|
-
that is not general-scoped per the content-based scope filter.
|
|
295
|
-
"""
|
|
296
|
-
return [
|
|
297
|
-
p
|
|
298
|
-
for p in self._inner.list_files(root)
|
|
299
|
-
if p.resolve() not in self._excluded and self._is_scope_included(p)
|
|
300
|
-
]
|
|
301
|
-
|
|
302
|
-
def is_dir(self, path: Path) -> bool:
|
|
303
|
-
"""Delegate to inner adapter."""
|
|
304
|
-
return self._inner.is_dir(path)
|
|
305
|
-
|
|
306
|
-
def is_file(self, path: Path) -> bool:
|
|
307
|
-
"""Delegate to inner adapter."""
|
|
308
|
-
return self._inner.is_file(path)
|
|
309
|
-
|
|
310
|
-
def read_text(self, path: Path) -> str:
|
|
311
|
-
"""Delegate to inner adapter."""
|
|
312
|
-
return self._inner.read_text(path)
|
|
313
|
-
|
|
314
|
-
def write_text(self, path: Path, content: str) -> None:
|
|
315
|
-
"""Delegate to inner adapter."""
|
|
316
|
-
self._inner.write_text(path, content)
|
|
317
|
-
|
|
318
|
-
def ensure_dir(self, path: Path) -> None:
|
|
319
|
-
"""Delegate to inner adapter."""
|
|
320
|
-
self._inner.ensure_dir(path)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def _passthrough_rewrite(
|
|
324
|
-
text: str,
|
|
325
|
-
) -> tuple[str, int, int, list[str]]:
|
|
326
|
-
"""Return unmodified text for payloads that do not need command rewrites."""
|
|
327
|
-
|
|
328
|
-
return text, 0, 0, []
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def push_down_customizations(
|
|
332
|
-
*,
|
|
333
|
-
repo_root: Path,
|
|
334
|
-
destination_root: Path,
|
|
335
|
-
fs: PushDownFileSystem,
|
|
336
|
-
source_root: Path | None = None,
|
|
337
|
-
artifact_root: Path | None = None,
|
|
338
|
-
) -> PushDownSummary:
|
|
339
|
-
"""Copy the `.claude` tree into the destination workspace.
|
|
340
|
-
|
|
341
|
-
Excludes paths in `EXCLUDED_RELATIVE_PATHS` by wrapping `fs` in
|
|
342
|
-
`_ExcludingFileSystem` before delegating to the shared engine.
|
|
343
|
-
"""
|
|
344
|
-
|
|
345
|
-
# Wrap the caller-supplied adapter so enumeration omits excluded paths.
|
|
346
|
-
excluding_fs = _ExcludingFileSystem(fs, repo_root, EXCLUDED_RELATIVE_PATHS)
|
|
347
|
-
return push_down_scoped_customizations(
|
|
348
|
-
repo_root=repo_root,
|
|
349
|
-
destination_root=destination_root,
|
|
350
|
-
fs=excluding_fs,
|
|
351
|
-
source_root=source_root,
|
|
352
|
-
artifact_root=artifact_root,
|
|
353
|
-
root_folders=ROOT_FOLDERS,
|
|
354
|
-
artifact_directory=ARTIFACT_DIRECTORY,
|
|
355
|
-
rewrite_references=_passthrough_rewrite,
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
360
|
-
"""Parse CLI arguments for the Claude customization push-down publisher."""
|
|
361
|
-
|
|
362
|
-
parser = argparse.ArgumentParser(
|
|
363
|
-
description=(
|
|
364
|
-
"Publish bundled Claude customizations with "
|
|
365
|
-
f"python -m {MODULE_ENTRY_POINT}."
|
|
366
|
-
)
|
|
367
|
-
)
|
|
368
|
-
parser.add_argument(
|
|
369
|
-
"--destination",
|
|
370
|
-
required=True,
|
|
371
|
-
help=("Destination workspace root that will receive the copied .claude tree."),
|
|
372
|
-
)
|
|
373
|
-
return parser.parse_args(argv)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def main(
|
|
377
|
-
argv: list[str] | None = None,
|
|
378
|
-
*,
|
|
379
|
-
repo_root: Path | None = None,
|
|
380
|
-
fs: PushDownFileSystem | None = None,
|
|
381
|
-
) -> int:
|
|
382
|
-
"""Run the Claude customization push-down publisher CLI."""
|
|
383
|
-
|
|
384
|
-
args = parse_args(argv)
|
|
385
|
-
# Resolve the source root to the bundled customizations directory so the
|
|
386
|
-
# template distributes the bundled `.claude` payload rather than copying a
|
|
387
|
-
# destination workspace's existing `.claude` back onto itself. This mirrors
|
|
388
|
-
# the codex template (`push_down_codex_and_agents_customizations.py`), which
|
|
389
|
-
# sets its source root to the sibling bundled directory. The optional
|
|
390
|
-
# `repo_root` argument still allows tests to inject an explicit root.
|
|
391
|
-
customizations_root = (
|
|
392
|
-
Path(__file__).resolve().parent.parent / "claude-customizations"
|
|
393
|
-
)
|
|
394
|
-
resolved_repo_root = resolve_cli_path(repo_root or customizations_root)
|
|
395
|
-
resolved_destination = resolve_cli_path(args.destination)
|
|
396
|
-
resolved_fs = fs or RealPushDownFileSystem()
|
|
397
|
-
summary = push_down_customizations(
|
|
398
|
-
repo_root=resolved_repo_root,
|
|
399
|
-
destination_root=resolved_destination,
|
|
400
|
-
fs=resolved_fs,
|
|
401
|
-
source_root=resolved_repo_root,
|
|
402
|
-
artifact_root=resolved_repo_root,
|
|
403
|
-
)
|
|
404
|
-
print(f"Wrote push-down summary artifact to: {summary.artifact_path}")
|
|
405
|
-
return 0
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if __name__ == "__main__":
|
|
409
|
-
raise SystemExit(main())
|