xtrm-tools 0.7.17 → 0.7.18
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/.xtrm/config/hooks.json +2 -0
- package/.xtrm/config/instructions/agents-top.md +2 -1
- package/.xtrm/registry.json +429 -712
- package/.xtrm/skills/default/creating-service-skills/scripts/bootstrap.py +82 -156
- package/.xtrm/skills/default/creating-service-skills/scripts/scaffolder.py +73 -121
- package/.xtrm/skills/default/hook-development/references/patterns.md +1 -1
- package/.xtrm/skills/default/last30days/scripts/test-v1-vs-v2.sh +2 -2
- package/.xtrm/skills/default/planning/SKILL.md +75 -29
- package/.xtrm/skills/default/releasing/SKILL.md +163 -57
- package/.xtrm/skills/default/security-pipeline/SKILL.md +192 -0
- package/.xtrm/skills/default/security-pipeline/scripts/security-bootstrap.sh +294 -0
- package/.xtrm/skills/default/security-pipeline/templates/.githooks/pre-push.template +39 -0
- package/.xtrm/skills/default/security-pipeline/templates/.github/workflows/gitleaks.yml +33 -0
- package/.xtrm/skills/default/security-pipeline/templates/.github/workflows/osv-scanner.yml +33 -0
- package/.xtrm/skills/default/security-pipeline/templates/.github/workflows/semgrep.yml +41 -0
- package/.xtrm/skills/default/security-pipeline/templates/.gitleaks.toml +44 -0
- package/.xtrm/skills/default/security-pipeline/templates/.pre-commit-config.yaml +67 -0
- package/.xtrm/skills/default/security-pipeline/templates/.semgrepignore +46 -0
- package/.xtrm/skills/default/security-pipeline/templates/scripts/security-scan.sh +57 -0
- package/.xtrm/skills/default/security-pipeline/templates/scripts/semgrep-diff.sh +68 -0
- package/.xtrm/skills/default/session-close-report/SKILL.md +167 -6
- package/.xtrm/skills/default/sync-docs/SKILL.md +1 -1
- package/.xtrm/skills/default/update-xt/SKILL.md +270 -4
- package/.xtrm/skills/default/updating-service-skills/scripts/drift_detector.py +22 -0
- package/.xtrm/skills/default/using-script-specialists/SKILL.md +7 -5
- package/.xtrm/skills/default/using-specialists/SKILL.md +13 -12
- package/.xtrm/skills/default/using-specialists-auto/SKILL.md +137 -0
- package/.xtrm/skills/default/using-specialists-v2/SKILL.md +14 -21
- package/.xtrm/skills/default/using-specialists-v3/SKILL.md +533 -21
- package/.xtrm/skills/default/vaultctl/SKILL.md +2 -2
- package/CHANGELOG.md +82 -3
- package/cli/dist/index.cjs +12425 -3770
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +9 -3
- package/package.json +27 -7
- package/packages/pi-extensions/package.json +1 -1
- package/.xtrm/skills/default/planning/evals/evals.json +0 -19
- package/.xtrm/skills/default/quality-gates/evals/evals.json +0 -181
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/FINAL-EVAL-SUMMARY.md +0 -75
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/edge-case-auto-fix-verification/with_skill/outputs/response.md +0 -59
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/edge-case-mixed-language-project/with_skill/outputs/response.md +0 -60
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/eval-summary.md +0 -105
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/partial-install-python-only/with_skill/outputs/response.md +0 -93
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/python-refactor-request/with_skill/outputs/response.md +0 -104
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/quality-gate-error-fix/with_skill/outputs/response.md +0 -74
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/should-not-trigger-general-chat/with_skill/outputs/response.md +0 -18
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/should-not-trigger-math-question/with_skill/outputs/response.md +0 -18
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/should-not-trigger-unrelated-coding/with_skill/outputs/response.md +0 -56
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/tdd-guard-blocking-confusion/with_skill/outputs/response.md +0 -67
- package/.xtrm/skills/default/quality-gates/workspace/iteration-1/typescript-feature-with-tests/with_skill/outputs/response.md +0 -97
- package/.xtrm/skills/default/sync-docs/evals/evals.json +0 -89
- package/.xtrm/skills/default/test-planning/evals/evals.json +0 -23
- package/.xtrm/skills/default/using-specialists/SKILL.safe.md +0 -1082
- package/.xtrm/skills/default/using-specialists/SKILL.ultra.md +0 -1082
- package/.xtrm/skills/default/using-specialists/evals/evals.json +0 -68
- package/.xtrm/skills/default/using-specialists-v3/evals/evals.json +0 -89
- 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: .
|
|
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
|
-
|
|
65
|
-
if
|
|
66
|
-
print(
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
371
|
+
$(PYTHON) health_probe.py
|
|
401
372
|
|
|
402
373
|
health-json:
|
|
403
|
-
|
|
374
|
+
$(PYTHON) health_probe.py --json
|
|
404
375
|
|
|
405
376
|
data:
|
|
406
|
-
|
|
377
|
+
$(PYTHON) data_explorer.py
|
|
407
378
|
|
|
408
379
|
data-json:
|
|
409
|
-
|
|
380
|
+
$(PYTHON) data_explorer.py --json --limit 5
|
|
410
381
|
|
|
411
382
|
logs:
|
|
412
|
-
|
|
383
|
+
$(PYTHON) log_hunter.py --tail 50
|
|
413
384
|
|
|
414
385
|
errors:
|
|
415
|
-
|
|
386
|
+
$(PYTHON) log_hunter.py --errors-only --tail 50
|
|
416
387
|
|
|
417
388
|
db:
|
|
418
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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="/
|
|
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
|
|
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
|
-
##
|
|
197
|
+
## PROBLEM
|
|
198
|
+
|
|
199
|
+
<2-3 sentences: what user/project problem this epic exists to solve. Why now.>
|
|
200
|
+
|
|
201
|
+
## SUCCESS
|
|
192
202
|
|
|
193
|
-
<
|
|
203
|
+
<End-state across all child beads. Observable, testable, in prose.>
|
|
194
204
|
|
|
195
|
-
##
|
|
205
|
+
## SCOPE
|
|
196
206
|
|
|
197
|
-
-
|
|
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
|
-
##
|
|
209
|
+
## NON_GOALS
|
|
201
210
|
|
|
202
|
-
-
|
|
211
|
+
- <Explicit boundary 1>
|
|
212
|
+
- <What this epic does NOT include even though tangentially related>
|
|
203
213
|
|
|
204
|
-
##
|
|
214
|
+
## CONSTRAINTS
|
|
205
215
|
|
|
206
|
-
-
|
|
207
|
-
-
|
|
216
|
+
- <Sequencing rules across children>
|
|
217
|
+
- <API / wire-format / migration compatibility requirements>
|
|
218
|
+
- <Branch / merge / release-gate rules>
|
|
208
219
|
|
|
209
|
-
##
|
|
220
|
+
## VALIDATION
|
|
210
221
|
|
|
211
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
249
|
+
## SUCCESS
|
|
227
250
|
|
|
228
|
-
|
|
251
|
+
<Observable acceptance criteria in prose. The bar for "done" before VALIDATION checkboxes.>
|
|
229
252
|
|
|
230
|
-
|
|
253
|
+
## SCOPE
|
|
231
254
|
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
- [ ] Criterion 2
|
|
236
|
-
- [ ] Tests pass / lint clean
|
|
257
|
+
## NON_GOALS
|
|
237
258
|
|
|
238
|
-
|
|
259
|
+
- <Related improvement explicitly excluded from this task>
|
|
260
|
+
- <Surface that looks adjacent but is out of scope>
|
|
239
261
|
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|