xtrm-tools 0.7.17 → 0.7.19

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 (57) hide show
  1. package/.xtrm/config/hooks.json +2 -0
  2. package/.xtrm/config/instructions/agents-top.md +2 -1
  3. package/.xtrm/registry.json +429 -712
  4. package/.xtrm/skills/default/creating-service-skills/scripts/bootstrap.py +82 -156
  5. package/.xtrm/skills/default/creating-service-skills/scripts/scaffolder.py +73 -121
  6. package/.xtrm/skills/default/hook-development/references/patterns.md +1 -1
  7. package/.xtrm/skills/default/last30days/scripts/test-v1-vs-v2.sh +2 -2
  8. package/.xtrm/skills/default/planning/SKILL.md +75 -29
  9. package/.xtrm/skills/default/releasing/SKILL.md +163 -57
  10. package/.xtrm/skills/default/security-pipeline/SKILL.md +192 -0
  11. package/.xtrm/skills/default/security-pipeline/scripts/security-bootstrap.sh +294 -0
  12. package/.xtrm/skills/default/security-pipeline/templates/.githooks/pre-push.template +39 -0
  13. package/.xtrm/skills/default/security-pipeline/templates/.github/workflows/gitleaks.yml +33 -0
  14. package/.xtrm/skills/default/security-pipeline/templates/.github/workflows/osv-scanner.yml +33 -0
  15. package/.xtrm/skills/default/security-pipeline/templates/.github/workflows/semgrep.yml +41 -0
  16. package/.xtrm/skills/default/security-pipeline/templates/.gitleaks.toml +44 -0
  17. package/.xtrm/skills/default/security-pipeline/templates/.pre-commit-config.yaml +67 -0
  18. package/.xtrm/skills/default/security-pipeline/templates/.semgrepignore +46 -0
  19. package/.xtrm/skills/default/security-pipeline/templates/scripts/security-scan.sh +57 -0
  20. package/.xtrm/skills/default/security-pipeline/templates/scripts/semgrep-diff.sh +68 -0
  21. package/.xtrm/skills/default/session-close-report/SKILL.md +167 -6
  22. package/.xtrm/skills/default/sync-docs/SKILL.md +1 -1
  23. package/.xtrm/skills/default/update-xt/SKILL.md +270 -4
  24. package/.xtrm/skills/default/updating-service-skills/scripts/drift_detector.py +22 -0
  25. package/.xtrm/skills/default/using-script-specialists/SKILL.md +7 -5
  26. package/.xtrm/skills/default/using-specialists/SKILL.md +13 -12
  27. package/.xtrm/skills/default/using-specialists-auto/SKILL.md +137 -0
  28. package/.xtrm/skills/default/using-specialists-v2/SKILL.md +14 -21
  29. package/.xtrm/skills/default/using-specialists-v3/SKILL.md +533 -21
  30. package/.xtrm/skills/default/vaultctl/SKILL.md +2 -2
  31. package/CHANGELOG.md +87 -3
  32. package/cli/dist/index.cjs +12429 -3769
  33. package/cli/dist/index.cjs.map +1 -1
  34. package/cli/package.json +9 -3
  35. package/package.json +27 -7
  36. package/packages/pi-extensions/package.json +1 -1
  37. package/.xtrm/skills/default/planning/evals/evals.json +0 -19
  38. package/.xtrm/skills/default/quality-gates/evals/evals.json +0 -181
  39. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/FINAL-EVAL-SUMMARY.md +0 -75
  40. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/edge-case-auto-fix-verification/with_skill/outputs/response.md +0 -59
  41. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/edge-case-mixed-language-project/with_skill/outputs/response.md +0 -60
  42. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/eval-summary.md +0 -105
  43. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/partial-install-python-only/with_skill/outputs/response.md +0 -93
  44. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/python-refactor-request/with_skill/outputs/response.md +0 -104
  45. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/quality-gate-error-fix/with_skill/outputs/response.md +0 -74
  46. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/should-not-trigger-general-chat/with_skill/outputs/response.md +0 -18
  47. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/should-not-trigger-math-question/with_skill/outputs/response.md +0 -18
  48. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/should-not-trigger-unrelated-coding/with_skill/outputs/response.md +0 -56
  49. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/tdd-guard-blocking-confusion/with_skill/outputs/response.md +0 -67
  50. package/.xtrm/skills/default/quality-gates/workspace/iteration-1/typescript-feature-with-tests/with_skill/outputs/response.md +0 -97
  51. package/.xtrm/skills/default/sync-docs/evals/evals.json +0 -89
  52. package/.xtrm/skills/default/test-planning/evals/evals.json +0 -23
  53. package/.xtrm/skills/default/using-specialists/SKILL.safe.md +0 -1082
  54. package/.xtrm/skills/default/using-specialists/SKILL.ultra.md +0 -1082
  55. package/.xtrm/skills/default/using-specialists/evals/evals.json +0 -68
  56. package/.xtrm/skills/default/using-specialists-v3/evals/evals.json +0 -89
  57. package/packages/pi-extensions/.serena/project.yml +0 -130
@@ -6,23 +6,22 @@ Phase 1 of the two-phase workflow: generates a structural skeleton for a new
6
6
  service skill by parsing docker-compose.yml, Dockerfiles, and dependency files.
7
7
  The skeleton contains [PENDING RESEARCH] markers for the agent to fill in Phase 2.
8
8
 
9
- Output location: .claude/skills/<service-id>/
9
+ Output location: .xtrm/skills/user/packs/<pack>/<service-id>/
10
10
  """
11
11
 
12
+ import re
13
+ import shutil
12
14
  import sys
13
15
  from pathlib import Path
14
16
 
15
- # Resolve bootstrap from this script's directory
16
17
  script_dir = Path(__file__).parent
17
18
  sys.path.insert(0, str(script_dir))
18
19
 
19
- from bootstrap import RootResolutionError, get_project_root, register_service # noqa: E402
20
+ from bootstrap import RootResolutionError, get_pack_path, get_project_root, register_service # noqa: E402
21
+
22
+ SERVICE_ID_PATTERN = re.compile(r"^[a-z0-9][a-z0-9-_]{0,63}$")
20
23
 
21
- # ---------------------------------------------------------------------------
22
- # Official documentation map — populated from detected technologies
23
- # ---------------------------------------------------------------------------
24
24
  OFFICIAL_DOCS: dict[str, tuple[str, str]] = {
25
- # Docker images / databases
26
25
  "postgres": ("PostgreSQL", "https://www.postgresql.org/docs/"),
27
26
  "timescale": ("TimescaleDB", "https://docs.timescale.com/"),
28
27
  "timescaledb": ("TimescaleDB", "https://docs.timescale.com/"),
@@ -34,7 +33,6 @@ OFFICIAL_DOCS: dict[str, tuple[str, str]] = {
34
33
  "rabbitmq": ("RabbitMQ", "https://www.rabbitmq.com/documentation.html"),
35
34
  "kafka": ("Apache Kafka", "https://kafka.apache.org/documentation/"),
36
35
  "clickhouse": ("ClickHouse", "https://clickhouse.com/docs/"),
37
- # Python packages
38
36
  "fastapi": ("FastAPI", "https://fastapi.tiangolo.com/"),
39
37
  "flask": ("Flask", "https://flask.palletsprojects.com/"),
40
38
  "django": ("Django", "https://docs.djangoproject.com/"),
@@ -51,19 +49,48 @@ OFFICIAL_DOCS: dict[str, tuple[str, str]] = {
51
49
  }
52
50
 
53
51
 
52
+ def validate_service_id(service_id: str) -> None:
53
+ if not SERVICE_ID_PATTERN.fullmatch(service_id):
54
+ raise ValueError("service_id must match ^[a-z0-9][a-z0-9-_]{0,63}$")
55
+
56
+
57
+ def ensure_legacy_symlink(target_dir: Path, legacy_dir: Path, pack_root: Path) -> None:
58
+ resolved_target = target_dir.resolve(strict=False)
59
+ resolved_pack_root = pack_root.resolve(strict=False)
60
+
61
+ if resolved_pack_root not in resolved_target.parents and resolved_target != resolved_pack_root:
62
+ raise ValueError(f"legacy symlink target must stay within {resolved_pack_root}")
63
+
64
+ if legacy_dir.is_symlink():
65
+ legacy_dir.unlink()
66
+ elif legacy_dir.exists():
67
+ if legacy_dir.is_dir() and not any(legacy_dir.iterdir()):
68
+ legacy_dir.rmdir()
69
+ else:
70
+ raise ValueError("legacy path exists and is not an empty symlink-safe directory")
71
+
72
+ legacy_dir.parent.mkdir(parents=True, exist_ok=True)
73
+ legacy_dir.symlink_to(target_dir, target_is_directory=True)
74
+
75
+
54
76
  def scaffold_service_skill(service_id: str, compose_data: dict) -> Path:
55
- """
56
- Main entry point for Phase 1 scaffolding.
57
- """
77
+ validate_service_id(service_id)
58
78
  try:
59
79
  project_root = get_project_root()
60
80
  except RootResolutionError as e:
61
81
  print(f"Error: {e}")
62
82
  sys.exit(1)
63
83
 
64
- skill_dir = Path(project_root) / ".claude" / "skills" / service_id
65
- if skill_dir.exists():
66
- print(f"Skill directory already exists: {skill_dir}")
84
+ pack_path = get_pack_path(project_root)
85
+ if pack_path is None:
86
+ print("Error: unable to resolve pack path. Set XTRM_PACK or leave only one pack under .xtrm/skills/user/packs.")
87
+ sys.exit(1)
88
+
89
+ skill_dir = pack_path / service_id
90
+ legacy_dir = Path(project_root) / ".claude" / "skills" / service_id
91
+ if skill_dir.exists() or legacy_dir.exists():
92
+ existing = skill_dir if skill_dir.exists() else legacy_dir
93
+ print(f"Skill directory already exists: {existing}")
67
94
  print("Aborting to prevent overwriting. Delete it manually if you want to re-scaffold.")
68
95
  sys.exit(1)
69
96
 
@@ -75,23 +102,13 @@ def scaffold_service_skill(service_id: str, compose_data: dict) -> Path:
75
102
  (skill_dir / "references").mkdir()
76
103
  (skill_dir / "assets").mkdir()
77
104
 
78
- # Detect service details from compose
79
105
  service_config = compose_data.get("services", {}).get(service_id, {})
80
-
81
- # 1. Generate SKILL.md
82
106
  write_skill_md(service_id, service_config, skill_dir)
83
-
84
- # 2. Generate script stubs
85
107
  write_script_stubs(service_id, skill_dir)
86
-
87
- # 3. Generate reference stubs
88
108
  write_reference_stubs(service_id, skill_dir)
89
109
 
90
- # 4. Register service in bootstrap state
91
- # [TODO] Fill in territory and name properly
92
- register_service(
93
- service_id, service_id, [], str((skill_dir / "SKILL.md").relative_to(project_root))
94
- )
110
+ register_service(service_id, service_id, [], str((skill_dir / "SKILL.md").relative_to(project_root)), project_root=project_root)
111
+ ensure_legacy_symlink(skill_dir, legacy_dir, pack_path)
95
112
 
96
113
  print(f"\n✅ Phase 1 Complete for {service_id}")
97
114
  print("Next step: Run Phase 2 deep dive for this service.")
@@ -99,13 +116,9 @@ def scaffold_service_skill(service_id: str, compose_data: dict) -> Path:
99
116
 
100
117
 
101
118
  def write_skill_md(service_id: str, config: dict, skill_dir: Path) -> None:
102
- """Generate the root SKILL.md file."""
103
119
  name = service_id.replace("-", " ").replace("_", " ").title()
104
120
  persona = f"{name} Expert"
105
-
106
- # Detect docs to link
107
121
  docs_section = ""
108
- # [PENDING] Implement documentation auto-detection logic here
109
122
 
110
123
  content = f"""---
111
124
  name: {service_id}
@@ -211,13 +224,8 @@ Minimum 5 rows required.
211
224
 
212
225
 
213
226
  def write_script_stubs(service_id: str, skill_dir: Path) -> None:
214
- """
215
- Write Phase 1 script stubs into the skill's scripts/ directory.
216
- """
217
227
  scripts_dir = skill_dir / "scripts"
218
228
  scripts_dir.mkdir(parents=True, exist_ok=True)
219
-
220
- # health_probe.py stub (using replace to avoid f-string escaping hell)
221
229
  health_probe_tpl = '''#!/usr/bin/env python3
222
230
  """Health probe for {{SERVICE_ID}}.
223
231
 
@@ -228,30 +236,21 @@ import subprocess
228
236
  import sys
229
237
 
230
238
  CONTAINER = "{{SERVICE_ID}}"
231
- # [PENDING RESEARCH] Set the actual external-mapped DB port (e.g. 5433 for host, 5432 for container)
232
239
  DB_PORT = 5433
233
- # [PENDING RESEARCH] Set the actual output table(s) and stale thresholds in minutes
234
- STALE_CHECKS: list[dict] = [
235
- # {"table": "table_name", "ts_col": "created_at", "stale_minutes": 10},
236
- ]
240
+ STALE_CHECKS: list[dict] = []
237
241
 
238
242
 
239
243
  def check_container() -> bool:
240
- result = subprocess.run(
241
- ["docker", "inspect", "-f", "{{.State.Running}}", CONTAINER],
242
- capture_output=True, text=True
243
- )
244
+ result = subprocess.run(["docker", "inspect", "-f", "{{.State.Running}}", CONTAINER], capture_output=True, text=True)
244
245
  running = result.stdout.strip() == "true"
245
246
  print(f"Container {CONTAINER}: {'RUNNING' if running else 'STOPPED'}")
246
247
  return running
247
248
 
248
249
 
249
250
  def check_table_freshness() -> bool:
250
- """[PENDING RESEARCH] Query actual output tables with correct stale thresholds."""
251
251
  if not STALE_CHECKS:
252
252
  print("Table freshness: NOT CONFIGURED (Phase 2 required)")
253
253
  return True
254
- # [PENDING RESEARCH] Implement actual DB checks here
255
254
  return True
256
255
 
257
256
 
@@ -272,27 +271,17 @@ if __name__ == "__main__":
272
271
  args = p.parse_args()
273
272
  main(as_json=args.json)
274
273
  '''
275
- (scripts_dir / "health_probe.py").write_text(
276
- health_probe_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8"
277
- )
278
-
279
- # log_hunter.py stub
274
+ (scripts_dir / "health_probe.py").write_text(health_probe_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8")
280
275
  log_hunter_tpl = '''#!/usr/bin/env python3
281
276
  """Log hunter for {{SERVICE_ID}}.
282
277
 
283
278
  [PENDING RESEARCH] Replace generic patterns with actual error strings
284
279
  found in the codebase exception handlers during Phase 2 deep dive.
285
280
  """
286
- import json
287
- import re
288
281
  import subprocess
289
- import sys
290
282
  from collections import defaultdict
291
283
 
292
284
  CONTAINER = "{{SERVICE_ID}}"
293
-
294
- # [PENDING RESEARCH] Replace with patterns sourced from the actual codebase.
295
- # Find them with: search_for_pattern("logger.error|raise|panic!")
296
285
  PATTERNS: list[tuple[str, str, str]] = [
297
286
  ("ConnectionError", "ERROR", "Database or Redis connectivity issue"),
298
287
  ("TimeoutError", "WARNING", "External service latency detected"),
@@ -300,19 +289,13 @@ PATTERNS: list[tuple[str, str, str]] = [
300
289
 
301
290
 
302
291
  def hunt_logs(tail: int = 200) -> dict:
303
- """Tails logs and matches against patterns."""
304
- result = subprocess.run(
305
- ["docker", "logs", "--tail", str(tail), CONTAINER],
306
- capture_output=True, text=True
307
- )
292
+ result = subprocess.run(["docker", "logs", "--tail", str(tail), CONTAINER], capture_output=True, text=True)
308
293
  logs = result.stdout + result.stderr
309
294
  matches = defaultdict(int)
310
-
311
295
  for line in logs.splitlines():
312
296
  for pattern, level, desc in PATTERNS:
313
297
  if pattern in line:
314
298
  matches[pattern] += 1
315
-
316
299
  return dict(matches)
317
300
 
318
301
 
@@ -329,30 +312,23 @@ def main() -> None:
329
312
  if __name__ == "__main__":
330
313
  main()
331
314
  '''
332
- (scripts_dir / "log_hunter.py").write_text(
333
- log_hunter_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8"
334
- )
335
-
336
- # data_explorer.py stub
315
+ (scripts_dir / "log_hunter.py").write_text(log_hunter_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8")
337
316
  data_explorer_tpl = '''#!/usr/bin/env python3
338
317
  """Data explorer for {{SERVICE_ID}} — read-only DB inspection.
339
318
 
340
319
  [PENDING RESEARCH] Fill in actual table names, columns, and host port
341
320
  during Phase 2 deep dive. All queries must use parameterized %s placeholders.
342
321
  """
343
- import json
344
322
  import sys
345
323
 
346
- # [PENDING RESEARCH] Set the actual table and connection settings
347
324
  TABLE = "[PENDING RESEARCH]"
348
325
  DB_HOST = "localhost"
349
- DB_PORT = 5433 # [PENDING RESEARCH] external mapped port, not container-internal
326
+ DB_PORT = 5433
350
327
  DB_NAME = "[PENDING RESEARCH]"
351
328
  DB_USER = "postgres"
352
329
 
353
330
 
354
331
  def recent_rows(limit: int = 20, as_json: bool = False) -> None:
355
- """[PENDING RESEARCH] Query most recent rows from the output table."""
356
332
  print(f"[PENDING RESEARCH] Implement: SELECT * FROM {TABLE} ORDER BY created_at DESC LIMIT %s")
357
333
  print("Use parameterized queries only — no f-strings in SQL.")
358
334
 
@@ -369,66 +345,55 @@ def main() -> None:
369
345
  if __name__ == "__main__":
370
346
  main()
371
347
  '''
372
- (scripts_dir / "data_explorer.py").write_text(
373
- data_explorer_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8"
374
- )
375
-
376
- # Makefile — standard diagnostic runner for every skill
348
+ (scripts_dir / "data_explorer.py").write_text(data_explorer_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8")
377
349
  makefile_tpl = """# Skill diagnostic scripts for {{SERVICE_ID}}
378
350
  # Usage: make <target> (from this directory)
379
351
  # Override python: make health PYTHON=/path/to/python3
380
352
 
381
- # Auto-detect: prefer project venv (4 levels up), fall back to system python3
382
353
  _VENV := $(wildcard ../../../../venv/bin/python3)
383
354
  PYTHON ?= $(if $(_VENV),../../../../venv/bin/python3,python3)
384
355
 
385
356
  .PHONY: health health-json data data-json logs errors db help
386
357
 
387
358
  help:
388
- \t@echo "Available targets:"
389
- \t@echo " health - Run health probe (human readable)"
390
- \t@echo " health-json - Run health probe (JSON output)"
391
- \t@echo " data - Show latest DB records"
392
- \t@echo " data-json - Show latest DB records (JSON, limit 5)"
393
- \t@echo " logs - Tail and analyze recent logs"
394
- \t@echo " errors - Show errors/criticals only"
395
- \t@echo " db - Run DB helper example queries"
396
- \t@echo ""
397
- \t@echo "Python: $(PYTHON)"
359
+ @echo "Available targets:"
360
+ @echo " health - Run health probe (human readable)"
361
+ @echo " health-json - Run health probe (JSON output)"
362
+ @echo " data - Show latest DB records"
363
+ @echo " data-json - Show latest DB records (JSON, limit 5)"
364
+ @echo " logs - Tail and analyze recent logs"
365
+ @echo " errors - Show errors/criticals only"
366
+ @echo " db - Run DB helper example queries"
367
+ @echo ""
368
+ @echo "Python: $(PYTHON)"
398
369
 
399
370
  health:
400
- \t$(PYTHON) health_probe.py
371
+ $(PYTHON) health_probe.py
401
372
 
402
373
  health-json:
403
- \t$(PYTHON) health_probe.py --json
374
+ $(PYTHON) health_probe.py --json
404
375
 
405
376
  data:
406
- \t$(PYTHON) data_explorer.py
377
+ $(PYTHON) data_explorer.py
407
378
 
408
379
  data-json:
409
- \t$(PYTHON) data_explorer.py --json --limit 5
380
+ $(PYTHON) data_explorer.py --json --limit 5
410
381
 
411
382
  logs:
412
- \t$(PYTHON) log_hunter.py --tail 50
383
+ $(PYTHON) log_hunter.py --tail 50
413
384
 
414
385
  errors:
415
- \t$(PYTHON) log_hunter.py --errors-only --tail 50
386
+ $(PYTHON) log_hunter.py --errors-only --tail 50
416
387
 
417
388
  db:
418
- \t$(PYTHON) db_helper.py
389
+ $(PYTHON) db_helper.py
419
390
  """
420
- (scripts_dir / "Makefile").write_text(
421
- makefile_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8"
422
- )
391
+ (scripts_dir / "Makefile").write_text(makefile_tpl.replace("{{SERVICE_ID}}", service_id), encoding="utf-8")
423
392
 
424
393
 
425
394
  def write_reference_stubs(service_id: str, skill_dir: Path) -> None:
426
- """Generate reference markdown files."""
427
395
  name = service_id.replace("-", " ").replace("_", " ").title()
428
-
429
- # deep_dive.md
430
- (skill_dir / "references" / "deep_dive.md").write_text(
431
- f"""# Phase 2 Research: {name}
396
+ (skill_dir / "references" / "deep_dive.md").write_text(f"""# Phase 2 Research: {name}
432
397
 
433
398
  ## Source Analysis
434
399
  - **Entry Point**: [FILL]
@@ -442,19 +407,12 @@ def write_reference_stubs(service_id: str, skill_dir: Path) -> None:
442
407
  ## Invariants
443
408
  - [Must always X]
444
409
  - [Must never Y]
445
- """,
446
- encoding="utf-8",
447
- )
448
-
449
- # architecture_ssot.md (stub)
450
- (skill_dir / "references" / "architecture_ssot.md").write_text(
451
- f"""# {name} Architecture
410
+ """, encoding="utf-8")
411
+ (skill_dir / "references" / "architecture_ssot.md").write_text(f"""# {name} Architecture
452
412
 
453
413
  [PENDING RESEARCH] Replace with link to project-level SSOT if exists,
454
414
  otherwise document high-level components here.
455
- """,
456
- encoding="utf-8",
457
- )
415
+ """, encoding="utf-8")
458
416
 
459
417
 
460
418
  if __name__ == "__main__":
@@ -463,20 +421,14 @@ if __name__ == "__main__":
463
421
  if len(sys.argv) < 2:
464
422
  print("Usage: scaffolder.py <docker-compose-path> [service-id]")
465
423
  sys.exit(1)
466
-
467
424
  compose_path = Path(sys.argv[1])
468
425
  if not compose_path.exists():
469
426
  print(f"Compose file not found: {compose_path}")
470
427
  sys.exit(1)
471
-
472
428
  with open(compose_path) as f:
473
429
  data = yaml.safe_load(f)
474
-
475
430
  if len(sys.argv) > 2:
476
- # Scaffold specific service
477
- sid = sys.argv[2]
478
- scaffold_service_skill(sid, data)
431
+ scaffold_service_skill(sys.argv[2], data)
479
432
  else:
480
- # Scaffold all services in compose
481
433
  for sid in data.get("services", {}).keys():
482
434
  scaffold_service_skill(sid, data)
@@ -335,7 +335,7 @@ fi
335
335
  {
336
336
  "strictMode": true,
337
337
  "maxFileSize": 500000,
338
- "allowedPaths": ["/tmp", "/home/user/projects"]
338
+ "allowedPaths": ["/tmp", "$HOME/projects"]
339
339
  }
340
340
  ```
341
341
 
@@ -6,7 +6,7 @@ set -euo pipefail
6
6
  # using `claude --print` to capture real end-to-end output.
7
7
 
8
8
  SKILL_DIR="$HOME/.claude/skills/last30days"
9
- REPO_DIR="/Users/mvanhorn/last30days-skill"
9
+ REPO_DIR="$HOME/last30days-skill"
10
10
 
11
11
  # Safety: always restore V2 SKILL.md on exit/crash
12
12
  cleanup() {
@@ -101,7 +101,7 @@ run_version() {
101
101
 
102
102
  # Run claude --print with the skill invocation
103
103
  # No timeout — claude --print exits on its own; kill manually if stuck
104
- if /Users/mvanhorn/.local/bin/claude --print \
104
+ if "${CLAUDE_BIN:-$(command -v claude)}" --print \
105
105
  "/last30days $query" \
106
106
  > "$outfile" 2>"$errfile"; then
107
107
  local end_time
@@ -182,33 +182,54 @@ under it with `--parent=<existing-epic-id>` and skip creating a new epic.
182
182
 
183
183
  If this is genuinely new work with no parent, create the epic first.
184
184
 
185
+ ### Bead contract format (aligned with `using-specialists-v3`)
186
+
187
+ Planner-created beads use the same 7-section contract that `using-specialists-v3` SKILL.md requires for orchestrator-written beads. Downstream executor / debugger / reviewer / code-sanity / security-auditor specialists read the bead via `bd show <id>` and expect this exact shape. Any drift between this template and the using-specialists-v3 contract creates partial contracts and weakens downstream specialist output.
188
+
189
+ The seven sections — `PROBLEM / SUCCESS / SCOPE / NON_GOALS / CONSTRAINTS / VALIDATION / OUTPUT` — are mandatory for every task and every epic. Optional auxiliary sections (`REFERENCES`, `APPROACH NOTES`) may follow at the bottom.
190
+
185
191
  ### Create the epic (new work only)
186
192
 
187
193
  ```bash
188
194
  bd create \
189
195
  --title="<Feature name — concise verb phrase>" \
190
196
  --description="$(cat <<'EOF'
191
- ## Overview
197
+ ## PROBLEM
198
+
199
+ <2-3 sentences: what user/project problem this epic exists to solve. Why now.>
200
+
201
+ ## SUCCESS
192
202
 
193
- <2-3 sentences: what this is and why it exists>
203
+ <End-state across all child beads. Observable, testable, in prose.>
194
204
 
195
- ## Goals
205
+ ## SCOPE
196
206
 
197
- - Goal 1: measurable outcome
198
- - Goal 2: measurable outcome
207
+ <Area of project affected. Name files, modules, packages, or bounded surfaces. Avoid generic paths like "src/". Cross-cutting epics may list multiple bounded surfaces.>
199
208
 
200
- ## Non-goals
209
+ ## NON_GOALS
201
210
 
202
- - What we are explicitly NOT doing
211
+ - <Explicit boundary 1>
212
+ - <What this epic does NOT include even though tangentially related>
203
213
 
204
- ## Success criteria
214
+ ## CONSTRAINTS
205
215
 
206
- - [ ] Criteria 1 (observable, testable)
207
- - [ ] Criteria 2
216
+ - <Sequencing rules across children>
217
+ - <API / wire-format / migration compatibility requirements>
218
+ - <Branch / merge / release-gate rules>
208
219
 
209
- ## Context / background
220
+ ## VALIDATION
210
221
 
211
- <Links to specs, related issues, existing code paths>
222
+ - [ ] <Observable criterion 1>
223
+ - [ ] <Observable criterion 2>
224
+ - [ ] <Test suite green / drift checks clean / smoke pass>
225
+
226
+ ## OUTPUT
227
+
228
+ <What the orchestrator reports back at epic close. Usually: a summary referencing each child's handoff + the integration evidence + residual risks.>
229
+
230
+ ## REFERENCES
231
+
232
+ <Optional: links to specs, related issues, existing code paths, prior session reports.>
212
233
  EOF
213
234
  )" \
214
235
  --type=epic \
@@ -221,23 +242,42 @@ EOF
221
242
  bd create \
222
243
  --title="<Action phrase — what gets built>" \
223
244
  --description="$(cat <<'EOF'
224
- ## Context
245
+ ## PROBLEM
246
+
247
+ <Why this task exists. What does it enable. Anchor to the epic's PROBLEM and name the specific gap this task closes.>
225
248
 
226
- <Why does this task exist? What does it enable? What comes before/after?>
249
+ ## SUCCESS
227
250
 
228
- ## What to build
251
+ <Observable acceptance criteria in prose. The bar for "done" before VALIDATION checkboxes.>
229
252
 
230
- <Specific deliverables. Not "implement X" — "X that does Y when Z">
253
+ ## SCOPE
231
254
 
232
- ## Acceptance criteria
255
+ <Files, symbols, modules this task MAY touch. Be explicit — file:line or symbol-list when possible. Cross-cutting tasks list every surface; otherwise narrow. Forbidden boundary ("do NOT touch") goes in NON_GOALS or CONSTRAINTS.>
233
256
 
234
- - [ ] Criterion 1
235
- - [ ] Criterion 2
236
- - [ ] Tests pass / lint clean
257
+ ## NON_GOALS
237
258
 
238
- ## Approach notes
259
+ - <Related improvement explicitly excluded from this task>
260
+ - <Surface that looks adjacent but is out of scope>
239
261
 
240
- <Relevant code paths (file:line), patterns to follow, discovered risks>
262
+ ## CONSTRAINTS
263
+
264
+ - <Hard rule: API compatibility, error-text backward-compat, migration safety>
265
+ - <Style / pattern: follow existing convention in <file>>
266
+ - <Do-not-touch boundary outside SCOPE>
267
+
268
+ ## VALIDATION
269
+
270
+ - [ ] <Lint / typecheck / unit test for this surface>
271
+ - [ ] <Regression test for the specific failure mode being fixed>
272
+ - [ ] <Integration / smoke check if applicable>
273
+
274
+ ## OUTPUT
275
+
276
+ <What the executing specialist hands back: changed files list, verification evidence (command output / test pass summary), residual risks. This is what `bd show <id>` will surface to reviewer at gate.>
277
+
278
+ ## APPROACH NOTES
279
+
280
+ <Optional: relevant code paths (file:line), patterns to follow, discovered risks from Phase 2 exploration. Advisory only — not a contract.>
241
281
  EOF
242
282
  )" \
243
283
  --type=task \
@@ -255,15 +295,21 @@ bd dep add <B-id> <A-id>
255
295
  bd dep relate <issue-a> <issue-b>
256
296
  ```
257
297
 
258
- ### Issue description quality bar
298
+ ### Issue description quality bar (7-section contract)
299
+
300
+ Every task and epic description must fill all seven mandatory sections:
301
+
302
+ 1. **PROBLEM** — why this exists, what user/project problem it solves
303
+ 2. **SUCCESS** — observable acceptance criteria in prose
304
+ 3. **SCOPE** — files / symbols / surfaces this work may touch (no generic "src/")
305
+ 4. **NON_GOALS** — related improvements explicitly excluded
306
+ 5. **CONSTRAINTS** — hard rules (API compat, style, do-not-touch boundaries)
307
+ 6. **VALIDATION** — checkbox list of proof-of-done
308
+ 7. **OUTPUT** — what the executing specialist hands back
259
309
 
260
- Every task issue description must answer:
261
- 1. **Why** — why does this exist? (not obvious from the title)
262
- 2. **What** — specific deliverables (not vague)
263
- 3. **When done** — acceptance criteria as checkboxes
264
- 4. **How** — approach hints, relevant code paths, patterns to follow
310
+ If you cannot fill all seven, the scope is still unclear — go back to Phase 1.
265
311
 
266
- If you can't fill in all four, the scope is still unclear go back to Phase 1.
312
+ **Why this matters**: the bead description is the only contract the executor / debugger / reviewer / code-sanity / security-auditor specialist sees via `bd show <id>`. The `using-specialists-v3` SKILL.md in the specialists project teaches the human orchestrator to write 7-section contracts; the planner must produce the same so the contract surface is uniform across human-orchestrated and planner-orchestrated chains. If this template drifts from `using-specialists-v3`, downstream specialists work against weaker contracts and produce noisier output. Any change to either skill must be mirrored in the other.
267
313
 
268
314
  ---
269
315