claude-turing 4.4.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +4 -4
- package/LICENSE +1 -1
- package/README.md +78 -555
- package/bin/cli.js +23 -4
- package/commands/doctor.md +1 -0
- package/commands/init.md +21 -3
- package/commands/turing.md +85 -77
- package/config/commands.yaml +928 -0
- package/config/defaults.yaml +2 -0
- package/package.json +7 -6
- package/src/command-registry.js +151 -0
- package/src/install.js +24 -35
- package/src/verify.js +45 -88
- package/templates/README.md +1 -1
- package/templates/__pycache__/evaluate.cpython-312.pyc +0 -0
- package/templates/__pycache__/prepare.cpython-312.pyc +0 -0
- package/templates/config.yaml +1 -1
- package/templates/features/__pycache__/__init__.cpython-312.pyc +0 -0
- package/templates/features/__pycache__/featurizers.cpython-312.pyc +0 -0
- package/templates/program.md +1 -1
- package/templates/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/ablation_study.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/architecture_surgery.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/budget_manager.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/build_ensemble.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/calibration.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/check_convergence.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/checkpoint_manager.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/citation_manager.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/cost_frontier.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/counterfactual_explanation.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/critique_hypothesis.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/curriculum_optimizer.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/diagnose_errors.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/draft_paper_sections.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/equivalence_checker.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_annotations.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_archive.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_diff.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_index.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_queue.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_replay.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_search.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_simulator.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/experiment_templates.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/export_card.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/export_formats.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/failure_postmortem.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/feature_intelligence.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/fork_experiment.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_baselines.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_brief.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_changelog.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_figures.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_logbook.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_model_card.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/generate_onboarding.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/harness_doctor.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/harness_doctor.cpython-314.pyc +0 -0
- package/templates/scripts/__pycache__/incremental_update.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/knowledge_transfer.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/latency_benchmark.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/leakage_detector.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/literature_search.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/log_experiment.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/manage_hypotheses.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/methodology_audit.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/model_distiller.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/model_lifecycle.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/model_merger.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/model_pruning.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/model_quantization.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/model_xray.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/novelty_guard.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/package_experiments.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/pareto_frontier.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/parse_metrics.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/pipeline_manager.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/profile_training.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/regression_gate.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/reproduce_experiment.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/research_planner.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/sanity_checks.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/scaffold.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/templates/scripts/__pycache__/scaling_estimator.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/seed_runner.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/sensitivity_analysis.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/session_flashback.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/show_experiment_tree.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/show_families.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/simulate_review.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/smart_retry.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/statistical_compare.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/suggest_next.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/sweep.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/synthesize_decision.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/training_monitor.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/treequest_suggest.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/trend_analysis.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/turing_io.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/update_state.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/verify_placeholders.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/warm_start.cpython-312.pyc +0 -0
- package/templates/scripts/__pycache__/whatif_engine.cpython-312.pyc +0 -0
- package/templates/scripts/harness_doctor.py +145 -1
- package/templates/scripts/scaffold.py +50 -28
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -32,7 +32,7 @@ REQUIRED_SCRIPTS = ["train.py", "prepare.py", "evaluate.py"]
|
|
|
32
32
|
REQUIRED_CONFIG_FIELDS = ["evaluation"]
|
|
33
33
|
|
|
34
34
|
CHECK_CATEGORIES = ["environment", "dependencies", "config", "experiment_log",
|
|
35
|
-
"scripts", "disk_space", "git_state"]
|
|
35
|
+
"scripts", "disk_space", "git_state", "claude_hooks"]
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
# --- Individual Checks ---
|
|
@@ -288,9 +288,136 @@ def check_git_state(project_dir: str = ".") -> dict:
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
def check_claude_hooks(settings_path: str = ".claude/settings.local.json") -> dict:
|
|
292
|
+
"""Check Claude Code project hook schema."""
|
|
293
|
+
path = Path(settings_path)
|
|
294
|
+
if not path.exists():
|
|
295
|
+
return {
|
|
296
|
+
"name": "Claude hooks",
|
|
297
|
+
"status": "WARN",
|
|
298
|
+
"detail": f"{settings_path} not found",
|
|
299
|
+
"issues": [],
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
settings = json.loads(path.read_text(encoding="utf-8"))
|
|
304
|
+
except json.JSONDecodeError as e:
|
|
305
|
+
return {
|
|
306
|
+
"name": "Claude hooks",
|
|
307
|
+
"status": "FAIL",
|
|
308
|
+
"detail": f"{settings_path} has JSON parse error",
|
|
309
|
+
"issues": [str(e)],
|
|
310
|
+
"fixable": False,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
hooks = settings.get("hooks", {})
|
|
314
|
+
if not isinstance(hooks, dict):
|
|
315
|
+
return {
|
|
316
|
+
"name": "Claude hooks",
|
|
317
|
+
"status": "FAIL",
|
|
318
|
+
"detail": "hooks must be a JSON object",
|
|
319
|
+
"issues": ["hooks must map event names to hook group arrays"],
|
|
320
|
+
"fixable": False,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
issues = []
|
|
324
|
+
fixable = False
|
|
325
|
+
group_count = 0
|
|
326
|
+
|
|
327
|
+
for event, entries in hooks.items():
|
|
328
|
+
if not isinstance(entries, list):
|
|
329
|
+
issues.append(f"hooks.{event} must be an array")
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
for index, entry in enumerate(entries):
|
|
333
|
+
if not isinstance(entry, dict):
|
|
334
|
+
issues.append(f"hooks.{event}[{index}] must be an object")
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
if (
|
|
338
|
+
set(entry.keys()) == {"type", "command"}
|
|
339
|
+
and entry["type"] == "command"
|
|
340
|
+
and isinstance(entry["command"], str)
|
|
341
|
+
):
|
|
342
|
+
issues.append(f"hooks.{event}[{index}] uses legacy bare command hook shape")
|
|
343
|
+
fixable = True
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
nested_hooks = entry.get("hooks")
|
|
347
|
+
if not isinstance(nested_hooks, list):
|
|
348
|
+
issues.append(f"hooks.{event}[{index}].hooks must be an array")
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
group_count += 1
|
|
352
|
+
for hook_index, hook in enumerate(nested_hooks):
|
|
353
|
+
if not isinstance(hook, dict):
|
|
354
|
+
issues.append(f"hooks.{event}[{index}].hooks[{hook_index}] must be an object")
|
|
355
|
+
continue
|
|
356
|
+
if hook.get("type") == "command" and not hook.get("command"):
|
|
357
|
+
issues.append(f"hooks.{event}[{index}].hooks[{hook_index}] command hook missing command")
|
|
358
|
+
|
|
359
|
+
status = "FAIL" if issues else "PASS"
|
|
360
|
+
result = {
|
|
361
|
+
"name": "Claude hooks",
|
|
362
|
+
"status": status,
|
|
363
|
+
"detail": f"{group_count} hook groups valid" if not issues else f"{len(issues)} hook schema issue(s)",
|
|
364
|
+
"issues": issues,
|
|
365
|
+
"fixable": fixable,
|
|
366
|
+
}
|
|
367
|
+
if fixable:
|
|
368
|
+
result["fix"] = "Run /turing:doctor --fix to migrate legacy bare command hooks"
|
|
369
|
+
return result
|
|
370
|
+
|
|
371
|
+
|
|
291
372
|
# --- Fix Operations ---
|
|
292
373
|
|
|
293
374
|
|
|
375
|
+
def fix_claude_hooks(settings_path: str = ".claude/settings.local.json") -> dict:
|
|
376
|
+
"""Migrate legacy bare command hooks into Claude Code hook groups."""
|
|
377
|
+
path = Path(settings_path)
|
|
378
|
+
if not path.exists():
|
|
379
|
+
return {"fixed": False, "reason": "Settings file not found"}
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
settings = json.loads(path.read_text(encoding="utf-8"))
|
|
383
|
+
except json.JSONDecodeError:
|
|
384
|
+
return {"fixed": False, "reason": "Settings file has invalid JSON"}
|
|
385
|
+
|
|
386
|
+
hooks = settings.get("hooks")
|
|
387
|
+
if not isinstance(hooks, dict):
|
|
388
|
+
return {"fixed": False, "reason": "hooks is not a JSON object"}
|
|
389
|
+
|
|
390
|
+
migrated = 0
|
|
391
|
+
for event, entries in hooks.items():
|
|
392
|
+
if not isinstance(entries, list):
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
fixed_entries = []
|
|
396
|
+
for entry in entries:
|
|
397
|
+
if (
|
|
398
|
+
isinstance(entry, dict)
|
|
399
|
+
and set(entry.keys()) == {"type", "command"}
|
|
400
|
+
and entry["type"] == "command"
|
|
401
|
+
and isinstance(entry["command"], str)
|
|
402
|
+
):
|
|
403
|
+
fixed_entries.append({
|
|
404
|
+
"matcher": "",
|
|
405
|
+
"hooks": [{"type": "command", "command": entry["command"]}],
|
|
406
|
+
})
|
|
407
|
+
migrated += 1
|
|
408
|
+
else:
|
|
409
|
+
fixed_entries.append(entry)
|
|
410
|
+
hooks[event] = fixed_entries
|
|
411
|
+
|
|
412
|
+
if not migrated:
|
|
413
|
+
return {"fixed": False, "reason": "No legacy bare command hooks found"}
|
|
414
|
+
|
|
415
|
+
backup = path.with_suffix(path.suffix + ".bak")
|
|
416
|
+
shutil.copy2(path, backup)
|
|
417
|
+
path.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
|
|
418
|
+
return {"fixed": True, "migrated": migrated, "backup": str(backup)}
|
|
419
|
+
|
|
420
|
+
|
|
294
421
|
def fix_corrupt_log(log_path: str = DEFAULT_LOG_PATH) -> dict:
|
|
295
422
|
"""Remove corrupt lines from experiment log."""
|
|
296
423
|
path = Path(log_path)
|
|
@@ -328,6 +455,7 @@ def fix_corrupt_log(log_path: str = DEFAULT_LOG_PATH) -> dict:
|
|
|
328
455
|
def run_doctor(
|
|
329
456
|
config_path: str = "config.yaml",
|
|
330
457
|
log_path: str = DEFAULT_LOG_PATH,
|
|
458
|
+
hooks_path: str = ".claude/settings.local.json",
|
|
331
459
|
fix: bool = False,
|
|
332
460
|
verbose: bool = False,
|
|
333
461
|
) -> dict:
|
|
@@ -336,6 +464,7 @@ def run_doctor(
|
|
|
336
464
|
Args:
|
|
337
465
|
config_path: Path to config.yaml.
|
|
338
466
|
log_path: Path to experiment log.
|
|
467
|
+
hooks_path: Path to Claude Code project settings.
|
|
339
468
|
fix: If True, auto-fix safe issues.
|
|
340
469
|
verbose: Include detailed info.
|
|
341
470
|
|
|
@@ -350,6 +479,7 @@ def run_doctor(
|
|
|
350
479
|
check_scripts(),
|
|
351
480
|
check_disk_space(),
|
|
352
481
|
check_git_state(),
|
|
482
|
+
check_claude_hooks(hooks_path),
|
|
353
483
|
]
|
|
354
484
|
|
|
355
485
|
# Apply fixes if requested
|
|
@@ -366,6 +496,18 @@ def run_doctor(
|
|
|
366
496
|
checks[i] = check_experiment_log(log_path)
|
|
367
497
|
break
|
|
368
498
|
|
|
499
|
+
hooks_check = next((c for c in checks if c["name"] == "Claude hooks"), None)
|
|
500
|
+
if hooks_check and hooks_check.get("fixable"):
|
|
501
|
+
fix_result = fix_claude_hooks(hooks_path)
|
|
502
|
+
if fix_result.get("fixed"):
|
|
503
|
+
fixes_applied.append(
|
|
504
|
+
f"Migrated {fix_result['migrated']} legacy Claude hook entries (backup: {fix_result['backup']})"
|
|
505
|
+
)
|
|
506
|
+
for i, c in enumerate(checks):
|
|
507
|
+
if c["name"] == "Claude hooks":
|
|
508
|
+
checks[i] = check_claude_hooks(hooks_path)
|
|
509
|
+
break
|
|
510
|
+
|
|
369
511
|
# Compute score
|
|
370
512
|
passed = sum(1 for c in checks if c["status"] == "PASS")
|
|
371
513
|
warned = sum(1 for c in checks if c["status"] == "WARN")
|
|
@@ -441,6 +583,7 @@ def main():
|
|
|
441
583
|
parser.add_argument("--verbose", action="store_true", help="Show detailed info")
|
|
442
584
|
parser.add_argument("--config", default="config.yaml", help="Path to config.yaml")
|
|
443
585
|
parser.add_argument("--log", default=DEFAULT_LOG_PATH, help="Path to experiment log")
|
|
586
|
+
parser.add_argument("--hooks", default=".claude/settings.local.json", help="Path to Claude Code settings")
|
|
444
587
|
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
445
588
|
|
|
446
589
|
args = parser.parse_args()
|
|
@@ -448,6 +591,7 @@ def main():
|
|
|
448
591
|
report = run_doctor(
|
|
449
592
|
config_path=args.config,
|
|
450
593
|
log_path=args.log,
|
|
594
|
+
hooks_path=args.hooks,
|
|
451
595
|
fix=args.fix,
|
|
452
596
|
verbose=args.verbose,
|
|
453
597
|
)
|
|
@@ -34,6 +34,8 @@ PLACEHOLDER_MAP = {
|
|
|
34
34
|
"ML_DIR": "ml_dir",
|
|
35
35
|
"DATA_SOURCE": "data_source",
|
|
36
36
|
"METRIC_DIRECTION": "metric_direction",
|
|
37
|
+
"LOWER_IS_BETTER": "lower_is_better",
|
|
38
|
+
"MEMORY_DIR_NAME": "memory_dir_name",
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
# Files to copy from templates/ to the ML directory
|
|
@@ -223,32 +225,49 @@ SHELL_SCRIPTS = [
|
|
|
223
225
|
|
|
224
226
|
def find_templates_dir() -> Path | None:
|
|
225
227
|
"""Locate the templates directory relative to this script or plugin root."""
|
|
226
|
-
|
|
228
|
+
env_templates_dir = os.environ.get("TURING_TEMPLATES_DIR")
|
|
229
|
+
if env_templates_dir:
|
|
230
|
+
candidate = Path(env_templates_dir).expanduser()
|
|
231
|
+
if (candidate / "prepare.py").exists():
|
|
232
|
+
return candidate
|
|
233
|
+
|
|
227
234
|
script_dir = Path(__file__).parent
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
# Search common plugin locations
|
|
241
|
-
home = Path.home()
|
|
242
|
-
for pattern in [
|
|
243
|
-
home / ".claude" / "plugins" / "*" / "templates",
|
|
236
|
+
project_command_templates = [
|
|
237
|
+
path / ".claude" / "commands" / "turing" / "templates"
|
|
238
|
+
for path in [Path.cwd(), *Path.cwd().parents]
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
for candidate in [
|
|
242
|
+
script_dir.parent,
|
|
243
|
+
script_dir.parent.parent / "templates",
|
|
244
|
+
*project_command_templates,
|
|
245
|
+
Path.home() / ".claude" / "commands" / "turing" / "templates",
|
|
246
|
+
Path.cwd() / "node_modules" / "claude-turing" / "templates",
|
|
244
247
|
]:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
+
if (candidate / "prepare.py").exists():
|
|
249
|
+
return candidate
|
|
250
|
+
|
|
251
|
+
plugins_dir = Path.home() / ".claude" / "plugins"
|
|
252
|
+
for match in sorted(plugins_dir.glob("*/templates")):
|
|
253
|
+
if (match / "prepare.py").exists():
|
|
254
|
+
return match
|
|
248
255
|
|
|
249
256
|
return None
|
|
250
257
|
|
|
251
258
|
|
|
259
|
+
def derive_values(values: dict[str, str]) -> dict[str, str]:
|
|
260
|
+
"""Add scaffold values derived from user-provided fields."""
|
|
261
|
+
derived = dict(values)
|
|
262
|
+
derived["lower_is_better"] = (
|
|
263
|
+
"true" if derived.get("metric_direction", "").lower() == "lower" else "false"
|
|
264
|
+
)
|
|
265
|
+
derived["memory_dir_name"] = re.sub(
|
|
266
|
+
r"[^a-zA-Z0-9_.-]+", "-", derived["project_name"]
|
|
267
|
+
).strip("-")
|
|
268
|
+
return derived
|
|
269
|
+
|
|
270
|
+
|
|
252
271
|
def replace_placeholders(text: str, values: dict[str, str]) -> str:
|
|
253
272
|
"""Replace all {{PLACEHOLDER}} markers in text with values."""
|
|
254
273
|
for placeholder, arg_name in PLACEHOLDER_MAP.items():
|
|
@@ -276,6 +295,7 @@ def scaffold_project(
|
|
|
276
295
|
Returns:
|
|
277
296
|
Dict with counts: files_copied, placeholders_replaced, dirs_created.
|
|
278
297
|
"""
|
|
298
|
+
values = derive_values(values)
|
|
279
299
|
target = Path(ml_dir)
|
|
280
300
|
target.mkdir(parents=True, exist_ok=True)
|
|
281
301
|
|
|
@@ -329,7 +349,7 @@ def scaffold_project(
|
|
|
329
349
|
continue
|
|
330
350
|
|
|
331
351
|
# Setup agent memory
|
|
332
|
-
memory_dir = Path(".claude") / "agent-memory" / "ml-researcher"
|
|
352
|
+
memory_dir = Path(".claude") / "agent-memory" / f"ml-researcher-{values['memory_dir_name']}"
|
|
333
353
|
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
334
354
|
memory_src = templates_dir / "MEMORY.md"
|
|
335
355
|
if memory_src.exists():
|
|
@@ -348,6 +368,14 @@ def scaffold_project(
|
|
|
348
368
|
return stats
|
|
349
369
|
|
|
350
370
|
|
|
371
|
+
def make_command_hook_group(command: str, matcher: str = "") -> dict:
|
|
372
|
+
"""Build a Claude Code command hook group."""
|
|
373
|
+
return {
|
|
374
|
+
"matcher": matcher,
|
|
375
|
+
"hooks": [{"type": "command", "command": command}],
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
351
379
|
def _setup_hooks(ml_dir: str) -> None:
|
|
352
380
|
"""Configure Claude Code hooks in .claude/settings.local.json."""
|
|
353
381
|
settings_path = Path(".claude") / "settings.local.json"
|
|
@@ -366,20 +394,14 @@ def _setup_hooks(ml_dir: str) -> None:
|
|
|
366
394
|
post_hooks = hooks.get("PostToolUse", [])
|
|
367
395
|
post_hook_cmd = f"bash {ml_dir}/scripts/post-train-hook.sh"
|
|
368
396
|
if not any(post_hook_cmd in str(h) for h in post_hooks):
|
|
369
|
-
post_hooks.append(
|
|
370
|
-
"matcher": "Bash",
|
|
371
|
-
"hooks": [{"type": "command", "command": post_hook_cmd}],
|
|
372
|
-
})
|
|
397
|
+
post_hooks.append(make_command_hook_group(post_hook_cmd, matcher="Bash"))
|
|
373
398
|
hooks["PostToolUse"] = post_hooks
|
|
374
399
|
|
|
375
400
|
# Stop hook for convergence
|
|
376
401
|
stop_hooks = hooks.get("Stop", [])
|
|
377
402
|
stop_hook_cmd = f"bash {ml_dir}/scripts/stop-hook.sh"
|
|
378
403
|
if not any(stop_hook_cmd in str(h) for h in stop_hooks):
|
|
379
|
-
stop_hooks.append(
|
|
380
|
-
"type": "command",
|
|
381
|
-
"command": stop_hook_cmd,
|
|
382
|
-
})
|
|
404
|
+
stop_hooks.append(make_command_hook_group(stop_hook_cmd))
|
|
383
405
|
hooks["Stop"] = stop_hooks
|
|
384
406
|
|
|
385
407
|
settings["hooks"] = hooks
|