claudekit-codex-sync 0.1.0 → 0.2.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/README.md +76 -59
- package/docs/codebase-summary.md +40 -43
- package/docs/codex-vs-claude-agents.md +53 -42
- package/docs/installation-guide.md +67 -23
- package/docs/project-overview-pdr.md +20 -26
- package/docs/project-roadmap.md +26 -32
- package/docs/system-architecture.md +45 -82
- package/package.json +4 -3
- package/plans/260223-1310-v02-cli-redesign-cleanup/phase-01-delete-dead-code.md +88 -0
- package/plans/260223-1310-v02-cli-redesign-cleanup/phase-02-cli-redesign.md +316 -0
- package/plans/260223-1310-v02-cli-redesign-cleanup/phase-03-symlink-venv.md +148 -0
- package/plans/260223-1310-v02-cli-redesign-cleanup/phase-04-wire-unused-functions.md +151 -0
- package/plans/260223-1310-v02-cli-redesign-cleanup/phase-05-safety-tests-docs.md +206 -0
- package/plans/260223-1310-v02-cli-redesign-cleanup/plan.md +18 -0
- package/plans/reports/planner-260223-v02-cli-redesign-cleanup-validation.md +95 -0
- package/plans/reports/project-manager-260223-v02-cli-redesign-cleanup-finalization.md +28 -0
- package/src/claudekit_codex_sync/asset_sync_dir.py +60 -9
- package/src/claudekit_codex_sync/asset_sync_zip.py +16 -5
- package/src/claudekit_codex_sync/clean_target.py +49 -0
- package/src/claudekit_codex_sync/cli.py +113 -81
- package/src/claudekit_codex_sync/constants.py +2 -13
- package/src/claudekit_codex_sync/dep_bootstrapper.py +71 -29
- package/src/claudekit_codex_sync/path_normalizer.py +2 -0
- package/src/claudekit_codex_sync/source_resolver.py +10 -2
- package/src/claudekit_codex_sync/sync_registry.py +1 -0
- package/templates/agents-md.md +5 -8
- package/tests/test_clean_target.py +55 -0
- package/tests/test_cli_args.py +57 -0
- package/reports/brainstorm-260222-2051-claudekit-codex-community-sync.md +0 -113
- package/scripts/bootstrap-claudekit-skill-scripts.sh +0 -150
- package/scripts/claudekit-sync-all.py +0 -1150
- package/scripts/export-claudekit-prompts.sh +0 -221
- package/scripts/normalize-claudekit-for-codex.sh +0 -261
|
@@ -41,7 +41,7 @@ def detect_claude_source() -> Path:
|
|
|
41
41
|
for p in candidates:
|
|
42
42
|
if p.exists() and (p / "skills").is_dir():
|
|
43
43
|
return p
|
|
44
|
-
raise SyncError("Claude Code not found. Use --source
|
|
44
|
+
raise SyncError("Claude Code not found. Use --source to specify.")
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def validate_source(source: Path) -> Dict[str, bool]:
|
|
@@ -61,11 +61,19 @@ def collect_skill_entries(zf: zipfile.ZipFile) -> Dict[str, List[Tuple[str, str]
|
|
|
61
61
|
for name in zf.namelist():
|
|
62
62
|
if name.endswith("/") or not name.startswith(".claude/skills/"):
|
|
63
63
|
continue
|
|
64
|
-
rel = name[len(".claude/skills/") :]
|
|
64
|
+
rel = name[len(".claude/skills/") :].replace("\\", "/")
|
|
65
|
+
path = Path(rel)
|
|
66
|
+
if path.is_absolute() or ".." in path.parts:
|
|
67
|
+
raise SyncError(f"Unsafe zip entry path: {name}")
|
|
65
68
|
parts = rel.split("/", 1)
|
|
66
69
|
if len(parts) != 2:
|
|
67
70
|
continue
|
|
68
71
|
skill, inner = parts
|
|
72
|
+
inner_path = Path(inner)
|
|
73
|
+
if Path(skill).is_absolute() or ".." in Path(skill).parts:
|
|
74
|
+
raise SyncError(f"Unsafe skill name in zip entry: {name}")
|
|
75
|
+
if inner_path.is_absolute() or ".." in inner_path.parts:
|
|
76
|
+
raise SyncError(f"Unsafe skill file path in zip entry: {name}")
|
|
69
77
|
skill_files.setdefault(skill, []).append((name, inner))
|
|
70
78
|
return skill_files
|
|
71
79
|
|
|
@@ -28,6 +28,7 @@ def load_registry(codex_home: Path) -> Dict[str, Any]:
|
|
|
28
28
|
def save_registry(codex_home: Path, registry: Dict[str, Any]) -> None:
|
|
29
29
|
"""Save sync registry to disk."""
|
|
30
30
|
registry_path = codex_home / REGISTRY_FILE
|
|
31
|
+
registry_path.parent.mkdir(parents=True, exist_ok=True)
|
|
31
32
|
registry["lastSync"] = datetime.now(timezone.utc).isoformat()
|
|
32
33
|
registry_path.write_text(json.dumps(registry, indent=2), encoding="utf-8")
|
|
33
34
|
|
package/templates/agents-md.md
CHANGED
|
@@ -35,11 +35,8 @@ Codex working profile for this workspace, adapted from ClaudeKit rules and workf
|
|
|
35
35
|
- Activate relevant skills intentionally per task.
|
|
36
36
|
- For legacy ClaudeKit command intents (`/ck-help`, `/coding-level`, `/ask`, `/docs/*`, `/journal`, `/watzup`), use `$claudekit-command-bridge`.
|
|
37
37
|
|
|
38
|
-
## Reference Material
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
- `~/.codex/claudekit/rules/orchestration-protocol.md`
|
|
44
|
-
- `~/.codex/claudekit/rules/documentation-management.md`
|
|
45
|
-
- `~/.codex/claudekit/rules/team-coordination-rules.md`
|
|
38
|
+
## Reference Material
|
|
39
|
+
|
|
40
|
+
- `README.md`
|
|
41
|
+
- `docs/`
|
|
42
|
+
- `plans/`
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Tests for clean_target module."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from claudekit_codex_sync.clean_target import clean_target
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_clean_removes_agents(tmp_path: Path):
|
|
10
|
+
"""Clean removes agents dir."""
|
|
11
|
+
agents = tmp_path / "agents"
|
|
12
|
+
agents.mkdir()
|
|
13
|
+
(agents / "planner.toml").write_text("model = 'test'")
|
|
14
|
+
(agents / "researcher.toml").write_text("model = 'test'")
|
|
15
|
+
|
|
16
|
+
removed = clean_target(tmp_path, dry_run=False)
|
|
17
|
+
assert not agents.exists()
|
|
18
|
+
assert removed >= 2
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_clean_keeps_venv(tmp_path: Path):
|
|
22
|
+
"""Clean keeps skills/.venv intact."""
|
|
23
|
+
skills = tmp_path / "skills"
|
|
24
|
+
skills.mkdir()
|
|
25
|
+
venv = skills / ".venv"
|
|
26
|
+
venv.mkdir()
|
|
27
|
+
(venv / "bin").mkdir()
|
|
28
|
+
(venv / "bin" / "python3").write_text("#!/usr/bin/env python3")
|
|
29
|
+
skill = skills / "my-skill"
|
|
30
|
+
skill.mkdir()
|
|
31
|
+
(skill / "SKILL.md").write_text("# test")
|
|
32
|
+
|
|
33
|
+
clean_target(tmp_path, dry_run=False)
|
|
34
|
+
assert venv.exists(), ".venv should survive cleaning"
|
|
35
|
+
assert not skill.exists(), "skill dirs should be removed"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_clean_dry_run(tmp_path: Path):
|
|
39
|
+
"""Dry run counts but doesn't delete."""
|
|
40
|
+
agents = tmp_path / "agents"
|
|
41
|
+
agents.mkdir()
|
|
42
|
+
(agents / "test.toml").write_text("x = 1")
|
|
43
|
+
|
|
44
|
+
removed = clean_target(tmp_path, dry_run=True)
|
|
45
|
+
assert removed >= 1
|
|
46
|
+
assert agents.exists(), "dry-run should not delete"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_clean_removes_registry(tmp_path: Path):
|
|
50
|
+
"""Clean clears sync registry."""
|
|
51
|
+
registry = tmp_path / ".claudekit-sync-registry.json"
|
|
52
|
+
registry.write_text(json.dumps({"version": 1}))
|
|
53
|
+
|
|
54
|
+
clean_target(tmp_path, dry_run=False)
|
|
55
|
+
assert not registry.exists()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Tests for CLI argument parsing."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
from claudekit_codex_sync.cli import parse_args
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_default_args():
|
|
10
|
+
"""Default: project scope, no flags."""
|
|
11
|
+
with patch.object(sys, "argv", ["ckc-sync"]):
|
|
12
|
+
args = parse_args()
|
|
13
|
+
assert not args.global_scope
|
|
14
|
+
assert not args.fresh
|
|
15
|
+
assert not args.force
|
|
16
|
+
assert not args.mcp
|
|
17
|
+
assert not args.no_deps
|
|
18
|
+
assert not args.dry_run
|
|
19
|
+
assert args.zip_path is None
|
|
20
|
+
assert args.source is None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_global_flag():
|
|
24
|
+
"""'-g' enables global scope."""
|
|
25
|
+
with patch.object(sys, "argv", ["ckc-sync", "-g"]):
|
|
26
|
+
args = parse_args()
|
|
27
|
+
assert args.global_scope
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_fresh_flag():
|
|
31
|
+
"""'-f' enables fresh clean."""
|
|
32
|
+
with patch.object(sys, "argv", ["ckc-sync", "-f"]):
|
|
33
|
+
args = parse_args()
|
|
34
|
+
assert args.fresh
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_combined_short_flags():
|
|
38
|
+
"""'-g -f -n' all work together."""
|
|
39
|
+
with patch.object(sys, "argv", ["ckc-sync", "-g", "-f", "-n"]):
|
|
40
|
+
args = parse_args()
|
|
41
|
+
assert args.global_scope
|
|
42
|
+
assert args.fresh
|
|
43
|
+
assert args.dry_run
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_zip_flag():
|
|
47
|
+
"""'--zip' sets zip path and implies zip mode."""
|
|
48
|
+
with patch.object(sys, "argv", ["ckc-sync", "--zip", "test.zip"]):
|
|
49
|
+
args = parse_args()
|
|
50
|
+
assert str(args.zip_path) == "test.zip"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_mcp_flag():
|
|
54
|
+
"""'--mcp' includes MCP skills."""
|
|
55
|
+
with patch.object(sys, "argv", ["ckc-sync", "--mcp"]):
|
|
56
|
+
args = parse_args()
|
|
57
|
+
assert args.mcp
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Brainstorm Report: ClaudeKit -> Codex Community Toolkit
|
|
2
|
-
|
|
3
|
-
## Problem Statement
|
|
4
|
-
You want a community-shareable toolkit that syncs ClaudeKit assets into Codex with minimal manual work, supports latest zip detection, and proves behavior is close to Claude workflows.
|
|
5
|
-
|
|
6
|
-
## Requirements
|
|
7
|
-
- Full-auto sync entrypoint (latest zip auto-select).
|
|
8
|
-
- Pull from both zip and local `~/.claude` where needed.
|
|
9
|
-
- Keep current exclusions default (`mcp`, `hooks`) but support opt-in.
|
|
10
|
-
- Add agent-role sync compatible with Codex multi-agent system.
|
|
11
|
-
- Publish for community consumption via:
|
|
12
|
-
- `npm`/`npx` install path.
|
|
13
|
-
- `git clone` + local run path.
|
|
14
|
-
- Provide evidence that behavior is close to ClaudeKit orchestration (not only file-copy parity).
|
|
15
|
-
|
|
16
|
-
## Current Reality Check
|
|
17
|
-
- Current script (`~/.codex/scripts/claudekit-sync-all.py`) is strong for assets/skills/rules/prompts sync.
|
|
18
|
-
- It is not yet productized as a public package.
|
|
19
|
-
- It does not yet implement Claude-agent -> Codex-agent role transpilation + parity benchmark harness.
|
|
20
|
-
- Behavior parity is therefore not yet provable by metrics.
|
|
21
|
-
|
|
22
|
-
## Approaches Evaluated
|
|
23
|
-
|
|
24
|
-
### Approach A: Keep Python Core + Add NPM Wrapper (Recommended for v1)
|
|
25
|
-
Pros:
|
|
26
|
-
- Fastest to ship.
|
|
27
|
-
- Reuses proven sync logic now.
|
|
28
|
-
- Lower regression risk.
|
|
29
|
-
- Easy to support both `npx` and `python3` direct run.
|
|
30
|
-
|
|
31
|
-
Cons:
|
|
32
|
-
- Requires Python on user machine.
|
|
33
|
-
- Wrapper layer adds runtime dependency checks.
|
|
34
|
-
|
|
35
|
-
### Approach B: Full Rewrite to TypeScript Core
|
|
36
|
-
Pros:
|
|
37
|
-
- Native npm experience.
|
|
38
|
-
- Simpler JS ecosystem integration.
|
|
39
|
-
|
|
40
|
-
Cons:
|
|
41
|
-
- High rewrite cost.
|
|
42
|
-
- High regression risk in edge behavior already solved in Python.
|
|
43
|
-
- Slower time-to-community release.
|
|
44
|
-
|
|
45
|
-
### Approach C: Maintain Two Cores (Python + TS)
|
|
46
|
-
Pros:
|
|
47
|
-
- Maximum flexibility.
|
|
48
|
-
|
|
49
|
-
Cons:
|
|
50
|
-
- Highest maintenance burden.
|
|
51
|
-
- Divergence risk.
|
|
52
|
-
- Not YAGNI.
|
|
53
|
-
|
|
54
|
-
## Recommended Strategy
|
|
55
|
-
- **Ship staged hybrid strategy**:
|
|
56
|
-
1. **v1**: Python core as source of truth + npm wrapper + git-clone workflow.
|
|
57
|
-
2. **v1.1**: Add agent transpiler and parity benchmark harness.
|
|
58
|
-
3. **v2**: Consider TS native core only if adoption justifies rewrite.
|
|
59
|
-
|
|
60
|
-
This gives speed now and keeps path to long-term polish without over-engineering.
|
|
61
|
-
|
|
62
|
-
## Claude -> Codex Customization That Matters Most
|
|
63
|
-
- Agent transpiler that converts Claude `agents/*.md` into Codex role config:
|
|
64
|
-
- `~/.codex/config.toml` `[agents.<role>]`
|
|
65
|
-
- `~/.codex/agents/<role>.toml`
|
|
66
|
-
- Role strategy:
|
|
67
|
-
- Prefix custom roles (`ck-*`) to avoid overriding built-ins accidentally.
|
|
68
|
-
- Allow explicit override mode if user wants `default/worker/explorer` replacement.
|
|
69
|
-
- Instruction normalization:
|
|
70
|
-
- rewrite Claude-specific paths/tooling references to Codex equivalents.
|
|
71
|
-
- preserve intent, not literal syntax.
|
|
72
|
-
- Execution guardrails:
|
|
73
|
-
- per-role `sandbox_mode`, `model_reasoning_effort`, `developer_instructions`.
|
|
74
|
-
|
|
75
|
-
## Proof Plan: “Near-Similar Behavior”
|
|
76
|
-
Define parity suite and publish numbers:
|
|
77
|
-
- 20-30 canonical scenarios:
|
|
78
|
-
- planning, research, review, debug, docs update, testing loops.
|
|
79
|
-
- A/B runs:
|
|
80
|
-
- A: Claude original flow.
|
|
81
|
-
- B: Codex transpiled flow.
|
|
82
|
-
- Score dimensions:
|
|
83
|
-
- task success rate.
|
|
84
|
-
- required artifact completeness.
|
|
85
|
-
- policy compliance (test/lint/docs gates).
|
|
86
|
-
- orchestration quality (spawn/wait/close behavior).
|
|
87
|
-
- defect quality (severity-weighted findings for review/debug cases).
|
|
88
|
-
|
|
89
|
-
Target thresholds:
|
|
90
|
-
- >=85% parity: acceptable “near-similar”.
|
|
91
|
-
- >=90% parity: strong community-grade claim.
|
|
92
|
-
|
|
93
|
-
## Risks and Mitigations
|
|
94
|
-
- Risk: Codex/Claude runtime semantics differ.
|
|
95
|
-
- Mitigation: parity scoring by outcomes, not literal step parity.
|
|
96
|
-
- Risk: multi-agent feature drift across Codex versions.
|
|
97
|
-
- Mitigation: version compatibility matrix + CI smoke tests.
|
|
98
|
-
- Risk: wrapper install friction.
|
|
99
|
-
- Mitigation: clear preflight checks + actionable errors.
|
|
100
|
-
|
|
101
|
-
## Success Metrics
|
|
102
|
-
- Install success rate >=95% on Ubuntu/macOS/WSL for default path.
|
|
103
|
-
- End-to-end sync success rate >=98% on supported fixtures.
|
|
104
|
-
- Parity benchmark >=85% before public “near-similar” claim.
|
|
105
|
-
- Time to run full sync + verify under 3 minutes on standard machine.
|
|
106
|
-
|
|
107
|
-
## Next Steps
|
|
108
|
-
- Create implementation plan with phases for:
|
|
109
|
-
- productization,
|
|
110
|
-
- transpiler,
|
|
111
|
-
- benchmark harness,
|
|
112
|
-
- npm + git distribution,
|
|
113
|
-
- release process.
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
5
|
-
SKILLS_DIR="${CODEX_HOME}/skills"
|
|
6
|
-
VENV_DIR="${SKILLS_DIR}/.venv"
|
|
7
|
-
RUN_PYTHON=true
|
|
8
|
-
RUN_NODE=true
|
|
9
|
-
DRY_RUN=false
|
|
10
|
-
INCLUDE_TEST_DEPS=false
|
|
11
|
-
INCLUDE_MCP=false
|
|
12
|
-
|
|
13
|
-
usage() {
|
|
14
|
-
cat <<'EOF'
|
|
15
|
-
Install runtime dependencies for ClaudeKit skills synced into Codex.
|
|
16
|
-
|
|
17
|
-
Usage:
|
|
18
|
-
bootstrap-claudekit-skill-scripts.sh [options]
|
|
19
|
-
|
|
20
|
-
Options:
|
|
21
|
-
--python-only Install Python dependencies only
|
|
22
|
-
--node-only Install Node.js dependencies only
|
|
23
|
-
--include-test-deps Also install requirements from test folders
|
|
24
|
-
--include-mcp Also include mcp-builder and mcp-management skills
|
|
25
|
-
--dry-run Print actions only
|
|
26
|
-
-h, --help Show help
|
|
27
|
-
EOF
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
while [[ $# -gt 0 ]]; do
|
|
31
|
-
case "$1" in
|
|
32
|
-
--python-only)
|
|
33
|
-
RUN_PYTHON=true
|
|
34
|
-
RUN_NODE=false
|
|
35
|
-
;;
|
|
36
|
-
--node-only)
|
|
37
|
-
RUN_PYTHON=false
|
|
38
|
-
RUN_NODE=true
|
|
39
|
-
;;
|
|
40
|
-
--include-test-deps)
|
|
41
|
-
INCLUDE_TEST_DEPS=true
|
|
42
|
-
;;
|
|
43
|
-
--include-mcp)
|
|
44
|
-
INCLUDE_MCP=true
|
|
45
|
-
;;
|
|
46
|
-
--dry-run)
|
|
47
|
-
DRY_RUN=true
|
|
48
|
-
;;
|
|
49
|
-
-h|--help)
|
|
50
|
-
usage
|
|
51
|
-
exit 0
|
|
52
|
-
;;
|
|
53
|
-
*)
|
|
54
|
-
echo "Unknown option: $1" >&2
|
|
55
|
-
usage >&2
|
|
56
|
-
exit 1
|
|
57
|
-
;;
|
|
58
|
-
esac
|
|
59
|
-
shift
|
|
60
|
-
done
|
|
61
|
-
|
|
62
|
-
if [[ ! -d "$SKILLS_DIR" ]]; then
|
|
63
|
-
echo "Skills directory not found: $SKILLS_DIR" >&2
|
|
64
|
-
exit 1
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
py_ok=0
|
|
68
|
-
py_fail=0
|
|
69
|
-
node_ok=0
|
|
70
|
-
node_fail=0
|
|
71
|
-
|
|
72
|
-
skip_mcp_path() {
|
|
73
|
-
local p="$1"
|
|
74
|
-
if [[ "$INCLUDE_MCP" == true ]]; then
|
|
75
|
-
return 1
|
|
76
|
-
fi
|
|
77
|
-
[[ "$p" == *"/mcp-builder/"* || "$p" == *"/mcp-management/"* ]]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if [[ "$RUN_PYTHON" == true ]]; then
|
|
81
|
-
if ! command -v python3 >/dev/null 2>&1; then
|
|
82
|
-
echo "python3 not found" >&2
|
|
83
|
-
exit 1
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
if [[ "$DRY_RUN" == true ]]; then
|
|
87
|
-
echo "[dry-run] python3 -m venv \"$VENV_DIR\""
|
|
88
|
-
else
|
|
89
|
-
python3 -m venv "$VENV_DIR"
|
|
90
|
-
"$VENV_DIR/bin/python3" -m pip install --upgrade pip >/dev/null
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
while IFS= read -r req; do
|
|
94
|
-
if skip_mcp_path "$req"; then
|
|
95
|
-
continue
|
|
96
|
-
fi
|
|
97
|
-
if [[ "$DRY_RUN" == true ]]; then
|
|
98
|
-
echo "[dry-run] $VENV_DIR/bin/python3 -m pip install -r \"$req\""
|
|
99
|
-
py_ok=$((py_ok + 1))
|
|
100
|
-
continue
|
|
101
|
-
fi
|
|
102
|
-
if "$VENV_DIR/bin/python3" -m pip install -r "$req" >/dev/null; then
|
|
103
|
-
py_ok=$((py_ok + 1))
|
|
104
|
-
else
|
|
105
|
-
echo "python deps failed: $req" >&2
|
|
106
|
-
py_fail=$((py_fail + 1))
|
|
107
|
-
fi
|
|
108
|
-
done < <(
|
|
109
|
-
find "$SKILLS_DIR" \
|
|
110
|
-
\( -path '*/.system/*' -o -path '*/node_modules/*' -o -path '*/.venv/*' -o -path '*/dist/*' -o -path '*/build/*' \) -prune -o \
|
|
111
|
-
-type f -name 'requirements*.txt' -print \
|
|
112
|
-
| if [[ "$INCLUDE_TEST_DEPS" == true ]]; then cat; else grep -Ev '/tests?/'; fi \
|
|
113
|
-
| sort -u
|
|
114
|
-
)
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
if [[ "$RUN_NODE" == true ]]; then
|
|
118
|
-
if ! command -v npm >/dev/null 2>&1; then
|
|
119
|
-
echo "npm not found" >&2
|
|
120
|
-
exit 1
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
while IFS= read -r pkg; do
|
|
124
|
-
if skip_mcp_path "$pkg"; then
|
|
125
|
-
continue
|
|
126
|
-
fi
|
|
127
|
-
dir="$(dirname "$pkg")"
|
|
128
|
-
if [[ "$DRY_RUN" == true ]]; then
|
|
129
|
-
echo "[dry-run] npm install --prefix \"$dir\""
|
|
130
|
-
node_ok=$((node_ok + 1))
|
|
131
|
-
continue
|
|
132
|
-
fi
|
|
133
|
-
if npm install --prefix "$dir" >/dev/null; then
|
|
134
|
-
node_ok=$((node_ok + 1))
|
|
135
|
-
else
|
|
136
|
-
echo "node deps failed: $dir" >&2
|
|
137
|
-
node_fail=$((node_fail + 1))
|
|
138
|
-
fi
|
|
139
|
-
done < <(
|
|
140
|
-
find "$SKILLS_DIR" \
|
|
141
|
-
\( -path '*/.system/*' -o -path '*/node_modules/*' -o -path '*/.venv/*' -o -path '*/dist/*' -o -path '*/build/*' \) -prune -o \
|
|
142
|
-
-type f -name package.json -print \
|
|
143
|
-
| sort -u
|
|
144
|
-
)
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
echo "python_ok=$py_ok python_fail=$py_fail node_ok=$node_ok node_fail=$node_fail"
|
|
148
|
-
if [[ $py_fail -gt 0 || $node_fail -gt 0 ]]; then
|
|
149
|
-
exit 2
|
|
150
|
-
fi
|