enigma-cli 1.15.1 → 1.15.3
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 +37 -0
- package/assets/commands/improve.md +81 -0
- package/assets/memory/AGENTS.md +1 -1
- package/assets/memory/CLAUDE.md +1 -1
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/SKILL.md +2 -4
- package/assets/skills/core-engineering-policy/skill.json +3 -3
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/frontend-design/SKILL.md +54 -0
- package/assets/skills/frontend-design/skill.json +8 -0
- package/assets/skills/frontend-policy/skill.json +1 -1
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/skill-creator/SKILL.md +485 -0
- package/assets/skills/skill-creator/agents/analyzer.md +274 -0
- package/assets/skills/skill-creator/agents/comparator.md +202 -0
- package/assets/skills/skill-creator/agents/grader.md +223 -0
- package/assets/skills/skill-creator/assets/eval_review.html +146 -0
- package/assets/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/assets/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/assets/skills/skill-creator/references/schemas.md +430 -0
- package/assets/skills/skill-creator/scripts/__init__.py +0 -0
- package/assets/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/assets/skills/skill-creator/scripts/generate_report.py +326 -0
- package/assets/skills/skill-creator/scripts/improve_description.py +247 -0
- package/assets/skills/skill-creator/scripts/package_skill.py +136 -0
- package/assets/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/assets/skills/skill-creator/scripts/run_eval.py +310 -0
- package/assets/skills/skill-creator/scripts/run_loop.py +328 -0
- package/assets/skills/skill-creator/scripts/utils.py +47 -0
- package/assets/skills/skill-creator/skill.json +8 -0
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/testing-policy/SKILL.md +81 -4
- package/assets/skills/testing-policy/skill.json +4 -4
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/package.json +1 -1
- package/assets/skills/test-organization-policy/SKILL.md +0 -88
- package/assets/skills/test-organization-policy/skill.json +0 -8
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Run the eval + improve loop until all pass or max iterations reached.
|
|
3
|
+
|
|
4
|
+
Combines run_eval.py and improve_description.py in a loop, tracking history
|
|
5
|
+
and returning the best description found. Supports train/test split to prevent
|
|
6
|
+
overfitting.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import random
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import time
|
|
15
|
+
import webbrowser
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from scripts.generate_report import generate_html
|
|
19
|
+
from scripts.improve_description import improve_description
|
|
20
|
+
from scripts.run_eval import find_project_root, run_eval
|
|
21
|
+
from scripts.utils import parse_skill_md
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def split_eval_set(eval_set: list[dict], holdout: float, seed: int = 42) -> tuple[list[dict], list[dict]]:
|
|
25
|
+
"""Split eval set into train and test sets, stratified by should_trigger."""
|
|
26
|
+
random.seed(seed)
|
|
27
|
+
|
|
28
|
+
# Separate by should_trigger
|
|
29
|
+
trigger = [e for e in eval_set if e["should_trigger"]]
|
|
30
|
+
no_trigger = [e for e in eval_set if not e["should_trigger"]]
|
|
31
|
+
|
|
32
|
+
# Shuffle each group
|
|
33
|
+
random.shuffle(trigger)
|
|
34
|
+
random.shuffle(no_trigger)
|
|
35
|
+
|
|
36
|
+
# Calculate split points
|
|
37
|
+
n_trigger_test = max(1, int(len(trigger) * holdout))
|
|
38
|
+
n_no_trigger_test = max(1, int(len(no_trigger) * holdout))
|
|
39
|
+
|
|
40
|
+
# Split
|
|
41
|
+
test_set = trigger[:n_trigger_test] + no_trigger[:n_no_trigger_test]
|
|
42
|
+
train_set = trigger[n_trigger_test:] + no_trigger[n_no_trigger_test:]
|
|
43
|
+
|
|
44
|
+
return train_set, test_set
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run_loop(
|
|
48
|
+
eval_set: list[dict],
|
|
49
|
+
skill_path: Path,
|
|
50
|
+
description_override: str | None,
|
|
51
|
+
num_workers: int,
|
|
52
|
+
timeout: int,
|
|
53
|
+
max_iterations: int,
|
|
54
|
+
runs_per_query: int,
|
|
55
|
+
trigger_threshold: float,
|
|
56
|
+
holdout: float,
|
|
57
|
+
model: str,
|
|
58
|
+
verbose: bool,
|
|
59
|
+
live_report_path: Path | None = None,
|
|
60
|
+
log_dir: Path | None = None,
|
|
61
|
+
) -> dict:
|
|
62
|
+
"""Run the eval + improvement loop."""
|
|
63
|
+
project_root = find_project_root()
|
|
64
|
+
name, original_description, content = parse_skill_md(skill_path)
|
|
65
|
+
current_description = description_override or original_description
|
|
66
|
+
|
|
67
|
+
# Split into train/test if holdout > 0
|
|
68
|
+
if holdout > 0:
|
|
69
|
+
train_set, test_set = split_eval_set(eval_set, holdout)
|
|
70
|
+
if verbose:
|
|
71
|
+
print(f"Split: {len(train_set)} train, {len(test_set)} test (holdout={holdout})", file=sys.stderr)
|
|
72
|
+
else:
|
|
73
|
+
train_set = eval_set
|
|
74
|
+
test_set = []
|
|
75
|
+
|
|
76
|
+
history = []
|
|
77
|
+
exit_reason = "unknown"
|
|
78
|
+
|
|
79
|
+
for iteration in range(1, max_iterations + 1):
|
|
80
|
+
if verbose:
|
|
81
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
82
|
+
print(f"Iteration {iteration}/{max_iterations}", file=sys.stderr)
|
|
83
|
+
print(f"Description: {current_description}", file=sys.stderr)
|
|
84
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
85
|
+
|
|
86
|
+
# Evaluate train + test together in one batch for parallelism
|
|
87
|
+
all_queries = train_set + test_set
|
|
88
|
+
t0 = time.time()
|
|
89
|
+
all_results = run_eval(
|
|
90
|
+
eval_set=all_queries,
|
|
91
|
+
skill_name=name,
|
|
92
|
+
description=current_description,
|
|
93
|
+
num_workers=num_workers,
|
|
94
|
+
timeout=timeout,
|
|
95
|
+
project_root=project_root,
|
|
96
|
+
runs_per_query=runs_per_query,
|
|
97
|
+
trigger_threshold=trigger_threshold,
|
|
98
|
+
model=model,
|
|
99
|
+
)
|
|
100
|
+
eval_elapsed = time.time() - t0
|
|
101
|
+
|
|
102
|
+
# Split results back into train/test by matching queries
|
|
103
|
+
train_queries_set = {q["query"] for q in train_set}
|
|
104
|
+
train_result_list = [r for r in all_results["results"] if r["query"] in train_queries_set]
|
|
105
|
+
test_result_list = [r for r in all_results["results"] if r["query"] not in train_queries_set]
|
|
106
|
+
|
|
107
|
+
train_passed = sum(1 for r in train_result_list if r["pass"])
|
|
108
|
+
train_total = len(train_result_list)
|
|
109
|
+
train_summary = {"passed": train_passed, "failed": train_total - train_passed, "total": train_total}
|
|
110
|
+
train_results = {"results": train_result_list, "summary": train_summary}
|
|
111
|
+
|
|
112
|
+
if test_set:
|
|
113
|
+
test_passed = sum(1 for r in test_result_list if r["pass"])
|
|
114
|
+
test_total = len(test_result_list)
|
|
115
|
+
test_summary = {"passed": test_passed, "failed": test_total - test_passed, "total": test_total}
|
|
116
|
+
test_results = {"results": test_result_list, "summary": test_summary}
|
|
117
|
+
else:
|
|
118
|
+
test_results = None
|
|
119
|
+
test_summary = None
|
|
120
|
+
|
|
121
|
+
history.append({
|
|
122
|
+
"iteration": iteration,
|
|
123
|
+
"description": current_description,
|
|
124
|
+
"train_passed": train_summary["passed"],
|
|
125
|
+
"train_failed": train_summary["failed"],
|
|
126
|
+
"train_total": train_summary["total"],
|
|
127
|
+
"train_results": train_results["results"],
|
|
128
|
+
"test_passed": test_summary["passed"] if test_summary else None,
|
|
129
|
+
"test_failed": test_summary["failed"] if test_summary else None,
|
|
130
|
+
"test_total": test_summary["total"] if test_summary else None,
|
|
131
|
+
"test_results": test_results["results"] if test_results else None,
|
|
132
|
+
# For backward compat with report generator
|
|
133
|
+
"passed": train_summary["passed"],
|
|
134
|
+
"failed": train_summary["failed"],
|
|
135
|
+
"total": train_summary["total"],
|
|
136
|
+
"results": train_results["results"],
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
# Write live report if path provided
|
|
140
|
+
if live_report_path:
|
|
141
|
+
partial_output = {
|
|
142
|
+
"original_description": original_description,
|
|
143
|
+
"best_description": current_description,
|
|
144
|
+
"best_score": "in progress",
|
|
145
|
+
"iterations_run": len(history),
|
|
146
|
+
"holdout": holdout,
|
|
147
|
+
"train_size": len(train_set),
|
|
148
|
+
"test_size": len(test_set),
|
|
149
|
+
"history": history,
|
|
150
|
+
}
|
|
151
|
+
live_report_path.write_text(generate_html(partial_output, auto_refresh=True, skill_name=name))
|
|
152
|
+
|
|
153
|
+
if verbose:
|
|
154
|
+
def print_eval_stats(label, results, elapsed):
|
|
155
|
+
pos = [r for r in results if r["should_trigger"]]
|
|
156
|
+
neg = [r for r in results if not r["should_trigger"]]
|
|
157
|
+
tp = sum(r["triggers"] for r in pos)
|
|
158
|
+
pos_runs = sum(r["runs"] for r in pos)
|
|
159
|
+
fn = pos_runs - tp
|
|
160
|
+
fp = sum(r["triggers"] for r in neg)
|
|
161
|
+
neg_runs = sum(r["runs"] for r in neg)
|
|
162
|
+
tn = neg_runs - fp
|
|
163
|
+
total = tp + tn + fp + fn
|
|
164
|
+
precision = tp / (tp + fp) if (tp + fp) > 0 else 1.0
|
|
165
|
+
recall = tp / (tp + fn) if (tp + fn) > 0 else 1.0
|
|
166
|
+
accuracy = (tp + tn) / total if total > 0 else 0.0
|
|
167
|
+
print(f"{label}: {tp+tn}/{total} correct, precision={precision:.0%} recall={recall:.0%} accuracy={accuracy:.0%} ({elapsed:.1f}s)", file=sys.stderr)
|
|
168
|
+
for r in results:
|
|
169
|
+
status = "PASS" if r["pass"] else "FAIL"
|
|
170
|
+
rate_str = f"{r['triggers']}/{r['runs']}"
|
|
171
|
+
print(f" [{status}] rate={rate_str} expected={r['should_trigger']}: {r['query'][:60]}", file=sys.stderr)
|
|
172
|
+
|
|
173
|
+
print_eval_stats("Train", train_results["results"], eval_elapsed)
|
|
174
|
+
if test_summary:
|
|
175
|
+
print_eval_stats("Test ", test_results["results"], 0)
|
|
176
|
+
|
|
177
|
+
if train_summary["failed"] == 0:
|
|
178
|
+
exit_reason = f"all_passed (iteration {iteration})"
|
|
179
|
+
if verbose:
|
|
180
|
+
print(f"\nAll train queries passed on iteration {iteration}!", file=sys.stderr)
|
|
181
|
+
break
|
|
182
|
+
|
|
183
|
+
if iteration == max_iterations:
|
|
184
|
+
exit_reason = f"max_iterations ({max_iterations})"
|
|
185
|
+
if verbose:
|
|
186
|
+
print(f"\nMax iterations reached ({max_iterations}).", file=sys.stderr)
|
|
187
|
+
break
|
|
188
|
+
|
|
189
|
+
# Improve the description based on train results
|
|
190
|
+
if verbose:
|
|
191
|
+
print(f"\nImproving description...", file=sys.stderr)
|
|
192
|
+
|
|
193
|
+
t0 = time.time()
|
|
194
|
+
# Strip test scores from history so improvement model can't see them
|
|
195
|
+
blinded_history = [
|
|
196
|
+
{k: v for k, v in h.items() if not k.startswith("test_")}
|
|
197
|
+
for h in history
|
|
198
|
+
]
|
|
199
|
+
new_description = improve_description(
|
|
200
|
+
skill_name=name,
|
|
201
|
+
skill_content=content,
|
|
202
|
+
current_description=current_description,
|
|
203
|
+
eval_results=train_results,
|
|
204
|
+
history=blinded_history,
|
|
205
|
+
model=model,
|
|
206
|
+
log_dir=log_dir,
|
|
207
|
+
iteration=iteration,
|
|
208
|
+
)
|
|
209
|
+
improve_elapsed = time.time() - t0
|
|
210
|
+
|
|
211
|
+
if verbose:
|
|
212
|
+
print(f"Proposed ({improve_elapsed:.1f}s): {new_description}", file=sys.stderr)
|
|
213
|
+
|
|
214
|
+
current_description = new_description
|
|
215
|
+
|
|
216
|
+
# Find the best iteration by TEST score (or train if no test set)
|
|
217
|
+
if test_set:
|
|
218
|
+
best = max(history, key=lambda h: h["test_passed"] or 0)
|
|
219
|
+
best_score = f"{best['test_passed']}/{best['test_total']}"
|
|
220
|
+
else:
|
|
221
|
+
best = max(history, key=lambda h: h["train_passed"])
|
|
222
|
+
best_score = f"{best['train_passed']}/{best['train_total']}"
|
|
223
|
+
|
|
224
|
+
if verbose:
|
|
225
|
+
print(f"\nExit reason: {exit_reason}", file=sys.stderr)
|
|
226
|
+
print(f"Best score: {best_score} (iteration {best['iteration']})", file=sys.stderr)
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
"exit_reason": exit_reason,
|
|
230
|
+
"original_description": original_description,
|
|
231
|
+
"best_description": best["description"],
|
|
232
|
+
"best_score": best_score,
|
|
233
|
+
"best_train_score": f"{best['train_passed']}/{best['train_total']}",
|
|
234
|
+
"best_test_score": f"{best['test_passed']}/{best['test_total']}" if test_set else None,
|
|
235
|
+
"final_description": current_description,
|
|
236
|
+
"iterations_run": len(history),
|
|
237
|
+
"holdout": holdout,
|
|
238
|
+
"train_size": len(train_set),
|
|
239
|
+
"test_size": len(test_set),
|
|
240
|
+
"history": history,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main():
|
|
245
|
+
parser = argparse.ArgumentParser(description="Run eval + improve loop")
|
|
246
|
+
parser.add_argument("--eval-set", required=True, help="Path to eval set JSON file")
|
|
247
|
+
parser.add_argument("--skill-path", required=True, help="Path to skill directory")
|
|
248
|
+
parser.add_argument("--description", default=None, help="Override starting description")
|
|
249
|
+
parser.add_argument("--num-workers", type=int, default=10, help="Number of parallel workers")
|
|
250
|
+
parser.add_argument("--timeout", type=int, default=30, help="Timeout per query in seconds")
|
|
251
|
+
parser.add_argument("--max-iterations", type=int, default=5, help="Max improvement iterations")
|
|
252
|
+
parser.add_argument("--runs-per-query", type=int, default=3, help="Number of runs per query")
|
|
253
|
+
parser.add_argument("--trigger-threshold", type=float, default=0.5, help="Trigger rate threshold")
|
|
254
|
+
parser.add_argument("--holdout", type=float, default=0.4, help="Fraction of eval set to hold out for testing (0 to disable)")
|
|
255
|
+
parser.add_argument("--model", required=True, help="Model for improvement")
|
|
256
|
+
parser.add_argument("--verbose", action="store_true", help="Print progress to stderr")
|
|
257
|
+
parser.add_argument("--report", default="auto", help="Generate HTML report at this path (default: 'auto' for temp file, 'none' to disable)")
|
|
258
|
+
parser.add_argument("--results-dir", default=None, help="Save all outputs (results.json, report.html, log.txt) to a timestamped subdirectory here")
|
|
259
|
+
args = parser.parse_args()
|
|
260
|
+
|
|
261
|
+
eval_set = json.loads(Path(args.eval_set).read_text())
|
|
262
|
+
skill_path = Path(args.skill_path)
|
|
263
|
+
|
|
264
|
+
if not (skill_path / "SKILL.md").exists():
|
|
265
|
+
print(f"Error: No SKILL.md found at {skill_path}", file=sys.stderr)
|
|
266
|
+
sys.exit(1)
|
|
267
|
+
|
|
268
|
+
name, _, _ = parse_skill_md(skill_path)
|
|
269
|
+
|
|
270
|
+
# Set up live report path
|
|
271
|
+
if args.report != "none":
|
|
272
|
+
if args.report == "auto":
|
|
273
|
+
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
|
274
|
+
live_report_path = Path(tempfile.gettempdir()) / f"skill_description_report_{skill_path.name}_{timestamp}.html"
|
|
275
|
+
else:
|
|
276
|
+
live_report_path = Path(args.report)
|
|
277
|
+
# Open the report immediately so the user can watch
|
|
278
|
+
live_report_path.write_text("<html><body><h1>Starting optimization loop...</h1><meta http-equiv='refresh' content='5'></body></html>")
|
|
279
|
+
webbrowser.open(str(live_report_path))
|
|
280
|
+
else:
|
|
281
|
+
live_report_path = None
|
|
282
|
+
|
|
283
|
+
# Determine output directory (create before run_loop so logs can be written)
|
|
284
|
+
if args.results_dir:
|
|
285
|
+
timestamp = time.strftime("%Y-%m-%d_%H%M%S")
|
|
286
|
+
results_dir = Path(args.results_dir) / timestamp
|
|
287
|
+
results_dir.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
else:
|
|
289
|
+
results_dir = None
|
|
290
|
+
|
|
291
|
+
log_dir = results_dir / "logs" if results_dir else None
|
|
292
|
+
|
|
293
|
+
output = run_loop(
|
|
294
|
+
eval_set=eval_set,
|
|
295
|
+
skill_path=skill_path,
|
|
296
|
+
description_override=args.description,
|
|
297
|
+
num_workers=args.num_workers,
|
|
298
|
+
timeout=args.timeout,
|
|
299
|
+
max_iterations=args.max_iterations,
|
|
300
|
+
runs_per_query=args.runs_per_query,
|
|
301
|
+
trigger_threshold=args.trigger_threshold,
|
|
302
|
+
holdout=args.holdout,
|
|
303
|
+
model=args.model,
|
|
304
|
+
verbose=args.verbose,
|
|
305
|
+
live_report_path=live_report_path,
|
|
306
|
+
log_dir=log_dir,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Save JSON output
|
|
310
|
+
json_output = json.dumps(output, indent=2)
|
|
311
|
+
print(json_output)
|
|
312
|
+
if results_dir:
|
|
313
|
+
(results_dir / "results.json").write_text(json_output)
|
|
314
|
+
|
|
315
|
+
# Write final HTML report (without auto-refresh)
|
|
316
|
+
if live_report_path:
|
|
317
|
+
live_report_path.write_text(generate_html(output, auto_refresh=False, skill_name=name))
|
|
318
|
+
print(f"\nReport: {live_report_path}", file=sys.stderr)
|
|
319
|
+
|
|
320
|
+
if results_dir and live_report_path:
|
|
321
|
+
(results_dir / "report.html").write_text(generate_html(output, auto_refresh=False, skill_name=name))
|
|
322
|
+
|
|
323
|
+
if results_dir:
|
|
324
|
+
print(f"Results saved to: {results_dir}", file=sys.stderr)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if __name__ == "__main__":
|
|
328
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Shared utilities for skill-creator scripts."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_skill_md(skill_path: Path) -> tuple[str, str, str]:
|
|
8
|
+
"""Parse a SKILL.md file, returning (name, description, full_content)."""
|
|
9
|
+
content = (skill_path / "SKILL.md").read_text()
|
|
10
|
+
lines = content.split("\n")
|
|
11
|
+
|
|
12
|
+
if lines[0].strip() != "---":
|
|
13
|
+
raise ValueError("SKILL.md missing frontmatter (no opening ---)")
|
|
14
|
+
|
|
15
|
+
end_idx = None
|
|
16
|
+
for i, line in enumerate(lines[1:], start=1):
|
|
17
|
+
if line.strip() == "---":
|
|
18
|
+
end_idx = i
|
|
19
|
+
break
|
|
20
|
+
|
|
21
|
+
if end_idx is None:
|
|
22
|
+
raise ValueError("SKILL.md missing frontmatter (no closing ---)")
|
|
23
|
+
|
|
24
|
+
name = ""
|
|
25
|
+
description = ""
|
|
26
|
+
frontmatter_lines = lines[1:end_idx]
|
|
27
|
+
i = 0
|
|
28
|
+
while i < len(frontmatter_lines):
|
|
29
|
+
line = frontmatter_lines[i]
|
|
30
|
+
if line.startswith("name:"):
|
|
31
|
+
name = line[len("name:"):].strip().strip('"').strip("'")
|
|
32
|
+
elif line.startswith("description:"):
|
|
33
|
+
value = line[len("description:"):].strip()
|
|
34
|
+
# Handle YAML multiline indicators (>, |, >-, |-)
|
|
35
|
+
if value in (">", "|", ">-", "|-"):
|
|
36
|
+
continuation_lines: list[str] = []
|
|
37
|
+
i += 1
|
|
38
|
+
while i < len(frontmatter_lines) and (frontmatter_lines[i].startswith(" ") or frontmatter_lines[i].startswith("\t")):
|
|
39
|
+
continuation_lines.append(frontmatter_lines[i].strip())
|
|
40
|
+
i += 1
|
|
41
|
+
description = " ".join(continuation_lines)
|
|
42
|
+
continue
|
|
43
|
+
else:
|
|
44
|
+
description = value.strip('"').strip("'")
|
|
45
|
+
i += 1
|
|
46
|
+
|
|
47
|
+
return name, description, content
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-creator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"provider": "FJRG2007/enigma",
|
|
5
|
+
"description": "Create new skills, modify and improve existing skills, and measure skill performance with evals and benchmarks.",
|
|
6
|
+
"cliVersion": "1.15.3",
|
|
7
|
+
"sha": "699586cce82ec0a5458288b598ee7e5ebdddb3dfcf19db354d8bc5e85e47c1c7"
|
|
8
|
+
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.1.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Exhaustive completion discipline for long/multi-item tasks - inventory, coverage ledger, verified done.",
|
|
6
|
-
"cliVersion": "1.15.
|
|
6
|
+
"cliVersion": "1.15.3",
|
|
7
7
|
"sha": "6e3facba307eb2b55cefbab2e4b2a346a2b82f93c3ef47e11ebeb78c3c9453a8"
|
|
8
8
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testing-policy
|
|
3
|
-
description: Test strategy (test pyramid), coverage gates, deterministic tests, mocking discipline,
|
|
3
|
+
description: Test strategy (test pyramid), coverage gates, deterministic tests, mocking discipline, regression-first bug fixing, and test-suite organization (directory structure by test type and domain, mirrored source paths, file naming, fixture/helper/factory placement). Use when writing or changing code that needs tests, when asked to add or fix tests, after fixing a bug to add a regression test, or when creating, moving, renaming, or structuring test files - never dump tests flat into a single tests/ folder.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Testing Policy (Senior Engineering Standards)
|
|
@@ -8,8 +8,9 @@ description: Test strategy (test pyramid), coverage gates, deterministic tests,
|
|
|
8
8
|
## Activation Scope
|
|
9
9
|
|
|
10
10
|
- Apply whenever code is written, changed, or fixed, and whenever the user asks for tests.
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- Apply whenever a test file is created, moved, or renamed, and whenever a test suite is scaffolded or restructured.
|
|
12
|
+
- Owns test strategy, coverage expectations, determinism, mocking discipline, and test-first discipline.
|
|
13
|
+
- Owns test-suite layout: directory structure, subfolders, file naming, and placement of fixtures, helpers, factories, and mocks.
|
|
13
14
|
|
|
14
15
|
---
|
|
15
16
|
|
|
@@ -70,8 +71,84 @@ description: Test strategy (test pyramid), coverage gates, deterministic tests,
|
|
|
70
71
|
|
|
71
72
|
---
|
|
72
73
|
|
|
74
|
+
## Suite Organization: Core Principle
|
|
75
|
+
|
|
76
|
+
- A test suite is navigable code: anyone must find the tests for a module in seconds from the module's path alone, and find the module from its test's path.
|
|
77
|
+
- Never dump test files flat into a single tests/ folder. Flat suites hide coverage gaps, cause name collisions, and stop scaling past a handful of files.
|
|
78
|
+
- Organize from the first test file. Structure is cheapest at file creation and most expensive after the folder is a mess.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Framework Convention First
|
|
83
|
+
|
|
84
|
+
- Every ecosystem has an established convention; it beats any custom layout:
|
|
85
|
+
- JS/TS (Vitest, Jest, Bun): colocated `*.test.ts` next to the source file, a `__tests__/` folder per directory, or a mirrored `tests/` tree - follow whichever the repo already uses.
|
|
86
|
+
- Python (pytest): a `tests/` package mirroring the source package; shared fixtures in `conftest.py` at the narrowest directory that covers their users.
|
|
87
|
+
- Go: `_test.go` colocated in the same package (mandated by the toolchain); black-box tests use the `_test` package suffix.
|
|
88
|
+
- Rust: unit tests in `#[cfg(test)] mod tests` inside the module; integration tests as separate files under the crate-root `tests/`.
|
|
89
|
+
- Java/Kotlin: `src/test/<lang>/` mirroring the `src/main/<lang>/` package path exactly.
|
|
90
|
+
- In an existing repo, detect the established layout and extend it; never introduce a second competing layout. Migrations to a better layout are proposed explicitly, not done by stealth.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Structure by Test Type, Then by Domain
|
|
95
|
+
|
|
96
|
+
- When more than one test type exists, separate types at the top level - they differ in speed, dependencies, and CI stage:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
tests/
|
|
100
|
+
unit/ fast, isolated, no I/O
|
|
101
|
+
integration/ module boundaries, DB, contracts
|
|
102
|
+
e2e/ critical user flows only
|
|
103
|
+
fixtures/ shared static data
|
|
104
|
+
helpers/ shared builders, factories, fakes
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- Inside each type, mirror the source tree: tests for `src/<domain>/<module>` live at `tests/<type>/<domain>/<module>.<suffix>`.
|
|
108
|
+
- Group e2e tests by user flow (e.g. `e2e/checkout/`), not by source module - flows cross modules.
|
|
109
|
+
- Default to one test file per module under test; split a large file by scenario, never by arbitrary size cuts.
|
|
110
|
+
- Keep the test runner's discovery config (`testMatch`, `testpaths`, includes) in sync with the layout; a test the runner cannot find is dead code.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Naming Conventions
|
|
115
|
+
|
|
116
|
+
- Test file name = module under test + the framework's suffix: `parser.test.ts`, `test_parser.py`, `parser_test.go`.
|
|
117
|
+
- When splitting by scenario, encode the scenario in the name: `parser.errors.test.ts`, `auth.session-expiry.test.ts`.
|
|
118
|
+
- Suite and case names describe behavior, not implementation - name tests by the behavior they verify, consistent with the Test Quality Rules above.
|
|
119
|
+
- Forbidden names: `test1`, `misc`, `temp`, `new`, `utils-tests`, or any name that does not identify what is verified.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Shared Test Code Placement
|
|
124
|
+
|
|
125
|
+
- Fixtures (static data), factories/builders (object construction), helpers (setup/assertion logic), and fakes each get their own folder; do not mix them in one grab-bag file.
|
|
126
|
+
- Place shared test code at the narrowest scope that covers its users; promote it upward only when a second consumer appears (same reuse rule as production code).
|
|
127
|
+
- Test helpers are production code: deduplicate, name well, and review them like any other module.
|
|
128
|
+
- Never import from another test file; extract the shared piece into a helper module instead.
|
|
129
|
+
- Large fixtures live as data files under `fixtures/`, named after the scenario they encode, not inlined into test bodies.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Scaling & Maintenance
|
|
134
|
+
|
|
135
|
+
- When adding a test to an existing flat or misplaced suite: place the new test correctly and surface the layout debt; do not extend the mess to match it.
|
|
136
|
+
- Layout migrations are pure moves: never change test logic in the same commit as a file move (atomic-commit rule in git-policy).
|
|
137
|
+
- After any move, run the affected suite to prove discovery still works, and delete emptied folders.
|
|
138
|
+
- If two layouts coexist after a partial migration, finish the migration or document the boundary; a half-migrated suite is worse than either layout.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Decision Rule: Colocated vs Centralized
|
|
143
|
+
|
|
144
|
+
- Colocated tests (next to source) fit unit tests in ecosystems that idiomatically support it (JS/TS, Go, Rust) - shortest navigation distance, moves with the code.
|
|
145
|
+
- A centralized `tests/` tree fits integration/e2e tests, packages that must exclude tests from the published artifact, and ecosystems whose tooling expects it (Python, Java).
|
|
146
|
+
- Mixing is fine when each side follows its rule (e.g. colocated unit + centralized integration); mixing within the same test type is not.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
73
150
|
## Reporting
|
|
74
151
|
|
|
75
152
|
- State plainly what was tested and the actual result.
|
|
76
153
|
- If tests fail, report the failure with output; do not claim success.
|
|
77
|
-
- If testing was skipped or partial, say so explicitly and why.
|
|
154
|
+
- If testing was skipped or partial, say so explicitly and why.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testing-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
|
-
"description": "Test strategy, coverage gates, deterministic tests, mocking discipline,
|
|
6
|
-
"cliVersion": "1.15.
|
|
7
|
-
"sha": "
|
|
5
|
+
"description": "Test strategy, coverage gates, deterministic tests, mocking discipline, regression-first bug fixing, and test-suite organization (layout by type/domain, mirrored paths, file naming, fixture/helper placement).",
|
|
6
|
+
"cliVersion": "1.15.3",
|
|
7
|
+
"sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Strict frontend + backend schema validation, schema consistency, and safe client-facing error handling.",
|
|
6
|
-
"cliVersion": "1.15.
|
|
6
|
+
"cliVersion": "1.15.3",
|
|
7
7
|
"sha": "a33622a2f810ee4cea39824cb1a7ca34b355a917d4224025df50d77dd74f0b3a"
|
|
8
8
|
}
|
package/bin/checksums.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enigma-darwin-arm64": "
|
|
3
|
-
"enigma-linux-arm64": "
|
|
4
|
-
"enigma-linux-x64": "
|
|
5
|
-
"enigma-win32-x64.exe": "
|
|
2
|
+
"enigma-darwin-arm64": "88d759633379d158070176c01ddd029f14af5806179c1d96347f4eef701733fe",
|
|
3
|
+
"enigma-linux-arm64": "6cae5efcc4da741d67c184a02f09d3137e182882f4457b0ec99e50cc204799f0",
|
|
4
|
+
"enigma-linux-x64": "9a1546028ff7fd826beaa05b13cf1ccf68dbd6635d5c0471c7f338cdffe7af4b",
|
|
5
|
+
"enigma-win32-x64.exe": "3c5f15b10d2612352a009bf3eecc6bdc114de0aa229ec8fb0ec2cc16b172b0d0"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.3",
|
|
4
4
|
"description": "Everything you need to work with a coding agent: install shared policy skills for Claude Code, OpenAI Codex and opencode, and set up portable git security hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: test-organization-policy
|
|
3
|
-
description: Expert test-suite organization - directory structure by test type and domain, mirrored source paths, file naming conventions, and fixture/helper/factory placement. Use when creating, moving, or renaming test files, scaffolding or restructuring a test suite, or deciding where a new test lives - never dump tests flat into a single tests/ folder.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Test Organization Policy (Senior Engineering Standards)
|
|
7
|
-
|
|
8
|
-
## Activation Scope
|
|
9
|
-
|
|
10
|
-
- Apply whenever a test file is created, moved, or renamed, and whenever a test suite is scaffolded or restructured.
|
|
11
|
-
- Owns test-suite layout: directory structure, subfolders, file naming, and placement of fixtures, helpers, factories, and mocks.
|
|
12
|
-
- Test strategy, coverage, determinism, and mocking discipline live in testing-policy; compose with it, never restate it.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Core Principle
|
|
17
|
-
|
|
18
|
-
- A test suite is navigable code: anyone must find the tests for a module in seconds from the module's path alone, and find the module from its test's path.
|
|
19
|
-
- Never dump test files flat into a single tests/ folder. Flat suites hide coverage gaps, cause name collisions, and stop scaling past a handful of files.
|
|
20
|
-
- Organize from the first test file. Structure is cheapest at file creation and most expensive after the folder is a mess.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Framework Convention First
|
|
25
|
-
|
|
26
|
-
- Every ecosystem has an established convention; it beats any custom layout:
|
|
27
|
-
- JS/TS (Vitest, Jest, Bun): colocated `*.test.ts` next to the source file, a `__tests__/` folder per directory, or a mirrored `tests/` tree - follow whichever the repo already uses.
|
|
28
|
-
- Python (pytest): a `tests/` package mirroring the source package; shared fixtures in `conftest.py` at the narrowest directory that covers their users.
|
|
29
|
-
- Go: `_test.go` colocated in the same package (mandated by the toolchain); black-box tests use the `_test` package suffix.
|
|
30
|
-
- Rust: unit tests in `#[cfg(test)] mod tests` inside the module; integration tests as separate files under the crate-root `tests/`.
|
|
31
|
-
- Java/Kotlin: `src/test/<lang>/` mirroring the `src/main/<lang>/` package path exactly.
|
|
32
|
-
- In an existing repo, detect the established layout and extend it; never introduce a second competing layout. Migrations to a better layout are proposed explicitly, not done by stealth.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Structure by Test Type, Then by Domain
|
|
37
|
-
|
|
38
|
-
- When more than one test type exists, separate types at the top level - they differ in speed, dependencies, and CI stage:
|
|
39
|
-
|
|
40
|
-
```text
|
|
41
|
-
tests/
|
|
42
|
-
unit/ fast, isolated, no I/O
|
|
43
|
-
integration/ module boundaries, DB, contracts
|
|
44
|
-
e2e/ critical user flows only
|
|
45
|
-
fixtures/ shared static data
|
|
46
|
-
helpers/ shared builders, factories, fakes
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
- Inside each type, mirror the source tree: tests for `src/<domain>/<module>` live at `tests/<type>/<domain>/<module>.<suffix>`.
|
|
50
|
-
- Group e2e tests by user flow (e.g. `e2e/checkout/`), not by source module - flows cross modules.
|
|
51
|
-
- Default to one test file per module under test; split a large file by scenario, never by arbitrary size cuts.
|
|
52
|
-
- Keep the test runner's discovery config (`testMatch`, `testpaths`, includes) in sync with the layout; a test the runner cannot find is dead code.
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Naming Conventions
|
|
57
|
-
|
|
58
|
-
- Test file name = module under test + the framework's suffix: `parser.test.ts`, `test_parser.py`, `parser_test.go`.
|
|
59
|
-
- When splitting by scenario, encode the scenario in the name: `parser.errors.test.ts`, `auth.session-expiry.test.ts`.
|
|
60
|
-
- Suite and case names describe behavior, not implementation (naming-by-behavior rules live in testing-policy).
|
|
61
|
-
- Forbidden names: `test1`, `misc`, `temp`, `new`, `utils-tests`, or any name that does not identify what is verified.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Shared Test Code Placement
|
|
66
|
-
|
|
67
|
-
- Fixtures (static data), factories/builders (object construction), helpers (setup/assertion logic), and fakes each get their own folder; do not mix them in one grab-bag file.
|
|
68
|
-
- Place shared test code at the narrowest scope that covers its users; promote it upward only when a second consumer appears (same reuse rule as production code).
|
|
69
|
-
- Test helpers are production code: deduplicate, name well, and review them like any other module.
|
|
70
|
-
- Never import from another test file; extract the shared piece into a helper module instead.
|
|
71
|
-
- Large fixtures live as data files under `fixtures/`, named after the scenario they encode, not inlined into test bodies.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Scaling & Maintenance
|
|
76
|
-
|
|
77
|
-
- When adding a test to an existing flat or misplaced suite: place the new test correctly and surface the layout debt; do not extend the mess to match it.
|
|
78
|
-
- Layout migrations are pure moves: never change test logic in the same commit as a file move (atomic-commit rule in git-policy).
|
|
79
|
-
- After any move, run the affected suite to prove discovery still works, and delete emptied folders.
|
|
80
|
-
- If two layouts coexist after a partial migration, finish the migration or document the boundary; a half-migrated suite is worse than either layout.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Decision Rule: Colocated vs Centralized
|
|
85
|
-
|
|
86
|
-
- Colocated tests (next to source) fit unit tests in ecosystems that idiomatically support it (JS/TS, Go, Rust) - shortest navigation distance, moves with the code.
|
|
87
|
-
- A centralized `tests/` tree fits integration/e2e tests, packages that must exclude tests from the published artifact, and ecosystems whose tooling expects it (Python, Java).
|
|
88
|
-
- Mixing is fine when each side follows its rule (e.g. colocated unit + centralized integration); mixing within the same test type is not.
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "test-organization-policy",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"provider": "FJRG2007/enigma",
|
|
5
|
-
"description": "Expert test-suite organization: structure by test type and domain, mirrored source paths, naming conventions, and fixture/helper placement.",
|
|
6
|
-
"cliVersion": "1.15.1",
|
|
7
|
-
"sha": "09184beb8e423efd26fce0221cd374d41b6ba0bb3d223d7edce250d9d978767e"
|
|
8
|
-
}
|