@einja/dev-cli 0.1.40 → 0.1.41
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 +89 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +71 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +187 -13
- package/dist/commands/sync.js.map +1 -1
- package/dist/lib/dependency-checker.d.ts.map +1 -1
- package/dist/lib/merger.d.ts +12 -0
- package/dist/lib/merger.d.ts.map +1 -1
- package/dist/lib/merger.js +28 -0
- package/dist/lib/merger.js.map +1 -1
- package/dist/lib/preset-update/cli-repo-detector.d.ts.map +1 -1
- package/dist/lib/preset-update/file-copier.d.ts.map +1 -1
- package/dist/lib/preset-update/preset-finder.d.ts.map +1 -1
- package/dist/lib/preset.d.ts.map +1 -1
- package/dist/lib/sync/category-validator.d.ts +1 -1
- package/dist/lib/sync/category-validator.d.ts.map +1 -1
- package/dist/lib/sync/category-validator.js +2 -1
- package/dist/lib/sync/category-validator.js.map +1 -1
- package/dist/lib/sync/category-validator.test.js +3 -1
- package/dist/lib/sync/category-validator.test.js.map +1 -1
- package/dist/lib/sync/conflict-reporter.d.ts.map +1 -1
- package/dist/lib/sync/diff-engine.d.ts.map +1 -1
- package/dist/lib/sync/file-filter.d.ts.map +1 -1
- package/dist/lib/sync/file-filter.js +1 -0
- package/dist/lib/sync/file-filter.js.map +1 -1
- package/dist/lib/sync/integration.test.js +255 -69
- package/dist/lib/sync/integration.test.js.map +1 -1
- package/dist/lib/sync/json-processor.d.ts +4 -4
- package/dist/lib/sync/json-processor.d.ts.map +1 -1
- package/dist/lib/sync/json-processor.js +11 -11
- package/dist/lib/sync/json-processor.js.map +1 -1
- package/dist/lib/sync/marker-processor.d.ts +60 -8
- package/dist/lib/sync/marker-processor.d.ts.map +1 -1
- package/dist/lib/sync/marker-processor.js +117 -26
- package/dist/lib/sync/marker-processor.js.map +1 -1
- package/dist/lib/sync/marker-processor.test.js +261 -40
- package/dist/lib/sync/marker-processor.test.js.map +1 -1
- package/dist/lib/sync/metadata-manager.d.ts +4 -0
- package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
- package/dist/lib/sync/metadata-manager.js +15 -0
- package/dist/lib/sync/metadata-manager.js.map +1 -1
- package/dist/lib/sync/metadata-manager.test.js +68 -0
- package/dist/lib/sync/metadata-manager.test.js.map +1 -1
- package/dist/lib/sync/orphan-cleaner.d.ts +29 -0
- package/dist/lib/sync/orphan-cleaner.d.ts.map +1 -0
- package/dist/lib/sync/orphan-cleaner.js +80 -0
- package/dist/lib/sync/orphan-cleaner.js.map +1 -0
- package/dist/lib/sync/orphan-cleaner.test.d.ts +2 -0
- package/dist/lib/sync/orphan-cleaner.test.d.ts.map +1 -0
- package/dist/lib/sync/orphan-cleaner.test.js +169 -0
- package/dist/lib/sync/orphan-cleaner.test.js.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.d.ts +52 -0
- package/dist/lib/sync/project-private-synchronizer.d.ts.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.js +106 -0
- package/dist/lib/sync/project-private-synchronizer.js.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.test.d.ts +2 -0
- package/dist/lib/sync/project-private-synchronizer.test.d.ts.map +1 -0
- package/dist/lib/sync/project-private-synchronizer.test.js +348 -0
- package/dist/lib/sync/project-private-synchronizer.test.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/sync.d.ts +36 -6
- package/dist/types/sync.d.ts.map +1 -1
- package/dist/types/sync.js +2 -2
- package/dist/types/sync.js.map +1 -1
- package/package.json +5 -4
- package/presets/default/.claude/agents/einja/Explore.md +140 -0
- package/presets/default/.claude/agents/einja/backend-architect.md +4 -0
- package/presets/default/.claude/agents/einja/codex-agent.md +4 -0
- package/presets/default/.claude/agents/einja/design-engineer.md +4 -0
- package/presets/default/.claude/agents/einja/docs/docs-updater.md +4 -0
- package/presets/default/.claude/agents/einja/frontend-architect.md +4 -0
- package/presets/default/.claude/agents/einja/frontend-coder.md +4 -0
- package/presets/default/.claude/agents/einja/git/conflict-resolver.md +4 -0
- package/presets/default/.claude/agents/einja/specs/spec-design-generator.md +4 -1
- package/presets/default/.claude/agents/einja/specs/spec-qa-generator.md +4 -0
- package/presets/default/.claude/agents/einja/specs/spec-requirements-generator.md +4 -1
- package/presets/default/.claude/agents/einja/specs/spec-tasks-generator.md +6 -2
- package/presets/default/.claude/agents/einja/specs/spec-tasks-validator.md +4 -0
- package/presets/default/.claude/agents/einja/task/task-executer.md +57 -115
- package/presets/default/.claude/agents/einja/task/task-modification-analyzer.md +4 -0
- package/presets/default/.claude/agents/einja/task/task-qa.md +4 -0
- package/presets/default/.claude/agents/einja/task/task-reviewer.md +4 -0
- package/presets/default/.claude/commands/einja/einja-sync.md +5 -1
- package/presets/default/.claude/commands/einja/frontend-implement.md +3 -1
- package/presets/default/.claude/commands/einja/issue-exec.md +403 -0
- package/presets/default/.claude/commands/einja/spec-create.md +15 -1
- package/presets/default/.claude/commands/einja/start-dev.md +4 -0
- package/presets/default/.claude/commands/einja/sync-cursor-commands.md +4 -0
- package/presets/default/.claude/commands/einja/task-exec.md +106 -14
- package/presets/default/.claude/commands/einja/update-docs-by-task-specs.md +4 -0
- package/presets/default/.claude/hooks/einja/plan-mode-skill-loader.sh +23 -0
- package/presets/default/.claude/settings.json +15 -1
- package/presets/default/.claude/skills/einja-conflict-resolver/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-general-context-loader/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-output-format/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-project-overview/SKILL.md +7 -3
- package/presets/default/.claude/skills/einja-skill-creator/SKILL.md +266 -274
- package/presets/default/.claude/skills/einja-skill-creator/agents/analyzer.md +274 -0
- package/presets/default/.claude/skills/einja-skill-creator/agents/comparator.md +202 -0
- package/presets/default/.claude/skills/einja-skill-creator/agents/grader.md +195 -0
- package/presets/default/.claude/skills/einja-skill-creator/assets/eval_review.html +146 -0
- package/presets/default/.claude/skills/einja-skill-creator/eval-viewer/generate_review.py +471 -0
- package/presets/default/.claude/skills/einja-skill-creator/eval-viewer/viewer.html +1325 -0
- package/presets/default/.claude/skills/einja-skill-creator/references/schemas.md +430 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/aggregate_benchmark.py +154 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/generate_report.py +265 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/improve_description.py +252 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/init_skill.py +13 -19
- package/presets/default/.claude/skills/einja-skill-creator/scripts/package_skill.py +36 -7
- package/presets/default/.claude/skills/einja-skill-creator/scripts/run_eval.py +310 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/run_loop.py +295 -0
- package/presets/default/.claude/skills/einja-skill-creator/scripts/utils.py +48 -0
- package/presets/default/.claude/skills/einja-spec-context-loader/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-task-commit/SKILL.md +4 -0
- package/presets/default/.claude/skills/einja-task-qa/SKILL.md +4 -0
- package/presets/default/.envrc +5 -0
- package/presets/default/.mcp.json +2 -12
- package/presets/default/CLAUDE.md.template +26 -4
- package/presets/default/docs/einja/example/specs/issues/issue999-example-task/tasks.md +1 -1
- package/presets/default/docs/einja/instructions/deployment-setup.md +3 -8
- package/presets/default/docs/einja/instructions/environment-setup.md +3 -8
- package/presets/default/docs/einja/instructions/issue-exec-workflow.md +276 -0
- package/presets/default/docs/einja/instructions/local-server-environment-and-worktree.md +70 -8
- package/presets/default/docs/einja/instructions/neon-cli-reference.md +3 -8
- package/presets/default/docs/einja/instructions/task-execute.md +23 -28
- package/presets/default/docs/einja/instructions/vercel-cli-reference.md +17 -10
- package/presets/default/docs/einja/steering/README.md +11 -11
- package/presets/default/docs/einja/steering/acceptance-criteria-and-qa-guide.md +3 -8
- package/presets/default/docs/einja/steering/architecture.md +3 -8
- package/presets/default/docs/einja/steering/branch-strategy.md +63 -70
- package/presets/default/docs/einja/steering/commit-rules.md +3 -8
- package/presets/default/docs/einja/steering/db-schema-design.md +3 -8
- package/presets/default/docs/einja/steering/development/api-development.md +3 -8
- package/presets/default/docs/einja/steering/development/backend-architecture.md +3 -8
- package/presets/default/docs/einja/steering/development/coding-standards.md +723 -0
- package/presets/default/docs/einja/steering/development/component-design.md +502 -0
- package/presets/default/docs/einja/steering/development/database-guidelines.md +2 -2
- package/presets/default/docs/einja/steering/development/frontend-development.md +3 -8
- package/presets/default/docs/einja/steering/development/playwright-guidelines.md +59 -0
- package/presets/default/docs/einja/steering/development/review-guidelines.md +3 -8
- package/presets/default/docs/einja/steering/development/testing-strategy.md +3 -8
- package/presets/default/docs/einja/steering/development-workflow.md +71 -124
- package/presets/default/docs/einja/steering/infrastructure/deployment.md +49 -55
- package/presets/default/docs/einja/steering/infrastructure/environment-variables.md +4 -8
- package/presets/default/docs/einja/steering/product.md +3 -8
- package/presets/default/docs/einja/steering/task-management.md +14 -98
- package/presets/default/scripts/ensure-serena.sh +75 -0
- package/presets/default/scripts/env-rotate-secrets.ts +336 -0
- package/presets/default/scripts/env-show.ts +130 -0
- package/presets/default/scripts/env.ts +479 -0
- package/presets/default/scripts/init.sh +92 -0
- package/presets/default/scripts/lib/env-common.ts +108 -0
- package/presets/default/scripts/lib/worktree-config.ts +64 -0
- package/presets/default/scripts/setup-dev.ts +640 -0
- package/presets/default/scripts/stop-serena.sh +25 -0
- package/presets/default/scripts/worktree/dev.ts +872 -0
- package/dist/lib/sync/seed-synchronizer.d.ts +0 -27
- package/dist/lib/sync/seed-synchronizer.d.ts.map +0 -1
- package/dist/lib/sync/seed-synchronizer.js +0 -72
- package/dist/lib/sync/seed-synchronizer.js.map +0 -1
- package/dist/lib/sync/seed-synchronizer.test.d.ts +0 -2
- package/dist/lib/sync/seed-synchronizer.test.d.ts.map +0 -1
- package/dist/lib/sync/seed-synchronizer.test.js +0 -147
- package/dist/lib/sync/seed-synchronizer.test.js.map +0 -1
- package/presets/default/.claude/skills/einja-api-development/SKILL.md +0 -14
- package/presets/default/.claude/skills/einja-backend-architecture/SKILL.md +0 -18
- package/presets/default/.claude/skills/einja-coding-standards/SKILL.md +0 -132
- package/presets/default/.claude/skills/einja-coding-standards/references/import-conventions.md +0 -69
- package/presets/default/.claude/skills/einja-coding-standards/references/naming-conventions.md +0 -107
- package/presets/default/.claude/skills/einja-coding-standards/references/prohibited-patterns.md +0 -169
- package/presets/default/.claude/skills/einja-coding-standards/references/typescript-rules.md +0 -247
- package/presets/default/.claude/skills/einja-component-design/SKILL.md +0 -109
- package/presets/default/.claude/skills/einja-component-design/references/directory-structure.md +0 -117
- package/presets/default/.claude/skills/einja-component-design/references/props-patterns.md +0 -159
- package/presets/default/.claude/skills/einja-component-design/references/styling-guide.md +0 -122
- package/presets/default/.claude/skills/einja-frontend-development/SKILL.md +0 -14
- package/presets/default/docs/einja/instructions/task-vibe-kanban-loop.md +0 -565
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""run_loop.pyの出力からHTMLレポートを生成。
|
|
3
|
+
|
|
4
|
+
run_loop.pyのJSON出力を受け取り、各descriptionの試行結果を
|
|
5
|
+
チェック/xで表示するHTMLレポートを生成する。
|
|
6
|
+
トレーニングとテストのクエリを区別して表示。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import html
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def generate_html(data: dict, auto_refresh: bool = False, skill_name: str = "") -> str:
|
|
17
|
+
"""ループ出力データからHTMLレポートを生成。auto_refreshがTrueの場合、メタリフレッシュタグを追加。"""
|
|
18
|
+
history = data.get("history", [])
|
|
19
|
+
holdout = data.get("holdout", 0)
|
|
20
|
+
title_prefix = html.escape(skill_name + " — ") if skill_name else ""
|
|
21
|
+
|
|
22
|
+
# トレーニングとテストの全ユニーククエリを取得(should_trigger情報付き)
|
|
23
|
+
train_queries: list[dict] = []
|
|
24
|
+
test_queries: list[dict] = []
|
|
25
|
+
if history:
|
|
26
|
+
for r in history[0].get("train_results", history[0].get("results", [])):
|
|
27
|
+
train_queries.append({"query": r["query"], "should_trigger": r.get("should_trigger", True)})
|
|
28
|
+
if history[0].get("test_results"):
|
|
29
|
+
for r in history[0].get("test_results", []):
|
|
30
|
+
test_queries.append({"query": r["query"], "should_trigger": r.get("should_trigger", True)})
|
|
31
|
+
|
|
32
|
+
refresh_tag = ' <meta http-equiv="refresh" content="5">\n' if auto_refresh else ""
|
|
33
|
+
|
|
34
|
+
html_parts = []
|
|
35
|
+
html_parts.append(f"""<!DOCTYPE html>
|
|
36
|
+
<html lang="ja">
|
|
37
|
+
<head>
|
|
38
|
+
<meta charset="UTF-8">
|
|
39
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
40
|
+
{refresh_tag} <title>{title_prefix}スキルDescription最適化</title>
|
|
41
|
+
<style>
|
|
42
|
+
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
43
|
+
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0a0a; color: #e0e0e0; padding: 20px; }}
|
|
44
|
+
h1 {{ font-size: 1.4em; margin-bottom: 4px; color: #fff; }}
|
|
45
|
+
.explainer {{ color: #888; font-size: 0.85em; margin-bottom: 16px; line-height: 1.4; }}
|
|
46
|
+
.summary {{ display: flex; gap: 24px; margin-bottom: 16px; flex-wrap: wrap; }}
|
|
47
|
+
.summary-card {{ background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 12px 16px; min-width: 120px; }}
|
|
48
|
+
.summary-card .label {{ font-size: 0.75em; color: #888; text-transform: uppercase; letter-spacing: 0.05em; }}
|
|
49
|
+
.summary-card .value {{ font-size: 1.5em; font-weight: 600; margin-top: 2px; }}
|
|
50
|
+
.legend {{ font-size: 0.8em; color: #888; margin-bottom: 12px; }}
|
|
51
|
+
.legend span {{ margin-right: 16px; }}
|
|
52
|
+
.table-container {{ overflow-x: auto; }}
|
|
53
|
+
table {{ border-collapse: collapse; font-size: 0.8em; width: 100%; }}
|
|
54
|
+
th, td {{ border: 1px solid #333; padding: 6px 8px; text-align: center; }}
|
|
55
|
+
th {{ background: #1a1a1a; color: #ccc; font-weight: 600; position: sticky; top: 0; z-index: 2; }}
|
|
56
|
+
th.query-header {{ writing-mode: vertical-rl; text-orientation: mixed; max-width: 30px; height: 180px; font-weight: 400; font-size: 0.85em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }}
|
|
57
|
+
th.query-header.negative {{ color: #ff6b6b; }}
|
|
58
|
+
th.section-header {{ background: #222; color: #aaa; font-size: 0.7em; text-transform: uppercase; letter-spacing: 0.1em; }}
|
|
59
|
+
td.desc {{ text-align: left; max-width: 300px; font-size: 0.85em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }}
|
|
60
|
+
td.desc:hover {{ white-space: normal; overflow: visible; position: relative; z-index: 10; background: #1a1a1a; }}
|
|
61
|
+
td.pass {{ background: rgba(34, 197, 94, 0.15); color: #22c55e; }}
|
|
62
|
+
td.fail {{ background: rgba(239, 68, 68, 0.15); color: #ef4444; }}
|
|
63
|
+
td.score {{ font-weight: 600; }}
|
|
64
|
+
td.score.perfect {{ color: #22c55e; }}
|
|
65
|
+
td.score.good {{ color: #86efac; }}
|
|
66
|
+
td.score.mid {{ color: #fbbf24; }}
|
|
67
|
+
td.score.bad {{ color: #ef4444; }}
|
|
68
|
+
tr.best-row {{ background: rgba(34, 197, 94, 0.05); }}
|
|
69
|
+
tr.best-row td {{ border-color: #22c55e44; }}
|
|
70
|
+
.best-badge {{ background: #22c55e; color: #000; font-size: 0.7em; padding: 1px 6px; border-radius: 3px; font-weight: 700; margin-left: 4px; }}
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<h1>{title_prefix}スキルDescription最適化</h1>
|
|
75
|
+
<p class="explainer">
|
|
76
|
+
各行はdescriptionの1イテレーションです。各列はクエリで、セルはそのdescriptionで
|
|
77
|
+
スキルがトリガーされたかどうかを示します。赤い列ヘッダーはトリガーすべきでないクエリです。
|
|
78
|
+
""")
|
|
79
|
+
|
|
80
|
+
if holdout:
|
|
81
|
+
html_parts.append(f' トレーニング/テスト分割: テスト用に{holdout}クエリをホールドアウト。\n')
|
|
82
|
+
|
|
83
|
+
html_parts.append(' </p>\n')
|
|
84
|
+
|
|
85
|
+
# サマリーカード
|
|
86
|
+
if history:
|
|
87
|
+
# 最良のイテレーションを見つける(テスト > トレーニングで優先)
|
|
88
|
+
best_idx = 0
|
|
89
|
+
best_test = -1
|
|
90
|
+
best_train = -1
|
|
91
|
+
for i, h in enumerate(history):
|
|
92
|
+
t_passed = h.get("test_passed", -1)
|
|
93
|
+
tr_passed = h.get("train_passed", h.get("passed", 0))
|
|
94
|
+
if t_passed > best_test or (t_passed == best_test and tr_passed > best_train):
|
|
95
|
+
best_test = t_passed
|
|
96
|
+
best_train = tr_passed
|
|
97
|
+
best_idx = i
|
|
98
|
+
|
|
99
|
+
original = history[0] if history else {}
|
|
100
|
+
best = history[best_idx] if history else {}
|
|
101
|
+
|
|
102
|
+
orig_train = f"{original.get('train_passed', original.get('passed', 0))}/{original.get('train_total', original.get('total', 0))}"
|
|
103
|
+
best_train_str = f"{best.get('train_passed', best.get('passed', 0))}/{best.get('train_total', best.get('total', 0))}"
|
|
104
|
+
|
|
105
|
+
html_parts.append(' <div class="summary">\n')
|
|
106
|
+
html_parts.append(f' <div class="summary-card"><div class="label">オリジナル (トレーニング)</div><div class="value">{orig_train}</div></div>\n')
|
|
107
|
+
|
|
108
|
+
if best.get("test_passed") is not None:
|
|
109
|
+
best_test_str = f"{best.get('test_passed', '?')}/{best.get('test_total', '?')}"
|
|
110
|
+
html_parts.append(f' <div class="summary-card"><div class="label">最良スコア (テスト)</div><div class="value">{best_test_str}</div></div>\n')
|
|
111
|
+
html_parts.append(f' <div class="summary-card"><div class="label">最良スコア (トレーニング)</div><div class="value">{best_train_str}</div></div>\n')
|
|
112
|
+
html_parts.append(f' <div class="summary-card"><div class="label">イテレーション</div><div class="value">{len(history)}</div></div>\n')
|
|
113
|
+
html_parts.append(' </div>\n')
|
|
114
|
+
|
|
115
|
+
# レジェンド
|
|
116
|
+
html_parts.append(' <div class="legend">\n')
|
|
117
|
+
html_parts.append(' <span>クエリカラム: 通常=トリガーすべき、<span style="color:#ff6b6b">赤</span>=トリガーすべきでない</span>\n')
|
|
118
|
+
html_parts.append(' </div>\n')
|
|
119
|
+
|
|
120
|
+
# テーブル
|
|
121
|
+
html_parts.append(' <div class="table-container">\n <table>\n')
|
|
122
|
+
|
|
123
|
+
# ヘッダー行
|
|
124
|
+
html_parts.append(' <tr>\n')
|
|
125
|
+
html_parts.append(' <th>回</th>\n')
|
|
126
|
+
html_parts.append(' <th>Description</th>\n')
|
|
127
|
+
|
|
128
|
+
if train_queries:
|
|
129
|
+
html_parts.append(f' <th class="section-header" colspan="{len(train_queries)}">トレーニング</th>\n')
|
|
130
|
+
if test_queries:
|
|
131
|
+
html_parts.append(f' <th class="section-header" colspan="{len(test_queries)}">テスト</th>\n')
|
|
132
|
+
|
|
133
|
+
html_parts.append(' <th>トレーニング</th>\n')
|
|
134
|
+
if test_queries:
|
|
135
|
+
html_parts.append(' <th>テスト</th>\n')
|
|
136
|
+
html_parts.append(' </tr>\n')
|
|
137
|
+
|
|
138
|
+
# クエリヘッダー行
|
|
139
|
+
html_parts.append(' <tr>\n')
|
|
140
|
+
html_parts.append(' <th></th>\n')
|
|
141
|
+
html_parts.append(' <th></th>\n')
|
|
142
|
+
|
|
143
|
+
for q in train_queries:
|
|
144
|
+
css_class = "query-header negative" if not q["should_trigger"] else "query-header"
|
|
145
|
+
html_parts.append(f' <th class="{css_class}" title="{html.escape(q["query"])}">{html.escape(q["query"][:60])}</th>\n')
|
|
146
|
+
for q in test_queries:
|
|
147
|
+
css_class = "query-header negative" if not q["should_trigger"] else "query-header"
|
|
148
|
+
html_parts.append(f' <th class="{css_class}" title="{html.escape(q["query"])}">{html.escape(q["query"][:60])}</th>\n')
|
|
149
|
+
|
|
150
|
+
html_parts.append(' <th></th>\n')
|
|
151
|
+
if test_queries:
|
|
152
|
+
html_parts.append(' <th></th>\n')
|
|
153
|
+
html_parts.append(' </tr>\n')
|
|
154
|
+
|
|
155
|
+
# 最良のイテレーションを見つける
|
|
156
|
+
best_idx = 0
|
|
157
|
+
if history:
|
|
158
|
+
best_test_score = -1
|
|
159
|
+
best_train_score = -1
|
|
160
|
+
for i, h in enumerate(history):
|
|
161
|
+
t_passed = h.get("test_passed", -1)
|
|
162
|
+
tr_passed = h.get("train_passed", h.get("passed", 0))
|
|
163
|
+
if t_passed > best_test_score or (t_passed == best_test_score and tr_passed > best_train_score):
|
|
164
|
+
best_test_score = t_passed
|
|
165
|
+
best_train_score = tr_passed
|
|
166
|
+
best_idx = i
|
|
167
|
+
|
|
168
|
+
# データ行
|
|
169
|
+
for i, h in enumerate(history):
|
|
170
|
+
row_class = ' class="best-row"' if i == best_idx else ""
|
|
171
|
+
html_parts.append(f' <tr{row_class}>\n')
|
|
172
|
+
|
|
173
|
+
# イテレーション番号
|
|
174
|
+
badge = ' <span class="best-badge">BEST</span>' if i == best_idx else ""
|
|
175
|
+
html_parts.append(f' <td>{i}{badge}</td>\n')
|
|
176
|
+
|
|
177
|
+
# Description
|
|
178
|
+
desc = html.escape(h.get("description", ""))
|
|
179
|
+
html_parts.append(f' <td class="desc" title="{desc}">{desc}</td>\n')
|
|
180
|
+
|
|
181
|
+
# トレーニング結果
|
|
182
|
+
train_results = h.get("train_results", h.get("results", []))
|
|
183
|
+
result_map = {r["query"]: r for r in train_results}
|
|
184
|
+
for q in train_queries:
|
|
185
|
+
r = result_map.get(q["query"])
|
|
186
|
+
if r:
|
|
187
|
+
css = "pass" if r["pass"] else "fail"
|
|
188
|
+
symbol = "✓" if r["pass"] else "✗"
|
|
189
|
+
rate = f'{r["triggers"]}/{r["runs"]}'
|
|
190
|
+
html_parts.append(f' <td class="{css}" title="rate={rate}">{symbol}</td>\n')
|
|
191
|
+
else:
|
|
192
|
+
html_parts.append(' <td>-</td>\n')
|
|
193
|
+
|
|
194
|
+
# テスト結果
|
|
195
|
+
test_results = h.get("test_results", [])
|
|
196
|
+
test_result_map = {r["query"]: r for r in test_results}
|
|
197
|
+
for q in test_queries:
|
|
198
|
+
r = test_result_map.get(q["query"])
|
|
199
|
+
if r:
|
|
200
|
+
css = "pass" if r["pass"] else "fail"
|
|
201
|
+
symbol = "✓" if r["pass"] else "✗"
|
|
202
|
+
rate = f'{r["triggers"]}/{r["runs"]}'
|
|
203
|
+
html_parts.append(f' <td class="{css}" title="rate={rate}">{symbol}</td>\n')
|
|
204
|
+
else:
|
|
205
|
+
html_parts.append(' <td>-</td>\n')
|
|
206
|
+
|
|
207
|
+
# トレーニングスコア
|
|
208
|
+
train_passed = h.get("train_passed", h.get("passed", 0))
|
|
209
|
+
train_total = h.get("train_total", h.get("total", 0))
|
|
210
|
+
if train_total > 0:
|
|
211
|
+
ratio = train_passed / train_total
|
|
212
|
+
if ratio >= 1.0:
|
|
213
|
+
score_class = "perfect"
|
|
214
|
+
elif ratio >= 0.8:
|
|
215
|
+
score_class = "good"
|
|
216
|
+
elif ratio >= 0.5:
|
|
217
|
+
score_class = "mid"
|
|
218
|
+
else:
|
|
219
|
+
score_class = "bad"
|
|
220
|
+
else:
|
|
221
|
+
score_class = "bad"
|
|
222
|
+
html_parts.append(f' <td class="score {score_class}">{train_passed}/{train_total}</td>\n')
|
|
223
|
+
|
|
224
|
+
# テストスコア
|
|
225
|
+
if test_queries:
|
|
226
|
+
test_passed = h.get("test_passed")
|
|
227
|
+
test_total = h.get("test_total")
|
|
228
|
+
if test_passed is not None and test_total is not None and test_total > 0:
|
|
229
|
+
ratio = test_passed / test_total
|
|
230
|
+
if ratio >= 1.0:
|
|
231
|
+
score_class = "perfect"
|
|
232
|
+
elif ratio >= 0.8:
|
|
233
|
+
score_class = "good"
|
|
234
|
+
elif ratio >= 0.5:
|
|
235
|
+
score_class = "mid"
|
|
236
|
+
else:
|
|
237
|
+
score_class = "bad"
|
|
238
|
+
html_parts.append(f' <td class="score {score_class}">{test_passed}/{test_total}</td>\n')
|
|
239
|
+
else:
|
|
240
|
+
html_parts.append(' <td>-</td>\n')
|
|
241
|
+
|
|
242
|
+
html_parts.append(' </tr>\n')
|
|
243
|
+
|
|
244
|
+
html_parts.append(' </table>\n </div>\n')
|
|
245
|
+
html_parts.append('</body>\n</html>\n')
|
|
246
|
+
|
|
247
|
+
return "".join(html_parts)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def main():
|
|
251
|
+
parser = argparse.ArgumentParser(description="run_loop.pyの出力からHTMLレポートを生成")
|
|
252
|
+
parser.add_argument("--input", required=True, help="run_loop.pyのJSON出力へのパス")
|
|
253
|
+
parser.add_argument("--output", required=True, help="HTMLレポートの出力先パス")
|
|
254
|
+
parser.add_argument("--auto-refresh", action="store_true", help="5秒ごとの自動リフレッシュを有効化(ライブモニタリング用)")
|
|
255
|
+
parser.add_argument("--skill-name", default="", help="レポートタイトルに表示するスキル名")
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
data = json.loads(Path(args.input).read_text())
|
|
259
|
+
html_content = generate_html(data, auto_refresh=args.auto_refresh, skill_name=args.skill_name)
|
|
260
|
+
Path(args.output).write_text(html_content)
|
|
261
|
+
print(f"レポートを生成しました: {args.output}", file=sys.stderr)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
main()
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""評価結果に基づいてスキルdescriptionを改善。
|
|
3
|
+
|
|
4
|
+
run_eval.pyからの評価結果を受け取り、extended thinkingを使用した
|
|
5
|
+
Claudeでdescriptionを改善する。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import anthropic
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from scripts.utils import parse_skill_md
|
|
18
|
+
except ImportError:
|
|
19
|
+
from utils import parse_skill_md
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def improve_description(
|
|
23
|
+
client: anthropic.Anthropic,
|
|
24
|
+
skill_name: str,
|
|
25
|
+
skill_content: str,
|
|
26
|
+
current_description: str,
|
|
27
|
+
eval_results: dict,
|
|
28
|
+
history: list[dict],
|
|
29
|
+
model: str,
|
|
30
|
+
test_results: dict | None = None,
|
|
31
|
+
log_dir: Path | None = None,
|
|
32
|
+
iteration: int | None = None,
|
|
33
|
+
) -> str:
|
|
34
|
+
"""評価結果に基づいてClaudeを呼び出しdescriptionを改善する。"""
|
|
35
|
+
failed_triggers = [
|
|
36
|
+
r for r in eval_results["results"]
|
|
37
|
+
if r["should_trigger"] and not r["pass"]
|
|
38
|
+
]
|
|
39
|
+
false_triggers = [
|
|
40
|
+
r for r in eval_results["results"]
|
|
41
|
+
if not r["should_trigger"] and not r["pass"]
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# スコアサマリーの構築
|
|
45
|
+
train_score = f"{eval_results['summary']['passed']}/{eval_results['summary']['total']}"
|
|
46
|
+
if test_results:
|
|
47
|
+
test_score = f"{test_results['summary']['passed']}/{test_results['summary']['total']}"
|
|
48
|
+
scores_summary = f"Train: {train_score}, Test: {test_score}"
|
|
49
|
+
else:
|
|
50
|
+
scores_summary = f"Train: {train_score}"
|
|
51
|
+
|
|
52
|
+
# NOTE: Claude APIへのプロンプトは精度維持のため英語のまま
|
|
53
|
+
prompt = f"""You are optimizing a skill description for a Claude Code skill called "{skill_name}". A "skill" is sort of like a prompt, but with progressive disclosure -- there's a title and description that Claude sees when deciding whether to use the skill, and then if it does use the skill, it reads the .md file which has lots more details and potentially links to other resources in the skill folder like helper files and scripts and additional documentation or examples.
|
|
54
|
+
|
|
55
|
+
The description appears in Claude's "available_skills" list. When a user sends a query, Claude decides whether to invoke the skill based solely on the title and on this description. Your goal is to write a description that triggers for relevant queries, and doesn't trigger for irrelevant ones.
|
|
56
|
+
|
|
57
|
+
Here's the current description:
|
|
58
|
+
<current_description>
|
|
59
|
+
"{current_description}"
|
|
60
|
+
</current_description>
|
|
61
|
+
|
|
62
|
+
Current scores ({scores_summary}):
|
|
63
|
+
<scores_summary>
|
|
64
|
+
"""
|
|
65
|
+
if failed_triggers:
|
|
66
|
+
prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n"
|
|
67
|
+
for r in failed_triggers:
|
|
68
|
+
prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
|
|
69
|
+
prompt += "\n"
|
|
70
|
+
|
|
71
|
+
if false_triggers:
|
|
72
|
+
prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n"
|
|
73
|
+
for r in false_triggers:
|
|
74
|
+
prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
|
|
75
|
+
prompt += "\n"
|
|
76
|
+
|
|
77
|
+
if history:
|
|
78
|
+
prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n"
|
|
79
|
+
for h in history:
|
|
80
|
+
train_s = f"{h.get('train_passed', h.get('passed', 0))}/{h.get('train_total', h.get('total', 0))}"
|
|
81
|
+
test_s = f"{h.get('test_passed', '?')}/{h.get('test_total', '?')}" if h.get('test_passed') is not None else None
|
|
82
|
+
score_str = f"train={train_s}" + (f", test={test_s}" if test_s else "")
|
|
83
|
+
prompt += f'<attempt {score_str}>\n'
|
|
84
|
+
prompt += f'Description: "{h["description"]}"\n'
|
|
85
|
+
if "results" in h:
|
|
86
|
+
prompt += "Train results:\n"
|
|
87
|
+
for r in h["results"]:
|
|
88
|
+
status = "PASS" if r["pass"] else "FAIL"
|
|
89
|
+
prompt += f' [{status}] "{r["query"][:80]}" (triggered {r["triggers"]}/{r["runs"]})\n'
|
|
90
|
+
if h.get("note"):
|
|
91
|
+
prompt += f'Note: {h["note"]}\n'
|
|
92
|
+
prompt += "</attempt>\n\n"
|
|
93
|
+
|
|
94
|
+
prompt += f"""</scores_summary>
|
|
95
|
+
|
|
96
|
+
Skill content (for context on what the skill does):
|
|
97
|
+
<skill_content>
|
|
98
|
+
{skill_content}
|
|
99
|
+
</skill_content>
|
|
100
|
+
|
|
101
|
+
Based on the failures, write a new and improved description that is more likely to trigger correctly. When I say "based on the failures", it's a bit of a tricky line to walk because we don't want to overfit to the specific cases you're seeing. So what I DON'T want you to do is produce an ever-expanding list of specific queries that this skill should or shouldn't trigger for. Instead, try to generalize from the failures to broader categories of user intent and situations where this skill would be useful or not useful. The reason for this is twofold:
|
|
102
|
+
|
|
103
|
+
1. Avoid overfitting
|
|
104
|
+
2. The list might get loooong and it's injected into ALL queries and there might be a lot of skills, so we don't want to blow too much space on any given description.
|
|
105
|
+
|
|
106
|
+
Concretely, your description should not be more than about 100-200 words, even if that comes at the cost of accuracy.
|
|
107
|
+
|
|
108
|
+
Here are some tips that we've found to work well in writing these descriptions:
|
|
109
|
+
- The skill should be phrased in the imperative -- "Use this skill for" rather than "this skill does"
|
|
110
|
+
- The skill description should focus on the user's intent, what they are trying to achieve, vs. the implementation details of how the skill works.
|
|
111
|
+
- The description competes with other skills for Claude's attention — make it distinctive and immediately recognizable.
|
|
112
|
+
- If you're getting lots of failures after repeated attempts, change things up. Try different sentence structures or wordings.
|
|
113
|
+
|
|
114
|
+
I'd encourage you to be creative and mix up the style in different iterations since you'll have multiple opportunities to try different approaches and we'll just grab the highest-scoring one at the end.
|
|
115
|
+
|
|
116
|
+
Please respond with only the new description text in <new_description> tags, nothing else."""
|
|
117
|
+
|
|
118
|
+
response = client.messages.create(
|
|
119
|
+
model=model,
|
|
120
|
+
max_tokens=16000,
|
|
121
|
+
thinking={
|
|
122
|
+
"type": "enabled",
|
|
123
|
+
"budget_tokens": 10000,
|
|
124
|
+
},
|
|
125
|
+
messages=[{"role": "user", "content": prompt}],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# レスポンスからthinkingとtextを抽出
|
|
129
|
+
thinking_text = ""
|
|
130
|
+
text = ""
|
|
131
|
+
for block in response.content:
|
|
132
|
+
if block.type == "thinking":
|
|
133
|
+
thinking_text = block.thinking
|
|
134
|
+
elif block.type == "text":
|
|
135
|
+
text = block.text
|
|
136
|
+
|
|
137
|
+
# <new_description>タグをパース
|
|
138
|
+
match = re.search(r"<new_description>(.*?)</new_description>", text, re.DOTALL)
|
|
139
|
+
description = match.group(1).strip().strip('"') if match else text.strip().strip('"')
|
|
140
|
+
|
|
141
|
+
# トランスクリプトのログ
|
|
142
|
+
transcript: dict = {
|
|
143
|
+
"iteration": iteration,
|
|
144
|
+
"prompt": prompt,
|
|
145
|
+
"thinking": thinking_text,
|
|
146
|
+
"response": text,
|
|
147
|
+
"parsed_description": description,
|
|
148
|
+
"char_count": len(description),
|
|
149
|
+
"over_limit": len(description) > 1024,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# 1024文字超過時、モデルに短縮を依頼
|
|
153
|
+
if len(description) > 1024:
|
|
154
|
+
shorten_prompt = f"Your description is {len(description)} characters, which exceeds the hard 1024 character limit. Please rewrite it to be under 1024 characters while preserving the most important trigger words and intent coverage. Respond with only the new description in <new_description> tags."
|
|
155
|
+
shorten_response = client.messages.create(
|
|
156
|
+
model=model,
|
|
157
|
+
max_tokens=16000,
|
|
158
|
+
thinking={
|
|
159
|
+
"type": "enabled",
|
|
160
|
+
"budget_tokens": 10000,
|
|
161
|
+
},
|
|
162
|
+
messages=[
|
|
163
|
+
{"role": "user", "content": prompt},
|
|
164
|
+
{"role": "assistant", "content": text},
|
|
165
|
+
{"role": "user", "content": shorten_prompt},
|
|
166
|
+
],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
shorten_thinking = ""
|
|
170
|
+
shorten_text = ""
|
|
171
|
+
for block in shorten_response.content:
|
|
172
|
+
if block.type == "thinking":
|
|
173
|
+
shorten_thinking = block.thinking
|
|
174
|
+
elif block.type == "text":
|
|
175
|
+
shorten_text = block.text
|
|
176
|
+
|
|
177
|
+
match = re.search(r"<new_description>(.*?)</new_description>", shorten_text, re.DOTALL)
|
|
178
|
+
shortened = match.group(1).strip().strip('"') if match else shorten_text.strip().strip('"')
|
|
179
|
+
|
|
180
|
+
transcript["rewrite_prompt"] = shorten_prompt
|
|
181
|
+
transcript["rewrite_thinking"] = shorten_thinking
|
|
182
|
+
transcript["rewrite_response"] = shorten_text
|
|
183
|
+
transcript["rewrite_description"] = shortened
|
|
184
|
+
transcript["rewrite_char_count"] = len(shortened)
|
|
185
|
+
description = shortened
|
|
186
|
+
|
|
187
|
+
transcript["final_description"] = description
|
|
188
|
+
|
|
189
|
+
if log_dir:
|
|
190
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
log_file = log_dir / f"improve_iter_{iteration or 'unknown'}.json"
|
|
192
|
+
log_file.write_text(json.dumps(transcript, indent=2))
|
|
193
|
+
|
|
194
|
+
return description
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def main():
|
|
198
|
+
parser = argparse.ArgumentParser(description="評価結果に基づいてスキルdescriptionを改善")
|
|
199
|
+
parser.add_argument("--eval-results", required=True, help="評価結果JSONへのパス(run_eval.pyの出力)")
|
|
200
|
+
parser.add_argument("--skill-path", required=True, help="スキルディレクトリへのパス")
|
|
201
|
+
parser.add_argument("--history", default=None, help="履歴JSONへのパス(過去の試行)")
|
|
202
|
+
parser.add_argument("--model", required=True, help="改善に使用するモデル")
|
|
203
|
+
parser.add_argument("--verbose", action="store_true", help="thinkingをstderrに出力")
|
|
204
|
+
args = parser.parse_args()
|
|
205
|
+
|
|
206
|
+
skill_path = Path(args.skill_path)
|
|
207
|
+
if not (skill_path / "SKILL.md").exists():
|
|
208
|
+
print(f"エラー: {skill_path} にSKILL.mdが見つかりません", file=sys.stderr)
|
|
209
|
+
sys.exit(1)
|
|
210
|
+
|
|
211
|
+
eval_results = json.loads(Path(args.eval_results).read_text())
|
|
212
|
+
history = []
|
|
213
|
+
if args.history:
|
|
214
|
+
history = json.loads(Path(args.history).read_text())
|
|
215
|
+
|
|
216
|
+
name, _, content = parse_skill_md(skill_path)
|
|
217
|
+
current_description = eval_results["description"]
|
|
218
|
+
|
|
219
|
+
if args.verbose:
|
|
220
|
+
print(f"現在: {current_description}", file=sys.stderr)
|
|
221
|
+
print(f"スコア: {eval_results['summary']['passed']}/{eval_results['summary']['total']}", file=sys.stderr)
|
|
222
|
+
|
|
223
|
+
client = anthropic.Anthropic()
|
|
224
|
+
new_description = improve_description(
|
|
225
|
+
client=client,
|
|
226
|
+
skill_name=name,
|
|
227
|
+
skill_content=content,
|
|
228
|
+
current_description=current_description,
|
|
229
|
+
eval_results=eval_results,
|
|
230
|
+
history=history,
|
|
231
|
+
model=args.model,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if args.verbose:
|
|
235
|
+
print(f"改善後: {new_description}", file=sys.stderr)
|
|
236
|
+
|
|
237
|
+
# 新しいdescriptionと更新された履歴をJSONとして出力
|
|
238
|
+
output = {
|
|
239
|
+
"description": new_description,
|
|
240
|
+
"history": history + [{
|
|
241
|
+
"description": current_description,
|
|
242
|
+
"passed": eval_results["summary"]["passed"],
|
|
243
|
+
"failed": eval_results["summary"]["failed"],
|
|
244
|
+
"total": eval_results["summary"]["total"],
|
|
245
|
+
"results": eval_results["results"],
|
|
246
|
+
}],
|
|
247
|
+
}
|
|
248
|
+
print(json.dumps(output, indent=2))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
main()
|
|
@@ -103,30 +103,24 @@ Claudeの処理と思考を導くためにコンテキストに読み込まれ
|
|
|
103
103
|
|
|
104
104
|
**不要なディレクトリは削除できます。** すべてのSkillが3種類のリソースすべてを必要とするわけではありません。
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
<!-- @einja:excluded:start -->
|
|
107
|
+
## プロジェクト固有セクションの記入
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
SKILL.md等のmdファイルの末尾には以下を記入する:
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
<!-- @einja:project-private:start id="unique-id" -->
|
|
112
|
+
<!-- プロジェクト固有の情報を記入 -->
|
|
113
|
+
<!-- @einja:project-private:end -->
|
|
114
|
+
<!-- @einja:excluded:end -->
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
<!-- @einja:managed:start -->
|
|
114
|
-
この内容は`einja sync`で上書きされる
|
|
115
|
-
<!-- @einja:managed:end -->
|
|
116
|
-
|
|
117
|
-
<!-- @einja:seed:start id="unique-id" -->
|
|
118
|
-
プロジェクト固有の内容をここに追記
|
|
119
|
-
<!-- @einja:seed:end -->
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### ビルドシステム連携
|
|
123
|
-
|
|
124
|
-
einja management templateでは、`.claude/skills/einja-*/` 内のファイルはビルド時に自動的に `presets/default/` にコピーされます。
|
|
116
|
+
---
|
|
125
117
|
|
|
126
|
-
|
|
118
|
+
<!-- @einja:project-private:start id="{skill_name}-project" -->
|
|
119
|
+
## プロジェクト固有の設定
|
|
127
120
|
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
<!-- このセクションはプロジェクト固有の内容を追記する場所です -->
|
|
122
|
+
<!-- einja syncで上書きされません -->
|
|
123
|
+
<!-- @einja:project-private:end -->
|
|
130
124
|
"""
|
|
131
125
|
|
|
132
126
|
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
|
|
@@ -10,10 +10,36 @@ Skillパッケージャー - Skillフォルダの配布可能な.skillファイ
|
|
|
10
10
|
python package_skill.py .claude/skills/einja-my-skill ./dist
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import fnmatch
|
|
13
14
|
import sys
|
|
14
15
|
import zipfile
|
|
15
16
|
from pathlib import Path
|
|
16
|
-
|
|
17
|
+
try:
|
|
18
|
+
from scripts.quick_validate import validate_skill
|
|
19
|
+
except ImportError:
|
|
20
|
+
from quick_validate import validate_skill
|
|
21
|
+
|
|
22
|
+
# パッケージ化時に除外するパターン
|
|
23
|
+
EXCLUDE_DIRS = {"__pycache__", "node_modules"}
|
|
24
|
+
EXCLUDE_GLOBS = {"*.pyc"}
|
|
25
|
+
EXCLUDE_FILES = {".DS_Store"}
|
|
26
|
+
# Skillルート直下のみ除外するディレクトリ
|
|
27
|
+
ROOT_EXCLUDE_DIRS = {"evals"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def should_exclude(rel_path: Path) -> bool:
|
|
31
|
+
"""パスをパッケージから除外すべきかチェック。"""
|
|
32
|
+
parts = rel_path.parts
|
|
33
|
+
if any(part in EXCLUDE_DIRS for part in parts):
|
|
34
|
+
return True
|
|
35
|
+
# rel_pathはskill_path.parentからの相対パス。parts[0]がSkillフォルダ名、
|
|
36
|
+
# parts[1](存在する場合)が最初のサブディレクトリ
|
|
37
|
+
if len(parts) > 1 and parts[1] in ROOT_EXCLUDE_DIRS:
|
|
38
|
+
return True
|
|
39
|
+
name = rel_path.name
|
|
40
|
+
if name in EXCLUDE_FILES:
|
|
41
|
+
return True
|
|
42
|
+
return any(fnmatch.fnmatch(name, pat) for pat in EXCLUDE_GLOBS)
|
|
17
43
|
|
|
18
44
|
|
|
19
45
|
def package_skill(skill_path, output_dir=None):
|
|
@@ -66,13 +92,16 @@ def package_skill(skill_path, output_dir=None):
|
|
|
66
92
|
# .skillファイル(zip形式)を作成
|
|
67
93
|
try:
|
|
68
94
|
with zipfile.ZipFile(skill_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
69
|
-
# Skill
|
|
95
|
+
# Skillディレクトリを走査し、ビルドアーティファクトを除外
|
|
70
96
|
for file_path in skill_path.rglob('*'):
|
|
71
|
-
if file_path.is_file():
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
print(f"
|
|
97
|
+
if not file_path.is_file():
|
|
98
|
+
continue
|
|
99
|
+
arcname = file_path.relative_to(skill_path.parent)
|
|
100
|
+
if should_exclude(arcname):
|
|
101
|
+
print(f" スキップ: {arcname}")
|
|
102
|
+
continue
|
|
103
|
+
zipf.write(file_path, arcname)
|
|
104
|
+
print(f" 追加: {arcname}")
|
|
76
105
|
|
|
77
106
|
print(f"\n✅ Skillを正常にパッケージ化しました: {skill_filename}")
|
|
78
107
|
return skill_filename
|