jorgex-stack 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/PRD.md +297 -297
  2. package/README.md +56 -58
  3. package/dist/cli.js +7 -2
  4. package/package.json +1 -1
  5. package/stack/agents/orchestrator.md +192 -192
  6. package/stack/agents/tester.md +71 -71
  7. package/stack/commands/xreview.md +80 -80
  8. package/stack/hooks/hooks.json +18 -18
  9. package/stack/scripts/post-pr-review.cjs +156 -156
  10. package/stack/skills/diagnose/SKILL.md +117 -117
  11. package/stack/skills/diagnose/scripts/hitl-loop.template.sh +41 -41
  12. package/stack/skills/find-skills/SKILL.md +133 -133
  13. package/stack/skills/mcp-builder/LICENSE.txt +201 -201
  14. package/stack/skills/mcp-builder/SKILL.md +236 -236
  15. package/stack/skills/mcp-builder/reference/evaluation.md +601 -601
  16. package/stack/skills/mcp-builder/reference/mcp_best_practices.md +249 -249
  17. package/stack/skills/mcp-builder/reference/node_mcp_server.md +969 -969
  18. package/stack/skills/mcp-builder/reference/python_mcp_server.md +718 -718
  19. package/stack/skills/mcp-builder/scripts/connections.py +151 -151
  20. package/stack/skills/mcp-builder/scripts/evaluation.py +373 -373
  21. package/stack/skills/mcp-builder/scripts/example_evaluation.xml +22 -22
  22. package/stack/skills/mcp-builder/scripts/requirements.txt +2 -2
  23. package/stack/skills/obsidian-cli/SKILL.md +106 -106
  24. package/stack/skills/obsidian-markdown/SKILL.md +196 -196
  25. package/stack/skills/obsidian-markdown/references/CALLOUTS.md +58 -58
  26. package/stack/skills/obsidian-markdown/references/EMBEDS.md +63 -63
  27. package/stack/skills/obsidian-markdown/references/PROPERTIES.md +61 -61
  28. package/stack/skills/react-doctor/SKILL.md +19 -19
  29. package/stack/skills/skill-creator/LICENSE.txt +201 -201
  30. package/stack/skills/skill-creator/agents/analyzer.md +274 -274
  31. package/stack/skills/skill-creator/agents/comparator.md +202 -202
  32. package/stack/skills/skill-creator/agents/grader.md +223 -223
  33. package/stack/skills/skill-creator/assets/eval_review.html +146 -146
  34. package/stack/skills/skill-creator/eval-viewer/generate_review.py +471 -471
  35. package/stack/skills/skill-creator/eval-viewer/viewer.html +1325 -1325
  36. package/stack/skills/skill-creator/references/schemas.md +430 -430
  37. package/stack/skills/skill-creator/scripts/aggregate_benchmark.py +401 -401
  38. package/stack/skills/skill-creator/scripts/generate_report.py +326 -326
  39. package/stack/skills/skill-creator/scripts/improve_description.py +248 -248
  40. package/stack/skills/skill-creator/scripts/package_skill.py +136 -136
  41. package/stack/skills/skill-creator/scripts/quick_validate.py +102 -102
  42. package/stack/skills/skill-creator/scripts/run_eval.py +310 -310
  43. package/stack/skills/skill-creator/scripts/run_loop.py +332 -332
  44. package/stack/skills/skill-creator/scripts/utils.py +47 -47
  45. package/stack/skills/supabase/SKILL.md +135 -135
  46. package/stack/skills/supabase/assets/feedback-issue-template.md +17 -17
  47. package/stack/skills/supabase/references/skill-feedback.md +17 -17
  48. package/stack/skills/supabase-postgres-best-practices/SKILL.md +64 -64
  49. package/stack/skills/supabase-postgres-best-practices/references/_contributing.md +170 -170
  50. package/stack/skills/supabase-postgres-best-practices/references/_sections.md +39 -39
  51. package/stack/skills/supabase-postgres-best-practices/references/_template.md +34 -34
  52. package/stack/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -55
  53. package/stack/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -49
  54. package/stack/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -46
  55. package/stack/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -44
  56. package/stack/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -41
  57. package/stack/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -46
  58. package/stack/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -54
  59. package/stack/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -53
  60. package/stack/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -50
  61. package/stack/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -50
  62. package/stack/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -56
  63. package/stack/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -68
  64. package/stack/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -50
  65. package/stack/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -54
  66. package/stack/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -45
  67. package/stack/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -55
  68. package/stack/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -55
  69. package/stack/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -44
  70. package/stack/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -40
  71. package/stack/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -48
  72. package/stack/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -43
  73. package/stack/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -45
  74. package/stack/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -80
  75. package/stack/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -46
  76. package/stack/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -59
  77. package/stack/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -55
  78. package/stack/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -55
  79. package/stack/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -61
  80. package/stack/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -54
  81. package/stack/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -50
  82. package/stack/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -63
  83. package/stack/skills/tdd/SKILL.md +109 -109
  84. package/stack/skills/tdd/deep-modules.md +33 -33
  85. package/stack/skills/tdd/interface-design.md +31 -31
  86. package/stack/skills/tdd/mocking.md +59 -59
  87. package/stack/skills/tdd/refactoring.md +10 -10
  88. package/stack/skills/tdd/tests.md +61 -61
  89. package/stack/skills/to-issues/SKILL.md +83 -83
  90. package/stack/skills/to-prd/SKILL.md +72 -72
  91. package/upstreams.json +96 -96
@@ -1,248 +1,248 @@
1
- #!/usr/bin/env python3
2
- """Improve a skill description based on eval results.
3
-
4
- Takes eval results (from run_eval.py) and generates an improved description
5
- using Claude with extended thinking.
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
- from scripts.utils import parse_skill_md
17
-
18
-
19
- def improve_description(
20
- client: anthropic.Anthropic,
21
- skill_name: str,
22
- skill_content: str,
23
- current_description: str,
24
- eval_results: dict,
25
- history: list[dict],
26
- model: str,
27
- test_results: dict | None = None,
28
- log_dir: Path | None = None,
29
- iteration: int | None = None,
30
- ) -> str:
31
- """Call Claude to improve the description based on eval results."""
32
- failed_triggers = [
33
- r for r in eval_results["results"]
34
- if r["should_trigger"] and not r["pass"]
35
- ]
36
- false_triggers = [
37
- r for r in eval_results["results"]
38
- if not r["should_trigger"] and not r["pass"]
39
- ]
40
-
41
- # Build scores summary
42
- train_score = f"{eval_results['summary']['passed']}/{eval_results['summary']['total']}"
43
- if test_results:
44
- test_score = f"{test_results['summary']['passed']}/{test_results['summary']['total']}"
45
- scores_summary = f"Train: {train_score}, Test: {test_score}"
46
- else:
47
- scores_summary = f"Train: {train_score}"
48
-
49
- 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.
50
-
51
- 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.
52
-
53
- Here's the current description:
54
- <current_description>
55
- "{current_description}"
56
- </current_description>
57
-
58
- Current scores ({scores_summary}):
59
- <scores_summary>
60
- """
61
- if failed_triggers:
62
- prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n"
63
- for r in failed_triggers:
64
- prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
65
- prompt += "\n"
66
-
67
- if false_triggers:
68
- prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n"
69
- for r in false_triggers:
70
- prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
71
- prompt += "\n"
72
-
73
- if history:
74
- prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n"
75
- for h in history:
76
- train_s = f"{h.get('train_passed', h.get('passed', 0))}/{h.get('train_total', h.get('total', 0))}"
77
- test_s = f"{h.get('test_passed', '?')}/{h.get('test_total', '?')}" if h.get('test_passed') is not None else None
78
- score_str = f"train={train_s}" + (f", test={test_s}" if test_s else "")
79
- prompt += f'<attempt {score_str}>\n'
80
- prompt += f'Description: "{h["description"]}"\n'
81
- if "results" in h:
82
- prompt += "Train results:\n"
83
- for r in h["results"]:
84
- status = "PASS" if r["pass"] else "FAIL"
85
- prompt += f' [{status}] "{r["query"][:80]}" (triggered {r["triggers"]}/{r["runs"]})\n'
86
- if h.get("note"):
87
- prompt += f'Note: {h["note"]}\n'
88
- prompt += "</attempt>\n\n"
89
-
90
- prompt += f"""</scores_summary>
91
-
92
- Skill content (for context on what the skill does):
93
- <skill_content>
94
- {skill_content}
95
- </skill_content>
96
-
97
- 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:
98
-
99
- 1. Avoid overfitting
100
- 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.
101
-
102
- Concretely, your description should not be more than about 100-200 words, even if that comes at the cost of accuracy.
103
-
104
- Here are some tips that we've found to work well in writing these descriptions:
105
- - The skill should be phrased in the imperative -- "Use this skill for" rather than "this skill does"
106
- - 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.
107
- - The description competes with other skills for Claude's attention — make it distinctive and immediately recognizable.
108
- - If you're getting lots of failures after repeated attempts, change things up. Try different sentence structures or wordings.
109
-
110
- 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.
111
-
112
- Please respond with only the new description text in <new_description> tags, nothing else."""
113
-
114
- response = client.messages.create(
115
- model=model,
116
- max_tokens=16000,
117
- thinking={
118
- "type": "enabled",
119
- "budget_tokens": 10000,
120
- },
121
- messages=[{"role": "user", "content": prompt}],
122
- )
123
-
124
- # Extract thinking and text from response
125
- thinking_text = ""
126
- text = ""
127
- for block in response.content:
128
- if block.type == "thinking":
129
- thinking_text = block.thinking
130
- elif block.type == "text":
131
- text = block.text
132
-
133
- # Parse out the <new_description> tags
134
- match = re.search(r"<new_description>(.*?)</new_description>", text, re.DOTALL)
135
- description = match.group(1).strip().strip('"') if match else text.strip().strip('"')
136
-
137
- # Log the transcript
138
- transcript: dict = {
139
- "iteration": iteration,
140
- "prompt": prompt,
141
- "thinking": thinking_text,
142
- "response": text,
143
- "parsed_description": description,
144
- "char_count": len(description),
145
- "over_limit": len(description) > 1024,
146
- }
147
-
148
- # If over 1024 chars, ask the model to shorten it
149
- if len(description) > 1024:
150
- 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."
151
- shorten_response = client.messages.create(
152
- model=model,
153
- max_tokens=16000,
154
- thinking={
155
- "type": "enabled",
156
- "budget_tokens": 10000,
157
- },
158
- messages=[
159
- {"role": "user", "content": prompt},
160
- {"role": "assistant", "content": text},
161
- {"role": "user", "content": shorten_prompt},
162
- ],
163
- )
164
-
165
- shorten_thinking = ""
166
- shorten_text = ""
167
- for block in shorten_response.content:
168
- if block.type == "thinking":
169
- shorten_thinking = block.thinking
170
- elif block.type == "text":
171
- shorten_text = block.text
172
-
173
- match = re.search(r"<new_description>(.*?)</new_description>", shorten_text, re.DOTALL)
174
- shortened = match.group(1).strip().strip('"') if match else shorten_text.strip().strip('"')
175
-
176
- transcript["rewrite_prompt"] = shorten_prompt
177
- transcript["rewrite_thinking"] = shorten_thinking
178
- transcript["rewrite_response"] = shorten_text
179
- transcript["rewrite_description"] = shortened
180
- transcript["rewrite_char_count"] = len(shortened)
181
- description = shortened
182
-
183
- transcript["final_description"] = description
184
-
185
- if log_dir:
186
- log_dir.mkdir(parents=True, exist_ok=True)
187
- log_file = log_dir / f"improve_iter_{iteration or 'unknown'}.json"
188
- log_file.write_text(json.dumps(transcript, indent=2))
189
-
190
- return description
191
-
192
-
193
- def main():
194
- parser = argparse.ArgumentParser(description="Improve a skill description based on eval results")
195
- parser.add_argument("--eval-results", required=True, help="Path to eval results JSON (from run_eval.py)")
196
- parser.add_argument("--skill-path", required=True, help="Path to skill directory")
197
- parser.add_argument("--history", default=None, help="Path to history JSON (previous attempts)")
198
- parser.add_argument("--model", required=True, help="Model for improvement")
199
- parser.add_argument("--verbose", action="store_true", help="Print thinking to stderr")
200
- args = parser.parse_args()
201
-
202
- skill_path = Path(args.skill_path)
203
- if not (skill_path / "SKILL.md").exists():
204
- print(f"Error: No SKILL.md found at {skill_path}", file=sys.stderr)
205
- sys.exit(1)
206
-
207
- eval_results = json.loads(Path(args.eval_results).read_text())
208
- history = []
209
- if args.history:
210
- history = json.loads(Path(args.history).read_text())
211
-
212
- name, _, content = parse_skill_md(skill_path)
213
- current_description = eval_results["description"]
214
-
215
- if args.verbose:
216
- print(f"Current: {current_description}", file=sys.stderr)
217
- print(f"Score: {eval_results['summary']['passed']}/{eval_results['summary']['total']}", file=sys.stderr)
218
-
219
- client = anthropic.Anthropic()
220
- new_description = improve_description(
221
- client=client,
222
- skill_name=name,
223
- skill_content=content,
224
- current_description=current_description,
225
- eval_results=eval_results,
226
- history=history,
227
- model=args.model,
228
- )
229
-
230
- if args.verbose:
231
- print(f"Improved: {new_description}", file=sys.stderr)
232
-
233
- # Output as JSON with both the new description and updated history
234
- output = {
235
- "description": new_description,
236
- "history": history + [{
237
- "description": current_description,
238
- "passed": eval_results["summary"]["passed"],
239
- "failed": eval_results["summary"]["failed"],
240
- "total": eval_results["summary"]["total"],
241
- "results": eval_results["results"],
242
- }],
243
- }
244
- print(json.dumps(output, indent=2))
245
-
246
-
247
- if __name__ == "__main__":
248
- main()
1
+ #!/usr/bin/env python3
2
+ """Improve a skill description based on eval results.
3
+
4
+ Takes eval results (from run_eval.py) and generates an improved description
5
+ using Claude with extended thinking.
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
+ from scripts.utils import parse_skill_md
17
+
18
+
19
+ def improve_description(
20
+ client: anthropic.Anthropic,
21
+ skill_name: str,
22
+ skill_content: str,
23
+ current_description: str,
24
+ eval_results: dict,
25
+ history: list[dict],
26
+ model: str,
27
+ test_results: dict | None = None,
28
+ log_dir: Path | None = None,
29
+ iteration: int | None = None,
30
+ ) -> str:
31
+ """Call Claude to improve the description based on eval results."""
32
+ failed_triggers = [
33
+ r for r in eval_results["results"]
34
+ if r["should_trigger"] and not r["pass"]
35
+ ]
36
+ false_triggers = [
37
+ r for r in eval_results["results"]
38
+ if not r["should_trigger"] and not r["pass"]
39
+ ]
40
+
41
+ # Build scores summary
42
+ train_score = f"{eval_results['summary']['passed']}/{eval_results['summary']['total']}"
43
+ if test_results:
44
+ test_score = f"{test_results['summary']['passed']}/{test_results['summary']['total']}"
45
+ scores_summary = f"Train: {train_score}, Test: {test_score}"
46
+ else:
47
+ scores_summary = f"Train: {train_score}"
48
+
49
+ 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.
50
+
51
+ 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.
52
+
53
+ Here's the current description:
54
+ <current_description>
55
+ "{current_description}"
56
+ </current_description>
57
+
58
+ Current scores ({scores_summary}):
59
+ <scores_summary>
60
+ """
61
+ if failed_triggers:
62
+ prompt += "FAILED TO TRIGGER (should have triggered but didn't):\n"
63
+ for r in failed_triggers:
64
+ prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
65
+ prompt += "\n"
66
+
67
+ if false_triggers:
68
+ prompt += "FALSE TRIGGERS (triggered but shouldn't have):\n"
69
+ for r in false_triggers:
70
+ prompt += f' - "{r["query"]}" (triggered {r["triggers"]}/{r["runs"]} times)\n'
71
+ prompt += "\n"
72
+
73
+ if history:
74
+ prompt += "PREVIOUS ATTEMPTS (do NOT repeat these — try something structurally different):\n\n"
75
+ for h in history:
76
+ train_s = f"{h.get('train_passed', h.get('passed', 0))}/{h.get('train_total', h.get('total', 0))}"
77
+ test_s = f"{h.get('test_passed', '?')}/{h.get('test_total', '?')}" if h.get('test_passed') is not None else None
78
+ score_str = f"train={train_s}" + (f", test={test_s}" if test_s else "")
79
+ prompt += f'<attempt {score_str}>\n'
80
+ prompt += f'Description: "{h["description"]}"\n'
81
+ if "results" in h:
82
+ prompt += "Train results:\n"
83
+ for r in h["results"]:
84
+ status = "PASS" if r["pass"] else "FAIL"
85
+ prompt += f' [{status}] "{r["query"][:80]}" (triggered {r["triggers"]}/{r["runs"]})\n'
86
+ if h.get("note"):
87
+ prompt += f'Note: {h["note"]}\n'
88
+ prompt += "</attempt>\n\n"
89
+
90
+ prompt += f"""</scores_summary>
91
+
92
+ Skill content (for context on what the skill does):
93
+ <skill_content>
94
+ {skill_content}
95
+ </skill_content>
96
+
97
+ 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:
98
+
99
+ 1. Avoid overfitting
100
+ 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.
101
+
102
+ Concretely, your description should not be more than about 100-200 words, even if that comes at the cost of accuracy.
103
+
104
+ Here are some tips that we've found to work well in writing these descriptions:
105
+ - The skill should be phrased in the imperative -- "Use this skill for" rather than "this skill does"
106
+ - 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.
107
+ - The description competes with other skills for Claude's attention — make it distinctive and immediately recognizable.
108
+ - If you're getting lots of failures after repeated attempts, change things up. Try different sentence structures or wordings.
109
+
110
+ 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.
111
+
112
+ Please respond with only the new description text in <new_description> tags, nothing else."""
113
+
114
+ response = client.messages.create(
115
+ model=model,
116
+ max_tokens=16000,
117
+ thinking={
118
+ "type": "enabled",
119
+ "budget_tokens": 10000,
120
+ },
121
+ messages=[{"role": "user", "content": prompt}],
122
+ )
123
+
124
+ # Extract thinking and text from response
125
+ thinking_text = ""
126
+ text = ""
127
+ for block in response.content:
128
+ if block.type == "thinking":
129
+ thinking_text = block.thinking
130
+ elif block.type == "text":
131
+ text = block.text
132
+
133
+ # Parse out the <new_description> tags
134
+ match = re.search(r"<new_description>(.*?)</new_description>", text, re.DOTALL)
135
+ description = match.group(1).strip().strip('"') if match else text.strip().strip('"')
136
+
137
+ # Log the transcript
138
+ transcript: dict = {
139
+ "iteration": iteration,
140
+ "prompt": prompt,
141
+ "thinking": thinking_text,
142
+ "response": text,
143
+ "parsed_description": description,
144
+ "char_count": len(description),
145
+ "over_limit": len(description) > 1024,
146
+ }
147
+
148
+ # If over 1024 chars, ask the model to shorten it
149
+ if len(description) > 1024:
150
+ 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."
151
+ shorten_response = client.messages.create(
152
+ model=model,
153
+ max_tokens=16000,
154
+ thinking={
155
+ "type": "enabled",
156
+ "budget_tokens": 10000,
157
+ },
158
+ messages=[
159
+ {"role": "user", "content": prompt},
160
+ {"role": "assistant", "content": text},
161
+ {"role": "user", "content": shorten_prompt},
162
+ ],
163
+ )
164
+
165
+ shorten_thinking = ""
166
+ shorten_text = ""
167
+ for block in shorten_response.content:
168
+ if block.type == "thinking":
169
+ shorten_thinking = block.thinking
170
+ elif block.type == "text":
171
+ shorten_text = block.text
172
+
173
+ match = re.search(r"<new_description>(.*?)</new_description>", shorten_text, re.DOTALL)
174
+ shortened = match.group(1).strip().strip('"') if match else shorten_text.strip().strip('"')
175
+
176
+ transcript["rewrite_prompt"] = shorten_prompt
177
+ transcript["rewrite_thinking"] = shorten_thinking
178
+ transcript["rewrite_response"] = shorten_text
179
+ transcript["rewrite_description"] = shortened
180
+ transcript["rewrite_char_count"] = len(shortened)
181
+ description = shortened
182
+
183
+ transcript["final_description"] = description
184
+
185
+ if log_dir:
186
+ log_dir.mkdir(parents=True, exist_ok=True)
187
+ log_file = log_dir / f"improve_iter_{iteration or 'unknown'}.json"
188
+ log_file.write_text(json.dumps(transcript, indent=2))
189
+
190
+ return description
191
+
192
+
193
+ def main():
194
+ parser = argparse.ArgumentParser(description="Improve a skill description based on eval results")
195
+ parser.add_argument("--eval-results", required=True, help="Path to eval results JSON (from run_eval.py)")
196
+ parser.add_argument("--skill-path", required=True, help="Path to skill directory")
197
+ parser.add_argument("--history", default=None, help="Path to history JSON (previous attempts)")
198
+ parser.add_argument("--model", required=True, help="Model for improvement")
199
+ parser.add_argument("--verbose", action="store_true", help="Print thinking to stderr")
200
+ args = parser.parse_args()
201
+
202
+ skill_path = Path(args.skill_path)
203
+ if not (skill_path / "SKILL.md").exists():
204
+ print(f"Error: No SKILL.md found at {skill_path}", file=sys.stderr)
205
+ sys.exit(1)
206
+
207
+ eval_results = json.loads(Path(args.eval_results).read_text())
208
+ history = []
209
+ if args.history:
210
+ history = json.loads(Path(args.history).read_text())
211
+
212
+ name, _, content = parse_skill_md(skill_path)
213
+ current_description = eval_results["description"]
214
+
215
+ if args.verbose:
216
+ print(f"Current: {current_description}", file=sys.stderr)
217
+ print(f"Score: {eval_results['summary']['passed']}/{eval_results['summary']['total']}", file=sys.stderr)
218
+
219
+ client = anthropic.Anthropic()
220
+ new_description = improve_description(
221
+ client=client,
222
+ skill_name=name,
223
+ skill_content=content,
224
+ current_description=current_description,
225
+ eval_results=eval_results,
226
+ history=history,
227
+ model=args.model,
228
+ )
229
+
230
+ if args.verbose:
231
+ print(f"Improved: {new_description}", file=sys.stderr)
232
+
233
+ # Output as JSON with both the new description and updated history
234
+ output = {
235
+ "description": new_description,
236
+ "history": history + [{
237
+ "description": current_description,
238
+ "passed": eval_results["summary"]["passed"],
239
+ "failed": eval_results["summary"]["failed"],
240
+ "total": eval_results["summary"]["total"],
241
+ "results": eval_results["results"],
242
+ }],
243
+ }
244
+ print(json.dumps(output, indent=2))
245
+
246
+
247
+ if __name__ == "__main__":
248
+ main()